From 7ff2412a97cb6a6d4fba6ad5209b3dc45a5f89be Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Fri, 25 Sep 2026 16:48:47 +0800 Subject: [PATCH 1/7] feat(sources): add categorized conversation resources and tool activity --- .../SessionSources.md | 17 + .../SessionSources.md | 9 + .../verification-2026-09-25/SessionSources.md | 17 + .../categories-dark-narrow.png | Bin 0 -> 27035 bytes .../session-sources/categories-light.png | Bin 0 -> 29542 bytes .../session-sources/empty.png | Bin 0 -> 13398 bytes .../session-sources/error.png | Bin 0 -> 15774 bytes .../session-sources/loading.png | Bin 0 -> 12979 bytes .../claude_code/history/windows/index.rs | 4 - .../claude_code/history/windows/sources.rs | 405 ++++++++++++-- .../sources/codex/app/transcript/sources.rs | 503 +++++++++++++++++- .../sources/imported_history/tool_activity.rs | 186 +++++++ .../sources/imported_history/user_sources.rs | 497 +++++++++++++---- .../imported_history/user_sources_tests.rs | 345 ++++++++++++ .../crates/session-persistence/src/lib.rs | 3 + .../src/source_messages.rs | 275 ++++++++++ .../history_commands/session_sources.rs | 10 +- src/api/tauri/session/sessionSources.ts | 24 +- .../components/SessionWorkstationRail.tsx | 24 +- .../SessionSourcesFileScope.tsx | 30 ++ .../extractSessionSources.test.ts | 318 +++++++++-- .../sessionSources/extractSessionSources.ts | 275 ++++++++-- .../sessionSources/openSessionSources.test.ts | 81 +++ .../sessionSources/openSessionSources.ts | 10 + .../sessionSources/useSessionSources.test.ts | 231 +++++++- .../sessionSources/useSessionSources.ts | 180 ++++++- .../SessionSources/SessionSourceIcon.tsx | 29 + .../SessionSourceImagePreview.tsx} | 18 +- .../SessionSources/SessionSourceRow.tsx | 34 ++ .../SessionSourceThumbnail.tsx} | 6 +- .../SessionSourcesView.images.test.ts | 112 ++++ .../SessionSources/SessionSourcesView.test.ts | 206 +++++++ .../SessionSources/SessionSourcesView.tsx | 154 ++++++ .../SessionToolActivityGroup.test.ts | 164 ++++++ .../SessionToolActivityGroup.tsx | 184 +++++++ .../aggregateToolActivity.test.ts | 78 +++ .../SessionSources/aggregateToolActivity.ts | 73 +++ .../SessionSources/presentation.test.ts | 47 ++ src/features/SessionSources/presentation.ts | 96 ++++ .../useSessionSourceNavigation.test.ts | 173 ++++++ .../useSessionSourceNavigation.ts | 79 +++ src/i18n/locales/de/common.json | 29 +- src/i18n/locales/en/common.json | 29 +- src/i18n/locales/es/common.json | 29 +- src/i18n/locales/fr/common.json | 29 +- src/i18n/locales/hi/common.json | 29 +- src/i18n/locales/id/common.json | 29 +- src/i18n/locales/ja/common.json | 29 +- src/i18n/locales/ko/common.json | 29 +- src/i18n/locales/pl/common.json | 29 +- src/i18n/locales/pt/common.json | 29 +- src/i18n/locales/ru/common.json | 29 +- src/i18n/locales/tr/common.json | 29 +- src/i18n/locales/vi/common.json | 29 +- src/i18n/locales/zh-Hant/common.json | 29 +- src/i18n/locales/zh/common.json | 29 +- .../useCodeEditorPrimarySidebarConfig.test.ts | 49 ++ .../useCodeEditorPrimarySidebarConfig.ts | 1 + .../WorkStation/TabContent/registry.ts | 5 + .../sessionSources.activities.test.ts | 183 +++++++ .../renderers/sessionSources.test.ts | 317 +++++++++++ .../TabContent/renderers/sessionSources.tsx | 45 ++ .../TabBar/components/WorkstationTabIcon.tsx | 2 + .../FocusedChatWorkstationRail.test.ts | 143 +++-- .../WorkstationItemRow.tsx | 4 +- .../WorkstationSections.tsx | 27 +- .../WorkstationSourcesSubmenu.tsx | 106 ---- .../FocusedChatWorkstationRail/index.tsx | 55 +- .../FocusedChatWorkstationRail/types.ts | 8 +- .../useWorkstationRailSources.ts | 145 ++--- .../useWorkstationRailTabs.ts | 2 + .../WorkstationTabHeader.sources.test.ts | 106 ++++ .../WorkbenchChrome/WorkstationTabHeader.tsx | 5 +- .../tabs/__tests__/sessionSources.test.ts | 64 +++ .../tabs/__tests__/workspaceState.test.ts | 3 +- .../tabs/factories/sessionSources.ts | 20 + src/store/workstation/tabs/storage.ts | 1 + src/store/workstation/tabs/tabFactory.ts | 1 + src/store/workstation/tabs/types.ts | 2 + 79 files changed, 6010 insertions(+), 616 deletions(-) create mode 100644 docs/frontend-ui-audit-2026-09-25/SessionSources.md create mode 100644 docs/org2-performance-guard-2026-09-25/SessionSources.md create mode 100644 docs/verification-2026-09-25/SessionSources.md create mode 100644 docs/verification-2026-09-25/session-sources/categories-dark-narrow.png create mode 100644 docs/verification-2026-09-25/session-sources/categories-light.png create mode 100644 docs/verification-2026-09-25/session-sources/empty.png create mode 100644 docs/verification-2026-09-25/session-sources/error.png create mode 100644 docs/verification-2026-09-25/session-sources/loading.png create mode 100644 src-tauri/crates/orgtrack-core/src/sources/imported_history/tool_activity.rs create mode 100644 src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources_tests.rs create mode 100644 src-tauri/crates/session-persistence/src/source_messages.rs create mode 100644 src/engines/ChatPanel/sessionSources/SessionSourcesFileScope.tsx create mode 100644 src/engines/ChatPanel/sessionSources/openSessionSources.test.ts create mode 100644 src/engines/ChatPanel/sessionSources/openSessionSources.ts create mode 100644 src/features/SessionSources/SessionSourceIcon.tsx rename src/{scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourceImagePreview.tsx => features/SessionSources/SessionSourceImagePreview.tsx} (69%) create mode 100644 src/features/SessionSources/SessionSourceRow.tsx rename src/{scaffold/AppLayout/FocusedChatWorkstationRail/RailImageThumbnail.tsx => features/SessionSources/SessionSourceThumbnail.tsx} (79%) create mode 100644 src/features/SessionSources/SessionSourcesView.images.test.ts create mode 100644 src/features/SessionSources/SessionSourcesView.test.ts create mode 100644 src/features/SessionSources/SessionSourcesView.tsx create mode 100644 src/features/SessionSources/SessionToolActivityGroup.test.ts create mode 100644 src/features/SessionSources/SessionToolActivityGroup.tsx create mode 100644 src/features/SessionSources/aggregateToolActivity.test.ts create mode 100644 src/features/SessionSources/aggregateToolActivity.ts create mode 100644 src/features/SessionSources/presentation.test.ts create mode 100644 src/features/SessionSources/presentation.ts create mode 100644 src/features/SessionSources/useSessionSourceNavigation.test.ts create mode 100644 src/features/SessionSources/useSessionSourceNavigation.ts create mode 100644 src/modules/WorkStation/CodeEditor/__tests__/useCodeEditorPrimarySidebarConfig.test.ts create mode 100644 src/modules/WorkStation/TabContent/renderers/sessionSources.activities.test.ts create mode 100644 src/modules/WorkStation/TabContent/renderers/sessionSources.test.ts create mode 100644 src/modules/WorkStation/TabContent/renderers/sessionSources.tsx delete mode 100644 src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourcesSubmenu.tsx create mode 100644 src/scaffold/WorkbenchChrome/WorkstationTabHeader.sources.test.ts create mode 100644 src/store/workstation/tabs/__tests__/sessionSources.test.ts create mode 100644 src/store/workstation/tabs/factories/sessionSources.ts diff --git a/docs/frontend-ui-audit-2026-09-25/SessionSources.md b/docs/frontend-ui-audit-2026-09-25/SessionSources.md new file mode 100644 index 0000000000..5be7db8686 --- /dev/null +++ b/docs/frontend-ui-audit-2026-09-25/SessionSources.md @@ -0,0 +1,17 @@ +# Session Sources UI audit + +| Line | Element | Verdict | Reason | Suggested change | +| ---------------------------- | ------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| SessionSourceRow.tsx | Resource rows | fix | Earlier standalone row styling duplicated sidebar chrome | Resolved with SidebarRow and shared file/model icons | +| SessionToolActivityGroup.tsx | Activity disclosure | fix | Local typography overrides conflicted with detail panel defaults | Resolved with SidebarSectionHeader and useCollapsible | +| SessionToolActivityGroup.tsx | Failure details | fix | Error surface should follow shared feedback appearance | Resolved with PageNotice | +| SessionSourcesView.tsx | Canvas | fix | Generic surface grey differed from work area | Resolved with EDITOR_TAB_CANVAS_BG_CLASS | +| SessionSourcesView.tsx | Category headers / pagination | keep with reason | Shared SidebarSectionHeader and Button own geometry and controls | Preserve shared components | +| SessionSourceThumbnail.tsx | 3px miniature radius and size | keep with reason | Tiny source previews need their icon-sized optical geometry; width follows caller's shared icon scale | No global token change | +| SessionSourcesView.tsx | 8px row / 16px category spacing | keep with reason | Explicit user-approved density uses standard spacing utilities | Preserve local layout | +| SessionSourceRow.tsx | Path tooltip | keep with reason | Shared Tooltip exposes truncated human-readable location; encoded transport refs stay private | Preserve | +| features/SessionSources | Shared presentation/navigation | abstract | Rail preview, source detail rows and complete page need one identity/navigation implementation | Implemented feature module; old rail-only wrappers removed | + +Totals: 4 resolved fixes, 4 keep with reason, 1 implemented abstraction. No raw production buttons, inputs or clickable substitutes introduced. Metadata colors use semantic tokens; shared components own typography/hover/focus. No global design-system sweep. + +Visual evidence in ../verification-2026-09-25/session-sources: final categorized light page, dark 340px page, empty/loading/error states. These render actual feature and shared components using fictional source fixtures; navigation and image loading are mocked. They are not native-app E2E evidence. Original user screenshot contains local filesystem paths and is not checked in. diff --git a/docs/org2-performance-guard-2026-09-25/SessionSources.md b/docs/org2-performance-guard-2026-09-25/SessionSources.md new file mode 100644 index 0000000000..8c901176f4 --- /dev/null +++ b/docs/org2-performance-guard-2026-09-25/SessionSources.md @@ -0,0 +1,9 @@ +# Session Sources lifecycle audit + +Source requests are session scoped, single-flight while pending and released by consumer reference count. Pending request ownership is removed at settlement/disposal; no completed-result global cache. Hidden documents pause noncritical refresh; visibility return revalidates. Session-keyed renderer prevents stale list/gallery/page state crossing sessions. Inactive tabs do not read history or resolve images. Old rows survive failed refresh; retry is explicit. + +Reader work is bounded at input-line, reference, pending-call, nested-resource and tool-action boundaries. Native SQL extracts compact visible source messages, not entire session objects. Structured references and completed tool facts are separate; errors never masquerade as successful resources, and edit receipts do not synthesize attachments. + +Category arrays and semantic detail maps live with the mounted view. Four category page counters start at 30 rows each (at most 30 image thumbnail reads); activity details start at 20 unique rows per action type. More content loads by explicit action. Duplicate display actions retain original call IDs and occurrence totals. No new recurring poller, worker or network transport. + +Validation covers coalesced reads, refresh/session races, hidden/inactive lifecycle, retry, page reset, image identity/gallery, activity aggregation and actual provider transcript readers. No native CPU/RSS sampling, large-history benchmark, dual-machine sharing run or complete packaged-app E2E in this PR. Verdict: scoped/bounded lifecycle supported by tests; no measured performance claim. diff --git a/docs/verification-2026-09-25/SessionSources.md b/docs/verification-2026-09-25/SessionSources.md new file mode 100644 index 0000000000..3d1ce4a2a0 --- /dev/null +++ b/docs/verification-2026-09-25/SessionSources.md @@ -0,0 +1,17 @@ +# Categorized conversation Sources + +The focused chat rail opens a stable session-scoped Sources tab. Images, files, links and tool activity appear in fixed category order, newest occurrence first inside each category. Empty categories disappear; each category independently pages 30 rows. Rail previews prioritize real resources before tool groups. View all stays reachable while folded; the duplicate Sources row is omitted from Opened Tabs. Sources hides the editor tree/shared toolbar without changing stored sidebar preferences. + +One read-only source projection serves native storage, Codex transcripts and Claude transcripts. It includes visible user attachments, assistant references and explicit successful structured resources, plus completed tool activity and error summaries. It never scans arbitrary shell/JavaScript/log text for resource paths. Codex FileChange receipts remain activity rather than fabricated artifacts. Repeated identical tool actions aggregate by exact tool/action/query/url/status/error, preserving call identities and total counts. Explicit artifacts/citations still appear. + +Source reference/navigation identity remains canonical; display paths unwrap transcript image envelopes without leaking session IDs. Shared/imported files use sender org/session/endpoint scope; the new develop shared-file tab opener is verified at its navigation boundary rather than the previous overlay seam. + +Compatibility: new optional role/toolName/toolActivity fields extend the existing source-message wire response, with defaults preserving old user-only producers. A new session-sources tab kind participates in persistence/classification. No database migration or write to message history. Older binaries may discard unknown saved tabs; reopening View all recreates this ephemeral view. Rollback is code revert; historical source projections update on reread. + +Architecture reviewed: module ownership, duplicate presentation, types, provider-neutral projection, fallback behavior, wire compatibility, reader entrypoints and scope-aware navigation. No auth or sync protocol changed. Root cause of repeated labels was many distinct calls projected identically, not duplicate persisted messages; no destructive history cleanup. + +## Verification + +Final checks and counts are recorded in the PR description. Frontend suite includes feature, reader hook/extraction, rail, actual renderer, image navigation, sidebar/header tab transitions and tab persistence classification. Rust suites exercise shared projector, Codex/Claude actual transcript readers and native SQL source extraction. Scoped lint and typecheck run from the isolated latest-develop worktree. + +Screenshots are actual production components rendered with fictional fixtures and mocked navigation/image resolution. Light and dark narrow layouts plus empty/loading/error states were inspected. These do not prove native application full-stack behavior. No complete application Rust build, Windows/Linux native verification, remote dual-instance or huge transcript benchmark was run on this isolated PR branch. diff --git a/docs/verification-2026-09-25/session-sources/categories-dark-narrow.png b/docs/verification-2026-09-25/session-sources/categories-dark-narrow.png new file mode 100644 index 0000000000000000000000000000000000000000..61d38359b99f930aba226b25d3311f98826ec2e8 GIT binary patch literal 27035 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 P&;QM&^wY(^*qZ)7GVsDV literal 0 HcmV?d00001 diff --git a/docs/verification-2026-09-25/session-sources/categories-light.png b/docs/verification-2026-09-25/session-sources/categories-light.png new file mode 100644 index 0000000000000000000000000000000000000000..7492cfadae9817d7462e79d849ce8b4827c853ee GIT binary patch literal 29542 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 M@&8)`i+|$&H-QwKWB>pF literal 0 HcmV?d00001 diff --git a/docs/verification-2026-09-25/session-sources/empty.png b/docs/verification-2026-09-25/session-sources/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..b398fb6a24b2b861645a41acd3c7834cb799cc1d GIT binary patch literal 13398 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 GefT$_wNh;W literal 0 HcmV?d00001 diff --git a/docs/verification-2026-09-25/session-sources/error.png b/docs/verification-2026-09-25/session-sources/error.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e25f4086ebc859986caaf26a5bf400da26d877 GIT binary patch literal 15774 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 OmXH5Hq5n#nY589a$BZEW literal 0 HcmV?d00001 diff --git a/docs/verification-2026-09-25/session-sources/loading.png b/docs/verification-2026-09-25/session-sources/loading.png new file mode 100644 index 0000000000000000000000000000000000000000..df72891a309de83ce830e84192da50c22e3b7576 GIT binary patch literal 12979 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, - /// The user row carries image blocks. `user_chunk` keeps URL refs only - /// (bounded); inline bytes stay in the source row. - pub(in crate::sources::claude_code::history) has_images: bool, } pub(in crate::sources::claude_code::history) fn claude_window_turn_id(start_offset: u64) -> String { @@ -255,7 +252,6 @@ pub(in crate::sources::claude_code::history) fn index_claude_user_turns( user_chunk, following_line_count: 0, last_assistant_text_line: None, - has_images, }); } Ok(turns) diff --git a/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs b/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs index 08b8631412..73d6b84c37 100644 --- a/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs +++ b/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs @@ -1,4 +1,4 @@ -//! Claude Code user-message sources, and on-demand reads of the images they +//! Claude Code conversation resource sources, and on-demand reads of the images they //! reference. //! //! Claude stores pasted images inline as base64 in the user row. Sources @@ -7,17 +7,27 @@ //! id), and `load_claude_code_image_from_path` re-reads that one row when a //! thumbnail or the image viewer asks for it. +use std::collections::{HashSet, VecDeque}; use std::fs::File; + +use memchr::memchr; use std::io::{BufRead, BufReader, Read, Seek, SeekFrom}; use std::path::Path; use serde_json::Value; -use crate::sources::imported_history::user_sources::{transcript_image_ref, UserSourceMessage}; +use crate::sources::imported_history::user_sources::{ + transcript_image_ref, SourceMessageRole, UserSourceMessage, +}; -use super::super::replay::claude_image_sources; -use super::super::types::ClaudeJsonlLine; -use super::index::{claude_window_turn_offset, index_claude_user_turns}; +use super::super::replay::{ + claude_image_sources, claude_local_command_input, claude_local_command_output, +}; +use super::super::types::{ + is_claude_compact_summary, is_harness_injected_user_line, ClaudeControlEnvelope, + ClaudeJsonlLine, +}; +use super::index::{claude_window_turn_id, claude_window_turn_offset}; const INLINE_IMAGE_REF_PREFIX: &str = "claude-inline-image:"; const MAX_USER_ROW_BYTES: u64 = 16 * 1024 * 1024; @@ -26,31 +36,147 @@ pub fn load_claude_code_user_source_messages_from_path( session_id: &str, path: &Path, ) -> Result, String> { - let turns = index_claude_user_turns(session_id, path)?; - let mut image_rows: Option = None; + let mut reader = BufReader::with_capacity(1024 * 1024, open_history(path)?); let mut messages = Vec::new(); - for turn in turns { - let turn_id = turn.user_chunk.chunk_id.clone(); - let text = turn - .user_chunk - .result - .pointer("/message/content") - .and_then(Value::as_str) - .unwrap_or_default(); - let images = if turn.has_images { - let file = match image_rows.as_mut() { - Some(file) => file, - None => image_rows.insert(open_history(path)?), - }; - user_row_image_refs(file, turn.start_offset)? - .iter() - .map(|original| transcript_image_ref(session_id, &turn_id, original)) - .collect() + let mut calls: VecDeque<(String, String, Value)> = VecDeque::new(); + let mut control = ClaudeControlEnvelope::default(); + visit_bounded_lines(&mut reader, MAX_USER_ROW_BYTES as usize, |offset, line| { + let Ok(parsed) = serde_json::from_slice::(line) else { + return; + }; + if control.observe(&parsed) + || is_harness_injected_user_line(&parsed) + || is_claude_compact_summary(&parsed) + || parsed.is_api_error_message + { + return; + } + if !matches!(parsed.r#type.as_str(), "user" | "assistant") { + return; + } + let Some(message) = &parsed.message else { + return; + }; + let id = claude_window_turn_id(offset); + let content = &message.content; + let text = if let Some(text) = content.as_str() { + text.to_owned() } else { - Vec::new() + content + .as_array() + .into_iter() + .flatten() + .filter(|part| part.get("type").and_then(Value::as_str) == Some("text")) + .filter_map(|part| part.get("text").and_then(Value::as_str)) + .collect::>() + .join("\n") }; - messages.extend(UserSourceMessage::new(turn_id, text, images)); - } + // Harness command stdout is presented as provider output, not as a + // human attachment; preserve the existing user-turn normalization. + let text = if parsed.r#type == "user" { + let stripped = crate::sources::imported_history::strip_orgii_exec_mode_bridge(&text); + if claude_local_command_output(stripped).is_some() { + return; + } + claude_local_command_input(stripped).unwrap_or_else(|| stripped.to_owned()) + } else { + text + }; + if parsed.r#type == "assistant" { + messages.extend(UserSourceMessage::with_role( + &id, + &text, + Vec::new(), + SourceMessageRole::Assistant, + )); + } else { + let images = + claude_image_sources(content) + .enumerate() + .filter_map(|(position, source)| { + match source.get("type").and_then(Value::as_str) { + Some("url") => { + source.get("url").and_then(Value::as_str).map(str::to_owned) + } + Some("base64") => Some(transcript_image_ref( + session_id, + &id, + &format!("{INLINE_IMAGE_REF_PREFIX}{position}"), + )), + _ => None, + } + }); + messages.extend(UserSourceMessage::new(&id, &text, images)); + } + for part in content.as_array().into_iter().flatten() { + match part.get("type").and_then(Value::as_str) { + Some("tool_use") if parsed.r#type == "assistant" => { + let (Some(call_id), Some(name)) = ( + part.get("id").and_then(Value::as_str), + part.get("name").and_then(Value::as_str), + ) else { + continue; + }; + let arguments = &part["input"]; + if call_id.len() > 512 + || name.len() > 256 + || arguments.to_string().len() > 64 * 1024 + { + continue; + } + calls.retain(|(key, _, _)| key != call_id); + if calls.len() == 256 { + calls.pop_front(); + } + calls.push_back((call_id.to_owned(), name.to_owned(), arguments.clone())); + } + Some("tool_result") if parsed.r#type == "user" => { + let Some(call_id) = part.get("tool_use_id").and_then(Value::as_str) else { + continue; + }; + let Some(index) = calls.iter().position(|(key, _, _)| key == call_id) else { + continue; + }; + let Some((_, name, arguments)) = calls.remove(index) else { + continue; + }; + let success = !part + .get("is_error") + .and_then(Value::as_bool) + .unwrap_or(false); + // Sidecar resources belong to this same logical call, not + // another activity. Never attach a sidecar to ambiguous multi-results. + let mut result = part.clone(); + if let Some(sidecar) = parsed.tool_use_result.as_ref().filter(|_| { + content + .as_array() + .into_iter() + .flatten() + .filter(|part| { + part.get("type").and_then(Value::as_str) == Some("tool_result") + }) + .count() + == 1 + }) { + result["structuredContent"] = sidecar.clone(); + } + messages.extend(UserSourceMessage::from_tool( + format!("claude-call:{call_id}"), + &name, + &arguments, + &result, + success, + )); + } + _ => {} + } + } + }) + .map_err(|err| format!("Failed to read Claude sources: {err}"))?; + let mut seen_calls = HashSet::new(); + messages.retain(|message| { + message.role != SourceMessageRole::Tool || seen_calls.insert(message.id.clone()) + }); Ok(messages) } @@ -98,26 +224,6 @@ fn open_history(path: &Path) -> Result { .map_err(|err| format!("Failed to open Claude history {}: {err}", path.display())) } -/// Web URLs as they are; inline images as positional stand-ins. -fn user_row_image_refs(file: &mut File, offset: u64) -> Result, String> { - let Some(message) = read_user_row(file, offset)?.and_then(|row| row.message) else { - return Ok(Vec::new()); - }; - Ok(claude_image_sources(&message.content) - .enumerate() - .filter_map( - |(position, source)| match source.get("type").and_then(Value::as_str) { - Some("url") => source - .get("url") - .and_then(Value::as_str) - .map(str::to_string), - Some("base64") => Some(format!("{INLINE_IMAGE_REF_PREFIX}{position}")), - _ => None, - }, - ) - .collect()) -} - fn read_user_row(file: &mut File, offset: u64) -> Result, String> { file.seek(SeekFrom::Start(offset)) .map_err(|err| format!("Failed to seek Claude history: {err}"))?; @@ -133,9 +239,52 @@ fn read_user_row(file: &mut File, offset: u64) -> Result .filter(|row| row.r#type == "user")) } +/// Visit each complete line with its starting byte offset. A line longer than +/// `max_line_bytes` is skipped without being buffered. +fn visit_bounded_lines( + reader: &mut impl BufRead, + max_line_bytes: usize, + mut visit: impl FnMut(u64, &[u8]), +) -> std::io::Result<()> { + let mut line = Vec::new(); + let mut offset = 0u64; + let mut line_start = 0u64; + let mut oversized = false; + loop { + let available = reader.fill_buf()?; + if available.is_empty() { + if !oversized && !line.is_empty() { + visit(line_start, &line); + } + return Ok(()); + } + let (consumed, complete) = match memchr(b'\n', available) { + Some(index) => (index + 1, true), + None => (available.len(), false), + }; + if !oversized { + if line.len() + consumed > max_line_bytes { + oversized = true; + line = Vec::new(); + } else { + line.extend_from_slice(&available[..consumed]); + } + } + reader.consume(consumed); + offset += consumed as u64; + if complete { + if !oversized { + visit(line_start, &line); + } + line.clear(); + oversized = false; + line_start = offset; + } + } +} + #[cfg(test)] mod tests { - use super::super::index::claude_window_turn_id; use super::*; #[test] @@ -153,8 +302,15 @@ mod tests { let turn_id = claude_window_turn_id(assistant.len() as u64 + 1); let inline = format!("{INLINE_IMAGE_REF_PREFIX}0"); assert_eq!( - messages, + messages + .iter() + .filter(|message| message.role == SourceMessageRole::User) + .cloned() + .collect::>(), vec![UserSourceMessage { + role: SourceMessageRole::User, + tool_name: None, + tool_activity: None, id: turn_id.clone(), text: "look at https://a.dev/page".to_string(), images: vec![ @@ -173,4 +329,155 @@ mod tests { ); std::fs::remove_dir_all(dir).unwrap(); } + #[test] + fn streams_visible_assistant_and_paired_successful_tool_resources() { + use serde_json::json; + let dir = std::env::temp_dir().join(format!( + "orgii-claude-source-conversation-{}", + std::process::id() + )); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("session.jsonl"); + let records = vec![ + json!({"type":"user","isMeta":true,"uuid":"meta","message":{"content":"https://hidden.example/meta"}}), + json!({"type":"user","isCompactSummary":true,"message":{"content":"https://hidden.example/summary"}}), + json!({"type":"assistant","message":{"content":[{"type":"thinking","thinking":"https://hidden.example/thought"},{"type":"text","text":"[Report](/repo/report.md) https://public.example"},{"type":"tool_use","id":"read1","name":"Read","input":{"file_path":"/repo/input.ts"}},{"type":"tool_use","id":"read2","name":"Read","input":{"file_path":"/missing.ts"}},{"type":"tool_use","id":"report","name":"make_report","input":{}}]}}), + json!({"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"read1","content":"https://untrusted.example/file-body"},{"type":"tool_result","tool_use_id":"read2","is_error":true,"content":"Not found"},{"type":"tool_result","tool_use_id":"report","content":[{"type":"resource_link","uri":"https://reports.example/result"},{"type":"text","text":"https://untrusted.example/tool-text"}]}]}}), + json!({"type":"assistant","isApiErrorMessage":true,"message":{"content":[{"type":"text","text":"https://hidden.example/api-error"}]}}), + json!({"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"unknown","content":[{"type":"resource_link","uri":"https://unknown.example"}]}]}}), + ]; + std::fs::write( + &path, + records + .iter() + .map(Value::to_string) + .collect::>() + .join("\n"), + ) + .unwrap(); + let messages = + load_claude_code_user_source_messages_from_path("claudecodeapp-x", &path).unwrap(); + let text = messages + .iter() + .map(|message| message.text.as_str()) + .collect::>() + .join("\n"); + for reference in [ + "/repo/report.md", + "https://public.example", + "/repo/input.ts", + "https://reports.example/result", + ] { + assert!(text.contains(reference), "missing {reference}: {text}"); + } + for excluded in [ + "hidden.example", + "untrusted.example", + "unknown.example", + "/missing.ts", + ] { + assert!(!text.contains(excluded), "unexpected {excluded}: {text}"); + } + assert_eq!( + messages + .iter() + .filter(|message| message.role == SourceMessageRole::Assistant) + .count(), + 1 + ); + assert!(messages + .iter() + .any(|message| message.tool_name.as_deref() == Some("Read"))); + assert!(messages + .iter() + .all(|message| message.role != SourceMessageRole::User)); + use std::io::Write; + let mut file = std::fs::OpenOptions::new() + .append(true) + .open(&path) + .unwrap(); + writeln!(file, "\n{}", json!({"type":"assistant","message":{"content":[{"type":"text","text":"https://appended.example/new"}]}})).unwrap(); + let reread = + load_claude_code_user_source_messages_from_path("claudecodeapp-x", &path).unwrap(); + assert_eq!(reread.len(), messages.len() + 1); + assert!(reread + .last() + .unwrap() + .text + .contains("https://appended.example/new")); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn retains_failed_terminal_and_web_actions_without_double_counting_sidecars_or_replayed_calls() + { + use crate::sources::imported_history::user_sources::{ + ToolActivityKind, ToolActivityStatus, + }; + use serde_json::json; + let dir = std::env::temp_dir().join(format!( + "orgii-claude-source-activities-{}", + std::process::id() + )); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("session.jsonl"); + let calls = json!({"type":"assistant","message":{"content":[ + {"type":"tool_use","id":"terminal","name":"mcp__codex_app__read_thread_terminal","input":{}}, + {"type":"tool_use","id":"web","name":"web.run","input":{"search_query":[{"q":"full query"}],"open":[{"ref_id":"https://docs.example/page"}]}} + ]}}); + let failed = json!({"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"terminal","is_error":true,"content":"No terminal attached"}]}}); + let web = json!({"type":"user","toolUseResult":{"artifact_path":"/repo/report.md"},"message":{"content":[{"type":"tool_result","tool_use_id":"web","content":"ordinary output"}]}}); + // A replayed provider record must not create another logical tool call. + std::fs::write( + &path, + [&calls, &failed, &web, &calls, &failed, &web] + .iter() + .map(|record| record.to_string()) + .collect::>() + .join("\n"), + ) + .unwrap(); + let messages = + load_claude_code_user_source_messages_from_path("claudecodeapp-x", &path).unwrap(); + let activities: Vec<_> = messages + .iter() + .filter_map(|message| message.tool_activity.as_ref()) + .collect(); + assert_eq!(activities.len(), 2); + let terminal = activities + .iter() + .find(|activity| activity.call_id == "claude-call:terminal") + .unwrap(); + assert_eq!(terminal.status, ToolActivityStatus::Error); + assert_eq!(terminal.error.as_deref(), Some("No terminal attached")); + assert_eq!(terminal.actions[0].kind, ToolActivityKind::ReadTerminal); + let web = activities + .iter() + .find(|activity| activity.call_id == "claude-call:web") + .unwrap(); + assert_eq!(web.actions[0].query.as_deref(), Some("full query")); + assert_eq!( + web.actions[1].url.as_deref(), + Some("https://docs.example/page") + ); + assert!(messages + .iter() + .any(|message| message.text.contains("/repo/report.md"))); + assert_eq!( + load_claude_code_user_source_messages_from_path("claudecodeapp-x", &path).unwrap(), + messages + ); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn bounded_stream_skips_oversized_line_and_preserves_next_image_offset() { + let mut reader = std::io::Cursor::new(format!("{}\nnext\n", "x".repeat(100))); + let mut seen = Vec::new(); + visit_bounded_lines(&mut reader, 8, |offset, bytes| { + seen.push((offset, bytes.to_vec())) + }) + .unwrap(); + assert_eq!(seen, vec![(101, b"next\n".to_vec())]); + } } diff --git a/src-tauri/crates/orgtrack-core/src/sources/codex/app/transcript/sources.rs b/src-tauri/crates/orgtrack-core/src/sources/codex/app/transcript/sources.rs index e6dbd161dc..4cfe35782f 100644 --- a/src-tauri/crates/orgtrack-core/src/sources/codex/app/transcript/sources.rs +++ b/src-tauri/crates/orgtrack-core/src/sources/codex/app/transcript/sources.rs @@ -1,18 +1,22 @@ -//! Every user message of a Codex rollout, reduced to its sources. +//! Explicit resources from user messages, visible assistant replies and successful tools. //! -//! Unlike the turn catalog this keeps each message's full text and every -//! image reference, but it still parses only lines that can be user -//! messages. Images become transcript refs keyed by the line's byte offset — +//! Lines are streamed under a read ceiling; only compact resource projections +//! and a bounded pending-call window survive each line. User images become +//! transcript refs keyed by the line's byte offset — //! the same turn id the catalog uses — so `load_codex_image_from_path` can //! read the embedded bytes back when a thumbnail asks for them. +use std::collections::{HashMap, VecDeque}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; -use memchr::{memchr, memmem}; +use memchr::memchr; +use serde_json::Value; -use crate::sources::imported_history::user_sources::{transcript_image_ref, UserSourceMessage}; +use crate::sources::imported_history::user_sources::{ + transcript_image_ref, SourceMessageRole, ToolActivityStatus, UserSourceMessage, +}; use super::super::CodexJsonlLine; use super::catalog::codex_lazy_turn_id; @@ -22,8 +26,6 @@ use super::messages::user_message_from_line; /// transcript ref stays small; `load_codex_image_from_path` maps it back to /// the row's image at that position. pub(super) const INLINE_IMAGE_REF_PREFIX: &str = "codex-inline-image:"; -const LEGACY_USER_MESSAGE_NEEDLE: &[u8] = b"\"user_message\""; -const PAGINATED_USER_MESSAGE_NEEDLE: &[u8] = b"\"UserMessage\""; /// Same ceiling the image resolver reads a user line with. const MAX_USER_LINE_BYTES: usize = 16 * 1024 * 1024; const READ_BUFFER_BYTES: usize = 1024 * 1024; @@ -36,15 +38,13 @@ pub fn load_codex_user_source_messages_from_path( .map_err(|err| format!("Failed to open Codex history {}: {err}", path.display()))?; let mut reader = BufReader::with_capacity(READ_BUFFER_BYTES, file); let mut messages = Vec::new(); + let mut calls: VecDeque<(String, String, Value)> = VecDeque::new(); visit_bounded_lines(&mut reader, MAX_USER_LINE_BYTES, |offset, line| { - if memmem::find(line, LEGACY_USER_MESSAGE_NEEDLE).is_none() - && memmem::find(line, PAGINATED_USER_MESSAGE_NEEDLE).is_none() - { - return; - } let Ok(parsed) = serde_json::from_slice::(line) else { return; }; + let id = codex_lazy_turn_id(offset); + collect_codex_conversation_sources(&parsed.payload, &id, &mut calls, &mut messages); let Some(message) = user_message_from_line(&parsed) else { return; }; @@ -65,7 +65,285 @@ pub fn load_codex_user_source_messages_from_path( messages.extend(UserSourceMessage::new(turn_id, &message.text, images)); }) .map_err(|err| format!("Failed to read Codex history {}: {err}", path.display()))?; - Ok(messages) + Ok(deduplicate_tool_sources(messages)) +} + +// The model-facing output and UI completion may both describe one call. +// This transient index is bounded by the returned activity rows, not retained +// between reads. Preserve resource text from either projection. +fn deduplicate_tool_sources(messages: Vec) -> Vec { + let mut indexes: HashMap = HashMap::new(); + let mut result: Vec = Vec::new(); + for message in messages { + if message.role == SourceMessageRole::Tool { + if let Some(index) = indexes.get(&message.id).copied() { + let previous = &mut result[index]; + for line in message.text.lines() { + if !previous.text.lines().any(|existing| existing == line) { + if !previous.text.is_empty() { + previous.text.push('\n'); + } + previous.text.push_str(line); + } + } + if let Some(activity) = message.tool_activity { + let replace = previous.tool_activity.as_ref().is_none_or(|old| { + (old.error.is_none() && activity.error.is_some()) + || (old.status == ToolActivityStatus::Success + && activity.status == ToolActivityStatus::Error) + || activity.actions.len() > old.actions.len() + }); + if replace { + previous.tool_activity = Some(activity); + } + } + continue; + } + indexes.insert(message.id.clone(), result.len()); + } + result.push(message); + } + result +} + +fn qualified_tool_name(namespace: Option<&str>, name: &str) -> String { + if name.contains('.') || name.starts_with("mcp__") { + return name.to_owned(); + } + match namespace { + Some(namespace) if !namespace.is_empty() => format!("{namespace}.{name}"), + _ => name.to_owned(), + } +} + +// Pair only a bounded window of pending calls. Keep structured arguments, never +// shell/log output; unknown or evicted calls cannot fabricate accessed resources. +const MAX_PENDING_SOURCE_CALLS: usize = 256; +const MAX_SOURCE_CALL_BYTES: usize = 64 * 1024; + +fn source_text(content: &Value) -> String { + if let Some(text) = content.as_str() { + return text.to_owned(); + } + content + .as_array() + .into_iter() + .flatten() + .filter(|part| { + matches!( + part.get("type").and_then(Value::as_str), + Some("text" | "output_text") + ) + }) + .filter_map(|part| part.get("text").and_then(Value::as_str)) + .collect::>() + .join("\n") +} + +fn collect_codex_conversation_sources( + payload: &Value, + id: &str, + calls: &mut VecDeque<(String, String, Value)>, + messages: &mut Vec, +) { + let kind = payload + .get("type") + .and_then(Value::as_str) + .unwrap_or_default(); + let assistant = match kind { + "message" + if payload.get("role").and_then(Value::as_str) == Some("assistant") + && payload.get("channel").and_then(Value::as_str) != Some("analysis") => + { + Some(source_text(&payload["content"])) + } + "agent_message" if payload.get("message").is_some() => payload + .get("message") + .and_then(Value::as_str) + .map(str::to_owned), + "item_completed" + if payload.pointer("/item/type").and_then(Value::as_str) == Some("AgentMessage") => + { + Some(source_text(&payload["item"]["content"])) + } + _ => None, + }; + if let Some(text) = assistant { + messages.extend(UserSourceMessage::with_role( + id, + &text, + Vec::new(), + SourceMessageRole::Assistant, + )); + } + if kind == "item_completed" { + let item = &payload["item"]; + let status = item + .get("status") + .and_then(Value::as_str) + .unwrap_or_default(); + let completed = matches!(status, "completed" | "failed" | "error" | "cancelled"); + let successful = status == "completed"; + let call_id = item.get("id").and_then(Value::as_str).unwrap_or(id); + let source_id = format!("codex-call:{call_id}"); + match item.get("type").and_then(Value::as_str) { + Some("McpToolCall" | "DynamicToolCall") if completed => { + if let Some(name) = item + .get("tool") + .or_else(|| item.get("name")) + .and_then(Value::as_str) + { + let server = item.get("server").and_then(Value::as_str).map(|server| { + if server.starts_with("mcp__") { + server.to_owned() + } else { + format!("mcp__{server}") + } + }); + let name = qualified_tool_name(server.as_deref(), name); + let mut result = item["result"].clone(); + if result.is_null() { + result = serde_json::json!({"error": item.get("error")}); + } + messages.extend(UserSourceMessage::from_tool( + source_id, + &name, + &item["arguments"], + &result, + successful, + )); + } + } + Some("Extension") if item.get("kind").and_then(Value::as_str) == Some("web.search") => { + // Nested web.run calls publish typed UI metadata even when the + // model-facing call is a functions.exec wrapper. Never parse scripts. + let action = &item["action"]; + let arguments = match action.get("type").and_then(Value::as_str) { + Some("search") => { + let queries: Vec = action + .get("queries") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + .take(32) + .map(|query| serde_json::json!({"q": query})) + .collect(); + if queries.is_empty() { + serde_json::json!({"search_query":[{"q":action.get("query").and_then(Value::as_str).or_else(|| item.get("query").and_then(Value::as_str)).unwrap_or_default()}]}) + } else { + serde_json::json!({"search_query":queries}) + } + } + Some("openPage") => { + serde_json::json!({"open":[{"ref_id": action.get("url").and_then(Value::as_str).unwrap_or_default()}]}) + } + _ => Value::Null, + }; + if !arguments.is_null() { + messages.extend(UserSourceMessage::from_tool( + source_id, + "web.run", + &arguments, + &Value::Null, + true, + )); + } + } + Some("FileChange") if completed => { + // A change receipt is activity, not a resource supplied to the conversation. + // Explicit assistant references and attached artifacts are projected separately. + let result = serde_json::json!({"error": if successful { Value::Null } else { item["stderr"].clone() }}); + messages.extend(UserSourceMessage::from_tool( + source_id, + "apply_patch", + &Value::Null, + &result, + successful, + )); + } + _ => {} + } + } + if matches!(kind, "function_call" | "custom_tool_call") { + let (Some(call_id), Some(name)) = ( + payload.get("call_id").and_then(Value::as_str), + payload.get("name").and_then(Value::as_str), + ) else { + return; + }; + if payload.get("namespace").and_then(Value::as_str) == Some("collaboration") + || name.starts_with("collaboration.") + { + return; + } + if call_id.len() > 512 || name.len() > 256 { + return; + } + let input = payload + .get("arguments") + .or_else(|| payload.get("input")) + .unwrap_or(&Value::Null); + if input.to_string().len() > MAX_SOURCE_CALL_BYTES { + return; + } + let arguments = input + .as_str() + .and_then(|text| serde_json::from_str(text).ok()) + .unwrap_or_else(|| input.clone()); + calls.retain(|(key, _, _)| key != call_id); + if calls.len() == MAX_PENDING_SOURCE_CALLS { + calls.pop_front(); + } + calls.push_back(( + call_id.to_owned(), + qualified_tool_name(payload.get("namespace").and_then(Value::as_str), name), + arguments, + )); + } else if matches!(kind, "function_call_output" | "custom_tool_call_output") { + let Some(call_id) = payload.get("call_id").and_then(Value::as_str) else { + return; + }; + let Some(index) = calls.iter().position(|(key, _, _)| key == call_id) else { + return; + }; + let Some((_, name, arguments)) = calls.remove(index) else { + return; + }; + let raw = &payload["output"]; + let result = raw + .as_str() + .and_then(|text| serde_json::from_str(text).ok()) + .unwrap_or_else(|| raw.clone()); + // A terminal output is successful unless the provider explicitly marks failure. + let success = !raw + .as_str() + .is_some_and(|text| text.trim_start().starts_with("Error:")) + && !matches!( + payload.get("status").and_then(Value::as_str), + Some("failed" | "cancelled" | "error") + ) + && !result + .get("isError") + .and_then(Value::as_bool) + .unwrap_or(false) + && !result + .get("is_error") + .and_then(Value::as_bool) + .unwrap_or(false) + && result.get("error").is_none_or(Value::is_null) + && result + .get("exit_code") + .and_then(Value::as_i64) + .is_none_or(|code| code == 0); + messages.extend(UserSourceMessage::from_tool( + format!("codex-call:{call_id}"), + &name, + &arguments, + &result, + success, + )); + } } /// Visit each complete line with its starting byte offset. A line longer than @@ -141,8 +419,15 @@ mod tests { let messages = load_codex_user_source_messages_from_path("codexapp-x", &path).unwrap(); let turn_id = codex_lazy_turn_id(image_record.len() as u64 + 1); assert_eq!( - messages, + messages + .iter() + .filter(|message| message.role == SourceMessageRole::User) + .cloned() + .collect::>(), vec![UserSourceMessage { + role: SourceMessageRole::User, + tool_name: None, + tool_activity: None, id: turn_id.clone(), text: "see https://example.com/a".to_string(), images: vec![transcript_image_ref( @@ -173,6 +458,9 @@ mod tests { assert_eq!( messages, vec![UserSourceMessage { + role: SourceMessageRole::User, + tool_name: None, + tool_activity: None, id: turn_id.clone(), text: String::new(), images: vec![transcript_image_ref("codexapp-x", &turn_id, &inline)], @@ -194,6 +482,191 @@ mod tests { std::fs::remove_dir_all(path.parent().unwrap()).unwrap(); } + #[test] + fn includes_visible_assistant_and_successful_structured_tool_resources_only() { + use serde_json::json; + let records = vec![ + json!({"type":"response_item","payload":{"type":"message","role":"assistant","channel":"analysis","content":[{"type":"output_text","text":"https://hidden.example"}]}}), + json!({"type":"response_item","payload":{"type":"agent_message","author":"/root","recipient":"/root/worker","content":[{"type":"text","text":"https://internal.example"}]}}), + json!({"type":"response_item","payload":{"type":"message","role":"assistant","channel":"final","content":[{"type":"output_text","text":"[Report](/repo/report.md) https://public.example"}]}}), + json!({"type":"event_msg","payload":{"type":"item_completed","item":{"type":"AgentMessage","content":[{"type":"text","text":"[Report](/repo/report.md) https://public.example"}]}}}), + json!({"type":"response_item","payload":{"type":"function_call","call_id":"r1","name":"read_file","arguments":"{\"path\":\"/repo/input.ts\"}"}}), + json!({"type":"response_item","payload":{"type":"function_call_output","call_id":"r1","output":"file body with https://untrusted.example/log"}}), + json!({"type":"response_item","payload":{"type":"function_call","call_id":"r2","name":"read_file","arguments":{"path":"/missing.ts"}}}), + json!({"type":"response_item","payload":{"type":"function_call_output","call_id":"r2","output":{"isError":true}}}), + json!({"type":"event_msg","payload":{"type":"item_completed","item":{"type":"McpToolCall","status":"completed","tool":"create_report","arguments":{},"result":{"content":[{"type":"resource_link","uri":"https://reports.example/result"},{"type":"text","text":"https://untrusted.example/tool-text"}]}}}}), + json!({"type":"event_msg","payload":{"type":"item_completed","item":{"type":"FileChange","status":"completed","changes":{"/repo/output.md":{"type":"add"},"/repo/deleted.md":{"type":"delete"}}}}}), + ]; + let lines: Vec = records.iter().map(Value::to_string).collect(); + let path = write_rollout("conversation", &lines); + let messages = load_codex_user_source_messages_from_path("codexapp-x", &path).unwrap(); + let text = messages + .iter() + .map(|message| message.text.as_str()) + .collect::>() + .join("\n"); + for reference in [ + "/repo/report.md", + "https://public.example", + "/repo/input.ts", + "https://reports.example/result", + ] { + assert!(text.contains(reference), "missing {reference}: {text}"); + } + for excluded in [ + "hidden.example", + "internal.example", + "untrusted.example", + "/missing.ts", + "/repo/deleted.md", + "/repo/output.md", + ] { + assert!(!text.contains(excluded), "unexpected {excluded}: {text}"); + } + assert_eq!( + messages + .iter() + .filter(|message| message.role == SourceMessageRole::Assistant) + .count(), + 2 + ); + assert!(messages + .iter() + .any(|message| message.tool_name.as_deref() == Some("read_file"))); + let patch = messages + .iter() + .find(|message| message.tool_name.as_deref() == Some("apply_patch")) + .unwrap(); + assert!(patch.text.is_empty()); + assert!(patch.tool_activity.is_some()); + // Modern/legacy duplicate visible messages remain compact projections; + // the frontend's source-key projection deduplicates the same resource. + use std::io::Write; + let mut file = std::fs::OpenOptions::new() + .append(true) + .open(&path) + .unwrap(); + writeln!(file, "{}", json!({"type":"event_msg","payload":{"type":"agent_message","message":"https://appended.example/new"}})).unwrap(); + let reread = load_codex_user_source_messages_from_path("codexapp-x", &path).unwrap(); + assert_eq!(reread.len(), messages.len() + 1); + assert!(reread + .last() + .unwrap() + .text + .contains("https://appended.example/new")); + std::fs::remove_dir_all(path.parent().unwrap()).unwrap(); + } + + #[test] + fn counts_failed_app_calls_and_typed_web_actions_once_across_provider_projections() { + use crate::sources::imported_history::user_sources::{ + ToolActivityKind, ToolActivityStatus, + }; + use serde_json::json; + let terminal_error = "No app terminal session is attached to this thread yet."; + let records = vec![ + json!({"type":"response_item","payload":{"type":"function_call","namespace":"mcp__codex_app","name":"read_thread_terminal","call_id":"terminal-1","arguments":"{}"}}), + json!({"type":"event_msg","payload":{"type":"item_completed","item":{"type":"McpToolCall","id":"terminal-1","server":"codex_app","tool":"read_thread_terminal","arguments":{},"status":"failed","result":{"content":[{"type":"text","text":terminal_error}],"isError":true}}}}), + json!({"type":"response_item","payload":{"type":"function_call_output","call_id":"terminal-1","output":{"isError":true,"content":[{"type":"text","text":terminal_error}]}}}), + json!({"type":"response_item","payload":{"type":"function_call","namespace":"web","name":"run","call_id":"web-1","arguments":{"search_query":[{"q":"explicit query"}],"open":[{"ref_id":"https://docs.example/page"}]}}}), + json!({"type":"response_item","payload":{"type":"function_call_output","call_id":"web-1","output":{"content":[{"type":"resource_link","uri":"https://resource.example/report"}]}}}), + json!({"type":"event_msg","payload":{"type":"item_completed","item":{"type":"DynamicToolCall","id":"web-1","name":"web.run","arguments":{"search_query":[{"q":"explicit query"}],"open":[{"ref_id":"https://docs.example/page"}]},"status":"completed","result":{}}}}), + json!({"type":"event_msg","payload":{"type":"item_completed","item":{"type":"Extension","kind":"web.search","id":"nested-search","query":"shortened...","action":{"type":"search","query":null,"queries":["first full query","second full query"]},"results":[]}}}), + json!({"type":"event_msg","payload":{"type":"item_completed","item":{"type":"Extension","kind":"web.search","id":"nested-open","query":"https://learn.example/docs","action":{"type":"openPage","url":"https://learn.example/docs"},"results":[]}}}), + json!({"type":"response_item","payload":{"type":"function_call","namespace":"collaboration","name":"send_message","call_id":"internal","arguments":{}}}), + json!({"type":"response_item","payload":{"type":"function_call_output","call_id":"internal","output":{}}}), + ]; + let path = write_rollout( + "activities", + &records.iter().map(Value::to_string).collect::>(), + ); + let messages = load_codex_user_source_messages_from_path("codexapp-x", &path).unwrap(); + let activities: Vec<_> = messages + .iter() + .filter_map(|message| message.tool_activity.as_ref()) + .collect(); + assert_eq!(activities.len(), 4); + assert!(messages + .iter() + .any(|message| message.text.contains("https://resource.example/report"))); + let terminal = activities + .iter() + .find(|activity| activity.group == "codex-app") + .unwrap(); + assert_eq!(terminal.call_id, "codex-call:terminal-1"); + assert_eq!(terminal.status, ToolActivityStatus::Error); + assert_eq!(terminal.error.as_deref(), Some(terminal_error)); + assert_eq!(terminal.actions[0].kind, ToolActivityKind::ReadTerminal); + let web = activities + .iter() + .find(|activity| activity.call_id == "codex-call:web-1") + .unwrap(); + assert_eq!(web.group, "web"); + assert_eq!(web.actions[0].query.as_deref(), Some("explicit query")); + assert_eq!( + web.actions[1].url.as_deref(), + Some("https://docs.example/page") + ); + let nested = activities + .iter() + .find(|activity| activity.call_id == "codex-call:nested-search") + .unwrap(); + assert_eq!(nested.actions.len(), 2); + assert_eq!(nested.actions[0].query.as_deref(), Some("first full query")); + assert_eq!( + activities + .iter() + .find(|activity| activity.call_id == "codex-call:nested-open") + .unwrap() + .actions[0] + .url + .as_deref(), + Some("https://learn.example/docs") + ); + assert_eq!( + load_codex_user_source_messages_from_path("codexapp-x", &path).unwrap(), + messages + ); + std::fs::remove_dir_all(path.parent().unwrap()).unwrap(); + } + + #[test] + fn bounds_pending_tool_calls_and_keeps_failed_activity_without_resources() { + use serde_json::json; + let mut calls = VecDeque::new(); + let mut messages = Vec::new(); + for index in 0..=MAX_PENDING_SOURCE_CALLS { + collect_codex_conversation_sources( + &json!({"type":"function_call","call_id":index.to_string(),"name":"read_file","arguments":{"path":"/repo/file.ts"}}), + "call", + &mut calls, + &mut messages, + ); + } + assert_eq!(calls.len(), MAX_PENDING_SOURCE_CALLS); + collect_codex_conversation_sources( + &json!({"type":"function_call_output","call_id":"0","output":{}}), + "evicted", + &mut calls, + &mut messages, + ); + collect_codex_conversation_sources( + &json!({"type":"function_call_output","call_id":"1","output":"Error: file not found"}), + "failed", + &mut calls, + &mut messages, + ); + assert_eq!(messages.len(), 1); + assert!(messages[0].text.is_empty()); + collect_codex_conversation_sources( + &json!({"type":"function_call_output","call_id":"2","output":{}}), + "ok", + &mut calls, + &mut messages, + ); + assert_eq!(messages.len(), 2); + } + #[test] fn skips_oversized_lines_without_losing_offsets() { let mut reader = std::io::Cursor::new(format!("{}\nab\ncd", "x".repeat(40))); diff --git a/src-tauri/crates/orgtrack-core/src/sources/imported_history/tool_activity.rs b/src-tauri/crates/orgtrack-core/src/sources/imported_history/tool_activity.rs new file mode 100644 index 0000000000..5c6a287ec6 --- /dev/null +++ b/src-tauri/crates/orgtrack-core/src/sources/imported_history/tool_activity.rs @@ -0,0 +1,186 @@ +//! Bounded metadata describing explicit tool operations, never raw tool logs. +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ToolActivity { + pub call_id: String, + pub tool_name: String, + pub group: String, + pub status: ToolActivityStatus, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + pub actions: Vec, +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ToolActivityStatus { + Success, + Error, +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ToolActivityKind { + Search, + Open, + ReadTerminal, + Generic, +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ToolActivityAction { + pub kind: ToolActivityKind, + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, +} +fn bounded(value: &str, limit: usize) -> String { + value.chars().take(limit).collect() +} +fn error_summary(result: &Value) -> Option { + let explicit = result + .pointer("/error/message") + .or_else(|| result.get("error")) + .and_then(Value::as_str) + .or_else(|| result.get("message").and_then(Value::as_str)); + let mcp_error = result + .get("isError") + .or_else(|| result.get("is_error")) + .and_then(Value::as_bool) + == Some(true); + let text = explicit.or_else(|| { + if !mcp_error { + return None; + } + let content = result.get("content")?; + content.as_str().or_else(|| { + content.as_array()?.iter().find_map(|part| { + (part.get("type")?.as_str()? == "text") + .then(|| part.get("text")?.as_str()) + .flatten() + }) + }) + }); + text.map(|value| bounded(value, 512)) + .filter(|value| !value.is_empty()) +} + +impl ToolActivity { + pub(super) fn project( + call_id: &str, + tool_name: &str, + args: &Value, + result: &Value, + success: bool, + ) -> Self { + let lower = tool_name.to_ascii_lowercase(); + let group = if lower.starts_with("web.") + || lower.starts_with("web__") + || lower.contains("__web__") + || matches!( + lower.as_str(), + "web_search" | "websearch" | "web_fetch" | "webfetch" + ) { + "web".to_string() + } else if lower.contains("codex_app") || lower.contains("chatgpt_app_tools") { + "codex-app".to_string() + } else if let Some(namespace) = lower + .strip_prefix("mcp__") + .and_then(|name| name.split("__").next()) + .and_then(|namespace| namespace.split('.').next()) + { + format!("mcp:{namespace}") + } else { + lower.split('.').next().unwrap_or(&lower).to_string() + }; + let mut actions = Vec::new(); + if group == "web" { + for key in ["search_query", "queries"] { + if let Some(items) = args.get(key).and_then(Value::as_array) { + for item in items.iter().take(32) { + if actions.len() >= 32 { + break; + } + if let Some(query) = item + .get("q") + .or_else(|| item.get("query")) + .and_then(Value::as_str) + .or_else(|| item.as_str()) + { + actions.push(ToolActivityAction { + kind: ToolActivityKind::Search, + query: Some(bounded(query, 2048)), + url: None, + }); + } + } + } + } + if actions.is_empty() { + if let Some(query) = args + .get("query") + .or_else(|| args.get("q")) + .and_then(Value::as_str) + { + actions.push(ToolActivityAction { + kind: ToolActivityKind::Search, + query: Some(bounded(query, 2048)), + url: None, + }); + } + } + if let Some(items) = args.get("open").and_then(Value::as_array) { + for item in items.iter().take(32) { + if actions.len() >= 32 { + break; + } + if let Some(url) = item + .get("ref_id") + .or_else(|| item.get("url")) + .and_then(Value::as_str) + { + actions.push(ToolActivityAction { + kind: ToolActivityKind::Open, + query: None, + url: Some(bounded(url, 2048)), + }); + } + } + } + if actions.is_empty() { + if let Some(url) = args.get("url").and_then(Value::as_str) { + actions.push(ToolActivityAction { + kind: ToolActivityKind::Open, + query: None, + url: Some(bounded(url, 2048)), + }); + } + } + } + if actions.is_empty() { + actions.push(ToolActivityAction { + kind: if lower.ends_with("read_thread_terminal") || lower.ends_with("read_terminal") + { + ToolActivityKind::ReadTerminal + } else { + ToolActivityKind::Generic + }, + query: None, + url: None, + }); + } + Self { + call_id: call_id.to_string(), + tool_name: bounded(tool_name, 256), + group: bounded(&group, 256), + status: if success { + ToolActivityStatus::Success + } else { + ToolActivityStatus::Error + }, + error: if success { None } else { error_summary(result) }, + actions, + } + } +} diff --git a/src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources.rs b/src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources.rs index bce92a34ed..703b1d1791 100644 --- a/src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources.rs +++ b/src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources.rs @@ -1,17 +1,20 @@ -//! User-message sources: the parts of a session's user messages that a +//! Conversation sources: explicitly referenced resources from visible messages that a //! Sources list reads — lines that can carry a web reference, and image //! references a thumbnail loads on demand. //! -//! Readers hand every user message of a session through -//! [`UserSourceMessage::new`], which keeps the payload small: prose without a -//! `://` never crosses IPC, embedded pill context (terminal output, page +//! Readers hand visible messages and successful structured tool resources through +//! [`UserSourceMessage`], retaining its historical wire name. Prose without a +//! reference never crosses IPC, embedded pill context (terminal output, page //! snapshots) is cut, and inline image bytes are never included. use core_types::activity::ActivityChunk; use serde::{Deserialize, Serialize}; use serde_json::Value; -use super::{strip_generated_prompt_context, FUNCTION_USER_MESSAGE}; +use super::{strip_generated_prompt_context, FUNCTION_ASSISTANT, FUNCTION_USER_MESSAGE}; +#[path = "tool_activity.rs"] +mod tool_activity; +pub use tool_activity::{ToolActivity, ToolActivityAction, ToolActivityKind, ToolActivityStatus}; /// Pills whose `::payload` holds their web destination (a GitHub card's URL); /// every other payload follows a destination that is already in the path. @@ -20,11 +23,27 @@ const TRANSCRIPT_IMAGE_PREFIX: &str = "orgii-transcript-image:"; /// A longer line (minified logs, pasted blobs) keeps only its URL-bearing words. const MAX_REFERENCE_LINE_BYTES: usize = 16 * 1024; +/// Wire provenance, separate from whether an agent actually read a resource. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SourceMessageRole { + #[default] + User, + Assistant, + Tool, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserSourceMessage { pub id: String, - /// The message's lines that contain `://`, in order. + #[serde(default)] + pub role: SourceMessageRole, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tool_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tool_activity: Option, + /// Reference-bearing lines and explicit attachment envelope entries, in order. pub text: String, /// Paths, web URLs, or transcript refs; never `data:` URLs. pub images: Vec, @@ -37,13 +56,33 @@ impl UserSourceMessage { text: &str, images: impl IntoIterator, ) -> Option { - let text = reference_text(&strip_generated_prompt_context(text)); + Self::with_role(id, text, images, SourceMessageRole::User) + } + + pub fn with_role( + id: impl Into, + text: &str, + images: impl IntoIterator, + role: SourceMessageRole, + ) -> Option { + let text = reference_text_visible( + &strip_generated_prompt_context(text), + role == SourceMessageRole::Assistant, + ); let images: Vec = images .into_iter() - .filter(|image| !image.is_empty() && !image.starts_with("data:")) + .filter(|image| { + !image.is_empty() + && !image.starts_with("data:") + && image.len() <= MAX_REFERENCE_LINE_BYTES + }) + .take(256) .collect(); (!text.is_empty() || !images.is_empty()).then(|| Self { id: id.into(), + role, + tool_name: None, + tool_activity: None, text, images, }) @@ -62,34 +101,347 @@ pub fn transcript_image_ref(session_id: &str, turn_id: &str, original: &str) -> ) } -/// Sources of the canonical user chunks in a replayed chunk stream. +/// Successful tool resources are taken only from explicit structured fields. +/// Text output, shell commands, logs and embedded document contents are never scanned. +impl UserSourceMessage { + pub fn from_tool( + id: impl Into, + tool_name: &str, + args: &Value, + result: &Value, + success: bool, + ) -> Option { + if pending_tool_result(result) { + return None; + } + let id = id.into(); + let success = success && !failed_tool_result(result); + let activity = ToolActivity::project(&id, tool_name, args, result, success); + if !success { + return Some(Self::tool_activity_only(id, tool_name, activity)); + } + let mut refs = Vec::new(); + let mut remaining_nodes = 2048; + collect_tool_resources(result, &mut refs, 0, &mut remaining_nodes); + // Provider wrappers often carry JSON as output; decode JSON only, never prose. + for field in ["output", "observation"] { + if let Some(encoded) = result.get(field).and_then(Value::as_str) { + if encoded.len() <= 256 * 1024 { + if let Ok(value) = serde_json::from_str::(encoded) { + if failed_tool_result(&value) { + let activity = + ToolActivity::project(&id, tool_name, args, &value, false); + return Some(Self::tool_activity_only(id, tool_name, activity)); + } + collect_tool_resources(&value, &mut refs, 0, &mut remaining_nodes); + } + } + } + } + let name = tool_name + .rsplit("__") + .next() + .unwrap_or(tool_name) + .rsplit('.') + .next() + .unwrap_or(tool_name) + .to_ascii_lowercase(); + if matches!( + name.as_str(), + "read" | "read_file" | "view_image" | "open_file" + ) { + for field in ["file_path", "filePath", "path", "filename"] { + if let Some(path) = args.get(field).and_then(Value::as_str) { + push_tool_reference(path, false, &mut refs); + } + } + } + if matches!( + name.as_str(), + "attach_artifact" | "read_resource" | "fetch" | "web_fetch" + ) { + for field in ["url", "uri"] { + if let Some(url) = args.get(field).and_then(Value::as_str) { + push_tool_reference(url, true, &mut refs); + } + } + } + let mut message = Self::tool_activity_only(id, tool_name, activity); + message.text = reference_text(&refs.join("\n")); + Some(message) + } + fn tool_activity_only(id: String, tool_name: &str, activity: ToolActivity) -> Self { + Self { + id, + role: SourceMessageRole::Tool, + tool_name: Some(tool_name.chars().take(256).collect()), + tool_activity: Some(activity), + text: String::new(), + images: Vec::new(), + } + } +} + +fn pending_tool_result(value: &Value) -> bool { + matches!( + value.get("status").and_then(Value::as_str), + Some("pending" | "running" | "in_progress") + ) +} +fn failed_tool_result(value: &Value) -> bool { + value + .get("isError") + .or_else(|| value.get("is_error")) + .and_then(Value::as_bool) + == Some(true) + || value.get("success").and_then(Value::as_bool) == Some(false) + || matches!( + value.get("status").and_then(Value::as_str), + Some("failed" | "error" | "cancelled" | "canceled" | "pending" | "interrupted") + ) +} + +fn push_tool_reference(value: &str, is_url: bool, refs: &mut Vec) { + if refs.len() >= 256 + || value.is_empty() + || value.len() > 8192 + || value.contains(['\n', '\r', '[', ']']) + || value.starts_with("data:") + { + return; + } + let web = value.starts_with("https://") || value.starts_with("http://"); + if is_url && !web && !value.starts_with("file://") { + return; + } + let kind = if web { "link" } else { "file" }; + let label = value + .rsplit(['/', '\\']) + .next() + .filter(|part| !part.is_empty()) + .unwrap_or("resource"); + let line = format!("{label} [{kind}:{value}]"); + if !refs.contains(&line) { + refs.push(line); + } +} + +fn collect_tool_resources( + value: &Value, + refs: &mut Vec, + depth: usize, + remaining_nodes: &mut usize, +) { + if depth > 5 || refs.len() >= 256 || *remaining_nodes == 0 { + return; + } + *remaining_nodes -= 1; + let Some(object) = value.as_object() else { + if let Some(items) = value.as_array() { + for item in items.iter().take(256) { + collect_tool_resources(item, refs, depth + 1, remaining_nodes); + } + } + return; + }; + if failed_tool_result(value) { + return; + } + // Output fields explicitly describe a produced/read resource; generic `path` + // is accepted only inside a typed resource/artifact envelope below. + for field in [ + "output_path", + "outputPath", + "file_path", + "filePath", + "artifact_path", + ] { + if let Some(path) = object.get(field).and_then(Value::as_str) { + push_tool_reference(path, false, refs); + } + } + for field in [ + "url", + "uri", + "artifact_url", + "download_url", + "pr_url", + "prUrl", + ] { + if let Some(url) = object.get(field).and_then(Value::as_str) { + push_tool_reference(url, true, refs); + } + } + if matches!( + object.get("type").and_then(Value::as_str), + Some("resource_link" | "resource" | "artifact" | "file") + ) { + if let Some(path) = object.get("path").and_then(Value::as_str) { + push_tool_reference(path, false, refs); + } + } + for field in [ + "resource", + "resources", + "artifact", + "artifacts", + "structuredContent", + ] { + if let Some(child) = object.get(field) { + if failed_tool_result(child) { + continue; + } + if let Some(path) = child.get("path").and_then(Value::as_str) { + push_tool_reference(path, false, refs); + } + if matches!(field, "artifacts" | "resources") { + if let Some(items) = child.as_array() { + for item in items.iter().take(256) { + if !failed_tool_result(item) { + if let Some(path) = item.get("path").and_then(Value::as_str) { + push_tool_reference(path, false, refs); + } + } + } + } + } + collect_tool_resources(child, refs, depth + 1, remaining_nodes); + } + } + // MCP content text is deliberately excluded; only typed resource blocks count. + if let Some(content) = object.get("content").and_then(Value::as_array) { + for block in content.iter().take(256) { + if matches!( + block.get("type").and_then(Value::as_str), + Some("resource_link" | "resource") + ) { + collect_tool_resources(block, refs, depth + 1, remaining_nodes); + } + } + } +} + +/// Sources from canonical visible messages and completed tools in replay order. +/// Kept under the historical function name to avoid churning provider imports. pub fn user_source_messages_from_chunks(chunks: &[ActivityChunk]) -> Vec { chunks .iter() - .filter(|chunk| chunk.function == FUNCTION_USER_MESSAGE) - .filter_map(|chunk| { - let text = chunk + .filter_map(source_message_from_chunk) + .collect() +} + +pub fn source_message_from_chunk(chunk: &ActivityChunk) -> Option { + if chunk.broadcast_only || chunk.result.get("is_delta").and_then(Value::as_bool) == Some(true) { + return None; + } + let images = chunk + .result + .get("images") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + .map(str::to_string); + if chunk.function == FUNCTION_USER_MESSAGE { + return UserSourceMessage::new( + &chunk.chunk_id, + chunk .result .pointer("/message/content") .and_then(Value::as_str) - .unwrap_or_default(); - let images = chunk - .result - .get("images") - .and_then(Value::as_array) - .into_iter() - .flatten() - .filter_map(Value::as_str) - .map(str::to_string); - UserSourceMessage::new(chunk.chunk_id.clone(), text, images) - }) - .collect() + .unwrap_or_default(), + images, + ); + } + if chunk.function == FUNCTION_ASSISTANT || chunk.action_type == "assistant" { + let text = chunk + .result + .get("content") + .or_else(|| chunk.result.get("observation")) + .or_else(|| chunk.result.pointer("/message/content")) + .and_then(Value::as_str) + .unwrap_or_default(); + return UserSourceMessage::with_role( + &chunk.chunk_id, + text, + images, + SourceMessageRole::Assistant, + ); + } + if chunk.action_type != "tool_call" { + return None; + } + let success = chunk.result.get("success").and_then(Value::as_bool) == Some(true) + || chunk.result.get("status").and_then(Value::as_str) == Some("completed"); + if pending_tool_result(&chunk.result) || (!success && !failed_tool_result(&chunk.result)) { + return None; + } + let tool = chunk + .result + .get("raw_tool_name") + .and_then(Value::as_str) + .unwrap_or(&chunk.function); + UserSourceMessage::from_tool( + chunk + .result + .get("call_id") + .and_then(Value::as_str) + .unwrap_or(&chunk.chunk_id), + tool, + &chunk.args, + &chunk.result, + success, + ) } pub fn reference_text(text: &str) -> String { + reference_text_visible(text, false) +} + +fn reference_text_visible(text: &str, skip_code: bool) -> String { let mut kept: Vec = Vec::new(); + let mut attachment_envelope = false; + let mut fence: Option<(char, usize)> = None; for line in text.lines() { - if !line.contains("://") { + if kept.len() >= 256 { + break; + } + let trimmed = line.trim(); + if skip_code { + let marker = trimmed.chars().next().filter(|c| *c == '`' || *c == '~'); + let count = marker + .map(|c| trimmed.chars().take_while(|v| *v == c).count()) + .unwrap_or(0); + if let Some((kind, length)) = fence { + if marker == Some(kind) && count >= length && trimmed[count..].trim().is_empty() { + fence = None; + } + continue; + } + if count >= 3 { + fence = marker.map(|c| (c, count)); + continue; + } + } + let heading = trimmed.trim_start_matches('#').trim(); + if heading.eq_ignore_ascii_case("Files mentioned by the user:") { + attachment_envelope = true; + kept.push(line.to_string()); + continue; + } + if attachment_envelope + && (heading.eq_ignore_ascii_case("My request:") + || heading.eq_ignore_ascii_case("My request for Codex:")) + { + attachment_envelope = false; + kept.push(line.to_string()); + continue; + } + let file_reference = line.contains("[file:") + || line.contains("[folder:") + || line.contains("](") + || (attachment_envelope && trimmed.starts_with("##") && line.contains(": ")); + if !line.contains("://") && !file_reference { continue; } let line = strip_pill_payloads(line); @@ -100,6 +452,10 @@ pub fn reference_text(text: &str) -> String { let words: Vec<&str> = line .split_whitespace() .filter(|word| word.contains("://") && word.len() <= MAX_REFERENCE_LINE_BYTES) + .scan(0usize, |bytes, word| { + *bytes += word.len() + 1; + (*bytes <= MAX_REFERENCE_LINE_BYTES).then_some(word) + }) .collect(); if !words.is_empty() { kept.push(words.join(" ")); @@ -148,92 +504,5 @@ fn strip_pill_payloads(line: &str) -> String { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn keeps_only_reference_lines_and_cuts_context_payloads() { - let text = "please review\nsee pr [link:https://example.com/a] now\n\ - page [browser:browser://https://example.com/b/1726000000000::SGVsbG8=]\n\ - log [terminal:terminal://t1::QUJD] done\n\ - card [pr:pr://org/repo/1::eyJwclVybCI6MX0=]"; - assert_eq!( - reference_text(text), - "see pr [link:https://example.com/a] now\n\ - page [browser:browser://https://example.com/b/1726000000000]\n\ - log [terminal:terminal://t1] done\n\ - card [pr:pr://org/repo/1::eyJwclVybCI6MX0=]" - ); - } - - #[test] - fn drops_urls_inside_generated_prompt_context() { - let message = UserSourceMessage::new( - "m", - "\nURL: https://page.dev/open\n\n\ - read https://example.com/mine", - Vec::new(), - ) - .unwrap(); - assert_eq!(message.text, "read https://example.com/mine"); - } - - #[test] - fn leaves_ipv6_hosts_and_closed_pills_alone() { - let text = "a [link:http://x] then http://[::1]:3000/path"; - assert_eq!(reference_text(text), text); - } - - #[test] - fn reduces_oversized_lines_to_their_urls() { - let text = format!("{} https://example.com/deep end", "x".repeat(20_000)); - assert_eq!(reference_text(&text), "https://example.com/deep"); - } - - #[test] - fn drops_inline_image_bytes_and_empty_messages() { - assert_eq!( - UserSourceMessage::new("m", "no links here", Vec::new()), - None - ); - let message = UserSourceMessage::new( - "m", - "", - vec![ - "data:image/png;base64,AAAA".to_string(), - "/tmp/shot.png".to_string(), - ], - ) - .unwrap(); - assert_eq!(message.images, vec!["/tmp/shot.png"]); - assert_eq!(message.text, ""); - } - - #[test] - fn wraps_local_image_refs_for_lazy_reads() { - assert_eq!( - transcript_image_ref("s", "t", "/tmp/a.png"), - r#"orgii-transcript-image:["s","t","/tmp/a.png"]"# - ); - assert_eq!( - transcript_image_ref("s", "t", "https://example.com/a.png"), - "https://example.com/a.png" - ); - } - - #[test] - fn reads_user_chunks_only() { - let mut user = super::super::user_message_chunk("s", "p", 0, "now", "https://a.dev/x"); - user.result["images"] = serde_json::json!(["/tmp/u.png", "data:image/png;base64,A"]); - let assistant = - super::super::assistant_message_chunk("s", "p", 1, "now", "https://b.dev/y"); - assert_eq!( - user_source_messages_from_chunks(&[user, assistant]), - vec![UserSourceMessage { - id: "p-user-0".to_string(), - text: "https://a.dev/x".to_string(), - images: vec!["/tmp/u.png".to_string()], - }] - ); - } -} +#[path = "user_sources_tests.rs"] +mod tests; diff --git a/src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources_tests.rs b/src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources_tests.rs new file mode 100644 index 0000000000..88cb50306c --- /dev/null +++ b/src-tauri/crates/orgtrack-core/src/sources/imported_history/user_sources_tests.rs @@ -0,0 +1,345 @@ +use super::*; + +#[test] +fn structured_tool_resources_and_successful_file_operations_are_sources() { + let result = serde_json::json!({"content":[ + {"type":"resource_link","uri":"https://example.com/report"}, + {"type":"resource","resource":{"uri":"file:///tmp/report.md","text":"private body"}}, + {"type":"text","text":"log https://not-a-source.example"}], + "artifacts":[{"path":"/tmp/output.pdf"}]}); + let message = + UserSourceMessage::from_tool("t", "mcp__report", &serde_json::json!({}), &result, true) + .unwrap(); + assert_eq!(message.role, SourceMessageRole::Tool); + assert_eq!(message.tool_name.as_deref(), Some("mcp__report")); + assert!(message.text.contains("https://example.com/report")); + assert!(message.text.contains("/tmp/output.pdf")); + assert!(message.text.contains("file:///tmp/report.md")); + assert!(!message.text.contains("not-a-source")); + let args = serde_json::json!({"file_path":"/tmp/read.md"}); + assert!( + UserSourceMessage::from_tool("read", "Read", &args, &serde_json::json!({}), true) + .unwrap() + .text + .contains("/tmp/read.md") + ); + assert!( + UserSourceMessage::from_tool("read", "Read", &args, &result, false) + .unwrap() + .text + .is_empty() + ); +} + +#[test] +fn shell_logs_failures_and_oversized_json_never_create_sources() { + let args = serde_json::json!({"cmd":"cat /tmp/not-a-source.md; curl https://hidden.example"}); + let result = serde_json::json!({"output":"read /tmp/not-a-source.md https://hidden.example"}); + assert!( + UserSourceMessage::from_tool("t", "exec_command", &args, &result, true) + .unwrap() + .text + .is_empty() + ); + let failure = serde_json::json!({"isError":true,"url":"https://failure.example"}); + assert!( + UserSourceMessage::from_tool("t", "Read", &args, &failure, true) + .unwrap() + .text + .is_empty() + ); + let large = serde_json::json!({"output":format!("{{\"url\":\"https://hidden.example\",\"body\":\"{}\"}}", "x".repeat(300_000))}); + assert!( + UserSourceMessage::from_tool("t", "report", &args, &large, true) + .unwrap() + .text + .is_empty() + ); + let refs = "https://example.com/ref\n".repeat(1000); + assert_eq!(reference_text(&refs).lines().count(), 256); + assert!( + reference_text(&"https://example.com/ref ".repeat(20_000)).len() + <= MAX_REFERENCE_LINE_BYTES + ); +} + +#[test] +fn assistant_files_prs_and_attached_artifact_args_have_truthful_roles() { + let assistant = super::super::assistant_message_chunk( + "s", + "p", + 0, + "now", + "[PR](https://github.com/example/repo/pull/1) [file](/tmp/report.md)", + ); + let projected = source_message_from_chunk(&assistant).unwrap(); + assert_eq!(projected.role, SourceMessageRole::Assistant); + assert!(projected.text.contains("[file](/tmp/report.md)")); + let args = serde_json::json!({"artifact_type":"pull_request","url":"https://github.com/example/repo/pull/1"}); + assert!(UserSourceMessage::from_tool( + "t", + "mcp__codex_app__attach_artifact", + &args, + &serde_json::json!({"success":true}), + true + ) + .unwrap() + .text + .contains("/pull/1")); + assert!(UserSourceMessage::from_tool( + "t", + "mcp__codex_app__attach_artifact", + &args, + &serde_json::json!({}), + false + ) + .unwrap() + .text + .is_empty()); + let mut hidden = assistant; + hidden.action_type = "thinking".into(); + hidden.function = "thinking".into(); + assert!(source_message_from_chunk(&hidden).is_none()); +} + +#[test] +fn nested_failed_artifacts_and_top_level_failure_cannot_leak_paths() { + let args = serde_json::json!({"path":"/tmp/args.md"}); + for failure in [ + serde_json::json!({"status":"failed"}), + serde_json::json!({"isError":true}), + serde_json::json!({"success":false}), + ] { + assert!( + UserSourceMessage::from_tool("t", "write_file", &args, &failure, true) + .unwrap() + .text + .is_empty() + ); + let mut failed = failure.clone(); + failed["path"] = serde_json::json!("/tmp/failed.md"); + let result = + serde_json::json!({"artifact":failed,"resources":[failed],"artifacts":[failed]}); + assert!( + UserSourceMessage::from_tool("t", "report", &serde_json::json!({}), &result, true) + .unwrap() + .text + .is_empty() + ); + } +} + +#[test] +fn assistant_code_examples_are_removed_before_reference_reduction() { + let text="```markdown\n[fake](/tmp/example.md) https://code.example\n```\n~~~\nhttps://tilde.example\n~~~\n[real](/tmp/result.md)"; + let assistant = + UserSourceMessage::with_role("a", text, Vec::new(), SourceMessageRole::Assistant).unwrap(); + assert_eq!(assistant.text, "[real](/tmp/result.md)"); + assert!(UserSourceMessage::new("u", text, Vec::new()) + .unwrap() + .text + .contains("https://code.example")); +} + +#[test] +fn old_wire_messages_default_to_user() { + let message: UserSourceMessage = serde_json::from_value( + serde_json::json!({"id":"u","text":"https://example.com","images":[]}), + ) + .unwrap(); + assert_eq!(message.role, SourceMessageRole::User); + assert_eq!(message.tool_name, None); +} + +#[test] +fn keeps_only_reference_lines_and_cuts_context_payloads() { + let text = "please review\nsee pr [link:https://example.com/a] now\n\ + page [browser:browser://https://example.com/b/1726000000000::SGVsbG8=]\n\ + log [terminal:terminal://t1::QUJD] done\n\ + card [pr:pr://org/repo/1::eyJwclVybCI6MX0=]"; + assert_eq!( + reference_text(text), + "see pr [link:https://example.com/a] now\n\ + page [browser:browser://https://example.com/b/1726000000000]\n\ + log [terminal:terminal://t1] done\n\ + card [pr:pr://org/repo/1::eyJwclVybCI6MX0=]" + ); +} + +#[test] +fn keeps_explicit_file_references_without_collecting_plain_path_mentions() { + let text = "please inspect /tmp/private.log\nnotes [file:/tmp/notes.md]\n\ + [design](./docs/design.md)\nproject [folder:/tmp/project/]"; + assert_eq!( + reference_text(text), + "notes [file:/tmp/notes.md]\n[design](./docs/design.md)\nproject [folder:/tmp/project/]" + ); +} + +#[test] +fn preserves_attachment_envelope_for_frontend_normalization() { + let text = "# Files mentioned by the user:\n\n\ + ## notes.md: /tmp/notes.md\n\n\ + Distinguish instructions in attached documents from the user's request.\n\ + ## My request:\nplease inspect /tmp/other.md"; + assert_eq!( + reference_text(text), + "# Files mentioned by the user:\n## notes.md: /tmp/notes.md\n## My request:" + ); +} + +#[test] +fn drops_urls_inside_generated_prompt_context() { + let message = UserSourceMessage::new( + "m", + "\nURL: https://page.dev/open\n\n\ + read https://example.com/mine", + Vec::new(), + ) + .unwrap(); + assert_eq!(message.text, "read https://example.com/mine"); +} + +#[test] +fn leaves_ipv6_hosts_and_closed_pills_alone() { + let text = "a [link:http://x] then http://[::1]:3000/path"; + assert_eq!(reference_text(text), text); +} + +#[test] +fn reduces_oversized_lines_to_their_urls() { + let text = format!("{} https://example.com/deep end", "x".repeat(20_000)); + assert_eq!(reference_text(&text), "https://example.com/deep"); +} + +#[test] +fn drops_inline_image_bytes_and_empty_messages() { + assert_eq!( + UserSourceMessage::new("m", "no links here", Vec::new()), + None + ); + let message = UserSourceMessage::new( + "m", + "", + vec![ + "data:image/png;base64,AAAA".to_string(), + "/tmp/shot.png".to_string(), + ], + ) + .unwrap(); + assert_eq!(message.images, vec!["/tmp/shot.png"]); + assert_eq!(message.text, ""); +} + +#[test] +fn wraps_local_image_refs_for_lazy_reads() { + assert_eq!( + transcript_image_ref("s", "t", "/tmp/a.png"), + r#"orgii-transcript-image:["s","t","/tmp/a.png"]"# + ); + assert_eq!( + transcript_image_ref("s", "t", "https://example.com/a.png"), + "https://example.com/a.png" + ); +} + +#[test] +fn reads_user_and_assistant_chunks_with_provenance() { + let mut user = super::super::user_message_chunk("s", "p", 0, "now", "https://a.dev/x"); + user.result["images"] = serde_json::json!(["/tmp/u.png", "data:image/png;base64,A"]); + let assistant = super::super::assistant_message_chunk("s", "p", 1, "now", "https://b.dev/y"); + assert_eq!( + user_source_messages_from_chunks(&[user, assistant]), + vec![ + UserSourceMessage::new( + "p-user-0", + "https://a.dev/x", + vec!["/tmp/u.png".to_string()] + ) + .unwrap(), + UserSourceMessage::with_role( + "p-asst-1", + "https://b.dev/y", + Vec::new(), + SourceMessageRole::Assistant + ) + .unwrap(), + ] + ); +} + +#[test] +fn failed_terminal_activity_and_web_actions_are_bounded_and_explicit() { + let failed=UserSourceMessage::from_tool("call-1","mcp__codex_app.read_thread_terminal",&serde_json::json!({}), + &serde_json::json!({"isError":true,"content":[{"type":"text","text":"No app terminal session is attached to this thread yet."}]}),false).unwrap(); + assert!(failed.text.is_empty() && failed.images.is_empty()); + let activity = failed.tool_activity.unwrap(); + assert_eq!(activity.group, "codex-app"); + assert_eq!(activity.call_id, "call-1"); + assert_eq!(activity.status, ToolActivityStatus::Error); + assert_eq!( + activity.error.as_deref(), + Some("No app terminal session is attached to this thread yet.") + ); + assert_eq!(activity.actions[0].kind, ToolActivityKind::ReadTerminal); + let web=UserSourceMessage::from_tool("web-1","web.run",&serde_json::json!({"search_query":[{"q":"search topic"}],"open":[{"ref_id":"https://example.com"}]}),&serde_json::json!({}),true).unwrap(); + let activity = web.tool_activity.unwrap(); + assert_eq!(activity.group, "web"); + assert_eq!(activity.actions.len(), 2); + assert_eq!(activity.actions[0].query.as_deref(), Some("search topic")); + assert_eq!( + activity.actions[1].url.as_deref(), + Some("https://example.com") + ); + let many = UserSourceMessage::from_tool( + "web-many", + "web.run", + &serde_json::json!({"search_query":vec![serde_json::json!({"q":"x".repeat(6000)});100]}), + &serde_json::json!({"error":{"message":"e".repeat(10000)}}), + false, + ) + .unwrap() + .tool_activity + .unwrap(); + assert_eq!(many.actions.len(), 32); + assert_eq!(many.actions[0].query.as_ref().unwrap().len(), 2048); + assert_eq!(many.error.unwrap().len(), 512); +} + +#[test] +fn unresolved_tools_are_not_completed_activity() { + let mut chunk = core_types::activity::ActivityChunk::new("s", "tool_call", "read_file"); + chunk.args = serde_json::json!({"path":"/tmp/file.md"}); + chunk.result = serde_json::json!({"status":"pending","success":false}); + assert!(source_message_from_chunk(&chunk).is_none()); + chunk.result = serde_json::json!({"status":"failed","success":false,"call_id":"stable-call"}); + let activity = source_message_from_chunk(&chunk) + .unwrap() + .tool_activity + .unwrap(); + assert_eq!(activity.call_id, "stable-call"); +} + +#[test] +fn file_mutation_arguments_are_not_conversation_resources() { + for tool in [ + "Write", + "Edit", + "MultiEdit", + "write_file", + "edit_file", + "edit_file_by_replace", + "apply_patch", + ] { + let message = UserSourceMessage::from_tool( + "change", + tool, + &serde_json::json!({"file_path":"/repo/changed.ts", "path":"/repo/changed.ts"}), + &serde_json::json!({"success":true}), + true, + ) + .unwrap(); + assert!(message.text.is_empty(), "{tool}"); + assert!(message.tool_activity.is_some(), "{tool}"); + } +} diff --git a/src-tauri/crates/session-persistence/src/lib.rs b/src-tauri/crates/session-persistence/src/lib.rs index 718dcfaa8f..687bab2010 100644 --- a/src-tauri/crates/session-persistence/src/lib.rs +++ b/src-tauri/crates/session-persistence/src/lib.rs @@ -84,3 +84,6 @@ pub use commands::{ pub(crate) static ORGII_HOME_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); mod auxiliary_usage; + +mod source_messages; +pub use source_messages::load_stored_source_messages; diff --git a/src-tauri/crates/session-persistence/src/source_messages.rs b/src-tauri/crates/session-persistence/src/source_messages.rs new file mode 100644 index 0000000000..3a3f3293fa --- /dev/null +++ b/src-tauri/crates/session-persistence/src/source_messages.rs @@ -0,0 +1,275 @@ +//! Source projection for native sessions. Uses the same canonical user filter +//! as the turn index, plus visible assistant messages and finalized tool activity. +use std::collections::HashMap; + +use core_types::activity::ActivityChunk; +use orgtrack_core::sources::imported_history::user_sources::{ + source_message_from_chunk, UserSourceMessage, +}; +use rusqlite::{params, Connection, Result}; +use serde_json::{json, Value}; + +use crate::turn_index::{load_stored_user_messages, StoredUserMessage}; + +pub fn load_stored_source_messages(session_id: &str) -> Result> { + // Retain the turn index's backfill and synthetic/internal user exclusions. + let users = load_stored_user_messages(session_id)?; + let conn = crate::connection::get_connection()?; + stored_source_messages(&conn, session_id, users) +} + +fn stored_source_messages( + conn: &Connection, + session_id: &str, + users: Vec, +) -> Result> { + let mut users: HashMap<_, _> = users + .into_iter() + .map(|user| (user.id.clone(), user)) + .collect(); + // Keep structured JSON bounded before crossing SQLite -> Rust; inline blobs + // and arbitrary huge tool logs have no place in the source registry. + let mut stmt = conn.prepare_cached( + "SELECT id, event_type, function_name, + CASE WHEN length(args_json) <= 262144 THEN args_json ELSE '{}' END, + CASE WHEN length(result_json) <= 262144 THEN result_json ELSE '{}' END, + CASE WHEN length(content) <= 262144 THEN content ELSE '' END, + CASE WHEN length(meta_json) <= 262144 THEN meta_json ELSE '{}' END, created_at + FROM events WHERE session_id = ?1 + ORDER BY history_sequence ASC, created_at ASC, id ASC", + )?; + let mut rows = stmt.query(params![session_id])?; + let mut sources = Vec::new(); + while let Some(row) = rows.next()? { + let id: String = row.get(0)?; + if let Some(user) = users.remove(&id) { + if let Some(source) = UserSourceMessage::new(user.id, &user.text, user.images) { + sources.push(source); + } + continue; + } + let event_type: String = row.get(1)?; + let function: Option = row.get(2)?; + let args: String = row.get(3)?; + let result: String = row.get(4)?; + let content: String = row.get(5)?; + let meta: Option = row.get(6)?; + let meta: Value = meta + .as_deref() + .and_then(|s| serde_json::from_str(s).ok()) + .unwrap_or(Value::Null); + if core_types::session_event::is_internal_lifecycle_action_type(&event_type) + || matches!( + function.as_deref(), + Some("thinking" | "reasoning" | "system" | "developer") + ) + || meta.get("isDelta").and_then(Value::as_bool) == Some(true) + || matches!( + meta.get("source").and_then(Value::as_str), + Some("system" | "developer") + ) + { + continue; + } + let is_assistant = event_type == "assistant" + || matches!( + function.as_deref(), + Some("assistant" | "assistant_message" | "agent_message" | "message") + ); + let is_tool = event_type == "tool_call"; + if !is_assistant && !is_tool { + continue; + } + if is_assistant + && (meta + .get("displayVariant") + .and_then(Value::as_str) + .is_some_and(|s| s != "message") + || meta + .get("displayStatus") + .and_then(Value::as_str) + .is_some_and(|s| s != "completed")) + { + continue; + } + if is_tool + && meta + .get("displayStatus") + .and_then(Value::as_str) + .is_some_and(|status| !matches!(status, "completed" | "failed" | "cancelled")) + { + continue; + } + let mut chunk = ActivityChunk::new( + session_id, + if is_assistant { + "assistant" + } else { + "tool_call" + }, + function.as_deref().unwrap_or_default(), + ); + chunk.chunk_id = id; + chunk.args = serde_json::from_str(&args).unwrap_or(Value::Null); + chunk.result = serde_json::from_str(&result).unwrap_or_else(|_| json!({})); + if is_tool { + if !chunk.result.is_object() { + chunk.result = json!({}); + } + if let Some(status) = meta.get("displayStatus").and_then(Value::as_str) { + if matches!(status, "failed" | "cancelled") { + chunk.result["success"] = json!(false); + } else if status == "completed" && chunk.result.get("success").is_none() { + chunk.result["success"] = json!(true); + } + } + if let Some(call_id) = meta.get("callId").and_then(Value::as_str) { + chunk.result["call_id"] = json!(call_id); + } + } + if is_assistant + && chunk + .result + .get("content") + .and_then(Value::as_str) + .is_none() + && !content.is_empty() + { + if !chunk.result.is_object() { + chunk.result = json!({}); + } + chunk.result["content"] = json!(content); + } + if let Some(source) = source_message_from_chunk(&chunk) { + sources.push(source); + } + } + Ok(sources) +} + +#[cfg(test)] +mod tests { + use super::*; + use orgtrack_core::sources::imported_history::user_sources::SourceMessageRole; + + #[test] + fn native_messages_and_successful_tool_resources_keep_order_and_provenance() { + let conn = Connection::open_in_memory().unwrap(); + conn.execute_batch("CREATE TABLE events(id TEXT,session_id TEXT,event_type TEXT,function_name TEXT,args_json TEXT,result_json TEXT,content TEXT,meta_json TEXT,created_at TEXT,history_sequence INTEGER);").unwrap(); + let records = [ + ("u", "raw", "user_message", "{}", "{}", "", "{}"), + ( + "a", + "assistant", + "assistant_message", + "{}", + "{}", + "[PR](https://github.com/example/repo/pull/1) [plan](/tmp/plan.md)", + "{}", + ), + ( + "t", + "tool_call", + "write_file", + r#"{"path":"/tmp/result.md"}"#, + r#"{"success":true}"#, + "", + "{}", + ), + ( + "f", + "tool_call", + "write_file", + r#"{"path":"/tmp/failed.md"}"#, + r#"{"success":false}"#, + "", + "{}", + ), + ( + "terminal", + "tool_call", + "mcp__codex_app.read_thread_terminal", + "{}", + r#"{"isError":true,"content":[{"type":"text","text":"No terminal session attached"}]}"#, + "", + r#"{"displayStatus":"failed","callId":"terminal-call"}"#, + ), + ( + "pending", + "tool_call", + "read_file", + r#"{"path":"/tmp/pending.md"}"#, + r#"{"success":false,"status":"pending"}"#, + "", + r#"{"displayStatus":"running"}"#, + ), + ( + "s", + "system", + "system", + "{}", + "{}", + "https://internal.example", + "{}", + ), + ( + "thinking", + "assistant", + "assistant_message", + "{}", + "{}", + "https://thought.example", + r#"{"displayVariant":"thinking"}"#, + ), + ]; + for (index, (id, kind, function, args, result, content, meta)) in + records.into_iter().enumerate() + { + conn.execute( + "INSERT INTO events VALUES(?1,'s',?2,?3,?4,?5,?6,?7,'now',?8)", + params![id, kind, function, args, result, content, meta, index], + ) + .unwrap(); + } + let sources = stored_source_messages( + &conn, + "s", + vec![StoredUserMessage { + id: "u".into(), + text: "https://input.example".into(), + images: vec![], + }], + ) + .unwrap(); + assert_eq!( + sources + .iter() + .map(|s| (s.id.as_str(), s.role)) + .collect::>(), + vec![ + ("u", SourceMessageRole::User), + ("a", SourceMessageRole::Assistant), + ("t", SourceMessageRole::Tool), + ("f", SourceMessageRole::Tool), + ("terminal-call", SourceMessageRole::Tool) + ] + ); + assert!(sources[1].text.contains("/tmp/plan.md")); + // Successful edit receipts contribute activity, not a resource row. + assert!(sources[2].text.is_empty()); + assert!(sources[2].tool_activity.is_some()); + assert!(sources[3].text.is_empty()); + assert_eq!( + sources[4].tool_activity.as_ref().unwrap().error.as_deref(), + Some("No terminal session attached") + ); + assert_eq!( + sources[4].tool_activity.as_ref().unwrap().call_id, + "terminal-call" + ); + assert_eq!( + sources[3].tool_activity.as_ref().unwrap().status, + orgtrack_core::sources::imported_history::user_sources::ToolActivityStatus::Error + ); + } +} diff --git a/src-tauri/src/orgtrack/history_commands/session_sources.rs b/src-tauri/src/orgtrack/history_commands/session_sources.rs index 0ddb878901..06bcdf4157 100644 --- a/src-tauri/src/orgtrack/history_commands/session_sources.rs +++ b/src-tauri/src/orgtrack/history_commands/session_sources.rs @@ -1,4 +1,4 @@ -//! `session_source_messages`: every user message of one session, reduced to +//! `session_source_messages`: explicit resources of one conversation, reduced to //! what the workstation trail's Sources list reads (reference lines and //! lazily loadable image refs). //! @@ -64,10 +64,6 @@ fn load_session_source_messages(session_id: &str) -> Result; +} + +/** One resource-bearing message as `session_source_messages` returns it. */ export interface SessionSourceMessage { id: string; text: string; images?: readonly string[]; + /** Older backends omit this and represent user messages only. */ + role?: "user" | "assistant" | "tool"; + /** Only successful structured tool references are projected by the backend. */ + toolName?: string; + toolActivity?: SessionToolActivity; } /** - * Every user message of a session, reduced by Rust to the lines that can - * carry a web reference and to image references a thumbnail loads on demand + * Resource-bearing messages of a session, reduced by Rust to the lines that can + * carry an explicit file/web reference and image references a thumbnail loads on demand * (never inline bytes). Reads the session's own history store, so turns that * are not loaded in the chat are included. */ diff --git a/src/engines/ChatPanel/components/SessionWorkstationRail.tsx b/src/engines/ChatPanel/components/SessionWorkstationRail.tsx index a72b62a524..b7597c4f78 100644 --- a/src/engines/ChatPanel/components/SessionWorkstationRail.tsx +++ b/src/engines/ChatPanel/components/SessionWorkstationRail.tsx @@ -6,6 +6,8 @@ import { IMPORTED_HISTORY_SOURCE_DESCRIPTORS } from "@src/api/tauri/externalHist import { formatAgentType } from "@src/assets/providers"; import { getIconProviderFromType } from "@src/components/ModelIcon/config"; import { resolveAgentIcon } from "@src/config/agentIcons"; +import { SessionSourcesFileScope } from "@src/engines/ChatPanel/sessionSources/SessionSourcesFileScope"; +import { openSessionSources } from "@src/engines/ChatPanel/sessionSources/openSessionSources"; import { useSessionSources } from "@src/engines/ChatPanel/sessionSources/useSessionSources"; import { useSubagentSessions } from "@src/engines/Simulator/hooks/useSubagentSessions"; import { useChannelWorkItem } from "@src/features/DiscussionChannels/ChannelPanelView/useChannelWorkItem"; @@ -198,6 +200,7 @@ interface ConnectedSessionWorkstationRailProps extends Omit< /** Session scope shared with the unlinked rail; `workItem` is replaced. */ sessionContext: FocusedChatSessionContext; sources: FocusedChatRailSource[]; + onOpenSources?: () => void; subagentIcon: FocusedChatRailIcon; subagents: FocusedChatRailSubagent[]; workItemId: string; @@ -209,6 +212,7 @@ const ConnectedSessionWorkstationRail: React.FC< compactMenuHost, conversationMinimapHostRef, orgId, + onOpenSources, projectSlug, sessionContext: baseSessionContext, sources, @@ -255,6 +259,7 @@ const ConnectedSessionWorkstationRail: React.FC< conversationMinimapHostRef={conversationMinimapHostRef} sessionContext={sessionContext} sources={sources} + onOpenSources={onOpenSources} subagentIcon={subagentIcon} subagents={subagents} topInset={topInset} @@ -262,7 +267,7 @@ const ConnectedSessionWorkstationRail: React.FC< ); }; -const SessionWorkstationRail: React.FC = ({ +const SessionWorkstationRailContent: React.FC = ({ compactMenuHost, conversationMinimapHostRef, session, @@ -280,6 +285,9 @@ const SessionWorkstationRail: React.FC = ({ ); // Same freshness signal: a new user message advances the session row. const sources = useSessionSources(sessionId, session?.updated_at); + const handleOpenSources = useCallback(() => { + if (sessionId) openSessionSources(sessionId, t("common:git.rail.sources")); + }, [sessionId, t]); // A subagent runs on its parent's harness, so the parent's mark identifies // every child row — resolved through the same projection the sidebar and // chat tab use, which means a Codex session's subagents carry the Codex @@ -325,12 +333,14 @@ const SessionWorkstationRail: React.FC = ({ if (context.workItemId) { return ( = ({ return ( = ({ ); }; -export default SessionWorkstationRail; +export default function SessionWorkstationRail( + props: SessionWorkstationRailProps +) { + return ( + + + + ); +} diff --git a/src/engines/ChatPanel/sessionSources/SessionSourcesFileScope.tsx b/src/engines/ChatPanel/sessionSources/SessionSourcesFileScope.tsx new file mode 100644 index 0000000000..af94354873 --- /dev/null +++ b/src/engines/ChatPanel/sessionSources/SessionSourcesFileScope.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +import { SharedSessionFilesProvider } from "@src/features/Org2Cloud/SharedSessionFilesContext"; +import type { Session } from "@src/store/session"; + +/** Sources also live outside the transcript's shared-file provider. */ +export function SessionSourcesFileScope({ + session, + children, +}: React.PropsWithChildren<{ session: Session | null | undefined }>) { + return ( + + {children} + + ); +} diff --git a/src/engines/ChatPanel/sessionSources/extractSessionSources.test.ts b/src/engines/ChatPanel/sessionSources/extractSessionSources.test.ts index d71add8710..1530357b10 100644 --- a/src/engines/ChatPanel/sessionSources/extractSessionSources.test.ts +++ b/src/engines/ChatPanel/sessionSources/extractSessionSources.test.ts @@ -3,64 +3,69 @@ import { describe, expect, it } from "vitest"; import { extractSessionSources } from "./extractSessionSources"; describe("extractSessionSources", () => { - it("lists images and links newest message first", () => { + it("lists sources newest message first with honest provision provenance", () => { const sources = extractSessionSources([ { id: "u1", text: "look at https://example.com/docs.", - images: ["/Users/me/.orgii/session-images/abc.png"], + images: ["/tmp/abc.png"], }, { id: "u2", text: "and this example.org/page [link:https://example.org/page]", }, ]); - expect(sources).toEqual([ { kind: "link", key: "link:https://example.org/page", url: "https://example.org/page", label: "example.org/page", + messageId: "u2", + origin: "provided-link", }, { kind: "image", - key: "image:/Users/me/.orgii/session-images/abc.png", - ref: "/Users/me/.orgii/session-images/abc.png", + key: "image:/tmp/abc.png", + ref: "/tmp/abc.png", fileName: "abc.png", + messageId: "u1", + origin: "attachment", }, { kind: "link", key: "link:https://example.com/docs", url: "https://example.com/docs", label: "example.com/docs", + messageId: "u1", + origin: "provided-link", }, ]); }); - it("keeps a repeated source only at its most recent position", () => { + it("keeps a repeated source at its most recent position and message", () => { const sources = extractSessionSources([ { id: "u1", text: "https://example.com/a/", images: ["/tmp/shot.png"] }, { id: "u2", text: "again https://example.com/a#intro" }, { id: "u3", text: "", images: ["/tmp/shot.png"] }, ]); - - expect(sources.map((source) => source.key)).toEqual([ - "image:/tmp/shot.png", - "link:https://example.com/a", + expect(sources.map(({ key, messageId }) => [key, messageId])).toEqual([ + ["image:/tmp/shot.png", "u3"], + ["link:https://example.com/a", "u2"], ]); }); - it("reads Codex attachment envelopes and Markdown links like the bubble does", () => { - const shot = "/var/folders/T/Screenshot 2026-09-16 at 10.35.47 PM.png"; + it("projects Codex attachment envelopes, explicit files and folders without duplicating images", () => { + const shot = "/var/folders/T/Screenshot with spaces.png"; const sources = extractSessionSources([ { id: "codex", text: [ "# Files mentioned by the user:", "", - `## Screenshot 2026-09-16 at 10.35.47 PM.png: ${shot}`, - "## notes.md: /Users/me/notes.md", + `## Screenshot with spaces.png: ${shot}`, + "## notes.md: /tmp/notes.md", + "## docs: /tmp/docs/", "", "## My request:", "should we merge [PR 602](https://github.com/org2AI/ORG2/pull/602)", @@ -68,18 +73,49 @@ describe("extractSessionSources", () => { images: [`orgii-transcript-image:${JSON.stringify(["s", "t", shot])}`], }, ]); - expect(sources).toEqual([ expect.objectContaining({ kind: "image", - fileName: "Screenshot 2026-09-16 at 10.35.47 PM.png", + fileName: "Screenshot with spaces.png", + messageId: "codex", }), { + kind: "file", + key: "file:/tmp/notes.md", + path: "/tmp/notes.md", + fileName: "notes.md", + isDirectory: false, + messageId: "codex", + origin: "provided-file", + }, + { + kind: "file", + key: "file:/tmp/docs/", + path: "/tmp/docs/", + fileName: "docs", + isDirectory: true, + messageId: "codex", + origin: "provided-file", + }, + expect.objectContaining({ kind: "link", - key: "link:https://github.com/org2AI/ORG2/pull/602", url: "https://github.com/org2AI/ORG2/pull/602", label: "org2AI/ORG2#602", + }), + ]); + }); + + it("recognizes persisted file pills and Markdown references but never plain path mentions", () => { + const sources = extractSessionSources([ + { + id: "u1", + text: "review /tmp/secret.md and src/config.ts\n[Plan](./docs/plan.md)\nnotes [file:/tmp/notes.md]", }, + { id: "u2", text: "[Notes](/tmp/notes.md)" }, + ]); + expect(sources.map(({ key, messageId }) => [key, messageId])).toEqual([ + ["file:/tmp/notes.md", "u2"], + ["file:./docs/plan.md", "u1"], ]); }); @@ -89,38 +125,252 @@ describe("extractSessionSources", () => { { id: "u1", text: "", images: [inline] }, { id: "u2", text: "", images: [inline] }, ]); - expect(sources).toEqual([ - { kind: "image", key: "image:u2:0", ref: inline, fileName: null }, - { kind: "image", key: "image:u1:0", ref: inline, fileName: null }, + { + kind: "image", + key: "image:u2:0", + ref: inline, + fileName: null, + messageId: "u2", + origin: "attachment", + }, + { + kind: "image", + key: "image:u1:0", + ref: inline, + fileName: null, + messageId: "u1", + origin: "attachment", + }, ]); }); - it("ignores non-web references, credentials, and URLs inside other words", () => { + it("ignores credentials, non-web text, session references and generated context", () => { + expect( + extractSessionSources([ + { + id: "u1", + text: [ + "https://ambient.dev/", + "ftp://example.com/file", + "https://user:secret@example.com/private", + "(https://example.com/wrapped)", + "see-https://example.com/joined", + "Earlier-work [session:sde-12345678-1234-1234-1234-123456789abc]", + ].join("\n"), + }, + ]) + ).toEqual([]); + }); + it("includes assistant PRs, titled documents and local image references with honest provenance", () => { const sources = extractSessionSources([ + { id: "user", text: "please review", images: ["/tmp/input.png"] }, { - id: "u1", - text: [ - "notes.md [file:/Users/me/notes.md]", - "ftp://example.com/file", - "https://user:secret@example.com/private", - "(https://example.com/wrapped)", - "see-https://example.com/joined", - ].join(" "), + id: "assistant", + role: "assistant", + text: "PR: [fix(chat): stabilize actions](https://github.com/org/repo/pull/42)\n[Implementation report](/tmp/implementation.md)\n![Screenshot](/tmp/preview.png)", }, ]); + expect(sources).toEqual([ + expect.objectContaining({ + kind: "link", + label: "fix(chat): stabilize actions", + url: "https://github.com/org/repo/pull/42", + origin: "assistant-reference", + messageId: "assistant", + }), + expect.objectContaining({ + kind: "file", + path: "/tmp/implementation.md", + title: "Implementation report", + fileName: "implementation.md", + origin: "assistant-reference", + }), + expect.objectContaining({ + kind: "image", + ref: "/tmp/preview.png", + origin: "assistant-reference", + }), + expect.objectContaining({ + kind: "image", + ref: "/tmp/input.png", + origin: "attachment", + }), + ]); + expect(sources.filter((source) => source.kind !== "image")).toHaveLength(2); + }); - expect(sources).toEqual([]); + it("includes explicit structured tool references and names their origin without claiming generation", () => { + expect( + extractSessionSources([ + { + id: "tool-1", + role: "tool", + toolName: "read_file", + text: "report.md [file:/tmp/report.md]", + }, + { + id: "tool-2", + role: "tool", + toolName: "web_search", + text: "resource [link:https://example.com/result]", + }, + ]) + ).toEqual([ + expect.objectContaining({ + kind: "link", + url: "https://example.com/result", + origin: "tool-result", + toolName: "web_search", + }), + expect.objectContaining({ + kind: "file", + path: "/tmp/report.md", + origin: "tool-result", + toolName: "read_file", + }), + ]); }); - it("does not count session references as web links", () => { + it("deduplicates across roles while retaining distinct provenance and latest assistant title", () => { const sources = extractSessionSources([ + { id: "u", text: "https://github.com/org/repo/pull/42" }, { - id: "u1", - text: "Earlier-work [session:sde-12345678-1234-1234-1234-123456789abc]", + id: "t", + role: "tool", + toolName: "github", + text: "resource [link:https://github.com/org/repo/pull/42]", + }, + { + id: "a", + role: "assistant", + text: "[Fix source navigation](https://github.com/org/repo/pull/42)", + }, + ]); + expect(sources).toHaveLength(1); + expect(sources[0]).toMatchObject({ + label: "Fix source navigation", + messageId: "a", + origin: "assistant-reference", + origins: ["assistant-reference", "tool-result", "provided-link"], + }); + }); + + it("does not treat assistant code examples, free-form paths or shell transcripts as offered files", () => { + expect( + extractSessionSources([ + { + id: "a", + role: "assistant", + text: "Mention /tmp/not-offered.txt.\n`https://inline.example`\n```sh\ncurl https://example-code.dev\necho '[file](/tmp/example.txt)'\n```", + }, + ]) + ).toEqual([]); + }); + it("accepts the backend's bare structured tool pills including relative paths", () => { + expect( + extractSessionSources([ + { + id: "tool", + role: "tool", + toolName: "read_file", + text: "[file:src/main.ts]\n[link:https://example.com/result]", + }, + ]) + ).toEqual([ + expect.objectContaining({ + kind: "file", + path: "src/main.ts", + fileName: "main.ts", + origin: "tool-result", + toolName: "read_file", + }), + expect.objectContaining({ + kind: "link", + url: "https://example.com/result", + origin: "tool-result", + }), + ]); + }); + it("groups tool activity including failures and deduplicates stable call ids without fake resources", () => { + const sources = extractSessionSources([ + { + id: "old", + role: "tool", + text: "[file:/tmp/stale.md]", + toolActivity: { + callId: "same", + toolName: "web.search", + group: "web", + status: "success", + actions: [{ kind: "search", query: "old query" }], + }, + }, + { + id: "terminal", + role: "tool", + text: "", + toolActivity: { + callId: "terminal", + toolName: "read_thread_terminal", + group: "codex-app", + status: "success", + actions: [{ kind: "read-terminal" }], + }, + }, + { + id: "failed", + role: "tool", + text: "[file:/tmp/never-created.md]", + images: ["/tmp/never-created.png"], + toolActivity: { + callId: "same", + toolName: "web.search", + group: "web", + status: "error", + error: "No results found", + actions: [{ kind: "search", query: "latest query" }], + }, }, ]); + expect(sources).toHaveLength(2); + expect(sources[0]).toMatchObject({ + kind: "tool-group", + group: "web", + operations: [ + { + callId: "same", + status: "error", + error: "No results found", + actions: [{ kind: "search", query: "latest query" }], + }, + ], + }); + expect(sources[1]).toMatchObject({ + kind: "tool-group", + group: "codex-app", + }); + expect(sources.every((source) => source.kind === "tool-group")).toBe(true); + }); - expect(sources).toEqual([]); + it("keeps successful referenced resources alongside their tool activity group", () => { + const sources = extractSessionSources([ + { + id: "tool", + role: "tool", + text: "[link:https://example.com]", + toolActivity: { + callId: "open", + toolName: "web.open", + group: "web", + status: "success", + actions: [{ kind: "open", url: "https://example.com" }], + }, + }, + ]); + expect(sources.map((source) => source.kind)).toEqual([ + "tool-group", + "link", + ]); }); }); diff --git a/src/engines/ChatPanel/sessionSources/extractSessionSources.ts b/src/engines/ChatPanel/sessionSources/extractSessionSources.ts index 37a5b2ae31..9f04a8d6b4 100644 --- a/src/engines/ChatPanel/sessionSources/extractSessionSources.ts +++ b/src/engines/ChatPanel/sessionSources/extractSessionSources.ts @@ -1,15 +1,19 @@ /** - * Session sources — the images and web links a user sent to the agent in - * one session, projected from that session's user messages. + * Session resources explicitly supplied by users, referenced in assistant + * responses, or projected from successful structured tool results, plus grouped + * completed tool activity (including failures). * * Pure text layer behind the workstation trail's Sources section. Links are * read the same way the sent bubble reads them (Codex attachment envelope * stripped, Markdown references and serialized pills parsed, session - * references lifted out), so a row here is always something the bubble shows - * as a link. Bare URLs count only as their own whitespace-delimited word, + * references lifted out). Tool activity comes from explicit backend metadata, + * independently of reference rows. Bare URLs count as a whitespace-delimited word, * matching the composer's typed-URL rule. */ -import type { SessionSourceMessage } from "@src/api/tauri/session/sessionSources"; +import type { + SessionSourceMessage, + SessionToolActivity, +} from "@src/api/tauri/session/sessionSources"; import { parseHttpUrlPill } from "@src/components/ComposerInput/httpUrl"; import { resolvePostedReferenceHref } from "@src/components/ComposerInput/postedReferenceHref"; import { @@ -22,21 +26,55 @@ import { imageRefToRustPath } from "@src/util/file/imageRefs"; export type { SessionSourceMessage }; -export type SessionSource = - | { - kind: "image"; - key: string; - /** Reference as stored on the message (path, asset/data URL, transcript ref). */ - ref: string; - /** File name, or null for an inline image that never had one. */ - fileName: string | null; - } - | { - kind: "link"; - key: string; - url: string; - label: string; - }; +export type SessionSourceOrigin = + | "attachment" + | "provided-link" + | "provided-file" + | "assistant-reference" + | "tool-result"; + +interface SessionSourceProvenance { + /** Latest message explicitly referencing this resource. */ + messageId?: string; + /** Describes provision, never evidence that the agent read the resource. */ + origin?: SessionSourceOrigin; + /** Distinct earlier provenance retained when roles reference the same resource. */ + origins?: SessionSourceOrigin[]; + toolName?: string; +} + +export type SessionSource = SessionSourceProvenance & + ( + | { + kind: "image"; + key: string; + /** Reference as stored on the message (path, asset/data URL, transcript ref). */ + ref: string; + /** File name, or null for an inline image that never had one. */ + fileName: string | null; + } + | { + kind: "link"; + key: string; + url: string; + label: string; + } + | { + kind: "tool-group"; + key: string; + group: string; + operations: SessionToolActivity[]; + } + | { + kind: "file"; + key: string; + path: string; + fileName: string; + /** Human-readable assistant link title, distinct from the filename. */ + title?: string; + isDirectory: boolean; + } + ); const TRAILING_SENTENCE_PUNCTUATION = /[.,;:!?]+$/u; @@ -84,17 +122,25 @@ function bareLinks(text: string): Array<{ url: string; label: string }> { return links; } -function messageLinks( - message: SessionSourceMessage -): Array<{ url: string; label: string }> { +function serializedReferences(message: SessionSourceMessage): SessionSource[] { const normalized = normalizeMarkdownReferencePills( normalizeUserMessageText(message.text, message.images) ); const { text } = projectMarkdownSessionReferences(normalized); - const links: Array<{ url: string; label: string }> = []; + const references: SessionSource[] = []; for (const segment of parseNormalizedUserMessage(text)) { if (segment.kind === "text") { - links.push(...bareLinks(segment.text)); + references.push( + ...bareLinks(segment.text).map( + (link): SessionSource => ({ + kind: "link", + key: linkKey(link.url), + ...link, + messageId: message.id, + origin: "provided-link", + }) + ) + ); continue; } if (segment.kind !== "pill") continue; @@ -105,9 +151,113 @@ function messageLinks( segment.terminalText ) ); - if (url) links.push({ url, label: segment.displayName || url }); + if (url) { + references.push({ + kind: "link", + key: linkKey(url), + url, + label: segment.displayName || url, + messageId: message.id, + origin: "provided-link", + }); + } else if (segment.pillType === "file" || segment.pillType === "folder") { + const path = segment.path; + // Only explicit file references become rows; free-form path mentions do not. + references.push({ + kind: "file", + key: `file:${path}`, + path, + fileName: + path + .replace(/[\\/]$/u, "") + .split(/[\\/]/u) + .pop() || + segment.displayName || + path, + isDirectory: segment.pillType === "folder", + messageId: message.id, + origin: "provided-file", + }); + } } - return links; + return references; +} + +function provenance( + message: SessionSourceMessage, + origin: SessionSourceOrigin +): SessionSourceProvenance { + return { + messageId: message.id, + origin: + message.role === "assistant" + ? "assistant-reference" + : message.role === "tool" + ? "tool-result" + : origin, + ...(message.role === "tool" && message.toolName + ? { toolName: message.toolName } + : {}), + }; +} + +/** Assistant Markdown carries meaningful titles that composer pills intentionally shorten. */ +function assistantReferences(message: SessionSourceMessage): SessionSource[] { + // Example code is not an offered resource. Never mine shell/log text for paths. + const text = message.text + .replace(/```[^\n]*\n[\s\S]*?(?:```|$)/gu, "") + .replace(/`[^`\n]*`/gu, ""); + const references: SessionSource[] = []; + const rest = text.replace( + /(!?)\[([^\]\r\n]+)\]\(([^)\r\n]+)\)/gu, + ( + match, + image: string, + label: string, + rawDestination: string, + offset: number + ) => { + if (offset > 0 && text[offset - 1] === "\\") return match; + const destination = rawDestination.trim().replace(/^<|>$/gu, ""); + if (image) { + // Remote image links remain browser links; local image refs use the established gallery. + if (webHref(destination)) { + references.push({ + kind: "link", + key: linkKey(destination), + url: destination, + label, + }); + } else { + const parsed = serializedReferences({ + ...message, + text: `[${label}](${destination})`, + }); + const file = parsed.find((source) => source.kind === "file"); + if (file?.kind === "file") + references.push({ + kind: "image", + key: imageKey(file.path, message.id, references.length), + ref: file.path, + fileName: imageFileName(file.path), + }); + } + return " "; + } + const parsed = serializedReferences({ ...message, text: match }); + for (const source of parsed) { + references.push( + source.kind === "link" + ? { ...source, label: label.trim() || source.label } + : source.kind === "file" + ? { ...source, title: label.trim() || undefined } + : source + ); + } + return parsed.length ? " " : match; + } + ); + return [...references, ...serializedReferences({ ...message, text: rest })]; } /** @@ -118,21 +268,72 @@ export function extractSessionSources( messages: readonly SessionSourceMessage[] ): SessionSource[] { const sources: SessionSource[] = []; - const seen = new Set(); + const byKey = new Map(); + const seenCalls = new Set(); + const add = (source: SessionSource) => { + const previous = byKey.get(source.key); + if (previous) { + const origins = new Set([ + ...(previous.origins ?? (previous.origin ? [previous.origin] : [])), + ...(source.origin ? [source.origin] : []), + ]); + if (origins.size > 1) previous.origins = [...origins]; + return; + } + byKey.set(source.key, source); + sources.push(source); + }; for (let index = messages.length - 1; index >= 0; index -= 1) { const message = messages[index]; + const activity = message.toolActivity; + if (activity?.callId && seenCalls.has(activity.callId)) continue; + if (activity && activity.callId) { + seenCalls.add(activity.callId); + const key = `tool-group:${activity.group}`; + const group = byKey.get(key); + if (group?.kind === "tool-group") group.operations.push(activity); + else + add({ + kind: "tool-group", + key, + group: activity.group, + operations: [activity], + }); + } + // Errors are activity only, even if an upstream producer accidentally includes references. + if (activity?.status === "error") continue; (message.images ?? []).forEach((ref, imageIndex) => { - const key = imageKey(ref, message.id, imageIndex); - if (seen.has(key)) return; - seen.add(key); - sources.push({ kind: "image", key, ref, fileName: imageFileName(ref) }); + add({ + kind: "image", + key: imageKey(ref, message.id, imageIndex), + ref, + fileName: imageFileName(ref), + ...provenance(message, "attachment"), + }); }); if (!message.text) continue; - for (const link of messageLinks(message)) { - const key = linkKey(link.url); - if (seen.has(key)) continue; - seen.add(key); - sources.push({ kind: "link", key, url: link.url, label: link.label }); + const references = + message.role === "assistant" + ? assistantReferences(message) + : serializedReferences( + message.role === "tool" + ? { + ...message, + // Structured tools may emit a bare pill without a composer label. + // The read-side pill parser requires a label before the token. + text: message.text.replace( + /(^|\n)\[(file|folder|link):([^\]\n]+)\]/gu, + (_match, boundary: string, kind: string, path: string) => + `${boundary}${kind === "link" ? (parseHttpUrlPill(path)?.displayName ?? "Resource") : "Resource"} [${kind}:${path}]` + ), + } + : message + ); + for (const reference of references) { + add({ + ...reference, + ...provenance(message, reference.origin ?? "provided-link"), + }); } } return sources; diff --git a/src/engines/ChatPanel/sessionSources/openSessionSources.test.ts b/src/engines/ChatPanel/sessionSources/openSessionSources.test.ts new file mode 100644 index 0000000000..7c7cebdfb3 --- /dev/null +++ b/src/engines/ChatPanel/sessionSources/openSessionSources.test.ts @@ -0,0 +1,81 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { ROUTES } from "@src/config/routes"; +import { EditorTabService } from "@src/services/workStation/EditorTabService"; +import { createSessionTab } from "@src/store/chatPanel/chatPanelTabFactories"; +import { chatPanelTabsAtom } from "@src/store/chatPanel/chatPanelTabsState"; +import { chatPanelMaximizedAtom } from "@src/store/ui/chatPanel/surfaceAtoms"; +import { stationModeAtom } from "@src/store/ui/simulatorAtom"; +import { + createInstrumentedStore, + resetInstrumentedStore, +} from "@src/util/core/state/instrumentedStore"; + +import { openSessionSources } from "./openSessionSources"; + +describe("openSessionSources", () => { + let root: Root; + let host: HTMLDivElement; + + beforeEach(() => { + localStorage.clear(); + resetInstrumentedStore(); + Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true); + window.history.replaceState({}, "", ROUTES.workStation.code.path); + host = document.createElement("div"); + document.body.appendChild(host); + root = createRoot(host); + }); + + afterEach(() => { + act(() => root.unmount()); + host.remove(); + resetInstrumentedStore(); + Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); + }); + + it("opens and refocuses the real session-bound tab while exposing the workstation", () => { + const store = createInstrumentedStore(); + const chat = createSessionTab({ sessionId: "session-a" }); + store.set(chatPanelTabsAtom, { tabs: [chat], activeTabId: chat.id }); + store.set(stationModeAtom, "agent-station"); + store.set(chatPanelMaximizedAtom, true); + act(() => + root.render( + React.createElement( + "button", + { onClick: () => openSessionSources("session-a", "Sources") }, + "View all" + ) + ) + ); + act(() => host.querySelector("button")!.click()); + + expect(store.get(stationModeAtom)).toBe("my-station"); + expect(store.get(chatPanelMaximizedAtom)).toBe(false); + const opened = EditorTabService.getActiveTab(); + expect(opened).toMatchObject({ + type: "session-sources", + data: { sessionId: "session-a" }, + }); + act(() => host.querySelector("button")!.click()); + expect(EditorTabService.getActiveTabId()).toBe(opened!.id); + expect( + EditorTabService.getTabs().filter((tab) => tab.type === "session-sources") + ).toHaveLength(1); + }); + + it("does not open a tab or reveal the workstation for an empty session reference", () => { + const store = createInstrumentedStore(); + store.set(stationModeAtom, "agent-station"); + store.set(chatPanelMaximizedAtom, true); + const initialTabs = EditorTabService.getTabs(); + openSessionSources(" ", "Sources"); + expect(EditorTabService.getTabs()).toEqual(initialTabs); + expect(store.get(stationModeAtom)).toBe("agent-station"); + expect(store.get(chatPanelMaximizedAtom)).toBe(true); + }); +}); diff --git a/src/engines/ChatPanel/sessionSources/openSessionSources.ts b/src/engines/ChatPanel/sessionSources/openSessionSources.ts new file mode 100644 index 0000000000..9b5c191c36 --- /dev/null +++ b/src/engines/ChatPanel/sessionSources/openSessionSources.ts @@ -0,0 +1,10 @@ +import { EditorTabService } from "@src/services/workStation/EditorTabService"; +import { createSessionSourcesTab } from "@src/store/workstation/tabs/factories/sessionSources"; +import { revealMyStation } from "@src/util/ui/revealMyStation"; + +/** Repeated activation focuses the same session-bound tab through the tab owner. */ +export function openSessionSources(sessionId: string, title: string): void { + if (!sessionId.trim()) return; + EditorTabService.openTab(createSessionSourcesTab(sessionId, title)); + revealMyStation(); +} diff --git a/src/engines/ChatPanel/sessionSources/useSessionSources.test.ts b/src/engines/ChatPanel/sessionSources/useSessionSources.test.ts index a89a7401e5..c94ca2d898 100644 --- a/src/engines/ChatPanel/sessionSources/useSessionSources.test.ts +++ b/src/engines/ChatPanel/sessionSources/useSessionSources.test.ts @@ -4,7 +4,7 @@ import { type Root, createRoot } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { SessionSource } from "./extractSessionSources"; -import { useSessionSources } from "./useSessionSources"; +import { useSessionSourcesState } from "./useSessionSources"; const api = vi.hoisted(() => ({ readSessionSourceMessages: vi.fn() })); @@ -23,7 +23,10 @@ function deferred() { describe("useSessionSources", () => { let root: Root; let container: HTMLDivElement; - const captured: { current: SessionSource[] } = { current: [] }; + const captured: { + current: SessionSource[]; + state: ReturnType | null; + } = { current: [], state: null }; function Probe({ sessionId, @@ -32,9 +35,10 @@ describe("useSessionSources", () => { sessionId: string | null; reloadKey?: string; }) { - const sources = useSessionSources(sessionId, reloadKey); + const state = useSessionSourcesState(sessionId, reloadKey); useEffect(() => { - captured.current = sources; + captured.current = state.sources; + captured.state = state; }); return null; } @@ -48,6 +52,10 @@ describe("useSessionSources", () => { beforeEach(() => { Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true); api.readSessionSourceMessages.mockReset(); + Object.defineProperty(document, "hidden", { + configurable: true, + value: false, + }); container = document.createElement("div"); root = createRoot(container); captured.current = []; @@ -56,6 +64,7 @@ describe("useSessionSources", () => { afterEach(() => { act(() => root.unmount()); Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); + Reflect.deleteProperty(document, "hidden"); }); it("never shows one session's sources for another", async () => { @@ -120,6 +129,220 @@ describe("useSessionSources", () => { expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(3); }); + it("shares concurrent equivalent reads and releases the registry after unmount", async () => { + const first = deferred(); + api.readSessionSourceMessages + .mockReturnValueOnce(first.promise) + .mockResolvedValueOnce([]); + act(() => + root.render( + React.createElement( + React.Fragment, + null, + React.createElement(Probe, { + sessionId: "session-a", + reloadKey: "v1", + }), + React.createElement(Probe, { + sessionId: "session-a", + reloadKey: "v1", + }) + ) + ) + ); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(1); + act(() => root.render(null)); + render("session-a", "v1"); + await act(async () => {}); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(2); + await act(async () => first.resolve([])); + }); + + it("starts a fresh read for invalidation while the old read is pending", async () => { + const old = deferred(); + const fresh = deferred(); + api.readSessionSourceMessages + .mockReturnValueOnce(old.promise) + .mockReturnValueOnce(fresh.promise); + render("session-a", "v1"); + render("session-a", "v2"); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(2); + await act(async () => + fresh.resolve([{ id: "new", text: "https://new.dev/" }]) + ); + await act(async () => + old.resolve([{ id: "old", text: "https://old.dev/" }]) + ); + expect(captured.current[0]).toMatchObject({ messageId: "new" }); + }); + + it("exposes loading and errors, retries, and updates a fragment URL with the same key", async () => { + const first = deferred(); + api.readSessionSourceMessages.mockReturnValueOnce(first.promise); + render("session-a"); + expect(captured.state).toMatchObject({ loading: true, error: false }); + await act(async () => first.reject(new Error("offline"))); + expect(captured.state).toMatchObject({ loading: false, error: true }); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "one", text: "https://a.dev/doc#first" }, + ]); + await act(async () => captured.state?.retry()); + expect(captured.state).toMatchObject({ loading: false, error: false }); + const firstList = captured.current; + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "one", text: "https://a.dev/doc#second" }, + ]); + await act(async () => captured.state?.retry()); + expect(captured.current).not.toBe(firstList); + expect(captured.current[0]).toMatchObject({ + url: "https://a.dev/doc#second", + }); + }); + + it("refreshes provenance and document titles when the resource identity stays unchanged", async () => { + api.readSessionSourceMessages.mockResolvedValueOnce([ + { + id: "tool", + role: "tool", + toolName: "read_file", + text: "[file:/tmp/report.md]", + }, + ]); + render("session-a", "v1"); + await act(async () => {}); + const first = captured.current; + expect(first[0]).toMatchObject({ + origin: "tool-result", + toolName: "read_file", + }); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { + id: "assistant", + role: "assistant", + text: "[Implementation report](/tmp/report.md)", + }, + ]); + render("session-a", "v2"); + await act(async () => {}); + expect(captured.current).not.toBe(first); + expect(captured.current[0]).toMatchObject({ + origin: "assistant-reference", + title: "Implementation report", + }); + }); + + it("updates tool activity details without changing stable group identity", async () => { + const activity = { + callId: "search", + toolName: "web.search", + group: "web", + status: "success", + actions: [{ kind: "search", query: "original" }], + }; + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "tool", role: "tool", text: "", toolActivity: activity }, + ]); + render("session-a", "v1"); + await act(async () => {}); + const first = captured.current; + expect(first[0]).toMatchObject({ + kind: "tool-group", + key: "tool-group:web", + }); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { + id: "tool", + role: "tool", + text: "", + toolActivity: { + ...activity, + status: "error", + error: "No results", + actions: [{ kind: "search", query: "updated" }], + }, + }, + ]); + render("session-a", "v2"); + await act(async () => {}); + expect(captured.current).not.toBe(first); + expect(captured.current[0]).toMatchObject({ + key: "tool-group:web", + operations: [ + { + status: "error", + error: "No results", + actions: [{ query: "updated" }], + }, + ], + }); + }); + + function setHidden(hidden: boolean) { + act(() => { + Object.defineProperty(document, "hidden", { + configurable: true, + value: hidden, + }); + document.dispatchEvent(new Event("visibilitychange")); + }); + } + + it("defers a hidden initial mount until visibility returns without a hidden spinner", async () => { + Object.defineProperty(document, "hidden", { + configurable: true, + value: true, + }); + api.readSessionSourceMessages.mockResolvedValue([]); + render("session-a", "v1"); + expect(api.readSessionSourceMessages).not.toHaveBeenCalled(); + expect(captured.state).toMatchObject({ loading: false, error: false }); + setHidden(false); + await act(async () => {}); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(1); + }); + + it("coalesces hidden history updates into one latest read and retains previous rows", async () => { + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "old", text: "https://old.dev" }, + ]); + render("session-a", "v1"); + await act(async () => {}); + const previous = captured.current; + setHidden(true); + render("session-a", "v2"); + render("session-a", "v3"); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(1); + expect(captured.current).toBe(previous); + expect(captured.state?.loading).toBe(false); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "latest", text: "https://latest.dev" }, + ]); + setHidden(false); + await act(async () => {}); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(2); + expect(captured.current[0]).toMatchObject({ messageId: "latest" }); + render("session-a", "v3"); + await act(async () => {}); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(2); + }); + + it("preserves errors while hidden and revalidates unchanged history on return", async () => { + api.readSessionSourceMessages.mockRejectedValueOnce(new Error("offline")); + render("session-a", "v1"); + await act(async () => {}); + setHidden(true); + render("session-a", "v2"); + expect(captured.state).toMatchObject({ loading: false, error: true }); + api.readSessionSourceMessages.mockResolvedValue([]); + setHidden(false); + await act(async () => {}); + expect(captured.state).toMatchObject({ loading: false, error: false }); + setHidden(true); + setHidden(false); + await act(async () => {}); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(3); + }); + it("reads nothing without a session", () => { render(null); expect(api.readSessionSourceMessages).not.toHaveBeenCalled(); diff --git a/src/engines/ChatPanel/sessionSources/useSessionSources.ts b/src/engines/ChatPanel/sessionSources/useSessionSources.ts index 0710eeadb7..26da231f1f 100644 --- a/src/engines/ChatPanel/sessionSources/useSessionSources.ts +++ b/src/engines/ChatPanel/sessionSources/useSessionSources.ts @@ -1,15 +1,15 @@ /** - * useSessionSources — the images and links the user sent in one session. + * useSessionSources — explicit resources and completed tool activity in one session. * * Read from the session's stored history, not from the event store: the * store keeps only previews of turns that are not on screen (imported text is - * cut, image lists are capped), so it cannot list everything a long session - * was given. The result is component state tagged with the session it was - * read for — nothing is cached per session, so it goes away with the rail + * cut, image lists are capped), so it cannot represent all resources and activity in a long + * session. The result is component state tagged with the session it was + * read for — only pending reads are shared, so loaded data goes away with its view * and never outlives the session's own data. `reloadKey` re-reads the same * session and keeps the last list until the fresh one lands. */ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { readSessionSourceMessages } from "@src/api/tauri/session/sessionSources"; import { createLogger } from "@src/hooks/logger"; @@ -26,6 +26,8 @@ const EMPTY_SOURCES: SessionSource[] = []; interface LoadedSources { sessionId: string; sources: SessionSource[]; + requestKey: string; + error: boolean; } function sameSources( @@ -37,23 +39,137 @@ function sameSources( left.every((source, index) => { const other = right[index]; if (source.key !== other.key || source.kind !== other.kind) return false; - return source.kind === "link" - ? other.kind === "link" && source.label === other.label - : other.kind === "image" && source.ref === other.ref; + if ( + source.messageId !== other.messageId || + source.origin !== other.origin || + source.toolName !== other.toolName || + source.origins?.join() !== other.origins?.join() + ) + return false; + if (source.kind === "tool-group") + return ( + other.kind === "tool-group" && + source.group === other.group && + JSON.stringify(source.operations) === JSON.stringify(other.operations) + ); + if (source.kind === "link") { + return ( + other.kind === "link" && + source.label === other.label && + source.url === other.url + ); + } + if (source.kind === "file") { + return ( + other.kind === "file" && + source.path === other.path && + source.fileName === other.fileName && + source.title === other.title && + source.isDirectory === other.isDirectory + ); + } + return ( + other.kind === "image" && + source.ref === other.ref && + source.fileName === other.fileName + ); }) ); } -export function useSessionSources( +// Only in-flight reads are shared. Settled results belong to mounted consumers, +// and the map deletes both successful and failed reads immediately. +const pendingReads = new Map< + string, + { + promise: ReturnType; + consumers: number; + } +>(); +function acquireSources( + sessionId: string, + reloadKey: string | undefined, + retryKey: number, + visibilityVersion: number +) { + const requestKey = JSON.stringify([ + sessionId, + reloadKey ?? null, + retryKey, + visibilityVersion, + ]); + let entry = pendingReads.get(requestKey); + if (!entry) { + entry = { promise: readSessionSourceMessages(sessionId), consumers: 0 }; + pendingReads.set(requestKey, entry); + const current = entry; + void entry.promise + .finally(() => { + if (pendingReads.get(requestKey) === current) + pendingReads.delete(requestKey); + }) + .catch(() => {}); + } + entry.consumers += 1; + const current = entry; + return { + promise: current.promise, + release: () => { + current.consumers -= 1; + // Unmounted sessions retain no registry entries, even if IPC is still pending. + if (current.consumers === 0 && pendingReads.get(requestKey) === current) { + pendingReads.delete(requestKey); + } + }, + }; +} + +export function useSessionSourcesState( sessionId: string | null | undefined, reloadKey?: string -): SessionSource[] { +) { + // No scan starts in a hidden window. Coalesce every hidden invalidation + // into one read of the latest session/reload key when visibility returns. + const [visibility, setVisibility] = useState(() => ({ + visible: typeof document === "undefined" || !document.hidden, + version: 0, + })); + useEffect(() => { + const onVisibilityChange = () => { + const visible = !document.hidden; + setVisibility((previous) => + previous.visible === visible + ? previous + : { + visible, + version: previous.version + (visible ? 1 : 0), + } + ); + }; + document.addEventListener("visibilitychange", onVisibilityChange); + return () => + document.removeEventListener("visibilitychange", onVisibilityChange); + }, []); + const [retryKey, setRetryKey] = useState(0); + const retry = useCallback(() => setRetryKey((value) => value + 1), []); const [loaded, setLoaded] = useState(null); + const requestKey = JSON.stringify([ + sessionId, + reloadKey ?? null, + retryKey, + visibility.version, + ]); useEffect(() => { - if (!sessionId) return; + if (!sessionId || !visibility.visible) return; let cancelled = false; - readSessionSourceMessages(sessionId) + const read = acquireSources( + sessionId, + reloadKey, + retryKey, + visibility.version + ); + read.promise .then((messages) => { if (cancelled) return; const sources = extractSessionSources(messages); @@ -62,8 +178,8 @@ export function useSessionSources( setLoaded((previous) => previous?.sessionId === sessionId && sameSources(previous.sources, sources) - ? previous - : { sessionId, sources } + ? { ...previous, requestKey, error: false } + : { sessionId, sources, requestKey, error: false } ); }) .catch((error: unknown) => { @@ -72,17 +188,41 @@ export function useSessionSources( // A failed re-read keeps this session's last good list. setLoaded((previous) => previous?.sessionId === sessionId - ? previous - : { sessionId, sources: EMPTY_SOURCES } + ? { ...previous, requestKey, error: true } + : { sessionId, sources: EMPTY_SOURCES, requestKey, error: true } ); }); return () => { cancelled = true; + read.release(); }; // reloadKey is a pure refetch signal for the same session. - }, [sessionId, reloadKey]); + }, [ + sessionId, + reloadKey, + retryKey, + requestKey, + visibility.visible, + visibility.version, + ]); + + const current = loaded?.sessionId === sessionId ? loaded : null; + return { + sources: current?.sources ?? EMPTY_SOURCES, + loading: + visibility.visible && !!sessionId && current?.requestKey !== requestKey, + error: + !!current && + (!visibility.visible || current.requestKey === requestKey) && + current.error, + retry, + }; +} - return loaded && loaded.sessionId === sessionId - ? loaded.sources - : EMPTY_SOURCES; +/** Rail callers that only need rows retain their existing API. */ +export function useSessionSources( + sessionId: string | null | undefined, + reloadKey?: string +): SessionSource[] { + return useSessionSourcesState(sessionId, reloadKey).sources; } diff --git a/src/features/SessionSources/SessionSourceIcon.tsx b/src/features/SessionSources/SessionSourceIcon.tsx new file mode 100644 index 0000000000..91d778a214 --- /dev/null +++ b/src/features/SessionSources/SessionSourceIcon.tsx @@ -0,0 +1,29 @@ +import AnyIcon from "@src/components/AnyIcon"; +import FileTypeIcon from "@src/components/FileTypeIcon"; +import ModelIcon from "@src/components/ModelIcon"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { SessionSourceThumbnail } from "./SessionSourceThumbnail"; +import { sourceIcon } from "./presentation"; + +/** Resource identity is shared by compact rows and tool-group headings. */ +export function SessionSourceIcon({ + source, + size = 14, +}: { + source: SessionSource; + size?: number; +}) { + if (source.kind === "image") + return ; + if (source.kind === "file" && !source.isDirectory) + return ( + + ); + if (source.kind === "tool-group" && source.group === "codex-app") + return ; + return ; +} diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourceImagePreview.tsx b/src/features/SessionSources/SessionSourceImagePreview.tsx similarity index 69% rename from src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourceImagePreview.tsx rename to src/features/SessionSources/SessionSourceImagePreview.tsx index 2fdbcc6d91..51edf9f2a9 100644 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourceImagePreview.tsx +++ b/src/features/SessionSources/SessionSourceImagePreview.tsx @@ -1,5 +1,5 @@ /** - * WorkstationSourceImagePreview — the chat's image viewer, opened from a + * SessionSourceImagePreview — the chat's image viewer, opened from a * Sources row with every image of the session as its gallery. * * The opened image is read here rather than borrowed from the row's @@ -7,27 +7,30 @@ * while the viewer is still showing it. */ import { useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import Message from "@src/components/Message"; import { resolveImageSrc, useResolvedImageSrc, } from "@src/engines/ChatPanel/ChatImageThumbnail"; import ImagePreviewOverlay from "@src/scaffold/ImagePreviewOverlay"; -export interface WorkstationSourceImage { +export interface SessionSourceImage { ref: string; fileName: string | null; } -export function WorkstationSourceImagePreview({ +export function SessionSourceImagePreview({ images, index, onClose, }: { - images: readonly WorkstationSourceImage[]; + images: readonly SessionSourceImage[]; index: number; onClose: () => void; }) { + const { t } = useTranslation(); const { src, failed } = useResolvedImageSrc(images[index].ref); const gallery = useMemo( () => @@ -39,8 +42,11 @@ export function WorkstationSourceImagePreview({ ); useEffect(() => { - if (failed) onClose(); - }, [failed, onClose]); + if (failed) { + Message.error(t("common:git.rail.sourceImageUnavailable")); + onClose(); + } + }, [failed, onClose, t]); if (!src) return null; return ( diff --git a/src/features/SessionSources/SessionSourceRow.tsx b/src/features/SessionSources/SessionSourceRow.tsx new file mode 100644 index 0000000000..25feaef5eb --- /dev/null +++ b/src/features/SessionSources/SessionSourceRow.tsx @@ -0,0 +1,34 @@ +import { useTranslation } from "react-i18next"; + +import { SidebarRow } from "@src/components/SidebarRow"; +import Tooltip from "@src/components/Tooltip"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { SessionSourceIcon } from "./SessionSourceIcon"; +import { sourceLabel, sourceLocation, sourceProvenance } from "./presentation"; + +/** Domain adapter only: SidebarRow owns the compact three-line interaction. */ +export function SessionSourceRow({ + source, + onOpenSource, +}: { + source: Exclude; + onOpenSource: (source: SessionSource) => void; +}) { + const { t } = useTranslation(); + const label = sourceLabel(t, source); + const location = sourceLocation(source); + return ( + + } + aria-label={label} + onClick={() => onOpenSource(source)} + > + {sourceProvenance(t, source)} + + + ); +} diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/RailImageThumbnail.tsx b/src/features/SessionSources/SessionSourceThumbnail.tsx similarity index 79% rename from src/scaffold/AppLayout/FocusedChatWorkstationRail/RailImageThumbnail.tsx rename to src/features/SessionSources/SessionSourceThumbnail.tsx index fefbf1cec4..d0b5288a17 100644 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/RailImageThumbnail.tsx +++ b/src/features/SessionSources/SessionSourceThumbnail.tsx @@ -1,15 +1,15 @@ /** - * RailImageThumbnail — an attached image drawn in a rail row's icon slot. + * SessionSourceThumbnail — the lazy image shared by source summaries and rows. * * The bytes are read only while the row is mounted and released with it, so - * a folded Sources section or a closed submenu holds no image data. + * a folded Sources section or an inactive full view holds no image data. */ import { memo } from "react"; import { useResolvedImageSrc } from "@src/engines/ChatPanel/ChatImageThumbnail"; import { HugeiconsIcon, Image01Icon, ImageNotFound01Icon } from "@src/icons"; -export const RailImageThumbnail = memo(function RailImageThumbnail({ +export const SessionSourceThumbnail = memo(function SessionSourceThumbnail({ imageRef, size, }: { diff --git a/src/features/SessionSources/SessionSourcesView.images.test.ts b/src/features/SessionSources/SessionSourcesView.images.test.ts new file mode 100644 index 0000000000..052069eebc --- /dev/null +++ b/src/features/SessionSources/SessionSourcesView.images.test.ts @@ -0,0 +1,112 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; + +import { extractSessionSources } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { SessionSourcesView } from "./SessionSourcesView"; + +const imageRuntime = vi.hoisted(() => ({ + mountedRef: vi.fn(), + resolve: vi.fn(async () => "data:image/png;base64,RESOLVED"), + overlay: vi.fn(), +})); +vi.mock("@src/engines/ChatPanel/ChatImageThumbnail", () => ({ + useResolvedImageSrc: (ref: string) => { + imageRuntime.mountedRef(ref); + return { src: "data:image/png;base64,THUMBNAIL", failed: false }; + }, + resolveImageSrc: imageRuntime.resolve, +})); +vi.mock("@src/scaffold/ImagePreviewOverlay", () => ({ + default: (props: unknown) => { + imageRuntime.overlay(props); + return React.createElement("div", { "data-testid": "image-gallery" }); + }, +})); +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +let root: Root; +let host: HTMLDivElement; +beforeEach(() => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + vi.clearAllMocks(); + host = document.createElement("div"); + document.body.appendChild(host); + root = createRoot(host); +}); +afterEach(() => { + act(() => root.unmount()); + host.remove(); + vi.unstubAllGlobals(); +}); + +it("shows readable image locations while preserving transcript identity through thumbnails and gallery navigation", async () => { + const path = "/tmp/Readable Screenshot.png"; + const firstRef = `orgii-transcript-image:${JSON.stringify([ + "private-session-marker", + "private-turn-marker", + path, + ])}`; + const inlineRef = `orgii-transcript-image:${JSON.stringify([ + "private-session-marker", + "other-turn-marker", + "codex-inline-image:0", + ])}`; + const sources = extractSessionSources([ + { id: "user-message", text: "", images: [firstRef, inlineRef] }, + ]); + await act(async () => { + root.render( + React.createElement(SessionSourcesView, { + sources, + onRetry: vi.fn(), + }) + ); + }); + + expect(host.textContent).toContain("Readable Screenshot.png"); + const visibleAndTooltipText = [ + host.textContent, + ...[...host.querySelectorAll("[title], [aria-label]")].map( + (element) => + `${element.getAttribute("title")} ${element.getAttribute("aria-label")}` + ), + ].join(" "); + for (const internal of [ + "orgii-transcript-image:", + "private-session-marker", + "private-turn-marker", + "codex-inline-image:", + ]) { + expect(visibleAndTooltipText).not.toContain(internal); + } + expect(imageRuntime.mountedRef).toHaveBeenCalledWith(firstRef); + expect(imageRuntime.mountedRef).toHaveBeenCalledWith(inlineRef); + + await act(async () => { + host + .querySelector( + 'button[aria-label="Readable Screenshot.png"]' + )! + .click(); + }); + + expect(host.querySelector('[data-testid="image-gallery"]')).not.toBeNull(); + const overlay = imageRuntime.overlay.mock.lastCall![0] as { + images: Array<{ src: string }>; + initialIndex: number; + resolveImage: (ref: string) => Promise; + }; + expect(overlay.images.map((image) => image.src)).toEqual([ + firstRef, + inlineRef, + ]); + expect(overlay.initialIndex).toBe(0); + await overlay.resolveImage(overlay.images[1].src); + expect(imageRuntime.resolve).toHaveBeenCalledWith(inlineRef); + expect(sources[0]).toMatchObject({ kind: "image", ref: firstRef }); +}); diff --git a/src/features/SessionSources/SessionSourcesView.test.ts b/src/features/SessionSources/SessionSourcesView.test.ts new file mode 100644 index 0000000000..e473f2f9b5 --- /dev/null +++ b/src/features/SessionSources/SessionSourcesView.test.ts @@ -0,0 +1,206 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { SessionSourcesView } from "./SessionSourcesView"; + +const navigation = vi.hoisted(() => ({ + openSource: vi.fn(), + closeImagePreview: vi.fn(), + imagePreview: null, +})); +vi.mock("./useSessionSourceNavigation", () => ({ + useSessionSourceNavigation: () => navigation, +})); +vi.mock("./SessionSourceThumbnail", () => ({ + SessionSourceThumbnail: () => + React.createElement("span", { "data-thumbnail": true }), +})); +vi.mock("./SessionSourceImagePreview", () => ({ + SessionSourceImagePreview: () => null, +})); +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +let container: HTMLDivElement; +let root: Root; +const retry = vi.fn(); +const sources: SessionSource[] = Array.from({ length: 65 }, (_, index) => ({ + kind: "image", + key: `image:${index}`, + ref: `/tmp/shot-${index}.png`, + fileName: `shot-${index}.png`, +})); +async function render( + props: Partial> = {}, + key = "session-a" +) { + await act(async () => { + root.render( + React.createElement(SessionSourcesView, { + key, + sources, + onRetry: retry, + ...props, + }) + ); + }); +} +function click(text: string) { + act(() => + [...container.querySelectorAll("button")] + .find((button) => button.textContent?.includes(text))! + .click() + ); +} +beforeEach(() => { + Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true); + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + vi.clearAllMocks(); +}); +afterEach(() => { + act(() => root.unmount()); + container.remove(); + Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); +}); + +describe("SessionSourcesView", () => { + it("bounds image reads, opens source rows and reveals further pages on request", async () => { + await render(); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(30); + click("shot-2.png"); + expect(navigation.openSource).toHaveBeenCalledWith(sources[2]); + click("loadMoreSources"); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(60); + await render({}, "session-b"); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(30); + }); + it("shows sources directly without search or refresh chrome", async () => { + await render(); + expect(container.querySelector("input")).toBeNull(); + expect( + container.querySelector('[aria-label="common:actions.refresh"]') + ).toBeNull(); + expect(container.querySelectorAll("li")).toHaveLength(30); + }); + it("renders and opens non-image resources with distinct assistant and tool provenance", async () => { + const mixed: SessionSource[] = [ + { + kind: "link", + key: "pr", + url: "https://github.com/org/repo/pull/42", + label: "fix(chat): source navigation", + origin: "assistant-reference", + origins: ["assistant-reference", "provided-link"], + }, + { + kind: "file", + key: "report", + path: "/tmp/report.md", + fileName: "report.md", + title: "Implementation report", + isDirectory: false, + origin: "assistant-reference", + }, + { + kind: "file", + key: "tool", + path: "/tmp/data.csv", + fileName: "data.csv", + isDirectory: false, + origin: "tool-result", + toolName: "read_file", + }, + ]; + await render({ sources: mixed }); + expect(container.querySelectorAll("li")).toHaveLength(3); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(0); + expect(container.textContent).toContain("sourceAssistantReference"); + expect(container.textContent).toContain("sourceLinkProvided"); + expect(container.textContent).toContain("sourceToolResultNamed"); + click("Implementation report"); + expect(navigation.openSource).toHaveBeenCalledWith(mixed[1]); + click("fix(chat): source navigation"); + expect(navigation.openSource).toHaveBeenCalledWith(mixed[0]); + }); + it("groups by type, preserves recency within each group and pages images independently", async () => { + const link: SessionSource = { + kind: "link", + key: "link", + url: "https://example.com", + label: "Reference", + }; + const tool: SessionSource = { + kind: "tool-group", + key: "tool", + group: "exec", + operations: [], + }; + const file: SessionSource = { + kind: "file", + key: "file", + path: "/tmp/report.md", + fileName: "report.md", + isDirectory: false, + }; + await render({ + sources: [tool, sources[0], link, file, ...sources.slice(1)], + }); + expect( + [...container.querySelectorAll("[data-source-category]")].map((element) => + element.getAttribute("data-source-category") + ) + ).toEqual(["image", "file", "link", "tool-group"]); + const images = container.querySelector('[data-source-category="image"]')!; + expect(images.querySelectorAll("li")).toHaveLength(30); + expect(images.querySelector("li")?.textContent).toContain("shot-0.png"); + expect( + container.querySelector('[data-source-category="link"]')?.textContent + ).toContain("Reference"); + expect( + container.querySelector('[data-source-category="file"]')?.textContent + ).toContain("report.md"); + click("loadMoreSources"); + expect(images.querySelectorAll("li")).toHaveLength(60); + await render({ sources: [link] }, "session-b"); + expect(container.querySelectorAll("[data-source-category]")).toHaveLength( + 1 + ); + expect( + container.querySelector('[data-source-category="image"]') + ).toBeNull(); + }); + it("preserves successful rows during failed refresh and offers retry", async () => { + await render({ error: true }); + expect(container.querySelector('[role="alert"]')?.textContent).toContain( + "sourcesLoadFailed" + ); + expect(container.querySelectorAll("li")).toHaveLength(30); + click("actions.retry"); + expect(retry).toHaveBeenCalledOnce(); + await render({ loading: true }); + expect(container.querySelector('[aria-busy="true"]')).not.toBeNull(); + expect(container.querySelectorAll("li")).toHaveLength(30); + expect(container.querySelector('[role="alert"]')).toBeNull(); + }); + it("distinguishes loading, empty and unavailable lists", async () => { + await render({ sources: [], loading: true }); + expect(container.querySelector('[role="status"]')?.textContent).toContain( + "status.loading" + ); + await render({ sources: [] }); + expect(container.querySelector('[role="status"]')?.textContent).toContain( + "noSources" + ); + await render({ sources: [], error: true }); + expect(container.querySelector('[role="status"]')?.textContent).toContain( + "sourcesUnavailable" + ); + }); +}); diff --git a/src/features/SessionSources/SessionSourcesView.tsx b/src/features/SessionSources/SessionSourcesView.tsx new file mode 100644 index 0000000000..41e13ce599 --- /dev/null +++ b/src/features/SessionSources/SessionSourcesView.tsx @@ -0,0 +1,154 @@ +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Button from "@src/components/Button"; +import PageNotice from "@src/components/PageNotice"; +import { SidebarSectionHeader } from "@src/components/SidebarSectionHeader"; +import { ListPanelScrollArea } from "@src/components/layout/blocks"; +import { EDITOR_TAB_CANVAS_BG_CLASS } from "@src/config/workstation/tokens"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { SessionSourceImagePreview } from "./SessionSourceImagePreview"; +import { SessionSourceRow } from "./SessionSourceRow"; +import { SessionToolActivityGroup } from "./SessionToolActivityGroup"; +import { useSessionSourceNavigation } from "./useSessionSourceNavigation"; + +const PAGE_SIZE = 30; +const CATEGORIES = [ + { kind: "image", label: "sourceCategoryImages" }, + { kind: "file", label: "sourceCategoryFiles" }, + { kind: "link", label: "sourceCategoryLinks" }, + { kind: "tool-group", label: "sourceCategoryTools" }, +] as const; + +/** Parent keys this view by session ID: pagination and gallery never cross sessions. */ +export function SessionSourcesView({ + sources, + basePath, + loading = false, + error = false, + onRetry, +}: { + sources: SessionSource[]; + basePath?: string; + loading?: boolean; + error?: boolean; + onRetry: () => void; +}) { + const { t } = useTranslation(); + const [visibleCounts, setVisibleCounts] = useState< + Partial> + >({}); + const categories = useMemo( + () => + CATEGORIES.map((category) => ({ + ...category, + items: sources.filter((source) => source.kind === category.kind), + })).filter((category) => category.items.length > 0), + [sources] + ); + const { openSource, imagePreview, closeImagePreview } = + useSessionSourceNavigation(sources, basePath); + + return ( +
+ + {error ? ( + + {t("common:git.rail.sourcesLoadFailed")} + + ) : null} + {sources.length === 0 ? ( +

+ {loading + ? t("common:status.loading") + : error + ? t("common:git.rail.sourcesUnavailable") + : t("common:git.rail.noSources")} +

+ ) : ( +
+ {categories.map(({ kind, label, items }) => { + const visibleCount = visibleCounts[kind] ?? PAGE_SIZE; + return ( +
+ + {items.length} + + } + /> +
    + {items.slice(0, visibleCount).map((source) => ( +
  • + {source.kind === "tool-group" ? ( + + ) : ( + + )} +
  • + ))} +
+ {items.length > visibleCount ? ( +
+ +
+ ) : null} +
+ ); + })} +
+ )} +
+ {imagePreview ? ( + + ) : null} +
+ ); +} diff --git a/src/features/SessionSources/SessionToolActivityGroup.test.ts b/src/features/SessionSources/SessionToolActivityGroup.test.ts new file mode 100644 index 0000000000..1e36c3ec50 --- /dev/null +++ b/src/features/SessionSources/SessionToolActivityGroup.test.ts @@ -0,0 +1,164 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; + +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { SessionToolActivityGroup } from "./SessionToolActivityGroup"; + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string, options?: { count?: number }) => + `${key}${options?.count === undefined ? "" : ` ${options.count}`}`, + }), +})); +let container: HTMLDivElement; +let root: Root; +const open = vi.fn(); +const source: Extract = { + kind: "tool-group", + key: "web", + group: "web", + operations: [ + { + callId: "search", + toolName: "web.search", + group: "web", + status: "error", + error: "No matching results", + actions: [{ kind: "search", query: "feature specifications" }], + }, + { + callId: "open", + toolName: "web.open", + group: "web", + status: "success", + actions: [{ kind: "open", url: "https://example.com/spec" }], + }, + ], +}; +async function render(value = source, key = "session-a") { + await act(async () => + root.render( + React.createElement(SessionToolActivityGroup, { + key, + source: value, + onOpenSource: open, + }) + ) + ); +} +function click(label: string) { + act(() => + [...container.querySelectorAll("button")] + .find((button) => button.textContent?.includes(label))! + .click() + ); +} +beforeEach(() => { + Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true); + container = document.createElement("div"); + root = createRoot(container); + vi.clearAllMocks(); +}); +afterEach(() => { + act(() => root.unmount()); + Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); +}); +it("discloses search and open independently, renders failures and routes valid web links", async () => { + await render(); + expect(container.textContent).toContain("toolGroupWeb"); + expect(container.querySelectorAll('[aria-expanded="false"]')).toHaveLength(2); + expect(container.textContent).not.toContain("feature specifications"); + click("toolSearchCount"); + expect(container.textContent).toContain("feature specifications"); + expect(container.querySelector('[role="status"]')?.textContent).toBe( + "No matching results" + ); + expect(container.querySelector('[role="status"]')?.className).toContain( + "page-notice" + ); + expect(container.textContent).not.toContain("https://example.com/spec"); + click("toolOpenCount"); + click("https://example.com/spec"); + expect(open).toHaveBeenCalledWith( + expect.objectContaining({ kind: "link", url: "https://example.com/spec" }) + ); + await render({ ...source, operations: [...source.operations] }); + expect(container.querySelectorAll('[aria-expanded="true"]')).toHaveLength(2); + await render(source, "session-b"); + expect(container.querySelectorAll('[aria-expanded="false"]')).toHaveLength(2); +}); +it("shows49 repeated generic calls as one row while preserving the activity total on refresh", async () => { + const operations: typeof source.operations = Array.from( + { length: 49 }, + (_, index) => ({ + callId: `call-${index}`, + toolName: "mcp__cua_repl__js", + group: "mcp:cua", + status: "success", + actions: [{ kind: "generic" }], + }) + ); + const repeated = { ...source, operations }; + await render(repeated); + expect(container.textContent).toContain("toolCallCount 49"); + click("toolCallCount"); + expect(container.querySelectorAll("[data-tool-activity-call]")).toHaveLength( + 1 + ); + expect(container.textContent).toContain("toolRepeatCount 49"); + await render({ ...repeated, operations: [...operations] }); + expect(container.querySelector('[aria-expanded="true"]')).not.toBeNull(); + expect(container.querySelectorAll("[data-tool-activity-call]")).toHaveLength( + 1 + ); + expect(container.textContent).toContain("toolCallCount 49"); +}); + +it("keeps differing failures separate and counts repeated failure rows", async () => { + await render({ + ...source, + operations: [ + { ...source.operations[0], callId: "a", error: "Error A" }, + { ...source.operations[0], callId: "b", error: "Error A" }, + { ...source.operations[0], callId: "c", error: "Error B" }, + ], + }); + click("toolSearchCount"); + expect(container.querySelectorAll("[data-tool-activity-call]")).toHaveLength( + 2 + ); + expect(container.querySelectorAll('[role="status"]')).toHaveLength(2); + expect(container.textContent).toContain("toolSearchCount 3"); + expect(container.textContent).toContain("toolRepeatCount 2"); +}); + +it("bounds expanded details and never opens unsafe tool URLs", async () => { + await render({ + ...source, + operations: Array.from({ length: 25 }, (_, index) => ({ + callId: `call-${index}`, + toolName: "web.open", + group: "web", + status: "success", + actions: [{ kind: "open", url: `javascript:alert(${index})` }], + })), + }); + click("toolOpenCount"); + expect(container.querySelectorAll("[data-tool-activity-call]")).toHaveLength( + 20 + ); + expect(container.textContent).toContain("javascript:"); + expect( + [...container.querySelectorAll("button")].some((button) => + button.textContent?.includes("javascript:") + ) + ).toBe(false); + click("toolLoadMore"); + expect(container.querySelectorAll("[data-tool-activity-call]")).toHaveLength( + 25 + ); + expect(open).not.toHaveBeenCalled(); +}); diff --git a/src/features/SessionSources/SessionToolActivityGroup.tsx b/src/features/SessionSources/SessionToolActivityGroup.tsx new file mode 100644 index 0000000000..df70084eca --- /dev/null +++ b/src/features/SessionSources/SessionToolActivityGroup.tsx @@ -0,0 +1,184 @@ +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Button from "@src/components/Button"; +import { parseHttpUrlPill } from "@src/components/ComposerInput/httpUrl"; +import PageNotice from "@src/components/PageNotice"; +import { SidebarSectionHeader } from "@src/components/SidebarSectionHeader"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; +import { useCollapsible } from "@src/hooks/ui/useCollapsible"; +import { HugeiconsIcon, Link01Icon } from "@src/icons"; +import { getToolDisplayLabelFromRegistry } from "@src/util/ui/rendering/registryToolLabel"; + +import { SessionSourceIcon } from "./SessionSourceIcon"; +import { + type AggregatedToolActivityDetail, + type ToolActionKind, + aggregateToolActivity, +} from "./aggregateToolActivity"; +import { sourceLabel } from "./presentation"; + +const COUNT_KEYS = { + search: "toolSearchCount", + open: "toolOpenCount", + "read-terminal": "toolTerminalCount", + generic: "toolCallCount", +} as const; +const DETAIL_PAGE_SIZE = 20; + +function ToolActivitySection({ + kind, + details, + totalCount, + onOpenSource, +}: { + kind: ToolActionKind; + details: AggregatedToolActivityDetail[]; + totalCount: number; + onOpenSource: (source: SessionSource) => void; +}) { + const { t } = useTranslation(); + const [visibleCount, setVisibleCount] = useState(DETAIL_PAGE_SIZE); + const { isOpen, toggle } = useCollapsible({ defaultOpen: false }); + return ( +
+ + {isOpen ? ( +
+ {details + .slice(0, visibleCount) + .map(({ key, operation, action, occurrenceCount }) => { + const url = + action.url && parseHttpUrlPill(action.url) ? action.url : null; + return ( +
+
+ {action.query ? ( +

+ {action.query} +

+ ) : null} + {url ? ( + + ) : null} + {action.url && !url ? ( +

+ {action.url} +

+ ) : null} + {action.kind === "generic" && + !action.query && + !action.url ? ( +

+ {getToolDisplayLabelFromRegistry(operation.toolName)} +

+ ) : null} + {operation.status === "error" ? ( + + {operation.error || t("common:git.rail.toolFailed")} + + ) : null} +
+ {occurrenceCount > 1 ? ( + + {t("common:git.rail.toolRepeatCount", { + count: occurrenceCount, + })} + + ) : null} +
+ ); + })} + {details.length > visibleCount ? ( + + ) : null} +
+ ) : null} +
+ ); +} + +export function SessionToolActivityGroup({ + source, + onOpenSource, +}: { + source: Extract; + onOpenSource: (source: SessionSource) => void; +}) { + const { t } = useTranslation(); + const categories = useMemo( + () => aggregateToolActivity(source.operations), + [source.operations] + ); + return ( +
+ } + /> + {categories.map(({ kind, details, totalCount }) => ( + + ))} +
+ ); +} diff --git a/src/features/SessionSources/aggregateToolActivity.test.ts b/src/features/SessionSources/aggregateToolActivity.test.ts new file mode 100644 index 0000000000..2dc12b5f0b --- /dev/null +++ b/src/features/SessionSources/aggregateToolActivity.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "vitest"; + +import type { SessionToolActivity } from "@src/api/tauri/session/sessionSources"; + +import { aggregateToolActivity } from "./aggregateToolActivity"; + +const activity = ( + callId: string, + patch: Partial = {} +): SessionToolActivity => ({ + callId, + toolName: "mcp__cua_repl__js", + group: "mcp:cua", + status: "success", + actions: [{ kind: "generic" }], + ...patch, +}); + +describe("aggregateToolActivity", () => { + it("merges49 equivalent calls but preserves count, call IDs and original facts", () => { + const operations = Array.from({ length: 49 }, (_, index) => + activity(`call-${index}`) + ); + const before = JSON.stringify(operations); + const [category] = aggregateToolActivity(operations); + expect(category.totalCount).toBe(49); + expect(category.details).toHaveLength(1); + expect(category.details[0]).toMatchObject({ + occurrenceCount: 49, + callIds: operations.map((item) => item.callId), + }); + expect(category.details[0].operation).toBe(operations[0]); + expect(JSON.stringify(operations)).toBe(before); + }); + it("keeps different tools, action kinds, queries, URLs, statuses and errors distinct", () => { + const operations = [ + activity("base"), + activity("tool", { toolName: "another_tool" }), + activity("kind", { actions: [{ kind: "search" }] }), + activity("query", { actions: [{ kind: "generic", query: "different" }] }), + activity("url", { + actions: [{ kind: "generic", url: "https://example.com" }], + }), + activity("status", { status: "error" }), + activity("error-a", { status: "error", error: "Error A" }), + activity("error-b", { status: "error", error: "Error B" }), + ]; + const categories = aggregateToolActivity(operations); + expect(categories.flatMap((category) => category.details)).toHaveLength(8); + expect( + categories.reduce((count, category) => count + category.totalCount, 0) + ).toBe(8); + }); + it("preserves latest-first row order and action totals including empty generic calls", () => { + const [category] = aggregateToolActivity([ + activity("new-a", { + actions: [ + { kind: "search", query: "A" }, + { kind: "search", query: "B" }, + ], + }), + activity("old-a", { actions: [{ kind: "search", query: "A" }] }), + ]); + expect(category.totalCount).toBe(3); + expect( + category.details.map((detail) => [ + detail.action.query, + detail.occurrenceCount, + ]) + ).toEqual([ + ["A", 2], + ["B", 1], + ]); + expect( + aggregateToolActivity([activity("empty", { actions: [] })])[0] + ).toMatchObject({ kind: "generic", totalCount: 1 }); + }); +}); diff --git a/src/features/SessionSources/aggregateToolActivity.ts b/src/features/SessionSources/aggregateToolActivity.ts new file mode 100644 index 0000000000..7216207283 --- /dev/null +++ b/src/features/SessionSources/aggregateToolActivity.ts @@ -0,0 +1,73 @@ +import type { SessionToolActivity } from "@src/api/tauri/session/sessionSources"; + +type ToolAction = SessionToolActivity["actions"][number]; +export type ToolActionKind = ToolAction["kind"]; + +export interface AggregatedToolActivityDetail { + key: string; + /** Latest occurrence supplies the displayed content; original records stay unchanged. */ + operation: SessionToolActivity; + action: ToolAction; + occurrenceCount: number; + callIds: string[]; +} + +export interface ToolActivityCategory { + kind: ToolActionKind; + totalCount: number; + details: AggregatedToolActivityDetail[]; +} + +/** + * Presentation-only grouping of the newest-first activity stream. Exact meaning, + * outcome and tool identity must agree; distinct errors/targets never collapse. + */ +export function aggregateToolActivity( + operations: readonly SessionToolActivity[] +): ToolActivityCategory[] { + const categories = new Map(); + const detailsByKey = new Map(); + const callsByKey = new Map>(); + for (const operation of operations) { + const actions = operation.actions.length + ? operation.actions + : [{ kind: "generic" as const }]; + for (const action of actions) { + let category = categories.get(action.kind); + if (!category) { + category = { kind: action.kind, totalCount: 0, details: [] }; + categories.set(action.kind, category); + } + category.totalCount += 1; + const key = JSON.stringify([ + operation.toolName, + action.kind, + action.query ?? null, + action.url ?? null, + operation.status, + operation.error ?? null, + ]); + const existing = detailsByKey.get(key); + if (existing) { + existing.occurrenceCount += 1; + const calls = callsByKey.get(key)!; + if (!calls.has(operation.callId)) { + calls.add(operation.callId); + existing.callIds.push(operation.callId); + } + continue; + } + const detail = { + key, + operation, + action, + occurrenceCount: 1, + callIds: [operation.callId], + }; + detailsByKey.set(key, detail); + callsByKey.set(key, new Set(detail.callIds)); + category.details.push(detail); + } + } + return [...categories.values()]; +} diff --git a/src/features/SessionSources/presentation.test.ts b/src/features/SessionSources/presentation.test.ts new file mode 100644 index 0000000000..06ddb6c0dd --- /dev/null +++ b/src/features/SessionSources/presentation.test.ts @@ -0,0 +1,47 @@ +import type { TFunction } from "i18next"; +import { describe, expect, it } from "vitest"; + +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { sourceLabel, sourceLocation } from "./presentation"; + +const t = ((key: string) => key) as TFunction; +function image(originalRef: string): Extract { + return { + kind: "image", + key: "opaque-image-key", + ref: `orgii-transcript-image:${JSON.stringify(["private-session-id", "private-turn-id", originalRef])}`, + fileName: "design.png", + origin: "attachment", + }; +} + +describe("source display projection", () => { + it("decodes a readable image location without changing the navigation identity", () => { + const source = image("/project/design.png"); + const original = source.ref; + expect(sourceLocation(source)).toBe("/project/design.png"); + expect(sourceLabel(t, source)).toBe("design.png"); + expect(source.ref).toBe(original); + }); + + it.each([ + "codex-inline-image:0", + "claude-inline-image:2", + "data:image/png;base64,secret", + "blob:browser-owned", + ])("does not expose opaque image location %s", (reference) => { + expect(sourceLocation(image(reference))).toBeNull(); + }); + + it("handles malformed transcript identities without leaking their payload", () => { + const source: SessionSource = { + kind: "image", + key: "malformed", + ref: 'orgii-transcript-image:["private-session-id"]', + fileName: 'orgii-transcript-image:["private-session-id"]', + }; + expect(sourceLocation(source)).toBeNull(); + expect(sourceLabel(t, source)).toBe("common:git.rail.sourceImage"); + }); +}); diff --git a/src/features/SessionSources/presentation.ts b/src/features/SessionSources/presentation.ts new file mode 100644 index 0000000000..0a505fb2f6 --- /dev/null +++ b/src/features/SessionSources/presentation.ts @@ -0,0 +1,96 @@ +/** Display metadata is derived separately from the references used for navigation. */ +import type { TFunction } from "i18next"; + +import { parseGitHubPillUrl } from "@src/components/ComposerInput/githubUrl"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; +import { + File01Icon, + FolderClosedIcon, + GitPullRequestIcon, + InternetIcon, + Link01Icon, + Wrench01Icon, +} from "@src/icons"; +import { imageRefToRustPath } from "@src/util/file/imageRefs"; +import { formatToolName } from "@src/util/ui/rendering/formatToolName"; + +/** These are valid transport identities, not user-facing file locations. */ +function isOpaqueLocation(value: string): boolean { + return /^(?:data|blob|orgii-transcript-image|codex-inline-image|claude-inline-image):/iu.test( + value + ); +} + +export function sourceLocation(source: SessionSource): string | null { + if (source.kind === "tool-group") return null; + const location = + source.kind === "image" + ? imageRefToRustPath(source.ref) + : source.kind === "file" + ? source.path + : source.url; + return location && !isOpaqueLocation(location) ? location : null; +} + +export function sourceLabel(t: TFunction, source: SessionSource): string { + switch (source.kind) { + case "tool-group": + return source.group === "web" + ? t("common:git.rail.toolGroupWeb") + : source.group === "codex-app" + ? t("common:git.rail.toolGroupCodexApp") + : source.group + ? formatToolName(source.group.replace(/^mcp:/u, "")) + : t("common:git.rail.toolGroupGeneric"); + case "link": + return source.label; + case "file": + return source.title || source.fileName; + case "image": + return source.fileName && !isOpaqueLocation(source.fileName) + ? source.fileName + : t("common:git.rail.sourceImage"); + } +} + +export function sourceIcon(source: SessionSource) { + if (source.kind === "tool-group") + return source.group === "web" ? InternetIcon : Wrench01Icon; + if (source.kind === "file") + return source.isDirectory ? FolderClosedIcon : File01Icon; + if ( + source.kind === "link" && + parseGitHubPillUrl(source.url)?.iconType === "pr" + ) + return GitPullRequestIcon; + return Link01Icon; +} + +export function sourceProvenance(t: TFunction, source: SessionSource): string { + if (source.kind === "tool-group") return ""; + const origins = source.origins ?? [ + source.origin ?? + (source.kind === "image" + ? "attachment" + : source.kind === "file" + ? "provided-file" + : "provided-link"), + ]; + return origins + .map((origin) => { + const key = + origin === "assistant-reference" + ? "sourceAssistantReference" + : origin === "tool-result" + ? source.toolName + ? "sourceToolResultNamed" + : "sourceToolResult" + : origin === "attachment" + ? "sourceAttached" + : origin === "provided-file" + ? "sourceFileProvided" + : "sourceLinkProvided"; + return t(`common:git.rail.${key}`, { tool: source.toolName }); + }) + .join(" · "); +} diff --git a/src/features/SessionSources/useSessionSourceNavigation.test.ts b/src/features/SessionSources/useSessionSourceNavigation.test.ts new file mode 100644 index 0000000000..7c113dcf51 --- /dev/null +++ b/src/features/SessionSources/useSessionSourceNavigation.test.ts @@ -0,0 +1,173 @@ +// @vitest-environment jsdom +import React, { act, useEffect } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { extractSessionSources } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; + +import { useSessionSourceNavigation } from "./useSessionSourceNavigation"; + +const mocks = vi.hoisted(() => ({ + file: vi.fn(), + shared: vi.fn(() => false), + error: vi.fn(), + browser: vi.fn(), + reveal: vi.fn(), + set: vi.fn(), + get: vi.fn(() => "workspace-a"), +})); +vi.mock("@src/util/ui/openFileInWorkStation", () => ({ + openFileInWorkStation: mocks.file, +})); +vi.mock("@src/util/ui/openLink", () => ({ openInBrowserApp: mocks.browser })); +vi.mock("@src/util/ui/revealMyStation", () => ({ + revealMyStation: mocks.reveal, +})); +vi.mock("@src/util/core/state/instrumentedStore", () => ({ + getInstrumentedStore: () => ({ set: mocks.set, get: mocks.get }), +})); +vi.mock("@src/store/workstation/tabs", () => ({ + createDirectoryTab: (path: string) => ({ type: "directory", path }), + openWorkstationTabAtom: "open", + presentedWorkstationWorkspaceKeyAtom: "workspace", +})); +vi.mock("@src/features/Org2Cloud/SharedSessionFilesContext", () => ({ + useOpenSessionSharedFile: () => mocks.shared, +})); +vi.mock("@src/components/Message", () => ({ default: { error: mocks.error } })); +let root: Root; +let host: HTMLDivElement; +let navigation: ReturnType; +function Harness({ + sources, + basePath, +}: { + sources: SessionSource[]; + basePath?: string; +}) { + const current = useSessionSourceNavigation(sources, basePath); + useEffect(() => { + navigation = current; + }, [current]); + return null; +} +async function render( + sources: SessionSource[], + key = "session-a", + basePath?: string +) { + await act(async () => { + root.render(React.createElement(Harness, { sources, key, basePath })); + }); +} +beforeEach(() => { + host = document.createElement("div"); + root = createRoot(host); + vi.clearAllMocks(); + mocks.shared.mockReturnValue(false); +}); +afterEach(() => { + act(() => root.unmount()); +}); +describe("source navigation", () => { + it("routes files, folders and links to existing workstation entry points", async () => { + await render([]); + navigation.openSource({ + kind: "file", + key: "f", + path: "/tmp/report.md", + fileName: "report.md", + isDirectory: false, + }); + expect(mocks.file).toHaveBeenCalledWith("/tmp/report.md", { + defaultPreviewMode: true, + }); + navigation.openSource({ + kind: "file", + key: "d", + path: "/tmp/docs", + fileName: "docs", + isDirectory: true, + }); + expect(mocks.set).toHaveBeenCalledWith("open", { + workspace: "workspace-a", + tab: { type: "directory", path: "/tmp/docs" }, + }); + navigation.openSource({ + kind: "link", + key: "l", + url: "https://example.com", + label: "Example", + }); + expect(mocks.browser).toHaveBeenCalledWith("https://example.com"); + }); + it("resolves relative paths in the originating session and rejects unknown roots", async () => { + const source: SessionSource = { + kind: "file", + key: "r", + path: "./docs/guide.md", + fileName: "guide.md", + isDirectory: false, + }; + await render([], "session-a", "/work/origin"); + navigation.openSource(source); + expect(mocks.file).toHaveBeenCalledWith("/work/origin/docs/guide.md", { + defaultPreviewMode: true, + }); + mocks.file.mockClear(); + await render([], "session-b"); + navigation.openSource(source); + expect(mocks.file).not.toHaveBeenCalled(); + expect(mocks.error).toHaveBeenCalledOnce(); + }); + it("lets the cloud source provider intercept before any local file access", async () => { + mocks.shared.mockReturnValue(true); + await render([]); + navigation.openSource({ + kind: "file", + key: "remote", + path: "/owner/private.md", + fileName: "private.md", + isDirectory: false, + }); + expect(mocks.shared).toHaveBeenCalledWith("/owner/private.md"); + expect(mocks.file).not.toHaveBeenCalled(); + expect(mocks.set).not.toHaveBeenCalled(); + }); + it("opens an MCP file URI from the actual tool DTO as a decoded local path", async () => { + const sources = extractSessionSources([ + { + id: "mcp", + role: "tool", + toolName: "resource", + text: "report.md [file:file:///tmp/report.md]\nMy%20Report.md [file:file:///tmp/My%20Report.md]", + }, + ]); + expect(sources).toHaveLength(2); + await render(sources); + navigation.openSource(sources[0]); + expect(mocks.file).toHaveBeenNthCalledWith(1, "/tmp/report.md", { + defaultPreviewMode: true, + }); + navigation.openSource(sources[1]); + expect(mocks.file).toHaveBeenNthCalledWith(2, "/tmp/My Report.md", { + defaultPreviewMode: true, + }); + }); + it("keeps a gallery snapshot through refresh and resets on session remount", async () => { + const image: SessionSource = { + kind: "image", + key: "i", + ref: "/tmp/image.png", + fileName: "image.png", + }; + await render([image]); + act(() => navigation.openSource(image)); + expect(navigation.imagePreview?.images).toHaveLength(1); + await render([]); + expect(navigation.imagePreview?.images).toHaveLength(1); + await render([], "session-b"); + expect(navigation.imagePreview).toBeNull(); + }); +}); diff --git a/src/features/SessionSources/useSessionSourceNavigation.ts b/src/features/SessionSources/useSessionSourceNavigation.ts new file mode 100644 index 0000000000..b2d766671f --- /dev/null +++ b/src/features/SessionSources/useSessionSourceNavigation.ts @@ -0,0 +1,79 @@ +/** Source navigation is shared by the rail summary and the complete Sources tab. */ +import { useCallback, useState } from "react"; + +import Message from "@src/components/Message"; +import { ROUTES } from "@src/config/routes"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; +import { useOpenSessionSharedFile } from "@src/features/Org2Cloud/SharedSessionFilesContext"; +import { sharedFileAbsolutePath } from "@src/features/Org2Cloud/sessionSharedFileCandidates"; +import i18n from "@src/i18n"; +import { + createDirectoryTab, + openWorkstationTabAtom, + presentedWorkstationWorkspaceKeyAtom, +} from "@src/store/workstation/tabs"; +import { getInstrumentedStore } from "@src/util/core/state/instrumentedStore"; +import { openFileInWorkStation } from "@src/util/ui/openFileInWorkStation"; +import { openInBrowserApp } from "@src/util/ui/openLink"; +import { revealMyStation } from "@src/util/ui/revealMyStation"; + +import type { SessionSourceImage } from "./SessionSourceImagePreview"; + +export interface SessionSourceImagePreviewState { + images: SessionSourceImage[]; + index: number; +} + +export function useSessionSourceNavigation( + sources: SessionSource[], + basePath?: string +) { + const openSharedFile = useOpenSessionSharedFile(); + const [imagePreview, setImagePreview] = + useState(null); + const closeImagePreview = useCallback(() => setImagePreview(null), []); + const openSource = useCallback( + (source: SessionSource) => { + if (source.kind === "tool-group") return; + if (source.kind === "link") { + openInBrowserApp(source.url); + return; + } + if (source.kind === "file") { + if (openSharedFile(source.path)) return; + const path = sharedFileAbsolutePath( + source.path.replace(/^folder:\/\//u, ""), + basePath + ); + if (!path) { + Message.error(i18n.t("common:git.rail.sourceFileUnavailable")); + return; + } + if (source.isDirectory) { + const store = getInstrumentedStore(); + revealMyStation({ path: ROUTES.workStation.code.path }); + store.set(openWorkstationTabAtom, { + workspace: store.get(presentedWorkstationWorkspaceKeyAtom), + tab: createDirectoryTab(path), + }); + } else { + openFileInWorkStation(path, { defaultPreviewMode: true }); + } + return; + } + // Snapshot this gallery so a refresh never changes the selected image. + const images = sources.filter((candidate) => candidate.kind === "image"); + const index = images.findIndex( + (candidate) => candidate.key === source.key + ); + if (index >= 0) { + setImagePreview({ + images: images.map(({ ref, fileName }) => ({ ref, fileName })), + index, + }); + } + }, + [sources, basePath, openSharedFile] + ); + return { openSource, imagePreview, closeImagePreview }; +} diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 5a3a29db28..c33c19a989 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "{{count}} Quelle", "sourcesCount_other": "{{count}} Quellen", "loadMoreSources": "Mehr laden (+{{count}})", - "sourceImage": "Bild" + "sourceImage": "Bild", + "viewAllSources": "Alle anzeigen", + "searchSources": "Quellen durchsuchen", + "sourcesLoadFailed": "Quellen konnten nicht geladen werden", + "sourcesUnavailable": "Quellen sind nicht verfügbar", + "noSources": "Keine Quellen in dieser Unterhaltung", + "toolGroupWeb": "Websuche", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Werkzeugaktivität", + "toolSearchCount": "{{count}} Suchen", + "toolOpenCount": "{{count}} Seiten geöffnet", + "toolTerminalCount": "Terminal {{count}} Mal gelesen", + "toolCallCount": "{{count}} Werkzeugaufrufe", + "sourceCategoryImages": "Bilder", + "sourceCategoryFiles": "Dateien", + "sourceCategoryLinks": "Links", + "sourceCategoryTools": "Werkzeugaktivität", + "toolRepeatCount": "{{count}} Mal", + "toolLoadMore": "Mehr Aktivitäten anzeigen", + "toolFailed": "Fehlgeschlagen", + "sourceAssistantReference": "Vom Assistenten referenziert", + "sourceToolResult": "Werkzeugergebnis", + "sourceToolResultNamed": "Werkzeugergebnis · {{tool}}", + "sourceAttached": "An die Unterhaltung angehängt", + "sourceFileProvided": "In der Unterhaltung bereitgestellte Datei", + "sourceLinkProvided": "In der Unterhaltung bereitgestellter Link", + "sourceFileUnavailable": "Datei nicht verfügbar", + "sourceImageUnavailable": "Bild nicht verfügbar" }, "pr": { "title": "Pull Request", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index da63bed471..91eec90126 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -969,7 +969,34 @@ "sourcesCount_one": "{{count}} Source", "sourcesCount_other": "{{count}} Sources", "loadMoreSources": "Load more (+{{count}})", - "sourceImage": "Image" + "sourceImage": "Image", + "viewAllSources": "View all", + "searchSources": "Search sources", + "sourcesLoadFailed": "Could not load sources", + "sourcesUnavailable": "Sources are unavailable", + "noSources": "No sources in this conversation", + "toolGroupWeb": "Web search", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Tool activity", + "toolSearchCount": "Searched {{count}} times", + "toolOpenCount": "Opened {{count}} pages", + "toolTerminalCount": "Read terminal {{count}} times", + "toolCallCount": "{{count}} tool calls", + "sourceCategoryImages": "Images", + "sourceCategoryFiles": "Files", + "sourceCategoryLinks": "Links", + "sourceCategoryTools": "Tool activity", + "toolRepeatCount": "{{count}} times", + "toolLoadMore": "Show more activity", + "toolFailed": "Failed", + "sourceAssistantReference": "Referenced by assistant", + "sourceToolResult": "Tool result", + "sourceToolResultNamed": "Tool result · {{tool}}", + "sourceAttached": "Attached to conversation", + "sourceFileProvided": "File provided in conversation", + "sourceLinkProvided": "Link provided in conversation", + "sourceFileUnavailable": "File unavailable", + "sourceImageUnavailable": "Image unavailable" }, "messages": { "pushSuccess": "Successfully pushed changes to remote", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index ef1a59d48a..08eef649a2 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "{{count}} fuente", "sourcesCount_other": "{{count}} fuentes", "loadMoreSources": "Cargar más (+{{count}})", - "sourceImage": "Imagen" + "sourceImage": "Imagen", + "viewAllSources": "Ver todo", + "searchSources": "Buscar fuentes", + "sourcesLoadFailed": "No se pudieron cargar las fuentes", + "sourcesUnavailable": "Fuentes no disponibles", + "noSources": "No hay fuentes en esta conversación", + "toolGroupWeb": "Búsqueda web", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Actividad de herramientas", + "toolSearchCount": "{{count}} búsquedas", + "toolOpenCount": "{{count}} páginas abiertas", + "toolTerminalCount": "Terminal leído {{count}} veces", + "toolCallCount": "{{count}} llamadas a herramientas", + "sourceCategoryImages": "Imágenes", + "sourceCategoryFiles": "Archivos", + "sourceCategoryLinks": "Enlaces", + "sourceCategoryTools": "Actividad de herramientas", + "toolRepeatCount": "{{count}} veces", + "toolLoadMore": "Mostrar más actividad", + "toolFailed": "Error", + "sourceAssistantReference": "Referenciado por el asistente", + "sourceToolResult": "Resultado de herramienta", + "sourceToolResultNamed": "Resultado de herramienta · {{tool}}", + "sourceAttached": "Adjunto a la conversación", + "sourceFileProvided": "Archivo proporcionado en la conversación", + "sourceLinkProvided": "Enlace proporcionado en la conversación", + "sourceFileUnavailable": "Archivo no disponible", + "sourceImageUnavailable": "Imagen no disponible" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 8f52ae28cb..a55094f55d 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "{{count}} source", "sourcesCount_other": "{{count}} sources", "loadMoreSources": "Charger plus (+{{count}})", - "sourceImage": "Image" + "sourceImage": "Image", + "viewAllSources": "Tout afficher", + "searchSources": "Rechercher des sources", + "sourcesLoadFailed": "Impossible de charger les sources", + "sourcesUnavailable": "Sources indisponibles", + "noSources": "Aucune source dans cette conversation", + "toolGroupWeb": "Recherche Web", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Activité des outils", + "toolSearchCount": "{{count}} recherches", + "toolOpenCount": "{{count}} pages ouvertes", + "toolTerminalCount": "Terminal lu {{count}} fois", + "toolCallCount": "{{count}} appels d’outil", + "sourceCategoryImages": "Images", + "sourceCategoryFiles": "Fichiers", + "sourceCategoryLinks": "Liens", + "sourceCategoryTools": "Activité des outils", + "toolRepeatCount": "{{count}} fois", + "toolLoadMore": "Afficher plus d’activité", + "toolFailed": "Échec", + "sourceAssistantReference": "Référencé par l’assistant", + "sourceToolResult": "Résultat d’outil", + "sourceToolResultNamed": "Résultat d’outil · {{tool}}", + "sourceAttached": "Joint à la conversation", + "sourceFileProvided": "Fichier fourni dans la conversation", + "sourceLinkProvided": "Lien fourni dans la conversation", + "sourceFileUnavailable": "Fichier indisponible", + "sourceImageUnavailable": "Image indisponible" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index bc8306b3a7..8ecd48b6a3 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -969,7 +969,34 @@ "sourcesCount_one": "{{count}} स्रोत", "sourcesCount_other": "{{count}} स्रोत", "loadMoreSources": "और लोड करें (+{{count}})", - "sourceImage": "छवि" + "sourceImage": "छवि", + "viewAllSources": "सभी देखें", + "searchSources": "स्रोत खोजें", + "sourcesLoadFailed": "स्रोत लोड नहीं हो सके", + "sourcesUnavailable": "स्रोत उपलब्ध नहीं हैं", + "noSources": "इस बातचीत में कोई स्रोत नहीं है", + "toolGroupWeb": "वेब खोज", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "टूल गतिविधि", + "toolSearchCount": "{{count}} बार खोजा", + "toolOpenCount": "{{count}} पेज खोले", + "toolTerminalCount": "टर्मिनल {{count}} बार पढ़ा", + "toolCallCount": "{{count}} टूल कॉल", + "sourceCategoryImages": "चित्र", + "sourceCategoryFiles": "फ़ाइलें", + "sourceCategoryLinks": "लिंक", + "sourceCategoryTools": "टूल गतिविधि", + "toolRepeatCount": "{{count}} बार", + "toolLoadMore": "और गतिविधि दिखाएँ", + "toolFailed": "विफल", + "sourceAssistantReference": "सहायक द्वारा संदर्भित", + "sourceToolResult": "टूल का परिणाम", + "sourceToolResultNamed": "टूल का परिणाम · {{tool}}", + "sourceAttached": "बातचीत में संलग्न", + "sourceFileProvided": "बातचीत में दी गई फ़ाइल", + "sourceLinkProvided": "बातचीत में दिया गया लिंक", + "sourceFileUnavailable": "फ़ाइल उपलब्ध नहीं है", + "sourceImageUnavailable": "छवि उपलब्ध नहीं है" }, "messages": { "pushSuccess": "बदलाव रिमोट पर सफलतापूर्वक पुश किए गए", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index 4851e37166..b6c40e14ac 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -969,7 +969,34 @@ "sourcesCount_one": "{{count}} Sumber", "sourcesCount_other": "{{count}} Sumber", "loadMoreSources": "Muat lebih banyak (+{{count}})", - "sourceImage": "Gambar" + "sourceImage": "Gambar", + "viewAllSources": "Lihat semua", + "searchSources": "Cari sumber", + "sourcesLoadFailed": "Tidak dapat memuat sumber", + "sourcesUnavailable": "Sumber tidak tersedia", + "noSources": "Tidak ada sumber dalam percakapan ini", + "toolGroupWeb": "Pencarian web", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Aktivitas alat", + "toolSearchCount": "Dicari {{count}} kali", + "toolOpenCount": "{{count}} halaman dibuka", + "toolTerminalCount": "Terminal dibaca {{count}} kali", + "toolCallCount": "{{count}} pemanggilan alat", + "sourceCategoryImages": "Gambar", + "sourceCategoryFiles": "File", + "sourceCategoryLinks": "Tautan", + "sourceCategoryTools": "Aktivitas alat", + "toolRepeatCount": "{{count}} kali", + "toolLoadMore": "Tampilkan aktivitas lainnya", + "toolFailed": "Gagal", + "sourceAssistantReference": "Dirujuk oleh asisten", + "sourceToolResult": "Hasil alat", + "sourceToolResultNamed": "Hasil alat · {{tool}}", + "sourceAttached": "Dilampirkan ke percakapan", + "sourceFileProvided": "File yang diberikan dalam percakapan", + "sourceLinkProvided": "Tautan yang diberikan dalam percakapan", + "sourceFileUnavailable": "File tidak tersedia", + "sourceImageUnavailable": "Gambar tidak tersedia" }, "messages": { "pushSuccess": "Perubahan berhasil di-push ke remote", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 09a0f3889a..9dd1a103ff 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "ソース {{count}}件", "sourcesCount_other": "ソース {{count}}件", "loadMoreSources": "さらに表示 (+{{count}})", - "sourceImage": "画像" + "sourceImage": "画像", + "viewAllSources": "すべて表示", + "searchSources": "ソースを検索", + "sourcesLoadFailed": "ソースを読み込めませんでした", + "sourcesUnavailable": "ソースを利用できません", + "noSources": "この会話にはソースがありません", + "toolGroupWeb": "ウェブ検索", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "ツールの履歴", + "toolSearchCount": "{{count}} 回検索", + "toolOpenCount": "{{count}} ページを開きました", + "toolTerminalCount": "ターミナルを {{count}} 回読み取り", + "toolCallCount": "{{count}} 回のツール呼び出し", + "sourceCategoryImages": "画像", + "sourceCategoryFiles": "ファイル", + "sourceCategoryLinks": "リンク", + "sourceCategoryTools": "ツールの実行", + "toolRepeatCount": "{{count}} 回", + "toolLoadMore": "履歴をさらに表示", + "toolFailed": "失敗", + "sourceAssistantReference": "アシスタントが参照", + "sourceToolResult": "ツールの結果", + "sourceToolResultNamed": "ツールの結果 · {{tool}}", + "sourceAttached": "会話に添付", + "sourceFileProvided": "会話で提供されたファイル", + "sourceLinkProvided": "会話で提供されたリンク", + "sourceFileUnavailable": "ファイルを利用できません", + "sourceImageUnavailable": "画像を利用できません" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 67e81e076f..9af25ea5ca 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "소스 {{count}}개", "sourcesCount_other": "소스 {{count}}개", "loadMoreSources": "더 보기 (+{{count}})", - "sourceImage": "이미지" + "sourceImage": "이미지", + "viewAllSources": "모두 보기", + "searchSources": "소스 검색", + "sourcesLoadFailed": "소스를 불러오지 못했습니다", + "sourcesUnavailable": "소스를 사용할 수 없습니다", + "noSources": "이 대화에 소스가 없습니다", + "toolGroupWeb": "웹 검색", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "도구 기록", + "toolSearchCount": "검색 {{count}}회", + "toolOpenCount": "웹페이지 {{count}}개 열기", + "toolTerminalCount": "터미널 읽기 {{count}}회", + "toolCallCount": "도구 호출 {{count}}회", + "sourceCategoryImages": "이미지", + "sourceCategoryFiles": "파일", + "sourceCategoryLinks": "링크", + "sourceCategoryTools": "도구 활동", + "toolRepeatCount": "{{count}}회", + "toolLoadMore": "기록 더 보기", + "toolFailed": "실패", + "sourceAssistantReference": "어시스턴트가 참조함", + "sourceToolResult": "도구 결과", + "sourceToolResultNamed": "도구 결과 · {{tool}}", + "sourceAttached": "대화에 첨부됨", + "sourceFileProvided": "대화에서 제공된 파일", + "sourceLinkProvided": "대화에서 제공된 링크", + "sourceFileUnavailable": "파일을 사용할 수 없습니다", + "sourceImageUnavailable": "이미지를 사용할 수 없습니다" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index a3fd47d69b..7a6855c59c 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "Źródła: {{count}}", "sourcesCount_other": "Źródła: {{count}}", "loadMoreSources": "Załaduj więcej (+{{count}})", - "sourceImage": "Obraz" + "sourceImage": "Obraz", + "viewAllSources": "Pokaż wszystko", + "searchSources": "Szukaj źródeł", + "sourcesLoadFailed": "Nie udało się wczytać źródeł", + "sourcesUnavailable": "Źródła są niedostępne", + "noSources": "Brak źródeł w tej rozmowie", + "toolGroupWeb": "Wyszukiwanie w sieci", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Aktywność narzędzi", + "toolSearchCount": "Wyszukiwania: {{count}}", + "toolOpenCount": "Otwarte strony: {{count}}", + "toolTerminalCount": "Odczyty terminala: {{count}}", + "toolCallCount": "Wywołania narzędzi: {{count}}", + "sourceCategoryImages": "Obrazy", + "sourceCategoryFiles": "Pliki", + "sourceCategoryLinks": "Linki", + "sourceCategoryTools": "Aktywność narzędzi", + "toolRepeatCount": "{{count}} razy", + "toolLoadMore": "Pokaż więcej aktywności", + "toolFailed": "Błąd", + "sourceAssistantReference": "Wskazane przez asystenta", + "sourceToolResult": "Wynik narzędzia", + "sourceToolResultNamed": "Wynik narzędzia · {{tool}}", + "sourceAttached": "Dołączono do rozmowy", + "sourceFileProvided": "Plik podany w rozmowie", + "sourceLinkProvided": "Link podany w rozmowie", + "sourceFileUnavailable": "Plik niedostępny", + "sourceImageUnavailable": "Obraz niedostępny" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/pt/common.json b/src/i18n/locales/pt/common.json index d0893aceba..ff12b41c77 100644 --- a/src/i18n/locales/pt/common.json +++ b/src/i18n/locales/pt/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "{{count}} fonte", "sourcesCount_other": "{{count}} fontes", "loadMoreSources": "Carregar mais (+{{count}})", - "sourceImage": "Imagem" + "sourceImage": "Imagem", + "viewAllSources": "Ver tudo", + "searchSources": "Pesquisar fontes", + "sourcesLoadFailed": "Não foi possível carregar as fontes", + "sourcesUnavailable": "Fontes indisponíveis", + "noSources": "Nenhuma fonte nesta conversa", + "toolGroupWeb": "Pesquisa na web", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Atividade de ferramentas", + "toolSearchCount": "{{count}} pesquisas", + "toolOpenCount": "{{count}} páginas abertas", + "toolTerminalCount": "Terminal lido {{count}} vezes", + "toolCallCount": "{{count}} chamadas de ferramentas", + "sourceCategoryImages": "Imagens", + "sourceCategoryFiles": "Arquivos", + "sourceCategoryLinks": "Links", + "sourceCategoryTools": "Atividade das ferramentas", + "toolRepeatCount": "{{count}} vezes", + "toolLoadMore": "Mostrar mais atividade", + "toolFailed": "Falhou", + "sourceAssistantReference": "Referenciado pelo assistente", + "sourceToolResult": "Resultado da ferramenta", + "sourceToolResultNamed": "Resultado da ferramenta · {{tool}}", + "sourceAttached": "Anexado à conversa", + "sourceFileProvided": "Arquivo fornecido na conversa", + "sourceLinkProvided": "Link fornecido na conversa", + "sourceFileUnavailable": "Arquivo indisponível", + "sourceImageUnavailable": "Imagem indisponível" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 4ff2b521b4..ab4e720d5b 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "Источники: {{count}}", "sourcesCount_other": "Источники: {{count}}", "loadMoreSources": "Показать ещё (+{{count}})", - "sourceImage": "Изображение" + "sourceImage": "Изображение", + "viewAllSources": "Показать все", + "searchSources": "Поиск источников", + "sourcesLoadFailed": "Не удалось загрузить источники", + "sourcesUnavailable": "Источники недоступны", + "noSources": "В этом разговоре нет источников", + "toolGroupWeb": "Поиск в интернете", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Действия инструментов", + "toolSearchCount": "Поисков: {{count}}", + "toolOpenCount": "Открыто страниц: {{count}}", + "toolTerminalCount": "Чтений терминала: {{count}}", + "toolCallCount": "Вызовов инструментов: {{count}}", + "sourceCategoryImages": "Изображения", + "sourceCategoryFiles": "Файлы", + "sourceCategoryLinks": "Ссылки", + "sourceCategoryTools": "Действия инструментов", + "toolRepeatCount": "{{count}} раз", + "toolLoadMore": "Показать ещё", + "toolFailed": "Ошибка", + "sourceAssistantReference": "Упомянуто ассистентом", + "sourceToolResult": "Результат инструмента", + "sourceToolResultNamed": "Результат инструмента · {{tool}}", + "sourceAttached": "Прикреплено к разговору", + "sourceFileProvided": "Файл, предоставленный в разговоре", + "sourceLinkProvided": "Ссылка, предоставленная в разговоре", + "sourceFileUnavailable": "Файл недоступен", + "sourceImageUnavailable": "Изображение недоступно" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 1d76a2a0b1..fc59926578 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "{{count}} kaynak", "sourcesCount_other": "{{count}} kaynak", "loadMoreSources": "Daha fazla yükle (+{{count}})", - "sourceImage": "Görsel" + "sourceImage": "Görsel", + "viewAllSources": "Tümünü görüntüle", + "searchSources": "Kaynaklarda ara", + "sourcesLoadFailed": "Kaynaklar yüklenemedi", + "sourcesUnavailable": "Kaynaklar kullanılamıyor", + "noSources": "Bu konuşmada kaynak yok", + "toolGroupWeb": "Web araması", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Araç etkinliği", + "toolSearchCount": "{{count}} arama yapıldı", + "toolOpenCount": "{{count}} sayfa açıldı", + "toolTerminalCount": "Terminal {{count}} kez okundu", + "toolCallCount": "{{count}} araç çağrısı", + "sourceCategoryImages": "Görseller", + "sourceCategoryFiles": "Dosyalar", + "sourceCategoryLinks": "Bağlantılar", + "sourceCategoryTools": "Araç etkinliği", + "toolRepeatCount": "{{count}} kez", + "toolLoadMore": "Daha fazla etkinlik göster", + "toolFailed": "Başarısız", + "sourceAssistantReference": "Asistan tarafından referans verildi", + "sourceToolResult": "Araç sonucu", + "sourceToolResultNamed": "Araç sonucu · {{tool}}", + "sourceAttached": "Konuşmaya eklendi", + "sourceFileProvided": "Konuşmada sağlanan dosya", + "sourceLinkProvided": "Konuşmada sağlanan bağlantı", + "sourceFileUnavailable": "Dosya kullanılamıyor", + "sourceImageUnavailable": "Görsel kullanılamıyor" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index c70ade1d14..806fba617d 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "{{count}} nguồn", "sourcesCount_other": "{{count}} nguồn", "loadMoreSources": "Tải thêm (+{{count}})", - "sourceImage": "Hình ảnh" + "sourceImage": "Hình ảnh", + "viewAllSources": "Xem tất cả", + "searchSources": "Tìm nguồn", + "sourcesLoadFailed": "Không thể tải nguồn", + "sourcesUnavailable": "Nguồn không khả dụng", + "noSources": "Cuộc trò chuyện này chưa có nguồn", + "toolGroupWeb": "Tìm kiếm trên web", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "Hoạt động công cụ", + "toolSearchCount": "Đã tìm kiếm {{count}} lần", + "toolOpenCount": "Đã mở {{count}} trang", + "toolTerminalCount": "Đã đọc terminal {{count}} lần", + "toolCallCount": "{{count}} lần gọi công cụ", + "sourceCategoryImages": "Hình ảnh", + "sourceCategoryFiles": "Tệp", + "sourceCategoryLinks": "Liên kết", + "sourceCategoryTools": "Hoạt động công cụ", + "toolRepeatCount": "{{count}} lần", + "toolLoadMore": "Hiện thêm hoạt động", + "toolFailed": "Thất bại", + "sourceAssistantReference": "Trợ lý tham chiếu", + "sourceToolResult": "Kết quả công cụ", + "sourceToolResultNamed": "Kết quả công cụ · {{tool}}", + "sourceAttached": "Đã đính kèm vào cuộc trò chuyện", + "sourceFileProvided": "Tệp được cung cấp trong cuộc trò chuyện", + "sourceLinkProvided": "Liên kết được cung cấp trong cuộc trò chuyện", + "sourceFileUnavailable": "Tệp không khả dụng", + "sourceImageUnavailable": "Hình ảnh không khả dụng" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/zh-Hant/common.json b/src/i18n/locales/zh-Hant/common.json index c90b6badbb..0d26fbbc43 100644 --- a/src/i18n/locales/zh-Hant/common.json +++ b/src/i18n/locales/zh-Hant/common.json @@ -1136,7 +1136,34 @@ "sourcesCount_one": "{{count}} 個來源", "sourcesCount_other": "{{count}} 個來源", "loadMoreSources": "載入更多(+{{count}})", - "sourceImage": "圖片" + "sourceImage": "圖片", + "viewAllSources": "查看全部", + "searchSources": "搜尋來源", + "sourcesLoadFailed": "無法載入來源", + "sourcesUnavailable": "來源暫時無法使用", + "noSources": "此對話尚無來源", + "toolGroupWeb": "網頁搜尋", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "工具記錄", + "toolSearchCount": "已搜尋 {{count}} 次", + "toolOpenCount": "已開啟 {{count}} 個網頁", + "toolTerminalCount": "已讀取終端機 {{count}} 次", + "toolCallCount": "已呼叫 {{count}} 次", + "sourceCategoryImages": "圖片", + "sourceCategoryFiles": "檔案", + "sourceCategoryLinks": "連結", + "sourceCategoryTools": "工具活動", + "toolRepeatCount": "{{count}} 次", + "toolLoadMore": "載入更多記錄", + "toolFailed": "失敗", + "sourceAssistantReference": "助手引用", + "sourceToolResult": "工具傳回", + "sourceToolResultNamed": "工具傳回 · {{tool}}", + "sourceAttached": "已附加至對話", + "sourceFileProvided": "在對話中提供的檔案", + "sourceLinkProvided": "在對話中提供的連結", + "sourceFileUnavailable": "檔案無法使用", + "sourceImageUnavailable": "圖片無法使用" }, "pr": { "title": "Pull request", diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json index 68decd327f..70664212f4 100644 --- a/src/i18n/locales/zh/common.json +++ b/src/i18n/locales/zh/common.json @@ -969,7 +969,34 @@ "sourcesCount_one": "{{count}} 个来源", "sourcesCount_other": "{{count}} 个来源", "loadMoreSources": "加载更多(+{{count}})", - "sourceImage": "图片" + "sourceImage": "图片", + "viewAllSources": "查看全部", + "searchSources": "搜索来源", + "sourcesLoadFailed": "无法加载来源", + "sourcesUnavailable": "来源暂不可用", + "noSources": "此会话暂无来源", + "toolGroupWeb": "网页搜索", + "toolGroupCodexApp": "ChatGPT App Tools", + "toolGroupGeneric": "工具记录", + "toolSearchCount": "已搜索 {{count}} 次", + "toolOpenCount": "已打开 {{count}} 个网页", + "toolTerminalCount": "已读取终端 {{count}} 次", + "toolCallCount": "已调用 {{count}} 次", + "sourceCategoryImages": "图片", + "sourceCategoryFiles": "文件", + "sourceCategoryLinks": "链接", + "sourceCategoryTools": "工具活动", + "toolRepeatCount": "{{count}} 次", + "toolLoadMore": "加载更多记录", + "toolFailed": "失败", + "sourceAssistantReference": "助手引用", + "sourceToolResult": "工具返回", + "sourceToolResultNamed": "工具返回 · {{tool}}", + "sourceAttached": "已附加到对话", + "sourceFileProvided": "在对话中提供的文件", + "sourceLinkProvided": "在对话中提供的链接", + "sourceFileUnavailable": "文件不可用", + "sourceImageUnavailable": "图片不可用" }, "pr": { "title": "Pull request", diff --git a/src/modules/WorkStation/CodeEditor/__tests__/useCodeEditorPrimarySidebarConfig.test.ts b/src/modules/WorkStation/CodeEditor/__tests__/useCodeEditorPrimarySidebarConfig.test.ts new file mode 100644 index 0000000000..2ea84817ca --- /dev/null +++ b/src/modules/WorkStation/CodeEditor/__tests__/useCodeEditorPrimarySidebarConfig.test.ts @@ -0,0 +1,49 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { expect, it, vi } from "vitest"; + +import type { UseWorkStationPanelsReturn } from "@src/hooks/tabHost/useWorkStationPanels"; +import type { WorkStationTab } from "@src/store/workstation/tabs"; + +import { useCodeEditorPrimarySidebarConfig } from "../useCodeEditorPrimarySidebarConfig"; + +it("hides the source tab tree without changing the saved sidebar state", () => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + const host = document.createElement("div"); + const root = createRoot(host); + const close = vi.fn(); + const panels = { + primarySidebarCollapsed: false, + primarySidebarWidth: 300, + closePrimarySidebar: close, + } as unknown as UseWorkStationPanelsReturn; + function Harness({ type }: { type: WorkStationTab["type"] }) { + const config = useCodeEditorPrimarySidebarConfig({ + activeTab: { id: type, type, title: type, data: {} }, + sidebarContent: null, + panels, + }); + return React.createElement("div", { + "data-collapsed": config.collapsed, + "data-size": config.size, + }); + } + try { + for (const [type, collapsed, size] of [ + ["file", "false", "300"], + ["session-sources", "true", "0"], + ["file", "false", "300"], + ] as const) { + act(() => root.render(React.createElement(Harness, { type }))); + expect(host.firstElementChild?.getAttribute("data-collapsed")).toBe( + collapsed + ); + expect(host.firstElementChild?.getAttribute("data-size")).toBe(size); + } + expect(close).not.toHaveBeenCalled(); + } finally { + act(() => root.unmount()); + vi.unstubAllGlobals(); + } +}); diff --git a/src/modules/WorkStation/CodeEditor/useCodeEditorPrimarySidebarConfig.ts b/src/modules/WorkStation/CodeEditor/useCodeEditorPrimarySidebarConfig.ts index db6c783f38..75abafe774 100644 --- a/src/modules/WorkStation/CodeEditor/useCodeEditorPrimarySidebarConfig.ts +++ b/src/modules/WorkStation/CodeEditor/useCodeEditorPrimarySidebarConfig.ts @@ -23,6 +23,7 @@ export function useCodeEditorPrimarySidebarConfig({ }: UseCodeEditorPrimarySidebarConfigOptions) { const activeTabHasNoSidebar = activeTab?.type === "shared-file" || + activeTab?.type === "session-sources" || activeTab?.type === "agent-config" || activeTab?.type === "chat-session" || activeTab?.type === "github-issue-detail" || diff --git a/src/modules/WorkStation/TabContent/registry.ts b/src/modules/WorkStation/TabContent/registry.ts index c85b42a977..4bfe41975b 100644 --- a/src/modules/WorkStation/TabContent/registry.ts +++ b/src/modules/WorkStation/TabContent/registry.ts @@ -74,6 +74,10 @@ const SearchSessionsEntry: RendererEntry = { Component: lazy(() => import("./renderers/searchSessions")), }; +const SessionSourcesEntry: RendererEntry = { + Component: lazy(() => import("./renderers/sessionSources")), +}; + const UrlPreviewEntry: RendererEntry = { Component: lazy(() => import("./renderers/urlPreview")), }; @@ -182,6 +186,7 @@ export const REGISTRY: TabContentRegistry = { terminal: TerminalEntry, search: SearchEntry, "search-sessions": SearchSessionsEntry, + "session-sources": SessionSourcesEntry, "url-preview": UrlPreviewEntry, // Browser diff --git a/src/modules/WorkStation/TabContent/renderers/sessionSources.activities.test.ts b/src/modules/WorkStation/TabContent/renderers/sessionSources.activities.test.ts new file mode 100644 index 0000000000..5a57e4b434 --- /dev/null +++ b/src/modules/WorkStation/TabContent/renderers/sessionSources.activities.test.ts @@ -0,0 +1,183 @@ +// @vitest-environment jsdom +import { Provider, atom, createStore } from "jotai"; +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; + +import { createSessionSourcesTab } from "@src/store/workstation/tabs/factories/sessionSources"; + +import SessionSourcesTabRenderer from "./sessionSources"; + +const api = vi.hoisted(() => ({ readSessionSourceMessages: vi.fn() })); +const navigation = vi.hoisted(() => ({ + openSource: vi.fn(), + closeImagePreview: vi.fn(), + imagePreview: null, +})); +vi.mock("@src/api/tauri/session/sessionSources", () => api); +vi.mock("@src/features/SessionSources/useSessionSourceNavigation", () => ({ + useSessionSourceNavigation: () => navigation, +})); +vi.mock( + "@src/engines/ChatPanel/sessionSources/SessionSourcesFileScope", + () => ({ + SessionSourcesFileScope: ({ children }: { children: React.ReactNode }) => + children, + }) +); +const session = atom({ updated_at: "initial" }); +vi.mock("@src/store/session", () => ({ sessionByIdAtom: () => session })); +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string, options?: { count?: number }) => + `${key}${options?.count === undefined ? "" : ` ${options.count}`}`, + }), +})); + +function terminalMessage(callId: string, error: string) { + return { + id: callId, + role: "tool", + text: "", + images: [], + toolActivity: { + callId, + toolName: "mcp__codex_app__read_thread_terminal", + group: "codex-app", + status: "error", + error, + actions: [{ kind: "read-terminal" }], + }, + }; +} + +let host: HTMLDivElement; +let root: Root; +let store: ReturnType; +async function render(sessionId: string) { + await act(async () => { + root.render( + React.createElement( + Provider, + { store }, + React.createElement(SessionSourcesTabRenderer, { + tab: createSessionSourcesTab(sessionId, "Sources"), + isActive: true, + }) + ) + ); + }); +} +function button(key: string) { + const found = [...host.querySelectorAll("button")].find((item) => + item.textContent?.includes(key) + ); + expect(found, key).toBeDefined(); + return found!; +} +beforeEach(() => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + vi.clearAllMocks(); + store = createStore(); + host = document.createElement("div"); + document.body.appendChild(host); + root = createRoot(host); +}); +afterEach(() => { + act(() => root.unmount()); + host.remove(); + vi.unstubAllGlobals(); +}); + +it("renders failed tool activity without a resource and resets disclosures on session switch", async () => { + api.readSessionSourceMessages.mockResolvedValueOnce([ + terminalMessage( + "a", + "No app terminal session is attached to this thread yet." + ), + ]); + await render("session-a"); + expect(host.textContent).toContain("toolGroupCodexApp"); + expect(host.textContent).not.toContain("noSources"); + expect(button("toolTerminalCount").getAttribute("aria-expanded")).toBe( + "false" + ); + act(() => button("toolTerminalCount").click()); + expect(host.textContent).toContain("No app terminal session is attached"); + + api.readSessionSourceMessages.mockResolvedValueOnce([ + terminalMessage("b", "Different session error"), + ]); + await render("session-b"); + expect(button("toolTerminalCount").getAttribute("aria-expanded")).toBe( + "false" + ); + expect(host.textContent).not.toContain("No app terminal session is attached"); + expect(host.textContent).not.toContain("Different session error"); + act(() => button("toolTerminalCount").click()); + expect(host.textContent).toContain("Different session error"); +}); + +it("keeps operation counts stable on refresh and opens a web action through source navigation", async () => { + const activity = { + id: "web-message", + role: "tool", + text: "", + toolActivity: { + callId: "web-call", + toolName: "web.run", + group: "web", + status: "success", + actions: [ + { kind: "search", query: "first query" }, + { kind: "search", query: "second query" }, + { kind: "open", url: "https://example.com/docs" }, + ], + }, + }; + api.readSessionSourceMessages.mockResolvedValue([activity, activity]); + await render("session-web"); + expect(button("toolSearchCount").textContent).toContain("2"); + expect(button("toolOpenCount").textContent).toContain("1"); + act(() => button("toolSearchCount").click()); + expect(host.textContent).toContain("first query"); + expect(host.textContent).toContain("second query"); + act(() => button("toolOpenCount").click()); + await act(async () => button("https://example.com/docs").click()); + expect(navigation.openSource).toHaveBeenCalledWith( + expect.objectContaining({ kind: "link", url: "https://example.com/docs" }) + ); + await act(async () => store.set(session, { updated_at: "refreshed" })); + expect(button("toolSearchCount").textContent).toContain("2"); + expect(button("toolSearchCount").getAttribute("aria-expanded")).toBe("true"); +}); + +it("aggregates identical real calls after ingestion without losing counts or distinct failures", async () => { + const messages = Array.from({ length: 49 }, (_, index) => ({ + id: `message-${index}`, + role: "tool", + text: "", + toolActivity: { + callId: `call-${index}`, + toolName: "mcp__cua_repl.js", + group: "mcp:cua_repl", + status: index < 46 ? "success" : "error", + ...(index >= 46 + ? { error: index < 48 ? "Element unavailable" : "Tab closed" } + : {}), + actions: [{ kind: "generic" }], + }, + })); + api.readSessionSourceMessages.mockResolvedValue([...messages, messages[0]]); + await render("session-repeated-tools"); + expect(button("toolCallCount").textContent).toContain("49"); + act(() => button("toolCallCount").click()); + expect(host.querySelectorAll("[data-tool-activity-call]")).toHaveLength(3); + expect(host.textContent).toContain("toolRepeatCount 46"); + expect(host.textContent).toContain("toolRepeatCount 2"); + expect(host.textContent).toContain("Element unavailable"); + expect(host.textContent).toContain("Tab closed"); + await act(async () => store.set(session, { updated_at: "refresh-repeats" })); + expect(button("toolCallCount").textContent).toContain("49"); + expect(host.querySelectorAll("[data-tool-activity-call]")).toHaveLength(3); +}); diff --git a/src/modules/WorkStation/TabContent/renderers/sessionSources.test.ts b/src/modules/WorkStation/TabContent/renderers/sessionSources.test.ts new file mode 100644 index 0000000000..ce613869b6 --- /dev/null +++ b/src/modules/WorkStation/TabContent/renderers/sessionSources.test.ts @@ -0,0 +1,317 @@ +// @vitest-environment jsdom +import { Provider, atom, createStore } from "jotai"; +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; +import { useSessionSourceNavigation } from "@src/features/SessionSources/useSessionSourceNavigation"; +import { createSessionSourcesTab } from "@src/store/workstation/tabs/factories/sessionSources"; + +import SessionSourcesTabRenderer from "./sessionSources"; + +const api = vi.hoisted(() => ({ readSessionSourceMessages: vi.fn() })); +const localFiles = vi.hoisted(() => ({ openFileInWorkStation: vi.fn() })); +const sharedFiles = vi.hoisted(() => ({ openSharedSessionFile: vi.fn() })); +vi.mock("@src/api/tauri/session/sessionSources", () => api); +vi.mock("@src/util/ui/openFileInWorkStation", () => localFiles); +vi.mock("@src/features/Org2Cloud/openSharedSessionFile", () => sharedFiles); + +const session = atom<{ + updated_at: string; + repoPath?: string; + importedFrom?: { + orgId: string; + sourceSessionId: string; + sourceEndpointUrl?: string; + shareEndpointUrl?: string; + }; +}>({ updated_at: "initial" }); +vi.mock("@src/store/session", () => ({ + sessionByIdAtom: () => session, +})); + +// The view owns search, pagination and navigation tests. This probe verifies +// the actual renderer -> resource hook -> durable-history API boundary. +vi.mock("@src/features/SessionSources/SessionSourcesView", () => ({ + SessionSourcesView: ({ + sources, + loading, + error, + onRetry, + basePath, + }: { + sources: SessionSource[]; + loading: boolean; + error: boolean; + onRetry: () => void; + basePath?: string; + }) => { + const { openSource } = useSessionSourceNavigation(sources, basePath); + return React.createElement( + "section", + { "aria-busy": loading, "data-error": error }, + sources.map((source) => source.key).join(" "), + React.createElement("button", { onClick: onRetry }, "Retry"), + ...sources.map((source) => + React.createElement( + "button", + { + key: source.key, + "data-source": source.key, + onClick: () => openSource(source), + }, + "Open source" + ) + ) + ); + }, +})); + +function deferred() { + let resolve!: (value: T) => void; + const promise = new Promise((done) => { + resolve = done; + }); + return { promise, resolve }; +} + +describe("session sources tab renderer", () => { + let root: Root; + let host: HTMLDivElement; + let store: ReturnType; + + async function render(sessionId: string, isActive = true) { + await act(async () => { + root.render( + React.createElement( + Provider, + { store }, + React.createElement(SessionSourcesTabRenderer, { + tab: createSessionSourcesTab(sessionId, "Sources"), + isActive, + }) + ) + ); + }); + } + + beforeEach(() => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + api.readSessionSourceMessages.mockReset(); + localFiles.openFileInWorkStation.mockReset(); + sharedFiles.openSharedSessionFile.mockReset(); + store = createStore(); + host = document.createElement("div"); + document.body.appendChild(host); + root = createRoot(host); + }); + + afterEach(() => { + act(() => root.unmount()); + host.remove(); + vi.unstubAllGlobals(); + }); + + it("loads only the active tab and ignores a read that resolves after hiding", async () => { + const first = deferred(); + api.readSessionSourceMessages.mockReturnValueOnce(first.promise); + await render("session-a", false); + expect(host.childElementCount).toBe(0); + expect(api.readSessionSourceMessages).not.toHaveBeenCalled(); + + await render("session-a"); + expect(api.readSessionSourceMessages).toHaveBeenCalledExactlyOnceWith( + "session-a" + ); + expect(host.querySelector("section")?.getAttribute("aria-busy")).toBe( + "true" + ); + await render("session-a", false); + await act(async () => + first.resolve([{ id: "a", text: "https://a.example/late" }]) + ); + expect(host.childElementCount).toBe(0); + + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "a", text: "https://a.example/fresh" }, + ]); + await render("session-a"); + expect(host.textContent).toContain("https://a.example/fresh"); + expect(host.textContent).not.toContain("late"); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(2); + }); + + it("binds to the tab's session and cannot commit a previous tab's pending read", async () => { + const first = deferred(); + api.readSessionSourceMessages + .mockReturnValueOnce(first.promise) + .mockResolvedValueOnce([{ id: "b", text: "https://b.example/" }]); + await render("session-a"); + await render("session-b"); + expect(api.readSessionSourceMessages.mock.calls).toEqual([ + ["session-a"], + ["session-b"], + ]); + await act(async () => + first.resolve([{ id: "a", text: "https://a.example/" }]) + ); + expect(host.textContent).toContain("https://b.example/"); + expect(host.textContent).not.toContain("https://a.example/"); + }); + + it("forwards read failures and retries through the resource API", async () => { + api.readSessionSourceMessages.mockRejectedValueOnce( + new Error("read failed") + ); + await render("retry-session"); + expect(host.querySelector("section")?.getAttribute("data-error")).toBe( + "true" + ); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "ok", text: "https://recovered.example/" }, + ]); + await act(async () => host.querySelector("button")!.click()); + expect(host.querySelector("section")?.getAttribute("data-error")).toBe( + "false" + ); + expect(host.textContent).toContain("https://recovered.example/"); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(2); + }); + + it("refreshes after session metadata changes and handles empty restored bindings", async () => { + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "a1", text: "https://a.example/old" }, + ]); + await render("session-a"); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "a2", text: "https://a.example/new" }, + ]); + await act(async () => store.set(session, { updated_at: "next" })); + expect(host.textContent).toContain("https://a.example/new"); + expect(host.textContent).not.toContain("https://a.example/old"); + await render(""); + expect(host.textContent).not.toContain("https://a.example/"); + expect(api.readSessionSourceMessages).toHaveBeenCalledTimes(2); + }); + + it("routes imported file references through the sender's cloud scope instead of the receiver's disk", async () => { + store.set(session, { + updated_at: "shared", + repoPath: "/sender/repo", + importedFrom: { + orgId: "remote-org", + sourceSessionId: "remote-session", + sourceEndpointUrl: "https://cloud.example", + }, + }); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { + id: "source", + role: "assistant", + text: "[Report](/sender/repo/report.md)", + }, + ]); + await render("imported-local-id"); + expect(api.readSessionSourceMessages).toHaveBeenCalledWith( + "imported-local-id" + ); + const fileButton = host.querySelector("[data-source]"); + expect(fileButton).not.toBeNull(); + await act(async () => fileButton!.click()); + expect(localFiles.openFileInWorkStation).not.toHaveBeenCalled(); + expect(sharedFiles.openSharedSessionFile).toHaveBeenCalledExactlyOnceWith( + { + id: "source", + endpoint: "https://cloud.example", + source: { + orgId: "remote-org", + sessionId: "remote-session", + path: "/sender/repo/report.md", + }, + }, + null + ); + + // An updated import scope must replace the previous sender without ever + // interpreting its absolute path as a receiver-local file. + await act(async () => + store.set(session, { + updated_at: "shared", + repoPath: "/sender/repo", + importedFrom: { + orgId: "next-org", + sourceSessionId: "next-session", + sourceEndpointUrl: "https://next-cloud.example", + }, + }) + ); + await act(async () => + host.querySelector("[data-source]")!.click() + ); + expect(sharedFiles.openSharedSessionFile).toHaveBeenCalledTimes(2); + expect(sharedFiles.openSharedSessionFile).toHaveBeenLastCalledWith( + { + id: "source", + endpoint: "https://next-cloud.example", + source: { + orgId: "next-org", + sessionId: "next-session", + path: "/sender/repo/report.md", + }, + }, + null + ); + expect(localFiles.openFileInWorkStation).not.toHaveBeenCalled(); + }); + + it("uses the local session's repository for relative file navigation", async () => { + store.set(session, { updated_at: "local", repoPath: "/local/project" }); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "source", text: "[report](./src/report.md)" }, + ]); + await render("local-session"); + const fileButton = host.querySelector("[data-source]"); + expect(fileButton).not.toBeNull(); + await act(async () => fileButton!.click()); + expect(localFiles.openFileInWorkStation).toHaveBeenCalledExactlyOnceWith( + "/local/project/src/report.md", + { defaultPreviewMode: true } + ); + expect(sharedFiles.openSharedSessionFile).not.toHaveBeenCalled(); + }); + + it("opens assistant and tool files alongside attachments from the same history read", async () => { + store.set(session, { updated_at: "mixed", repoPath: "/local/project" }); + api.readSessionSourceMessages.mockResolvedValueOnce([ + { id: "user", text: "", images: ["/local/reference.png"] }, + { + id: "assistant", + role: "assistant", + text: "[Report](./docs/report.md)\n[Fix PR](https://github.com/example/project/pull/1)", + }, + { + id: "tool", + role: "tool", + toolName: "read_file", + text: "[file:src/main.ts]", + }, + ]); + await render("mixed-session"); + expect(host.querySelectorAll("[data-source]")).toHaveLength(4); + expect(host.textContent).toContain( + "https://github.com/example/project/pull/1" + ); + for (const key of ["file:./docs/report.md", "file:src/main.ts"]) { + const button = Array.from( + host.querySelectorAll("[data-source]") + ).find((element) => element.dataset.source === key); + expect(button).toBeDefined(); + await act(async () => button!.click()); + } + expect(localFiles.openFileInWorkStation.mock.calls).toEqual([ + ["/local/project/docs/report.md", { defaultPreviewMode: true }], + ["/local/project/src/main.ts", { defaultPreviewMode: true }], + ]); + }); +}); diff --git a/src/modules/WorkStation/TabContent/renderers/sessionSources.tsx b/src/modules/WorkStation/TabContent/renderers/sessionSources.tsx new file mode 100644 index 0000000000..909d939b37 --- /dev/null +++ b/src/modules/WorkStation/TabContent/renderers/sessionSources.tsx @@ -0,0 +1,45 @@ +import { useAtomValue } from "jotai"; +import React from "react"; + +import { SessionSourcesFileScope } from "@src/engines/ChatPanel/sessionSources/SessionSourcesFileScope"; +import { useSessionSourcesState } from "@src/engines/ChatPanel/sessionSources/useSessionSources"; +import { SessionSourcesView } from "@src/features/SessionSources/SessionSourcesView"; +import { sessionByIdAtom } from "@src/store/session"; + +import type { UnifiedTabContentProps } from "../types"; + +function SessionSourcesContent({ sessionId }: { sessionId: string }) { + const session = useAtomValue(sessionByIdAtom(sessionId)); + const { sources, loading, error, retry } = useSessionSourcesState( + sessionId, + session?.updated_at + ); + const basePath = + session?.repoRootPath ?? + (session?.importedFrom + ? undefined + : (session?.worktreePath ?? session?.repoPath)); + return ( + + + + ); +} + +export default function SessionSourcesTabRenderer({ + tab, + isActive, +}: UnifiedTabContentProps) { + const sessionId = + typeof tab.data.sessionId === "string" ? tab.data.sessionId.trim() : ""; + // Inactive tabs must not read history or resolve thumbnails. Remounting also + // resets pagination/gallery state when a different conversation owns the tab. + if (!isActive) return null; + return ; +} diff --git a/src/modules/WorkStation/shared/TabBar/components/WorkstationTabIcon.tsx b/src/modules/WorkStation/shared/TabBar/components/WorkstationTabIcon.tsx index 6ca6f05529..7eeb96bbda 100644 --- a/src/modules/WorkStation/shared/TabBar/components/WorkstationTabIcon.tsx +++ b/src/modules/WorkStation/shared/TabBar/components/WorkstationTabIcon.tsx @@ -37,6 +37,7 @@ import { Layout01Icon as Layout, DashboardSquare01Icon as LayoutGrid, LayoutListIcon, + Link01Icon, ListChecksIcon, BubbleChatIcon as MessageCircle, Message01Icon as MessageSquare, @@ -71,6 +72,7 @@ export const WORKSTATION_TAB_ICONS = { Layout, LayoutGrid, LayoutList: LayoutListIcon, + Link: Link01Icon, ListChecks: ListChecksIcon, MessageCircle, MessageSquare, diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/FocusedChatWorkstationRail.test.ts b/src/scaffold/AppLayout/FocusedChatWorkstationRail/FocusedChatWorkstationRail.test.ts index 9136630dc6..4a507fb7c8 100644 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/FocusedChatWorkstationRail.test.ts +++ b/src/scaffold/AppLayout/FocusedChatWorkstationRail/FocusedChatWorkstationRail.test.ts @@ -132,6 +132,12 @@ describe.each(["wide rail", "compact menu"])( store.set(workstationLayoutAtom, { mainPane: { tabs: [ + { + id: "session-sources:current", + type: "session-sources", + title: "Sources duplicate tab", + data: { sessionId: "current" }, + }, { id: "file:readme", type: "file", @@ -167,7 +173,8 @@ describe.each(["wide rail", "compact menu"])( async function mount( subagents?: FocusedChatRailSubagent[], sessionContext?: FocusedChatSessionContext, - sources?: FocusedChatRailSource[] + sources?: FocusedChatRailSource[], + onOpenSources?: () => void ) { await act(async () => { root.render( @@ -185,6 +192,7 @@ describe.each(["wide rail", "compact menu"])( conversationMinimapHostRef: () => {}, sessionContext, sources: sources ?? [], + onOpenSources, subagentIcon: resolveAgentIcon(SDE_AGENT_ICON_ID), subagents: subagents ?? [], }) @@ -616,6 +624,50 @@ describe.each(["wide rail", "compact menu"])( ).toBeNull(); }); + it.each([0, 3])( + "keeps View all reachable with %s sources while folded and after refolding", + async (count) => { + const openSources = vi.fn(); + await mount( + undefined, + undefined, + Array.from({ length: count }, (_, index) => ({ + kind: "link" as const, + key: `link:${index}`, + url: `https://example.com/${index}`, + label: `Source ${index}`, + })), + openSources + ); + const host = view === "wide rail" ? container : menuHost; + expect(host.textContent).not.toContain("Sources duplicate tab"); + const toggle = host.querySelector( + '[data-workstation-group-toggle="sources"]' + )!; + const section = toggle.closest("section")!; + const viewAll = () => + [...section.querySelectorAll("button")].filter((button) => + button.textContent?.includes("View all") + ); + + expect(toggle.getAttribute("aria-expanded")).toBe("false"); + expect(viewAll()).toHaveLength(1); + expect(section.textContent).not.toContain("Source 0"); + act(() => viewAll()[0].click()); + expect(openSources).toHaveBeenCalledTimes(1); + expect(toggle.getAttribute("aria-expanded")).toBe("false"); + + act(() => toggle.click()); + expect(viewAll()).toHaveLength(1); + if (count > 0) expect(section.textContent).toContain("Source 0"); + act(() => toggle.click()); + expect(section.textContent).not.toContain("Source 0"); + expect(viewAll()).toHaveLength(1); + act(() => viewAll()[0].click()); + expect(openSources).toHaveBeenCalledTimes(2); + } + ); + it("folds sources by default and opens links in the Browser and images in the viewer", async () => { openLinkMocks.openInBrowserApp.mockClear(); const image = (index: number): FocusedChatRailSource => ({ @@ -630,26 +682,47 @@ describe.each(["wide rail", "compact menu"])( url: `https://example.com/page-${index}`, label: `example.com/page-${index}`, }); - await mount(undefined, undefined, [ - link(0), - image(0), - image(1), - link(1), - link(2), - image(2), - link(3), - ]); + const openSources = vi.fn(); + await mount( + undefined, + undefined, + [ + ...["exec", "cua_repl", "apply_patch"].map( + (group): FocusedChatRailSource => ({ + kind: "tool-group", + key: `tool-group:${group}`, + group, + operations: [], + }) + ), + link(0), + image(0), + image(1), + link(1), + link(2), + image(2), + link(3), + ], + openSources + ); const host = view === "wide rail" ? container : menuHost; const sourceSection = () => [...host.querySelectorAll("section")].find((section) => - section.textContent?.includes("7 Sources") + section.textContent?.includes("10 Sources") ); - // Default collapsed: heading only, no rows and no image reads. + // Default collapsed: no preview rows or image reads, but navigation remains available. expect(sourceSection()).toBeDefined(); expect(sourceSection()?.textContent).not.toContain("example.com"); expect(sourceSection()?.querySelector("img")).toBeNull(); + expect(sourceSection()?.textContent).toContain("View all"); + act(() => + [...sourceSection()!.querySelectorAll("button")] + .find((row) => row.textContent?.includes("View all"))! + .click() + ); + expect(openSources).toHaveBeenCalledTimes(1); act(() => host @@ -660,57 +733,41 @@ describe.each(["wide rail", "compact menu"])( ); const expanded = sourceSection()!; - for (const label of [ - "example.com/page-0", - "Image", - "shot-1.png", - "example.com/page-1", - "example.com/page-2", - ]) { + for (const label of ["example.com/page-0", "Image", "shot-1.png"]) { expect(expanded.textContent).toContain(label); } expect(expanded.textContent).not.toContain("shot-2.png"); - expect(expanded.textContent).toContain("Load more (+2)"); + expect(expanded.textContent).toContain("View all"); expect(expanded.querySelectorAll("img")).toHaveLength(2); // A link opens in My Station's Browser. const linkRow = [...expanded.querySelectorAll("button")].find((button) => - button.textContent?.includes("example.com/page-1") + button.textContent?.includes("example.com/page-0") )!; act(() => linkRow.click()); expect(openLinkMocks.openInBrowserApp).toHaveBeenCalledWith( - "https://example.com/page-1" + "https://example.com/page-0" ); - // The last row opens the full list; an image there opens the viewer - // with every image of the session as its gallery. + // View all delegates to the session-scoped workstation tab opener. act(() => - sourceSection()! - .querySelector('[aria-haspopup="menu"]')! + [...sourceSection()!.querySelectorAll("button")] + .find((row) => row.textContent?.includes("View all"))! .click() ); - const submenu = document.querySelector( - '[data-testid="workstation-trail-sources-submenu"]' + expect(openSources).toHaveBeenCalledTimes(2); + // Summary thumbnails share the entire session gallery, not only the preview. + act(() => + [...sourceSection()!.querySelectorAll("button")] + .find((row) => row.textContent?.includes("shot-1.png"))! + .click() ); - expect(submenu).not.toBeNull(); - expect(submenu!.textContent).toContain("shot-2.png"); - expect(submenu!.textContent).toContain("example.com/page-3"); - const imageRow = [...submenu!.querySelectorAll('[role="menuitem"]')].find( - (row) => row.textContent?.includes("shot-2.png") - ) as HTMLElement; - act(() => imageRow.click()); - - expect( - document.querySelector( - '[data-testid="workstation-trail-sources-submenu"]' - ) - ).toBeNull(); const preview = document.querySelector( '[data-testid="source-image-preview"]' ); - expect(preview?.getAttribute("data-src")).toBe("data:image/png;base64,2"); + expect(preview?.getAttribute("data-src")).toBe("data:image/png;base64,1"); expect(preview?.getAttribute("data-count")).toBe("3"); - expect(preview?.getAttribute("data-index")).toBe("2"); + expect(preview?.getAttribute("data-index")).toBe("1"); }); it("keeps pins excluded while collapsed and restores them only on release", async () => { diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationItemRow.tsx b/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationItemRow.tsx index 9438fbaa81..5c083b6ecd 100644 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationItemRow.tsx +++ b/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationItemRow.tsx @@ -14,6 +14,7 @@ import { WORKSTATION_TRAIL_ROW, WORKSTATION_TRAIL_ROW_HOVER_CLASS, } from "@src/components/layout/tokens/workstationTrailTokens"; +import { SessionSourceThumbnail } from "@src/features/SessionSources/SessionSourceThumbnail"; import { useWorkingTreeDiffTotals } from "@src/hooks/git/useWorkingTreeDiffTotals"; import { ArrowRight01Icon, @@ -23,7 +24,6 @@ import { StopCircleIcon, } from "@src/icons"; -import { RailImageThumbnail } from "./RailImageThumbnail"; import { RailItemStatus } from "./RailItemStatus"; import type { FocusedChatRailItem } from "./types"; @@ -64,7 +64,7 @@ export function WorkstationItemRow({ > {item.imageRef ? ( - {sections.map((section) => { - // Every section folds behind its heading, in both presentations. + // Folding hides previews; explicitly retained section navigation stays reachable. const groupCollapsed = collapsedGroupKeys?.has(section.key) === true; // In the wide rail, the panel header is also the heading for the first @@ -173,17 +173,20 @@ export function WorkstationSections({ onRequestClose={onRequestClose} /> ) : null} - {!groupCollapsed && - section.items - .filter((item) => item !== changesItem) - .map((item) => ( - - ))} + {section.items + .filter( + (item) => + item !== changesItem && + (!groupCollapsed || item.visibleWhenCollapsed) + ) + .map((item) => ( + + ))} ); })} diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourcesSubmenu.tsx b/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourcesSubmenu.tsx deleted file mode 100644 index e23ccc8174..0000000000 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/WorkstationSourcesSubmenu.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/** - * WorkstationSourcesSubmenu — second-level panel listing every image and link - * the user sent in the active session, opened from the Sources section's - * "load more" row. Geometry, scrolling and the filter field live in - * `WorkstationRailSubmenu`. - */ -import type React from "react"; -import { useCallback } from "react"; -import { useTranslation } from "react-i18next"; - -import AnyIcon from "@src/components/AnyIcon"; -import { DropdownItem } from "@src/components/Dropdown/exports"; -import type { SubmenuAnchor } from "@src/components/Dropdown/submenuLayout"; -import { DROPDOWN_ITEM } from "@src/components/Dropdown/tokens"; -import { Link01Icon } from "@src/icons"; - -import { RailImageThumbnail } from "./RailImageThumbnail"; -import { - WorkstationRailSubmenuPanel, - useWorkstationRailSubmenuFilter, -} from "./WorkstationRailSubmenu"; -import type { FocusedChatRailSource } from "./types"; -import { sourceRowLabel } from "./useWorkstationRailSources"; - -export function WorkstationSourcesSubmenu({ - anchor, - maxHeight, - onClose, - onOpenSource, - panelRef, - sources, - width, -}: { - anchor: SubmenuAnchor; - /** Height cap; the rows scroll under it. */ - maxHeight: number; - onClose: () => void; - onOpenSource: (source: FocusedChatRailSource) => void; - panelRef: React.RefObject; - sources: FocusedChatRailSource[]; - /** Same width as the list the panel opened from. */ - width: number; -}) { - const { t } = useTranslation(); - // A link is found by its full URL, not only by the shortened row label. - const sourceSearchText = useCallback( - (source: FocusedChatRailSource) => - source.kind === "link" - ? `${source.label} ${source.url}` - : sourceRowLabel(t, source), - [t] - ); - const { query, rows, setQuery, showSearch } = useWorkstationRailSubmenuFilter( - sources, - sourceSearchText - ); - - return ( - - {rows.map((source) => { - const label = sourceRowLabel(t, source); - return ( - - ) : ( - - ) - } - onClick={() => onOpenSource(source)} - > - - {label} - - - ); - })} - - ); -} diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/index.tsx b/src/scaffold/AppLayout/FocusedChatWorkstationRail/index.tsx index ebed63a49e..2fdbe989da 100644 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/index.tsx +++ b/src/scaffold/AppLayout/FocusedChatWorkstationRail/index.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { @@ -12,13 +12,12 @@ import { resolveFocusedChatWorkstationRailInsetStyle, resolveFocusedChatWorkstationRailTrackClass, } from "@src/engines/ChatPanel/focusedChatWorkstationLayout"; +import { SessionSourceImagePreview } from "@src/features/SessionSources/SessionSourceImagePreview"; import { WorkstationCollapsedDiffStats } from "./WorkstationCollapsedDiffStats"; import { WorkstationCollapsedRailItems } from "./WorkstationCollapsedRailItems"; import { WorkstationCompactMenu } from "./WorkstationCompactMenu"; import { WorkstationSections } from "./WorkstationSections"; -import { WorkstationSourceImagePreview } from "./WorkstationSourceImagePreview"; -import { WorkstationSourcesSubmenu } from "./WorkstationSourcesSubmenu"; import { WorkstationSubagentsSubmenu } from "./WorkstationSubagentsSubmenu"; import { WorkstationTrailHeaderActions } from "./WorkstationTrailHeaderActions"; import { WorkstationTrailTerminal } from "./WorkstationTrailTerminal"; @@ -46,6 +45,7 @@ export function FocusedChatWorkstationRail({ conversationMinimapHostRef, sessionContext, sources, + onOpenSources, subagentIcon, subagents, topInset = 0, @@ -98,30 +98,14 @@ export function FocusedChatWorkstationRail({ subagentsSubmenuWidth, } = useWorkstationRailSubagents({ setMenuOpen, subagentIcon, subagents, t }); - const { - closeImagePreview, - closeSourcesSubmenu, - imagePreview, - openSource, - sourceItems, - sourcesSubmenuAnchor, - sourcesSubmenuMaxHeight, - sourcesSubmenuPanelRef, - sourcesSubmenuWidth, - } = useWorkstationRailSources({ setMenuOpen, sources, t }); - - // Both "load more" panels belong to the compact menu they open from. - const compactMenuInsideRefs = useMemo( - () => [...subagentsSubmenuInsideRefs, sourcesSubmenuPanelRef], - [sourcesSubmenuPanelRef, subagentsSubmenuInsideRefs] - ); - const handleMenuVisibleChange = useCallback( - (visible: boolean) => { - handleSubagentsMenuVisibleChange(visible); - if (!visible) closeSourcesSubmenu(); - }, - [closeSourcesSubmenu, handleSubagentsMenuVisibleChange] - ); + const { closeImagePreview, imagePreview, sourceItems } = + useWorkstationRailSources({ + setMenuOpen, + sources, + t, + onOpenSources, + basePath: sessionContext?.worktreePath ?? sessionContext?.repoPath, + }); const environmentLabel = t("navigation:labels.sessionEnvironment"); const { @@ -153,7 +137,7 @@ export function FocusedChatWorkstationRail({ const compactMenu = compactMenuHost ? ( setMenuOpen(false)} onToggleGroup={toggleGroup} - onVisibleChange={handleMenuVisibleChange} + onVisibleChange={handleSubagentsMenuVisibleChange} sections={compactSections} /> ) : null; @@ -281,19 +265,8 @@ export function FocusedChatWorkstationRail({ width={subagentsSubmenuWidth} /> ) : null} - {sourcesSubmenuAnchor ? ( - - ) : null} {imagePreview ? ( - ) => void; + /** Section-level navigation remains reachable when its preview rows are folded. */ + visibleWhenCollapsed?: boolean; /** * Row opens a second-level panel anchored to itself: it must not close the * compact menu it lives in, and it advertises the popup to AT. @@ -76,7 +78,7 @@ export interface FocusedChatRailSubagent { status: "pending" | "running" | "completed" | "failed"; } -/** One image or web link the user sent in the rail's active session. */ +/** One resource or tool activity group in the rail's active session. */ export type FocusedChatRailSource = SessionSource; export interface FocusedChatWorkstationRailProps { @@ -86,8 +88,10 @@ export interface FocusedChatWorkstationRailProps { conversationMinimapHostRef: (node: HTMLDivElement | null) => void; /** Active session scope moved out of the transcript's former context row. */ sessionContext?: FocusedChatSessionContext; - /** Images and links the user sent in the active session, newest first. */ + /** Conversation resources and tool activity groups, newest first. */ sources: FocusedChatRailSource[]; + /** Open or focus the session-scoped Sources workstation tab. */ + onOpenSources?: () => void; /** The active session's spawned subagent sessions, newest first. */ subagents: FocusedChatRailSubagent[]; /** diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailSources.ts b/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailSources.ts index 1521c117fe..3b646e75b5 100644 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailSources.ts +++ b/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailSources.ts @@ -1,118 +1,75 @@ -/** - * Source rows of the focused-chat workstation rail: the images and links the - * user sent in the active session. Follows the Subagents section — a short - * inline preview, the rest behind a "load more" submenu. A link opens in My - * Station's Browser; an image opens the chat's image viewer with the - * session's other images as its gallery. - */ +/** A compact preview of the session's sources with a full-list tab entry. */ import type { TFunction } from "i18next"; -import type React from "react"; -import { useCallback, useMemo, useState } from "react"; +import { createElement, useCallback, useMemo } from "react"; -import { Link01Icon, MoreHorizontalIcon } from "@src/icons"; -import { openInBrowserApp } from "@src/util/ui/openLink"; +import { SessionSourceIcon } from "@src/features/SessionSources/SessionSourceIcon"; +import { sourceLabel } from "@src/features/SessionSources/presentation"; +import { useSessionSourceNavigation } from "@src/features/SessionSources/useSessionSourceNavigation"; +import { Link01Icon } from "@src/icons"; -import { useWorkstationRailSubmenu } from "./WorkstationRailSubmenu"; -import type { WorkstationSourceImage } from "./WorkstationSourceImagePreview"; import type { FocusedChatRailItem, FocusedChatRailSource } from "./types"; -/** Source rows shown inline; the rest sit behind the "load more" submenu. */ -const SOURCE_PREVIEW_COUNT = 5; - -type ImageSource = Extract; - -export interface WorkstationSourceImagePreviewState { - images: WorkstationSourceImage[]; - index: number; -} - -export function sourceRowLabel( - t: TFunction, - source: FocusedChatRailSource -): string { - if (source.kind === "link") return source.label; - return source.fileName ?? t("common:git.rail.sourceImage"); -} +const SOURCE_PREVIEW_COUNT = 3; export function useWorkstationRailSources({ setMenuOpen, sources, t, + onOpenSources, + basePath, }: { setMenuOpen: (open: boolean) => void; sources: FocusedChatRailSource[]; t: TFunction; + onOpenSources?: () => void; + basePath?: string; }) { const { - anchor: sourcesSubmenuAnchor, - close: closeSourcesSubmenu, - maxHeight: sourcesSubmenuMaxHeight, - panelRef: sourcesSubmenuPanelRef, - toggle: toggleSourcesSubmenu, - width: sourcesSubmenuWidth, - } = useWorkstationRailSubmenu(); - const [imagePreview, setImagePreview] = - useState(null); - const closeImagePreview = useCallback(() => setImagePreview(null), []); - + openSource: navigateSource, + imagePreview, + closeImagePreview, + } = useSessionSourceNavigation(sources, basePath); const openSource = useCallback( (source: FocusedChatRailSource) => { - closeSourcesSubmenu(); setMenuOpen(false); - if (source.kind === "link") { - openInBrowserApp(source.url); - return; - } - // Snapshot the gallery: a refetch while the viewer is open must not - // shift the image under the user. - const imageSources = sources.filter( - (candidate): candidate is ImageSource => candidate.kind === "image" - ); - const index = imageSources.findIndex( - (candidate) => candidate.key === source.key - ); - if (index < 0) return; - setImagePreview({ - images: imageSources.map(({ ref, fileName }) => ({ ref, fileName })), - index, - }); + navigateSource(source); }, - [closeSourcesSubmenu, setMenuOpen, sources] + [navigateSource, setMenuOpen] ); - const sourceItems = useMemo(() => { - const previewed = sources.slice(0, SOURCE_PREVIEW_COUNT).map((source) => ({ - key: `source:${source.key}`, - label: sourceRowLabel(t, source), - icon: Link01Icon, - imageRef: source.kind === "image" ? source.ref : undefined, - onClick: () => openSource(source), - })); - if (sources.length <= SOURCE_PREVIEW_COUNT) return previewed; - return [ - ...previewed, - { - key: "sources-load-more", - label: t("common:git.rail.loadMoreSources", { - count: sources.length - SOURCE_PREVIEW_COUNT, - }), - icon: MoreHorizontalIcon, - submenu: true, - onClick: (event: React.MouseEvent) => - toggleSourcesSubmenu(event.currentTarget), - }, + // Resource previews must not be displaced by frequently updated tool groups. + // Keep source order within each category; activity fills unused preview slots. + const previewCandidates = [ + ...sources.filter((source) => source.kind !== "tool-group"), + ...sources.filter((source) => source.kind === "tool-group"), ]; - }, [openSource, sources, t, toggleSourcesSubmenu]); - - return { - closeImagePreview, - closeSourcesSubmenu, - imagePreview, - openSource, - sourceItems, - sourcesSubmenuAnchor, - sourcesSubmenuMaxHeight, - sourcesSubmenuPanelRef, - sourcesSubmenuWidth, - }; + const previewed: FocusedChatRailItem[] = previewCandidates + .slice(0, SOURCE_PREVIEW_COUNT) + .map((source) => ({ + key: `source:${source.key}`, + label: sourceLabel(t, source), + icon: ({ size }: { size?: number }) => + createElement(SessionSourceIcon, { source, size }), + imageRef: source.kind === "image" ? source.ref : undefined, + onClick: () => { + if (source.kind === "tool-group") { + setMenuOpen(false); + onOpenSources?.(); + } else openSource(source); + }, + })); + if (onOpenSources) + previewed.push({ + key: "sources-view-all", + visibleWhenCollapsed: true, + label: t("common:git.rail.viewAllSources"), + icon: Link01Icon, + onClick: () => { + setMenuOpen(false); + onOpenSources(); + }, + }); + return previewed; + }, [onOpenSources, openSource, setMenuOpen, sources, t]); + return { imagePreview, closeImagePreview, openSource, sourceItems }; } diff --git a/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailTabs.ts b/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailTabs.ts index 87846b0666..354fcf9775 100644 --- a/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailTabs.ts +++ b/src/scaffold/AppLayout/FocusedChatWorkstationRail/useWorkstationRailTabs.ts @@ -143,6 +143,8 @@ export function useWorkstationRailTabs({ tab.type !== "terminal" && tab.type !== "start" && tab.type !== "explorer" && + // Sources already has its dedicated View all entry in this rail. + tab.type !== "session-sources" && tab.type !== "source-control" ) .slice(0, 6) diff --git a/src/scaffold/WorkbenchChrome/WorkstationTabHeader.sources.test.ts b/src/scaffold/WorkbenchChrome/WorkstationTabHeader.sources.test.ts new file mode 100644 index 0000000000..d075236ae1 --- /dev/null +++ b/src/scaffold/WorkbenchChrome/WorkstationTabHeader.sources.test.ts @@ -0,0 +1,106 @@ +// @vitest-environment jsdom +import { Provider, createStore } from "jotai"; +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; + +import { workstationTabHeaderAtomByHost } from "@src/store/workstation"; +import { + type WorkStationTab, + workstationTabsStateAtom, +} from "@src/store/workstation/tabs"; +import { createSessionSourcesTab } from "@src/store/workstation/tabs/factories/sessionSources"; +import { emptyWorkstationTabsState } from "@src/store/workstation/tabs/storage"; + +import WorkstationTabHeader from "./WorkstationTabHeader"; + +vi.mock("@src/components/Tooltip", () => ({ + default: ({ children }: { children: React.ReactNode }) => children, +})); +vi.mock("./CodeSidebarHeaderActions", () => ({ + CodeSidebarHeaderActions: () => "sidebar-search-shortcut", +})); +vi.mock("./SourceControlHeaderActions", () => ({ + SourceControlHeaderActions: () => null, +})); + +let host: HTMLDivElement; +let root: Root; +let store: ReturnType; +beforeEach(() => { + Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); + store = createStore(); + host = document.createElement("div"); + document.body.append(host); + root = createRoot(host); +}); +afterEach(() => { + act(() => root.unmount()); + host.remove(); + Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); +}); + +it("hides the complete shared toolbar only while Sources is active and restores it on tab switches", () => { + const file: WorkStationTab = { + id: "file:/workspace/readme.md", + type: "file", + title: "readme.md", + data: { path: "/workspace/readme.md" }, + }; + const sources = createSessionSourcesTab("session-1", "Sources"); + const browser: WorkStationTab = { + id: "browser-session:1", + type: "browser-session", + title: "Browser", + data: {}, + }; + const state = emptyWorkstationTabsState(); + state.globalWorkspace = { + tabs: [file, sources, browser], + activeTabRef: { partition: "workspace", tabId: file.id }, + tabOrder: [file, sources, browser].map((tab) => ({ + partition: "workspace", + tabId: tab.id, + })), + }; + store.set(workstationTabsStateAtom, state); + store.set(workstationTabHeaderAtomByHost.code, { + content: React.createElement("span", null, "file-breadcrumb"), + }); + store.set(workstationTabHeaderAtomByHost.browser, { + content: React.createElement("span", null, "browser-url"), + }); + act(() => { + root.render( + React.createElement( + Provider, + { store }, + React.createElement(WorkstationTabHeader) + ) + ); + }); + function select(tab: WorkStationTab) { + act(() => { + store.set(workstationTabsStateAtom, (previous) => ({ + ...previous, + globalWorkspace: { + ...previous.globalWorkspace, + activeTabRef: { partition: "workspace", tabId: tab.id }, + }, + })); + }); + } + expect(host.textContent).toContain("file-breadcrumb"); + expect(host.textContent).toContain("sidebar-search-shortcut"); + select(sources); + expect(host.innerHTML).toBe(""); + select(file); + expect(host.querySelector("[data-workstation-tab-header]")).not.toBeNull(); + expect(host.textContent).toContain("file-breadcrumb"); + expect(host.textContent).toContain("sidebar-search-shortcut"); + select(sources); + expect(host.innerHTML).toBe(""); + select(browser); + expect(host.querySelector("[data-workstation-tab-header]")).not.toBeNull(); + expect(host.textContent).toContain("browser-url"); +}); diff --git a/src/scaffold/WorkbenchChrome/WorkstationTabHeader.tsx b/src/scaffold/WorkbenchChrome/WorkstationTabHeader.tsx index 3c49b5d3c4..6ae6dd6664 100644 --- a/src/scaffold/WorkbenchChrome/WorkstationTabHeader.tsx +++ b/src/scaffold/WorkbenchChrome/WorkstationTabHeader.tsx @@ -55,9 +55,10 @@ const WorkstationTabHeader: React.FC = memo(() => { if (headerSlots?.hidden) return null; - // The Launchpad has no header controls, so it should not reserve an empty + // Launchpad and Sources have no header controls, so do not reserve an empty // 36px row below the tab bar. - if (activeTab?.type === "start") return null; + if (activeTab?.type === "start" || activeTab?.type === "session-sources") + return null; return (
localStorage.clear()); + +describe("session sources tab lifecycle", () => { + it("reuses a session's tab while keeping another session independently selectable", () => { + const first = createSessionSourcesTab("session-a", "Sources"); + const second = createSessionSourcesTab("session-b", "Sources"); + let pane: PanelState = { tabs: [], activeTabId: null }; + pane = openTab(pane, first); + pane = openTab(pane, second); + pane = openTab(pane, createSessionSourcesTab("session-a", "来源")); + + expect(pane.tabs).toHaveLength(2); + expect(pane.activeTabId).toBe(first.id); + expect(pane.tabs.map((tab) => tab.data.sessionId)).toEqual([ + "session-a", + "session-b", + ]); + expect(first.id).not.toBe(second.id); + + pane = closeTab(pane, first.id); + expect(pane.tabs.map((tab) => tab.id)).toEqual([second.id]); + pane = openTab(pane, first); + expect(pane.tabs).toHaveLength(2); + expect(pane.activeTabId).toBe(first.id); + }); + + it("restores session binding and active selection in the owning workspace", () => { + const first = createSessionSourcesTab("session-a", "Sources"); + const second = createSessionSourcesTab("session-b", "Sources"); + const workspace = ( + tab: ReturnType + ): WorkstationWorkspaceState => ({ + tabs: [tab], + activeTabRef: { partition: "workspace", tabId: tab.id }, + tabOrder: [{ partition: "workspace", tabId: tab.id }], + }); + const state = emptyWorkstationTabsState(); + state.sessionWorkspaces["session-a"] = workspace(first); + state.sessionWorkspaces["session-b"] = workspace(second); + + expect(getWorkstationTabOwnership(first.type)).toBe("workspace-local"); + expect(persistWorkstationTabsState(state)).toBe(true); + const restored = loadWorkstationTabsState(); + expect(restored.sessionWorkspaces["session-a"]).toMatchObject( + workspace(first) + ); + expect(restored.sessionWorkspaces["session-b"]).toMatchObject( + workspace(second) + ); + expect(restored.shared.tabs).toEqual([]); + }); +}); diff --git a/src/store/workstation/tabs/__tests__/workspaceState.test.ts b/src/store/workstation/tabs/__tests__/workspaceState.test.ts index b6cf80862c..fc15b3d4c8 100644 --- a/src/store/workstation/tabs/__tests__/workspaceState.test.ts +++ b/src/store/workstation/tabs/__tests__/workspaceState.test.ts @@ -44,6 +44,7 @@ const EXPECTED_OWNERSHIP: Record = terminal: "shared-resource", search: "workspace-local", "search-sessions": "workspace-local", + "session-sources": "workspace-local", "url-preview": "workspace-local", "browser-session": "shared-resource", "project-dashboard": "shared-resource", @@ -112,7 +113,7 @@ describe("WorkStation tab ownership policy", () => { }) ); - expect(results).toHaveLength(32); + expect(results).toHaveLength(33); expect(results.every(({ actual, expected }) => actual === expected)).toBe( true ); diff --git a/src/store/workstation/tabs/factories/sessionSources.ts b/src/store/workstation/tabs/factories/sessionSources.ts new file mode 100644 index 0000000000..658c887f2c --- /dev/null +++ b/src/store/workstation/tabs/factories/sessionSources.ts @@ -0,0 +1,20 @@ +import { defineTabFactory } from "../tabFactory"; + +/** Keep only the session reference; resource rows are read from durable history. */ +const sessionSourcesTabFactory = defineTabFactory<{ + sessionId: string; + title: string; +}>({ + tabType: "session-sources", + idStrategy: { + type: "keyed", + prefix: "session-sources", + getKey: ({ sessionId }) => sessionId, + }, + getTitle: ({ title }) => title, + icon: "Link", +}); + +export function createSessionSourcesTab(sessionId: string, title: string) { + return sessionSourcesTabFactory({ sessionId, title }); +} diff --git a/src/store/workstation/tabs/storage.ts b/src/store/workstation/tabs/storage.ts index 392d457de0..f2ade6dd59 100644 --- a/src/store/workstation/tabs/storage.ts +++ b/src/store/workstation/tabs/storage.ts @@ -42,6 +42,7 @@ const VALID_WORKSTATION_TAB_TYPES = new Set([ "terminal", "search", "search-sessions", + "session-sources", "url-preview", "browser-session", "project-dashboard", diff --git a/src/store/workstation/tabs/tabFactory.ts b/src/store/workstation/tabs/tabFactory.ts index 9cbeb818fa..7398d28580 100644 --- a/src/store/workstation/tabs/tabFactory.ts +++ b/src/store/workstation/tabs/tabFactory.ts @@ -84,6 +84,7 @@ export const DEFAULT_CATEGORY_BY_TYPE: Record< terminal: "terminal", search: "search", "search-sessions": "search-sessions", + "session-sources": "preview", "url-preview": "preview", "browser-session": "browser", "project-dashboard": "project", diff --git a/src/store/workstation/tabs/types.ts b/src/store/workstation/tabs/types.ts index fb06b24539..9a33dacb78 100644 --- a/src/store/workstation/tabs/types.ts +++ b/src/store/workstation/tabs/types.ts @@ -30,6 +30,7 @@ export type WorkStationTabType = | "terminal" | "search" // Repository-wide search tab | "search-sessions" // Session search + table (reuses SessionTable; launchpad tab) + | "session-sources" // Durable input resources associated with one conversation | "url-preview" // URL preview (agent-triggered webview in editor) // Browser tabs | "browser-session" @@ -234,6 +235,7 @@ export function getWorkstationTabOwnership( case "dom-component-preview": case "search": case "search-sessions": + case "session-sources": case "url-preview": case "subagent-detail": case "canvas-preview": From 5888a9e4caf3cfbc56d3c3a3e17826e0bad3255f Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Fri, 25 Sep 2026 16:55:49 +0800 Subject: [PATCH 2/7] docs(sources): include user-provided application screenshot Pre-commit hook ran. Total eslint: 0, total circular: 0 --- .../SessionSources.md | 2 +- .../session-sources/user-screenshot.png | Bin 0 -> 415045 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/verification-2026-09-25/session-sources/user-screenshot.png diff --git a/docs/frontend-ui-audit-2026-09-25/SessionSources.md b/docs/frontend-ui-audit-2026-09-25/SessionSources.md index 5be7db8686..97d562cb37 100644 --- a/docs/frontend-ui-audit-2026-09-25/SessionSources.md +++ b/docs/frontend-ui-audit-2026-09-25/SessionSources.md @@ -14,4 +14,4 @@ Totals: 4 resolved fixes, 4 keep with reason, 1 implemented abstraction. No raw production buttons, inputs or clickable substitutes introduced. Metadata colors use semantic tokens; shared components own typography/hover/focus. No global design-system sweep. -Visual evidence in ../verification-2026-09-25/session-sources: final categorized light page, dark 340px page, empty/loading/error states. These render actual feature and shared components using fictional source fixtures; navigation and image loading are mocked. They are not native-app E2E evidence. Original user screenshot contains local filesystem paths and is not checked in. +Visual evidence in ../verification-2026-09-25/session-sources: final categorized light page, dark 340px page, empty/loading/error states. These render actual feature and shared components using fictional source fixtures; navigation and image loading are mocked. They are not native-app E2E evidence. The user subsequently explicitly requested their original application screenshot; user-screenshot.png is included unchanged as the primary PR illustration. diff --git a/docs/verification-2026-09-25/session-sources/user-screenshot.png b/docs/verification-2026-09-25/session-sources/user-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..4d02df443ad6093a429a86304f8b97ed74343e8c GIT binary patch literal 415045 zcmYhj1yoyKuss|~p)EyJ0LfO~VlcBKYw1M3PXXJ_3Q>fh0tQmEF?z7hS#ZHmBQ;mxrNeZ=XrLJ6jU=Aj}KR zdZ&t4?{VO9fFXvB;@KUF(thIqPE20GHqHkf7HBtX7ztx`^&#h zxp8}w+aKO<)?pA`(Z}X+?8`)eqVQ~3GEXsEwOC#XCq`3*N#gGmj%GSRI#hRw=INql zXLw@mR$PV^>gDQ;eU;%kbbXckyVi zA_WuK6Fc(7Q^1A)TeygDrDs;5IZ=A@qPjNOOjYo$unMZSRFy+%$h)^l+fl}M`srmS@ zs=6D9Bg6w!Q!yhn&*c8THmQ=1ZvQo>edkfEa&_a8J+r6H&-OIYIyPXL+x6S@S?fIL zM99bY&i|czP)m@-qCM@W{G|n%z7k&G{ysk%T7(~`;n1Q?usD_C$zH&mRd#uw8O-d| zZf13F??dI>lhtJ7R5B_lEB_obb2^v*>5((1{CcVv*lZ)#Y&v=lk`RzD1@;Es)8+X@?I89z|9iPFHON*pvB_uQRz`Cv zHCsF1h0a?>87r714P4W@GM?i{yAKbQSRCYy3Mqg8TsJtHz=j~H-;FO-tYnPU({D4V z;1Yg`ek4|muT%Zsy;XN~hRZ?$T-}jBwqRs)U0t`&85c}jJW*dwIuTfA?au=dmvpX8U+Pb6My%QCs!TiP98Er zLqm(&>~ZCP7e_0B^Y-mqGmacp$bTy`tg@*s8#^a$Ba0+IKAL4^cd&O)BFJB2&f3li z$}Krs{f?n`I3sUn<3*lJEfH7{?2Kj$e3r&>J)Mm~yB-H;LgJgW>p3f6{T;xCu(ffm zj5K-}Bg3AyS@Q&ToK_U9=MCu24*)h4gC5Hv@ zf7F1$k`jLZfIDpg>i@p17Mi2amaHv+4#FfC94MSH?He9$pO^@6v4*qSeHK!~Dpp`i zmIi!GIY;(??nYMG+>c49^-XNQKlP7PUA3*2&E6?stx!m0w^9UN|F(E_CQo84!vzMD z)5M+&434mLDM{nw19K(8uA19F=|N@i(Hn9Vx)XJBLeI)MvsaS;402uil}kq;71hYOf; za#+uh?*Q|6U0gZ%TaKDqLigv7(L316PT?0VdQWR#|DbC)=ahvRE1R@fqW;S><<)#t z6;9k`BVu%dZzF2l(P*h_4-2Wf)9daW}?zC-n&CF$TM0o5GWBu7;3 zHGO{m+k(M*?cRjUX;qU8J7i7g_1CPMn}pxFv`CfRJU#0?xHmUQ^uPQGsUlW+A;X6% zDUF~*!v&+3qL+twE50ekyuQ9>PNlr*kSaUwy=evE2fV;qS!z67Noi@ezRcwH4*DSF zYdl2Z`)v9hd^@xGFs9ay>Rn4hDvg}cFawLK=e_qW=26G~jQeiLS0eC51KTB& zR!dBEb;`_S(>8)HZRuL6cxr8DXJ^v-DHfcs=Z+9o*~1Zd&ejr38nw1UJG{w?OR=YB!hM4FU_l2O%ub^kR3rjN3 zIes?0zmS+27=qFA@Wki8Z+CMKiCu1pNsgEC@SqVEM!LGb0hZgjM`Z|u8D(ZA5}tpu z!2ZZkD_syto&>(cpRcj(yruBziPUMlzPP2Rzsz88Z1~r~()!`Uhxlz>ulV&|xs#2@ zx55zUu+LTNva0>o;2eqVZGt(NN1%;{MDL}4RC+9Ipym<8YP@FV<=v#ipOX?6BK3}j zIizHt3>`I>DOF0ioa5#qrkPSFN}ZqU z>Sma6lI*8%4OIF|DrWbgiRXr#4KiYq3kcijBE4?akh~l|Fq^KH5DCp{@x+QwONrD@OS|cK zMC7W7Iuiay4A|%G$D#&Ao9cN7cTgh7NOs@x$f=Ofg)|X$-u-St(no~-#vZ5sJJ74% zV1=ROvY%`I?M+$h5s^fHVH58o?*6ub+ClW=g6w4}Icy=H4s8!?wYd@mv1flBKfj5#GienVc1*op%j`*&0CBFtOcy24Bo*n2y&cQZ|oh%F`N{*KuHLa6cenc6J%innm*ubg?KZB6RZAB^ zpNoIl1!1-HbW;_zF!5bfc};m3tl^|rHviiCE}hDskd1uHZS3j%-O=98`d?cM|NXya z&UtZh6D};lYkDzMD!2>zO2WtWG#=4Jv*m^l=H|rDQBb3jw7EuI{`K{vV(V))+vFZx zy57VcF9=%abnX=?)6-#=J0H$hNaxSX!DdeOLY@06I{+tex0|*eCU@hU6(D7#;{at% z#5a3t#dY<#)ikT@@8Wpv*qT0{0pXxQ;3-pbl>$BaZoSisL}eygaUJK@q|3tbvIXth zW;t0uP-fYr4sb`Xrd68W9W4tPIaEOo(_dD`U!I-_T{&+yIMk+wN3d8{ah1k-JHShk zMZ2|cPT+OUSl~rFoj{oB=2moC465H;d(I(G_gZ$Q=ihD$7=1`oI1PrbVRM4EEdP6B?(znSYFp$}KN-3tS2B zSJneD<>Dj4oBA3lyfil*kWtSVaP=ql&YO4S*qoR%%tAS}2S(iR`2i0g|6p7%9=MrlqME|jHNjCEnjXJ;) zQ&6uNCKp+f=lk=~-vi3Bondw&sXJQZ^B%^CjH>0M4wHbCk?z z%{1cKGFNaAWQr&**6PKR8uNL^@m#zgY{T$3gXVD%NC>dv;zA5ax+_Ld~=)y zx0JX+-`jfrd!OH7AJ5+2{%3ITM6OnWWTHyp zB*!agCW`0YS?clW>1tr29xV+`LP^O!+a)Qw_`}js*}-yfBpGtskjzK|#JF`Ph2vGe zZ);0@q!>_Vei|4U-c0R=f&NXFa@H>KXE3MFC-V8a#`PPmUG-Nn>O8#IW)`h5J7x<) z|94RRI(0ZkXV>2-Hf!GbH#%B%sUC0fqof*dUH;4-D^bZdjc!J1S!YH@w7I3(D8$jT zz_wK@@uG_sC?C_mc!=l28p0B8pOc_Ze=88Diht$QW7?VVb5~68yqoY@=GgHE8XJf3^AwYP9QnEJ>p#C)AMX#MeHVH&SvP+lve>G1w`601+iqVuErC9UHY z^E>dRVsFsKq_S*=;LvzxqWw&I?aBD5+>C`6ef+rqXRH>lXjwOw+*zyi6f6*nyv;&m7KRQS6c@D^BY zsO4HXD00WT9wFKzfQ2$u^_ec;iCTEjSV*iP;01Q;SC+8I3XPY5D-a3(Wzq9~WZ2`f z+U)2iB6zF})o_)8JS346HhW&4PO!8h*}>0Ji}AG?R@b+sEIfFDta$kCTiYfs(sCD- zd+Tm`UgzD01FlY^OXr&t*vbe zC&qjJCac8ajSVAR4`Z8pXQ0L$SQKuhciBP6;StBXm)ol=-oSwLyM)1^- zm`eB!nKgMlvyvA=!VpvF>mTaug%{7uP&zoA99>+LC^&}XP$JVy1nR0?D|e3zaSeS* zRlT)mPK-?ru2mcAVVt}dyuQp&_p8C2@imcN@2i-gF35i}JZBPp#{?$6OGktV9;gbD zGJs+DRkf}}GH(sY5diRu@nO?7GJ_x1g4|n|FL%K1DFtuYes{TGkBjsWujdVvAzBh=8$ca^2PQaB-?1NJuX11WHN}B|C5LbT5?hxZ36G zUz-eSV{DZE@vua0jt}(j-$&y!%)EmUG&mn9fA_QAgOYD-Y_U~b8LX`cBhxRICMFWf z@!$9@-sa>QULP0e9W2oT5J+2npIMt`SP`_nW?aNS;NN&5IjwB^x|b0LM-&L&{f`@p zSQzPN-?pxm*qtBhqt|$}_}lM(wn4n))@Wr1vE07+lWz)ogGaQohArarkoPe{*Lk$N z(J4V!qEl9l&h^5SK0_p{#VS}JcD@|5(h|3dFSf|Q$Z|V;{V@SThgsTQ{vO%wJf867 z>t+t`olIZwX$Pz&Yj4ycWn$g{HM z2Li3yLxb)^%bmGgzFO(V!X85=cR3m6(hwy=0&@28Pv3G}*|%l9Rqz7wExGJgM(vqJ zyA_RC0c6x12e0oRc0iKdz1_mX{tJ4r)lxr{{PtIPBo&y2qFGj|m|)fEa5`EmKm1Ga z$J?=6%XJs%;ewo-Iy<7#lLV%A-`(=17dTTQC>frk+)R|oVIdlvaS+`oxlcn)A9((d z`eE-^M#$1x9OFLXZTAsFfcVfUe$5Zd-={{bg#219MGty@1aWtSs zk*jW$zsM1ZNas_&J-4#jHOPFvx9zHAZ$I-rpj5bQ!v5gs$6d7-6Oe&XvK%I$E_y2| z;Q8EjlZ2b$koDD#brz|2?gmv*Y241xu(4HLuO`OJzAiuC9ZOa!7*R4}t+eS>MU)sw zZl?%=b3rXjx*S-R1IH8F3_2}>aoQEI;r?!Y)|{C=dAz`Gp|a=i{zyc9aaN|5Fjz~9 zPUCit+)vY%P2-W9i99IMmh)qRyF5b!6F#;i&NjLV!F+Dhfp-*@m5mykz2RKp6s`iU zBX#68nnNx3FZ%l{WK;i5Rv=pR4(qouRUy{_fB%^!Fg>Pg6)bCS@=Ezsa(y!+z%k(S zsb6US66}80zg?luNG9YHW18X7G_ay$HW7vP+x+4{i0(hoKA!I5{pjG2e3hvw4(=%2 zg5l`oWR>%dbNzcE_|+Ob6Si1dR4!CxC^_9SZt0+kr;SXsPzK(n%-tE~>gdjW9cjX7xafq(zfc%aH`e;i7RZbAwWkrmw~O3fHuUrVr1GNwZ?-vQRD0$2c%hR(`}mB zq*rP(^w09^Xtg;mqVZ9It*Xl1{z6;p1%%MF71J*>lVaKH`jbv$9EQiwz@U>k+C#_n zp;}yG_h_+YmO>Rj$c6xu`|mOiFmxtLMfu?(#wz<+?=G0HOIN`Tf4U>(mBh!VXu}0q zQKMrf6D|`ABmG)cSwf}tsC|a_oe;(m(t40~Aie z<(HXQ)@{aCWq#Wt$sVr_`D2gNR!!PFPeP|GRxrWl-eK0Fb3$5iEP;oI$D3D`l^FaY zHwZ(2F$i*ZeyEx+N38Yzpu*o@gv+J|72F&CcA)-Lp~lC{^qYjtixM@&nTI9RQqvLyq4Cb7$cz7GkLD@PgexTwRsN`Jh#g_`FkQdFl2`2MKM^cuEp zEi(qPm8*;3Jdmhpb(Y#l3ZZqWWZ2f~f(LBqsn%q?bHK)>jf9-hI{?Vr0p*&n$WUz8f2xE~UC$)H?oQI>Vgq{)3N7l)urx4RGCxe_a|sYEiS`dWf#;cFhR{^tsl@ciy)brDJYVOA`#77%pCXw-X1F z{=~z|ymegmgD-m`-ioW6y(CVaD}k2>ZWcdKe)<^rR0+aH_|3EqPi=jGX$N8c>)d!X zgM*DND=A5$<$~CLR4t`tqfeOG@*OBRY*Ix00rPx_*ofl*GSkk32^t(SqR-l1l*FCW06CDRSonzeLQaC6lJtCD%h!@Bvi~fGQcw~3^j*B_c7i?pEKJVPs({qyf zC}`2>A?Vn1;4HXwdzji^RI}GVFtB!en0VCJ`3SL_djwESfB$DGT-E~{qzs6QyeSL! z^Udvl3l##k%x;}5hL(|dzqubsA7Ps?O z_`wBX#yth{3sv2yr3PzD?^?rNxoPe;)itzx%tG+j-d|Jg2*okO^Jj2I$`=OCcfkNHFBN+Dt0c)vG5{z60JEb-5*|X5#UEc}eGbWr)yizaHGz z@9eW`o$Y}WtHh(4R>OT$`L6HZDl(7D8u};uS7`#S(H94r^#;FGLgkw9uFN%u%rS-} z@mV6IB%L+=m`XHWHhVg)wngKXXta*m_{k&~Pe0TU`@O&STt8HEPA~cLRqmvfaM{5O z{Z!9;Ai-Nz6Top(V5KJ-j0B1kY~1!Y&x&Ur!`jU{X5eA)d|h55S;lkTVD9>;{nSJ6 z9eIn#U+?b+I92LMP*AmtEOBs2zrvxAx%vrS??>nkII*~Y8ft9`Kpj0@uMFMxeMgj( zjUc#eJ3+7TW!p<4e*WxoKq>_=^o^sj^)tlcL;_QSUK(*~bh3<>*R#r;S+*mZ@W#lv zxJfp66m^Kn(7^Ov8kaNL!-Eh1=^XD8gp@m5vTP4tLUd$!w;>V)GAzo(O|H=dVlzuLT-h+Gcv;h3h4Sp)m&Z zvC+xax20uK1DeY<*>uUt6UADzKxCvepuG8CE0e&01|i%vt`ooRv^1wu2e8(Uh0_5;pM8ZIQNmbUFT?q+9(pq+q@|x_ zX;kGe2&_la&zzp0zaL%xeKsf!H$-zT*r1*tnStAY&{s-#zz!=%ukco`3Qy6=XM>k# z?!)|Rj_!17WudoJ0(HJD9!@*J_L{2KASfE)t+y8+H*F@&CZW7q{U3K~+&zqe117p_ zO*&D_HbuYIC9yS=1-!@_bm*(OTHT9pVbYnOPN#kuyc$sU(Q&!WN8tK|4~&KucI7z{ ziAt(3B~eA7b$(`7Rvz?S?Ae_+(3!6+iAvdR8bNC7vZvy)+jtIGA(?FycaX{kt^zK=2Eeydldl<#6bTe{gEy$TXJgQXXC@FCm-S*#GLZ$%E$phaFCo);c9K(G7DE!3LZ^WhmO*!KHm-*>OG2QLFd z!(=bRZq9quv(}(sillB&3Qc4Qh$dHA>~Tb<)9XqrA>`{H!7x|8#U^9Y)LJVBM|jSm zkI|T`P(u9>9G*(9^*~?y0Hi}~zDr~V28Q+DbIzZs2xpu>li67&a&68i)#05ZSNop5 z7mEAZ-_H5U`#`8fy|I0DQw{P+IQ^)#!=^-|+biG_IYHVGK4>MVcZLgKl8$EXdK4@+ zsU87e&b9V5O`tV`=KA(jFPsb6R4emb%q0{33um873qwTw$Ep z)kFJ?^{*Za{{llPAG<}@yT~-|FD9y(*C>H*2}M+!2b&V=Rc{*i0`W|5@rco7i-qtr z-9GBWTVryL?{A$fCy}^Tdb5vG zJqQjY>(55Bbr^!#w|SwqLtURDQPzug@wAgCwkahfWtnjG8R|Q9<<)=ASygBSF!69k zT>T71w!+uhYyluz6&Ed(mskMg!KM~wzh1h&PfMuYY$9L{uinZab{#En9YITc^qwL~ z0ox8QvfFGg1imiOJ*D+6S#B#yW=|93YSs3I-d=Fkiv8> zlEc+tj}$VBn#3y_7TQ4k)aNgd(RPxPjZC+dle;fE*YcqIoPJ$h)Gc~Vah^qe2C(7b z3TJzBoc%l5PaPis!ir+rdjiF7OP!maDhb9ou_f(A`FSaXv$&cT4XR#VipkPc6cj9; z#h?|0&yHxXhVvOtEM(3SVRIwNCl`7@e17qk-=r~g`|z;y#~0cGrgbf^6HTC?a^Cjo z39nq%TpYXq8lCRzMxx7~H+Z8VRVCx-BC0r^=T}}80H%)*CGCmaIJNaP0Zv8$<{B+2 zJpaTj-s`Y7$pZplfq_+P)EB6e4WEx_ZCj;hjVF$b=0pGs4311U*m;D!r>DsPFWvhe z3i-kalrgML?%yin1;$Nsh#Wje9JbyPpoI>&?j&6z2;(CQZOfSPbzhp5Feg|rpI#>} zk5_#01pK=PQEAKM_B+$}9)p5{0&4pZYxxz^6zIRC3Hi2X8;0!8SLLpSqX)FNoxu_B z+2~UUm{M?Yai!ecxQ~yItF7kz^%A_o8a>ZhwnW9ck7j~=levA+g@uKQLgfsanw#ku z%S{UK2?!ik+OkswJ_4K{dg)~D6jkiic-f~WUJj=tMiPe&9(kxGDI8!*{d1q|2CF$# z2W*8XZCkb1yg)QZ43le8719sSvLz2a;d}rTdOVU(&Wu&AlVPlS17`cc6c$BSR7`ABI-k70 z#%W`)r83clDt8vi2d*|SG{;~Ip4|!&u3HHIE;Y9p3mNSvlvJIF*V^+ z)}w-Ommh9-A^(nP!)ohJ`e}hOn`6M@On`2^dj}fn`+k3YJfpI)Nm#x$GPK}3!UO^` z5#sV`eX_~2#0iR8Hh>gXquH>}6++YgBb=D?=QQ>zzrd|^C^{;1e|BWn5_0Ii15%gv zs$2xY$I}RUAb7GX;c!Vhljt~gH;<9V9? zi8B-cBg!tW?6+&Z14*J%p)%Awn|!{;q|a}Ds=ib%lB*2XSE-w(f@D}mu4tZKkJH@X zy0#EVH_papDKI>*$SgGpoZ8hkb7_5l)i2(1ca#frHbx~!Cl9q#O?&KG7mcA0sFbwV zj6fDeoHZ#^z-hehoZri?6|4()^;0<&5a9|j)Qpwkj10!1G(qkRWYV_(vWa_ty7O&! zAHr+F*w{lMk&&x=wuJ2V9qNtg+L4*u{f`;$Au;+3#Z{SKk!%_#H2WU<3v^fZ$^`r<9 zVs&R#)<^h#E94=p{oa$ushk$Z*82GUV1S5dMa>*)DzoFvTS~$C6KDi(Ei*ONohFd7 zCJ#ZkKH!#8C@{}%S{%$*{}FRz2O6(DP8&a&+JN@{o41JR6tCiG8~xAA2?aT?!}M^Zm?upCWM z9o)n1lvRrc$O#!yPy(F}#$SQK;7O9bidfyU-igits;`mQgcKuPuh#d4emyY{)f|ME zjo5gqn>7mI} zjql*-h~(yoU^v^KYrZ<1YvR6j22iKu>&#&Hy$Cg8?}L}{hT~VSnl5g%M8$Qh1yrr$ zv{&UVNk$X%$qOco$BWF=i8nOJ&i*22zi&4(GI6{a3`9%(NY8e3G*Jdb;=0F-9qBqB z?$vA=30awKAPXLhu`Okdsy|#&XiE_w0IY#_q+NkYO6OkvafA!eVqLkoIbE`>toOwt zO1oL^p~^k+KyqY{h2H{9S||!M*{j}qV{52hS*~L~@Hz78Ls+oo$@rIQwzC#xlk>W} zXx^x4XBH>pB3oJ1be*O4oF2q1{|XN^?#=jusq^W<6lSv)wnw$Z<(xCo<5>Sxv0n;t z$s4gA0pL9mIzT{V=S@%egu#K|3h*re>ysE6gf8Z*7S|gOxV1c*qtK4%>M#E=)K-#6 z7t1yQ2yK#1_8{{u0arVj<)sqcV_YG|hme}R$E~|-sa7h0e}zuL`wOt3e7)8dC>)D| z$D~n+pE2&{Xl`5YbaQ4oTTx;#^O9aBC638@<=|jwJF{gZ-17l8()tiIX11}Jn8Bc= z-9QOTC<8(!@?*?YM}%gAI%iPE{- zuE8wSkoJT9Sz?B7XllN6CB$xD1fO`-CamQ(y>Cs&u1unhY*{d*yU6Rf()S8D z3SonMHkC52Z+Wv=09p|YU_8%JW+@I8D}=Gq%a(c6q+{I49{KNkZXsjEuGHY8qdo?BH;^Yn7&!3!w9$mGyN) z7^_kgOGUb|4uv5-@inM6F1D1R2w20 zv%{Que|Z(aPBd9Yc#A~=fM`4&YDgJ1JtbSFh1)Qg~yEjQ{OUnj$6ua35}6)E?l76 zbJ9F{jh-~bcB43yJO4t{>>%$w+epP{jTaRERI#d-8v@lS_M{^XVYL^EW)%Mxe(j{% z4z^Fg!hmMtv%iukJtY+Owze!=gFcb3)_W7QE=&1rsou3^@;DMk0UahNIo}*KcSCoN z3(;_@s_XHvqxpNn3W_>gq@)Fu2A$MRA%^1R7iBWQ4pgiJl({30ALDlD>bBdk3GVC2 z`~(FW-w^5F{~#5-BAI?{4tzr7k5coJhD>_mik$6$w4T80iW8u)x`G8<7ZmM>T5g<1 z$d$O=&YlN~p%QoAd`-gz=JGn8f zndL|g6{y+m?BLx{8jvP z3JYeXhD;mL2p!1MbvEa)KxFcy*Dl@q1NG(-P_6@ra8J3gxA_zsnJX9!3>*>~xt__? zI9ulo)*rg@(%(Ac&1|siti#@)^^7_jl$z+GjU-n%IKachk4}nPJ35i^53t}sso8kP zCm;|DfWlh6-vQ(_fW6>!>xEu)qF$RnR;$6;(T{L4LBC%|@=amLLBNUJ)lGXmlAvQX z-%>EpDA3q7w3K{@{OXD=|x40a)|?5@LM=oBWcKL&1u zz)=a&KXl7XPa<$XkwonC>0^|ivkcGQ=5d2u7i*{(_Ji)*yxBFXF6186FTq$#LSf|| z1N%Tvy-u;(n!LRS(#Yd+$6>yc3^e)F8oWCH%BpyOVE1Qqu^7^sC`j#`yH@yQ&;M|j z3R)0y^a!w;j!Q6MTE)R50!X75`|;%3$VZIlshNp--IG})bm>DoI~w<>GQ*0@ZTT}= z0W2m};W^r&B*E7rkMeb@UhUVBM*;-aj@=pW~)aY zveCZdyC#%X01-JQWWBfCV~7`%oL5p)9QDV;13Cp)60z%|?B~zjdLFnvzl~S?9vB~v zX`xn&!a$;gB>dwHF&7~L4lnq6IWM=T5GREMjk&DL_V6h$m1X-umN#MUzW>0daWn=7 z3JuoVq2I62j{t5WD?pE7ib*l5e&3#VYBWPTrV{%B=mEZ0k_X|vNSGQ|hL7#w? zEI$h&*)l{rXs`V=f&8{EZ_;zR_$%R3<|Y53&k2*lr{lXSW5P$PITXLlGH_@~oF~MY zmuX!b%g_$x7H)Ye2<&45nZ)0wO!;Im&a^N65@S*9b#S)0>N(?%&J4{hhxBWwgEce+wQnQUzgc zCKtSAn8-rXKOWaZA`-gr032tw#u7_ROw7&0W4!7s7?XmhCq-x)ne}{&l)-B-DvD-- z)t<2xqTZbY^d$zQMy59kpCWt>>gC}OaHN&@vDy0GVbm`3|6opsC#Dwunb3HI(4hg? zBqsSt++_Cjo{p=aYK{^5hsD2^MSuS(c5-!|TRU^w>q37rg1Hxntq|Mo@1$B-EdQPl}i zQ%$cs=ZK_LsM^cpgrsqN8mFHjrphJF>dtR$Ki8f1D&~B>wBf`ILV135+O>}L^yG6_ zR;7vMG*&H`;JBiBfgwDCNWVUU5MJA@Vx{pCN_dDT97xHbpi_)Lbtu$JM?C;A?`Z?| zd@f(ZT`97%%73wMmvEj@ONbS?Si9Y|6GPEscSnmDFvAlN)v6vAwYegxfk(r8BX<90 zKQfDVW0NL+Gpl;j;A$=xd;y6zo+HpcEqS;8`7fy_mgXKi#_r5(Agl&YkC{z|6OI3_ z@wMLSN=n8$5{`jtd~U*0o83kgbqKm(wS+taDElV;1Mp-_M8B~}+a3ZDl7{h_sDtc#3H zDjmGel)yItj~XajTo3}vG;OG({3XPP!`=y*iI43+sAzvlee1A-M#ZOFYKa$*jRJ(~ zj745{=kfEaYo6_AemC@o&*n<4NkNJe6Fkm0=;^!(EFbkp{9Oh0cSfeoM{s*0DmeM= zpoc!{9le=8=C=$G$F zwSj!^yuJGdhgWC&t7Mmb`nm4!y+#f8;B$#hz;ik*#xRqqQ-X(9WEyy?4=O8&g zKhM@XM7a1$8%jB2TTgcd9&qdle}U7g0a;(fbmJjHs}6YXFKw?MbQ@wVbmu-c$5x+7 zuf;0%!5FeMzP-sFO5rB!V>sC9H90;rtSV~G&@C{W)=^4!PONbeakAo<__*_v6C>aK zbd{%%YV`C4`5CUo+QQEzk8bRA>)h<7%lmeyMEABH12pWU|0T z9M&zOErMhJ>Z6-S11<@UGyq%Vy;$HRR{D~qSh)KK-z($?@(uQzH$Z)lDwXgs&{SWh zTtB?oUQ@fJ@~|`|UYvjpBu;nt_wFah!AVY?S0r*37nhgo6Tg?gcMnW8-7obf4JEKG zjzEmrvP>^+oc3okX6xLzY?n~gtENIj(K)Pf0NoLz+~Xm(YzFVpST3!+JhxL<%8tfO zZH;#$6gVgY{>^gd7#gI%)wh(_IRQ|qS5?>JgW(MJ2|YhQbsnFb7+n{gcXes}o~m+i z=pX3cyz4dt#7Coh&ir1>=}nJ2wdzIyiHkZI3bdDO^JPk#!asYPEmrb8(Wh6&aDhr3 zTfTGfPxI|>&?r46nGAB-SKSVMj-#C!DG8YoeY@5rXwM6^$_H=HqwQAsFFEP0f=$c% zMTrQQk0x}QfC#Z%@D*QbhN;mrv9U1&Xm<}1pj3&8D{15Q=uokjMs)RDg@U;mmBkX* zJ`TO*Rd@GXG?8-0>t$&8sZQZ(DboF7e0`p-&XPpWZ^$b8lNKIz|IHE?b$Y-D&6{tc zn|&$-(Ph$>6d}Xy&Q)bQ=e)etmNLOEQ(AJ6gkNTF#YIociwIq^=S~GF0oEW;Y8?T# zyw62NR@jPh?01=wq$fB!qos~E03`iWEVqniv zcVj-f^Z3GL82=WiOmhH+?GpxB7|UtMBGl^V{Q?t-^`5};3(c2kBnn#=0PSKo%W_V+ z0SG*D(pF^I*+a99k07A&J_6N&h#P8^cEL~edY6~6Ak)`*Orl(^?_LTtI_nQ~?S($J zu>Hme!a~4~+Py>mMj^hg)?<^c0K-K z67!a00sNZmKK+d`)8#DJ-^|Z%ZgVDfqNr~PCF9l}*^`GhHa8ua+2&QX+%`s$&cRm} zyHm}M^*s(D-4ky|#Kr6NMTfxv7VxlCO%C8G&fUDwL*CD|!w2RhI6o5$XnE5KYBVFB z(M=htxq3Lo*c=ZA)v-hs>B01Dg0 z?K~^gSh4R20s+`CT!__tx6}Z}h;MUFM#Rk4c_fA=5qQzcnqL#2EU@6Gx_DKpzV##o zV{r4VUq3il-Cd1~we)LcN#aS=s9G_-1#!;QsID)T8w9I`A10O>ZBmeeu9m-CvKoAw zD@pI{l{=mG)B_M4Hoy~}SWD+@r9VY2A&sLZ=b4{qFZvleJu`h^)9JUxHN!w*;=UX0 zxo0LS@+CGi^LPKoovcct=g}(dTm@`Cd_CxuVf|_3j&Xh%0`41S>#IHxNj`n!dXBAK zTxtz>t#n;R0(g)e=QApgD;!{OBB$?Ne|0J?{Fy;CVeU6s*$Q|e2OyHtdmPaW7@iqP z9Lp7dLo#2YI+D#>t;v*;flB>Z)6LmRUlQCUNzlRO%9(83GZJbqz^keY@ zv!lUr4qnVOT(zHm89c*) zxN{A}4;WQO>FJBcWJ$z=g85osMSN9N#grS;{j6zk?QfGlKfy*xCZ_N!QfdXD+WT=f z{;P7;&6cpcf8L}`tdCoY?rS^WAEGo>yb81tEqnC9C6VIP92`KR2&9}-*XsaF>tvFh zWzOFUBrN>n>iQ}sCP7?wI8SvOESu7{z{4_io%C~{zP^4{HDKrHNwa+)79nK>pf}pT zB0td55(bO`)a<|InZFC30c4DfjJH2ItwjJgAQ6?}X{QHN4^w+wl|b#xR^?Enn~n3( zB6xNXfeAHRc$?5C<;g6qV+TgHe={74s*X!uI$2x$~xKk#p z_{q~Rx=^RA$^E=04^&?t*vEEt<5pL#0~syv2CB=6%9?#XC%oK3uXhcWy-;naR5N?#x|h=YqA0(0iM`Lu$XclU0OuANM6jprFwykw}|JcH`qig4MC_E$|U;8 z-(+ANMwMAJ#rCcYT3Pq7W{hSv!Qrm?^#&NmcrBy=bIB9w^^R#p=D)Q z>AWskYI63QP1yiR+0ETOMEu2n;%;u0cqFdaerZijF0_@+!Y@h|MP}I=Yfo}xp?TKV z-GZ|^TP-bHJ#({dR_|Gsf1$lZyMR>5dI5}ZQ9nR?ZTh#&Jf|c6i!|1gUiW!6vof(h zavzL^nzjHGKcGq1>eL%4Qq{E$ztPK4003}_YoD;@>DrEs6NdelM%`*nLBu`;BIQWG{O~Y>Bf0ps4sDBki z>NjWbo{8zT1}a3nqw|X%bL^0j!qr|{!a1R`Rd`t@a+1P#f}R=$qKV<3s*Bvnl27{i z>6H{}VfiQu5_{0cr}={bL>uk(YaIdf_P~Y3#j!n@-aDXw4y=q_5YPi`n?HLeKt-y7 zT0_DwaQ02{I=lv0dFcOR?<>Qq?7D6@4bp;iBT9ED-2zIdba!_*g0!RvNDD}JcZrmA zcS(nIp0%I%_4&T*{60Sq7e4^EYv1?UYt1$17-No`_T!?%Q?I8g;#;qIjwk2dX89F# z`rD^uF%g@U)Brbwf1ChdqyyU_dexeWTH?@&mcdb$AZB6XQi1*w+^BW{A=cniF*V-O z=N>3fd1C9yH z@%g!E*>r=uG4z7$O=3+5gE}a_Md)sxc?x*8+$+6+{+$AOSZxnXN7d{pYWR-*AV6I~jB8DbrRksP>K=l`^2fcLi2k{Ig|f7#`V5o+Ht=z&l6PNcCJE;#QcD9%kPzUl5%*k$UbTTaH|oFE z9D&8Ow3UI344;Sa81r=BO2f)yy73DvN~|bBKQYUG!wQhv103tsNvxDTOrlJPT)r6a z=fF#!_>YN!nH+j~q5^J@rgz&9VZ~4VNs})oB7qaf*0-J^*%;ljXM|SJn_V$Bts(@i z_5H?A32oE~z|vMLlE@j&91UaM{bw@jCDY+&n9Vp-*a&4c7(t({B=bXAPj*;QPmc=y zGs~JMHP0yvX(r-=HJP#|Ek-SRI`^0f)!n?yn##C4e zytYIVxS#-Uk)Ho-d+k!)|I2*EzUHY-BJ_j)@As7-s2BOa(?615d1-pm2vDNXV6h7S zCPjdUN2Q{5ovyvmSv)*!9J+64(sR-AdLxbiFYbB7m{N&h&6$nViRo zL(EiZ>jl>Ny(bxY*C3PG@WSiMr4fN`r!XlP#F?*1&{l>i8AC7@-}C(Me?;<>@E@AesT=9db@+JN zmYOPib#(g{^zQ_KoX(*0^QzpmfbR{-lXrNnQ`^-y zlG!S3Zi4*bpg+XbeUGU%4t_ZQ@PSQ+kG3k<> zzdGvoF5cTajc3)iW+~q)6sj!h9+t8bL`ex|M8%tpfL!uSW zKv@rZn8;dr)@hqfXdvP_%Kq4ciupmu#l;)FwLR6nc}C^;s??A7SEzo08G3t++2ACx zYrf%wN4#>gXj3z-f`Io15g(fe4T?S-@2FcfHK%hFra&O_n3!lE_qLVZ{~Nkebg}iT z(A{C$iykjwhI7Box2UOjBUG8a>lw7bIX?f6e!3U4PzPZf{SHw>E#w zb-px-c@NBMW>S%96#Jui)6X}MSKJ@g{XaL-EjE7`9d z=c|FEuRWOo_pvGIp-0^N-Ln(UoII)a^w<1yG#P#SI|n0;7ABluHruSMh!q_3n$t~E z6;+iZ%3~?SgF}Q%)S8Y_nx<1G&>;RXb@*$D-P+q@SpQvP_tod&kko_cNTWVoY+g6U zxVU%*&W5mHYle^a?`YK6GWcFj^Tc8aVO5#F>ltXfc^x@4^vZ=By-)51*wL6dPeTo- zA96?!%W6yI9&U#&lbjpV{2mTC2!uSvrv)dkw6OyVb7DR>@5#~|#&0wk6qFzNKQkL(+ z=qTX1%Jfvn&``~qUaL0M!4kv}^mXEgXaDeMgV~oVO0s z{0&Xnx}^HI?mP4(GQ1(j<@()y=)!lI5A`k=oQ%4x_+}#}hGX@LI!oV1XChJYS>Pbw zSJkU5ejM;*$j3!gheH zLm(X@0bu&zfoO|u|3k+wOTI@o401lWpiyK<7%|rTJ;ITQnNse}@#UY6_L~haE?@G4?;PUq1y^7i+7iPG@IV5^1@SwomCGtsHEA99i~4 z2TLq{Zu&wX6xi-T*56?6)myKfk2E1d5+}K6X&DhAktE#k^oietxc)q3u-2pRo^m9SV0G7K;4OUTjJ2qN1Kq>J(Io_8 zru89#S4mYT;?egw@9v1tHodUESfFFpe)_DKA0m9$t^ryhbqA>a22e#lPaS`Bb0LMT zABmOW7|9;DT`Y=P>X?wv?7p~?7<&To@Sy5nk7215O5KT&7jTb~5&JurI-`5A)|yd> zIK3|#s}Dbp7Xr;1#&W9Ak*V?K7IlFB9Kr z?+ZpTW2P?1)#Yh_`u{tC;%)UoOaGG1-+K{1*mK6Yk@v#0*M2W|@YPM;&8RsJ0Z zyGE($DXUTJzgH-gji8;n(B6pQRQ2~Yqw*zA#>-|A{1q{N!+b%mJn_FO}xMhmuT*k;n z{5$f5V2Q3Bygod{-ShX#<|a&(BopSjGv}Eyt^@|c-$AKhGrt#&M-08d`I0J&CGkv! z9=lag_xRY}3P;=tSH+6uS1$$0-|N4hDeO*SwhW}mtgVGX%pvqgfWG$3IT^1KCEq9*})YM>9is7~rE@`3A@)F@e-1v9r^?e`0Qe+Upvd=3ib^awny~9D&lTF;{3gZ24FZTQld8SXi=kS&iQ3eoy-h|8&XhO_(oZo zqnY0YG`uIM{igWwO{q|yJJkSuv#0FCS`PO7JFSbdV1)c}k)lb_qDkF9_k1K&mYb98 z5$09>3#Ok7s#dclbFdMEV1|Z<_DlZ#UjlNt5&$(FU@NpLS%jp@y_C2kk#%1Ay$oK) zN)!bwC^72#{$~ZA>y>o9 zlp^cUq<-qGbwEF3=b{d(pOBq-O{rTwuw(-WgwT#IbMNCNe&fy)P)63X1nCS zo4|Lj(I?E@jSY3ITp>ymohO}$h9(bWNxvF)uqN_jvTcAC8tk*IpA(Sye%Knxp0s~s z&&Jap#k{Xyq*YkozMgJw2Ht+_a1e8L0$^F#WOB=!j5*_Os8YsDWE8kpBYU$9@22&u1MIr~)C7Z?UiQG`F@Em6F2Z_quX-hNd*ClQm)CXhD*Hx(JwA!l{_?lRKyTkYFBK z7b}OX92k|4o`+MFCa4ORYhV$Q&21*&EsjdkSulVugC`L(VBDy7VL$VU>C!i?traLH zn~BF~_HU%@Gsxc&kEu@o;m@_R?o6zF0xAV+*7mZud-N6ziN2=s{gGppju2sUdn|w| zHda5J^Ot@M3)6PdfwLyvbb(W0O&KiLfLBqQ1dgHY*4$!wYH-;gyb0%ziwJ90EaSg*sayhHcnZohI~aCJ|)@7J1BfWI|6P4nL!lr=mWf zg3heRKZ!bXeG&3PVIYsWUiN$NJId_W-=An~d+b?9#zeLKS_aJ zNJt2V4-16#4yT`8?L_~u==P31+Z)Za>mDK34bD1&&OQSerJRa%`K#G$k=W-+f^50xTyt zAq0ZGN+}f<)3t_B*FHYpm3PqS$>>(wDuF%Y_WPCG*VmzyKP|7*{)~-dQho~!`jgI- zLydkT*?hjrJ~1(6wEqL1-~Q5OQM2~&s)bHbTl0g-vaLAq(fgw1+2>W?M9KZ)N!{T7 zmGOY}qaQbm~c8T(;V(OT%5>yz>3zEEY zggr-(I^CmBd@`!IT7NtInt;o@V39Y2Nh>Ha?N089TCkM-tFN;|UE-{&Oq-E9N}xab z{KYEpo+~IWP1L(Qv+v7wpmw-ScbY?Y2WvFZcdKffz1(vV12-|D^Wv13vmSG6B=uuq zyks3QqtkhP>(T)V2wj=1?NjjqbczYEc7PlzC@TwM)?2E-WHW38$R(whn} zSfiC`Lv5<`8sb*%vYKkv*YiXRRfl_hD{pWa_PIg6Zm?Gx1o3|>K|>&5_#@_m?zU#q z32FYj%Y21LpYQcD8QI~ny_1-Y4;)b{+KkT%LG)LtUeNKBK$2;0$)4Gq{r&B&g{|!Y zx)mz{$g!pKr=Ky3rcUqfonK9BWn>rRqCxb1d?~_}FcVpl{9+>a`S~xq?=k8;POXUq z9}M%ntCklQt|?C%@R?Urxl%WXsYLAg&vdZhn{WGh0N_B^_6=5_m7AJ|3AJ!%*8QO$ ziPd1x{@N$cWu6p3IVa1QN&P(9OtSnkG6RZAn$Z!-5htN~f}@+dPvc6-2hvOIAXmPB zn2VPy^N04PM#hvIm3oUlQXTIk7k!F%!}iAoCF$acm$KpGr}!(&%@0$=?lnYsioqQ- zk;Q4=#@id~4-$0-OZyfW({6cDm3h@AGcAtUm;yqKwd= z%EY~j+jh3=c9<^~3#z87)0sP-rM0ncp#Uq*PcaZMf9yjE^m$auj@Z5b2l7v8={e zwqMxs{GG+pzkChR@rt41Vj9RRHvvV&kx%VHXXJ#B@AT(Y+a@+@Xaqm{oIL}54OCrX4$}u!v9B1(xVUOw0obf{ zxf%7f2mZ;_8@<&{{0w`Gsom1wGCmPh=tE}*0dv(uP;{Xo98 zgZS}EwoX9dsl-aUe5;78jlhDlCgT?&q^f-RI3X=*3qm#Tx@Y~{91gTQoNMbh114=o zhiLDLitOBO11a=(!d)Q#>CQh|K{%ujb zSd9}Ui|Ac$O!IJZ`sbIW7R%;;78U?;9PMqSwEY(RE=$68h*1)s)H^r8FPF|opABk5 zLA-(Cp!)ly)P2FaIIzZQp=+$Rk*j@bDOO$`q}IUE`1g4fC=tVuA6GX}vX{VF|CB}G zmX&!!uXo~O&abY^rQBb@pbiG|X*J9Jy65FS^9pd(GhKQGLn#8|e&3Qu+oBY(zX1Cj zjWifoXL0jnM_bRjKp?E1-3m?K+%+yG0_@*p$;LOmSwJ_Z2*8l4HPL;~2fOMGq|)w| z5>Rs3z$tRKsdtpMA@dsJMS9#oqs6TtIB~>uxed%o#@HSCTJ#j*1>ZLkflT6b+}A8z zrj%mGm>r_O+db+k_R#zMg}$)0-rY{dQKrapSWT+i01S7YOVPwPn_B}`Za@*O_2iSs zdD*ys2sP}m;mK*34FDZ)IZw~pi~Md7?!0|AF2TAx-qsTe0ou<%fZ)wE?orr& z;U7M1UbQ4P*|gMx(NHF{4n?M5qa+&<^eyw2<;>LML_CX-w6~{7@@~P~e5U&2r=#WB z&|fV;f8Bg5bkWvpXrMvd&jLbhqBo%u^0Ni%t4lv22{m093{o2=ueIYVjJgbGcGZX5 zE#jBXZ&P&g&iGtaCDq8H)8lqmr)GK9Ks5Y$9b}wHg0+1_fab1DylE9j73TLv@GSF15gsJ7dZXd*DoUqzz<*= zA*#}xqe^uIdEqSth8@>-(UGvFyknjQR@&y4{T^egFXCr6U5L-3Se@A)! zR`wQu8#K@(K?WNg;<|`6-Q0KvKl747V!`p@*^C6|T<$=S^2p9+LcNLoxSJ)2?X1M3GF6n$cLj zc<6xngylODp{I~ozn#i1leAf%6

Ish)GF7_*MhvdiJs{^1`5vrDT3u3hj1gzG=1 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

$O8PkSH)se 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 z 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%0Dj*x*0? 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

DKC0H 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

|1qz-5-Zeo97k|3BbzXa~99m*B4>* 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

6S+33(p4Y!j5ZzD}g)wLuN` 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%0AYp*0y-#DU_@9@dyBO4ZC}c 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 Oyi}Dm70aH!3i>ZSFyJWw literal 0 HcmV?d00001 From c353ae9c248f15df32c95334b57c62c1fd03176d Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Fri, 25 Sep 2026 17:26:15 +0800 Subject: [PATCH 3/7] fix(sources): satisfy strict Clippy checks for test fixtures Pre-commit hook ran. Total eslint: 0, total circular: 0 --- .../src/sources/claude_code/history/windows/sources.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs b/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs index 73d6b84c37..36e2f2658a 100644 --- a/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs +++ b/src-tauri/crates/orgtrack-core/src/sources/claude_code/history/windows/sources.rs @@ -338,7 +338,7 @@ mod tests { )); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("session.jsonl"); - let records = vec![ + let records = [ json!({"type":"user","isMeta":true,"uuid":"meta","message":{"content":"https://hidden.example/meta"}}), json!({"type":"user","isCompactSummary":true,"message":{"content":"https://hidden.example/summary"}}), json!({"type":"assistant","message":{"content":[{"type":"thinking","thinking":"https://hidden.example/thought"},{"type":"text","text":"[Report](/repo/report.md) https://public.example"},{"type":"tool_use","id":"read1","name":"Read","input":{"file_path":"/repo/input.ts"}},{"type":"tool_use","id":"read2","name":"Read","input":{"file_path":"/missing.ts"}},{"type":"tool_use","id":"report","name":"make_report","input":{}}]}}), From b41f40d3f5f35d8f2b4556b3ad8b1ff5b08ab5e7 Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Fri, 25 Sep 2026 18:51:03 +0800 Subject: [PATCH 4/7] fix(ci): initialize turn intent in app-server canary Pre-commit hook ran. Total eslint: 0, total circular: 0 --- src-tauri/src/agent_sessions/cli/session_runner/codex_reserve.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src-tauri/src/agent_sessions/cli/session_runner/codex_reserve.rs b/src-tauri/src/agent_sessions/cli/session_runner/codex_reserve.rs index a3ca2e926c..0bc28bf130 100644 --- a/src-tauri/src/agent_sessions/cli/session_runner/codex_reserve.rs +++ b/src-tauri/src/agent_sessions/cli/session_runner/codex_reserve.rs @@ -39,6 +39,7 @@ async fn live_luna_reserve_app_server() { let result = tokio::time::timeout(std::time::Duration::from_secs(90), run_app_server_turn( child.stdin.take().unwrap(), child.stdout.take().unwrap(), CodexAppServerTurn { session_id: "reserve-live-canary".into(), + turn_intent_id: None, user_input: "Reply exactly ORG2_APP_SERVER_RESERVE_OK. Do not use tools, read files or run commands.".into(), developer_instructions: None, working_dir: workspace.path().to_string_lossy().into_owned(), project_id: None, resume_thread_id: None, model: wire, From 1105d7ec7afc08abe24a2b3514935a34c3bcc5d8 Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Mon, 28 Sep 2026 17:11:30 +0800 Subject: [PATCH 5/7] feat(sources): open collapsible resources beside chat Pre-commit hook ran. Total eslint: 0, total circular: 0 --- .../ChatPanelCompanionLayout.md | 67 ++++++ .../SessionSourceCategory.md | 13 + .../ChatPanelCompanionLayout.test.ts | 227 ++++++++++++++++++ .../ChatPanel/ChatPanelCompanionLayout.tsx | 157 ++++++++++++ src/engines/ChatPanel/ChatPanelShell.tsx | 33 +-- src/engines/ChatPanel/index.tsx | 1 + .../sessionSources/openSessionSources.test.ts | 38 ++- .../sessionSources/openSessionSources.ts | 14 +- .../SessionSources/SessionSourceCategory.tsx | 77 ++++++ .../SessionSources/SessionSourcesContent.tsx | 32 +++ .../SessionSources/SessionSourcesView.test.ts | 80 ++++++ .../SessionSources/SessionSourcesView.tsx | 75 +----- .../TabContent/renderers/sessionSources.tsx | 30 +-- .../Resize/hooks/useColumnResize.test.ts | 53 ++++ src/scaffold/Resize/hooks/useColumnResize.ts | 4 +- src/store/chatPanel/chatPanelCompanionAtom.ts | 30 +++ 16 files changed, 819 insertions(+), 112 deletions(-) create mode 100644 docs/frontend-ui-audit-2026-09-28/ChatPanelCompanionLayout.md create mode 100644 docs/frontend-ui-audit-2026-09-28/SessionSourceCategory.md create mode 100644 src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts create mode 100644 src/engines/ChatPanel/ChatPanelCompanionLayout.tsx create mode 100644 src/features/SessionSources/SessionSourceCategory.tsx create mode 100644 src/features/SessionSources/SessionSourcesContent.tsx create mode 100644 src/scaffold/Resize/hooks/useColumnResize.test.ts create mode 100644 src/store/chatPanel/chatPanelCompanionAtom.ts diff --git a/docs/frontend-ui-audit-2026-09-28/ChatPanelCompanionLayout.md b/docs/frontend-ui-audit-2026-09-28/ChatPanelCompanionLayout.md new file mode 100644 index 0000000000..e58f755064 --- /dev/null +++ b/docs/frontend-ui-audit-2026-09-28/ChatPanelCompanionLayout.md @@ -0,0 +1,67 @@ +# ChatPanelCompanionLayout UI audit + +| Line | Element | Verdict | Reason | Suggested change | +| ---------------------------------- | ---------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `ChatPanelCompanionLayout.tsx:60` | Companion header and actions | keep with reason | Reuses PanelHeader, PANEL_HEADER_TOKENS, shared Button, existing translated title/close/move labels; no native action elements or custom clickable substitutes added | None | +| `ChatPanelCompanionLayout.tsx:48` | Resizable divider | keep with reason | Reuses VerticalResizeHandle and useColumnResize, including existing drag cancellation/unmount cleanup | None | +| `ChatPanelCompanionLayout.tsx:25` | Reading width constraints | keep with reason | Feature-owned initial/min/max reading widths; a 50% CSS cap reserves at least half the available pane for chat even in narrow layouts | None | +| `ChatPanelCompanionLayout.tsx:135` | Chat/companion composition | keep with reason | Stable chat subtree; companion replaces only the summary rail and unmounts on close or owner change | None | +| `SessionSourcesContent.tsx:10` | Shared source data container | keep with reason | Both the companion and Station renderer consume the same source hook, shared-file scope and SessionSourcesView; category disclosures, navigation, retry and theme tokens remain shared | None | + +Verdict totals: **0 fix**, **5 keep with reason**, **0 abstract**. + +## State and architecture + +The owning state is one transient per-store `ChatPanelCompanion`, identified by type, session ID and owner tab ID. Opening is idempotent and validates the active conversation at the write boundary. The host rejects stale scope immediately, then clears the slot on navigation/unmount. No new persistent setting, tab schema, API, database or wire protocol is introduced. Existing Station sources tabs remain compatible; opening there is explicit. + +Architecture review covered state ownership, type/control flow, default destination, content reuse, entry-point parity and session-scope resolution. Backend/schema/wire and migration layers are unchanged. This is not a general-purpose tab framework: the supported companion type is currently `session-sources`. + +The data path is View all → openSessionSources → openChatPanelSourcesAtom → ChatPanelShell/ChatPanelCompanionLayout → shared SessionSourcesContent → existing source history reader. Loading, empty, error/retry and same-session refresh behavior remain owned by useSessionSourcesState and SessionSourcesView. Closing releases the content; reopening is a fresh view, not a persisted cache. Category state is preserved through same-session refresh but resets after closing. + +## Verification + +- `pnpm test:app src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts src/engines/ChatPanel/sessionSources/openSessionSources.test.ts src/engines/ChatPanel/sessionSources/useSessionSources.test.ts src/features/SessionSources/SessionSourcesView.test.ts src/modules/WorkStation/TabContent/renderers/sessionSources.test.ts src/modules/WorkStation/TabContent/renderers/sessionSources.activities.test.ts` — 41 tests passed in 6 files +- `pnpm typecheck:fast` — passed after adding the concrete HTMLDivElement ref type in the new test +- ESLint on all changed/new production and test files — passed +- Changed production controls inspected: only shared Button actions; no raw button/input or clickable element substitutes introduced +- Native visual automation cannot select the unbundled ORG2 Dev executable; browser-mode app cannot access desktop session IPC. Isolated real-component rendering is used for layout evidence, not claimed as native/data integration verification + +## Performance guard + +| Area | Verdict | Evidence | Change or reason kept | Verification | +| ------------------ | ------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Background work | keep | Existing useSessionSourcesState history reads and visibility listener | Only mounted source content owns reads; no new polling; existing in-flight sharing and stale-response guard retained | Existing hook visibility/stale/error tests plus companion disposal tests | +| Memory | keep | One nullable atom slot and one mounted companion | No per-session map or app-lifetime result cache; close/navigation/unmount releases content | Composition tests assert disposal and slot clearing | +| Scope/isolation | keep | Owner tab ID plus session ID at write and render boundaries | Stale open is rejected; scope changes remove old content before effect cleanup | Stale-action and session/tab-switch tests | +| Rendering/hot path | keep | Chat subtree retains position; source content lazy loads; shared resize hook uses DOM writes | Open/repeat/close preserves the same input node and unsent draft | Parent shell composition test | + +Performance verdict: **blocked** for native runtime measurement only. Unit ownership/lifecycle checks passed; native idle CPU/RSS, native window resizing and cloud-session manual validation were not measured. No runtime performance improvement is claimed. + +### Rendered follow-up + +- Real ChatPanelCompanionLayout, shared header/buttons, resizing, SessionSourcesContent and SessionSourcesView rendered in an isolated browser fixture. Only desktop state/API/navigation dependencies and source payloads were fixtures. +- Verified View all opens alongside the chat draft, repeated open retains the view, category collapse works, and close restores the summary. Inspected dark/narrow and light/wide rendering. +- At 1200px, source content measured 420px and chat 779px. Dragging the divider 80px left grew sources to 500px. +- At 600px, sources were capped at 300px. Found that the shared resize hook initially used the uncapped stored width; changed drag initialization to read the actual layout width. Verified dragging 20px right immediately shrinks the pane to 280px. The hook still uses its configured width when no measured width is available. +- `pnpm test:app src/scaffold/Resize/hooks/useColumnResize.test.ts src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts` — 5 passed, including the new constrained-width regression (42 distinct relevant tests passed across both runs). +- Typecheck and lint passed after the resize change; `git diff --check` for the tracked files touched by this change passed. Native runtime measurement remains unverified as stated above. + +### Floating chrome overlap regression + +The initial isolated preview used an in-flow sample header, so it missed the live shell's absolute overlay chrome. The source companion started at y=0 and its header/actions overlapped the global conversation toolbar. ChatPanel now passes its canonical chromeTopInsetPx through ChatPanelShell to the companion and its resize divider, just as the transcript and summary rail already consume it. No independent header-height constants were added. + +- Regression coverage renders real ChatPanelShell and ChatPanelChrome, transitions between collapsed overlay (44px), expanded overlay (80px), and in-flow (0px), and asserts that the source pane, divider and unchanged chat/source mounts follow the shared inset. +- `pnpm test:app src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts src/engines/ChatPanel/header/ChatPanelChrome.test.ts src/engines/ChatPanel/header/chatPanelHeaderLayout.test.ts` — 28 tests passed in 3 files. +- `pnpm typecheck:fast` and ESLint on the four changed production/test files passed; tracked-file diff whitespace checks passed. +- Rendered real ChatPanelChrome and its published-header slot component with the companion: light 1280px collapsed toolbar and dark 600px expanded toolbar. Source header/actions appear below the global toolbar in both. Collapsed companion top measured 44px; narrow expanded screenshot confirms separation and 50% width cap. Closing sources works and retains the chat draft. +- Preview fixtures replace desktop state/platform dependencies, source payloads and navigation; this is rendered component evidence, not native app verification. Native automation limitation described above still applies. + +### Isolated PR branch verification + +After integrating latest `origin/develop` without rewriting reviewed history: + +- `pnpm test src/features/SessionSources src/engines/ChatPanel/sessionSources src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts src/engines/ChatPanel/header/ChatPanelChrome.test.ts src/engines/ChatPanel/header/chatPanelHeaderLayout.test.ts src/scaffold/Resize/hooks/useColumnResize.test.ts src/modules/WorkStation/TabContent/renderers/sessionSources.test.ts src/modules/WorkStation/TabContent/renderers/sessionSources.activities.test.ts` — 98 tests passed in 15 files. +- `pnpm typecheck:fast` — passed on the isolated PR branch. +- ESLint on the 14 changed/new TypeScript files — passed. +- `pnpm check:test-placement` — passed, 636 directories. +- `git diff --cached --check` — passed; staged scope and private-path/debug-output inspection found no unrelated additions. diff --git a/docs/frontend-ui-audit-2026-09-28/SessionSourceCategory.md b/docs/frontend-ui-audit-2026-09-28/SessionSourceCategory.md new file mode 100644 index 0000000000..834e69b8b7 --- /dev/null +++ b/docs/frontend-ui-audit-2026-09-28/SessionSourceCategory.md @@ -0,0 +1,13 @@ +# Session source categories UI audit + +| Line | Element | Verdict | Reason | Suggested change | +| ---------------------------------------------------------- | ------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| `src/features/SessionSources/SessionSourceCategory.tsx:31` | Category disclosure | keep with reason | Reuses SidebarSectionHeader with expanded/onToggle, matching existing tool activity disclosures. Shared Button supplies native keyboard activation and aria-expanded; DisclosureChevron supplies shared motion and reduced-motion rules. | Keep the existing header family; no new accordion primitive. | +| `src/features/SessionSources/SessionSourceCategory.tsx:40` | Category body | keep with reason | Preserves existing row spacing, tokens and shared source/tool rows. Hidden bodies unmount; category-local page count survives reopening. | Keep shared row composition. | +| `src/features/SessionSources/SessionSourceCategory.tsx:60` | More sources | keep with reason | Existing tertiary small Button and translated label are retained. | No raw controls or new visual tokens. | + +Verdict totals: **0 fix**, **3 keep with reason**, **0 abstract**. + +All four categories share one SessionSourceCategory adapter. This adapter owns only local disclosure and pagination and calls the existing useCollapsible hook; no persistence or generic component API is added. Existing grouping, provenance, navigation and gallery ownership stay in place. + +Verified independent mouse/keyboard collapse, count visibility, refresh/error/retry continuity, retained pagination and session-key remount reset. Chrome component preview uses real UI components, Tailwind, utility styles and light/dark token sheets; native file/image navigation is stubbed in that preview. Light 1000px and dark 420px screenshots are in docs/verification-2026-09-28/source-collapse. No native App or mobile device visual claim is made. diff --git a/src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts b/src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts new file mode 100644 index 0000000000..00ca3aa1d1 --- /dev/null +++ b/src/engines/ChatPanel/ChatPanelCompanionLayout.test.ts @@ -0,0 +1,227 @@ +// @vitest-environment jsdom +import { Provider } from "jotai"; +import React, { act, useEffect } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { chatPanelCompanionAtom } from "@src/store/chatPanel/chatPanelCompanionAtom"; +import { createSessionTab } from "@src/store/chatPanel/chatPanelTabFactories"; +import type { ChatPanelTab } from "@src/store/chatPanel/chatPanelTabsModel"; +import { chatPanelTabsAtom } from "@src/store/chatPanel/chatPanelTabsState"; +import { + createInstrumentedStore, + resetInstrumentedStore, +} from "@src/util/core/state/instrumentedStore"; + +import { ChatPanelShell } from "./ChatPanelShell"; +import { ChatPanelChrome } from "./header/ChatPanelChrome"; +import { resolveChatPanelChromeTopInsetPx } from "./header/chatPanelHeaderLayout"; +import { openSessionSources } from "./sessionSources/openSessionSources"; + +const mounts = vi.hoisted(() => ({ chat: 0, sources: 0, dispose: 0 })); +vi.mock("./TabContent/UnifiedChatPanelTabContent", () => ({ + UnifiedChatPanelTabContent: ({ + chatColumn, + }: { + chatColumn: React.ReactNode; + }) => chatColumn, +})); +vi.mock("@src/features/SessionSources/SessionSourcesContent", () => ({ + SessionSourcesContent: ({ sessionId }: { sessionId: string }) => { + useEffect(() => { + mounts.sources++; + return () => { + mounts.dispose++; + }; + }, []); + return React.createElement( + "div", + { "data-source-session": sessionId }, + "Sources body" + ); + }, +})); +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +function Chat() { + useEffect(() => { + mounts.chat++; + }, []); + return React.createElement("input", { + "aria-label": "Draft", + defaultValue: "unsent message", + }); +} +let root: Root; +let host: HTMLDivElement; +let store: ReturnType; +let chat: ChatPanelTab; +async function render( + activeTab: ChatPanelTab = chat, + chrome?: { overlay: boolean; collapsed: boolean } +) { + await act(async () => + root.render( + React.createElement( + Provider, + { store }, + React.createElement(ChatPanelShell, { + activeTab, + chatColumn: React.createElement(Chat), + focusedWorkstationRail: React.createElement( + "div", + { "data-rail": true }, + "Summary" + ), + borderClasses: "", + chatPanelOpacityStyle: {}, + chatWidth: 1000, + chatWidthStyleValue: "100%", + fullScreen: true, + hasTabBar: false, + chromeTopInset: chrome + ? resolveChatPanelChromeTopInsetPx(chrome.overlay, chrome.collapsed) + : 0, + headerSection: chrome + ? React.createElement(ChatPanelChrome, { + tabStrip: "Session tab", + toolbar: "Chat actions", + publishedHeaderSlots: { content: "Conversation" }, + overlayPublishedHeader: chrome.overlay, + tabRowCollapsed: chrome.collapsed, + }) + : null, + isDragging: false, + isLeftPosition: true, + isTerminalTabActive: false, + onResizeMouseDown: () => {}, + panelRef: React.createRef(), + resizeTooltipLabel: "Resize", + resizeTooltipShortcut: "", + sessionModals: null, + showResizeHandle: false, + terminalTabs: [], + useExternalWidth: true, + }) + ) + ) + ); +} +async function open() { + await act(async () => { + openSessionSources(chat.sessionId!, "Sources"); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); +} +beforeEach(() => { + Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true); + resetInstrumentedStore(); + store = createInstrumentedStore(); + chat = createSessionTab({ sessionId: "a" }); + store.set(chatPanelTabsAtom, { tabs: [chat], activeTabId: chat.id }); + mounts.chat = mounts.sources = mounts.dispose = 0; + host = document.createElement("div"); + document.body.append(host); + root = createRoot(host); +}); +afterEach(() => { + act(() => root.unmount()); + host.remove(); + resetInstrumentedStore(); + Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); +}); +describe("ChatPanel companion composition", () => { + it("reserves floating chrome space through collapsed, expanded and in-flow headers", async () => { + await render(chat, { overlay: true, collapsed: true }); + const input = host.querySelector("input"); + await open(); + const pane = host.querySelector( + '[data-testid="chat-panel-companion"]' + )!; + for (const [overlay, collapsed, inset] of [ + [true, true, 44], + [true, false, 80], + [false, false, 0], + [true, true, 44], + ] as const) { + await render(chat, { overlay, collapsed }); + expect( + host.querySelector('[data-testid="chat-panel-header-surface"]') + ).not.toBeNull(); + expect(pane.style.marginTop).toBe(`${inset}px`); + expect((pane.previousElementSibling as HTMLElement).style.marginTop).toBe( + `${inset}px` + ); + expect(host.querySelector('[data-testid="chat-panel-companion"]')).toBe( + pane + ); + expect(host.querySelector("input")).toBe(input); + } + expect(mounts.chat).toBe(1); + expect(mounts.sources).toBe(1); + expect(mounts.dispose).toBe(0); + }); + it("keeps the chat DOM and draft mounted through open, repeat-open and close", async () => { + await render(); + const input = host.querySelector("input")!; + input.value = "still composing"; + await open(); + expect(host.querySelector('[data-source-session="a"]')).not.toBeNull(); + expect(host.querySelector("[data-rail]")).toBeNull(); + expect(host.querySelector("input")).toBe(input); + expect(input.value).toBe("still composing"); + await open(); + expect(mounts.sources).toBe(1); + act(() => + host + .querySelector( + '[aria-label="common:actions.close"]' + )! + .click() + ); + expect( + host.querySelector('[data-testid="chat-panel-companion"]') + ).toBeNull(); + expect(host.querySelector("[data-rail]")).not.toBeNull(); + expect(host.querySelector("input")).toBe(input); + expect(mounts.chat).toBe(1); + expect(mounts.dispose).toBe(1); + }); + it("closes on session switch and never shows the previous session on return", async () => { + await render(); + await open(); + await render({ ...chat, sessionId: "b" }); + expect(host.querySelector("[data-source-session]")).toBeNull(); + expect(store.get(chatPanelCompanionAtom)).toBeNull(); + expect(mounts.dispose).toBe(1); + await render(); + expect(host.querySelector("[data-source-session]")).toBeNull(); + }); + it("closes when switching to another tab or unmounting the shell", async () => { + await render(); + await open(); + await render({ ...chat, id: "other", type: "start-page" }); + expect(store.get(chatPanelCompanionAtom)).toBeNull(); + await render(); + await open(); + act(() => root.render(null)); + expect(store.get(chatPanelCompanionAtom)).toBeNull(); + expect(mounts.dispose).toBe(2); + }); + it("opens the same content in Station only through the explicit action", async () => { + await render(); + await open(); + act(() => + host + .querySelector( + '[aria-label="sessions:chat.moveToWorkstation"]' + )! + .click() + ); + expect(store.get(chatPanelCompanionAtom)).toBeNull(); + expect(host.querySelector("[data-source-session]")).toBeNull(); + expect(host.querySelector("input")).not.toBeNull(); + }); +}); diff --git a/src/engines/ChatPanel/ChatPanelCompanionLayout.tsx b/src/engines/ChatPanel/ChatPanelCompanionLayout.tsx new file mode 100644 index 0000000000..9d600e08ca --- /dev/null +++ b/src/engines/ChatPanel/ChatPanelCompanionLayout.tsx @@ -0,0 +1,157 @@ +import { useAtom } from "jotai"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Button from "@src/components/Button"; +import PanelHeader, { + PANEL_HEADER_TOKENS, +} from "@src/components/layout/blocks/PanelHeader"; +import { Cancel01Icon, ComputerIcon, HugeiconsIcon } from "@src/icons"; +import { VerticalResizeHandle, useColumnResize } from "@src/scaffold/Resize"; +import { chatPanelCompanionAtom } from "@src/store/chatPanel/chatPanelCompanionAtom"; +import type { ChatPanelTab } from "@src/store/chatPanel/chatPanelTabsModel"; + +import { openSessionSources } from "./sessionSources/openSessionSources"; + +const SessionSourcesContent = React.lazy(() => + import("@src/features/SessionSources/SessionSourcesContent").then( + (module) => ({ + default: module.SessionSourcesContent, + }) + ) +); + +// Preferred reading width; the CSS cap always leaves at least half for chat. +const COMPANION_WIDTH = { initial: 420, min: 240, max: 720 }; + +function SourcesCompanion({ + topInset, + sessionId, + onClose, +}: { + topInset: number; + sessionId: string; + onClose: () => void; +}) { + const { t } = useTranslation(); + const [width, setWidth] = useState(COMPANION_WIDTH.initial); + const { columnRef, handleMouseDown, isResizing } = useColumnResize({ + width, + setWidth, + min: COMPANION_WIDTH.min, + max: COMPANION_WIDTH.max, + inverted: true, + }); + const title = t("common:git.rail.sources"); + const closeLabel = t("common:actions.close"); + const stationLabel = t("sessions:chat.moveToWorkstation"); + return ( + <> +

+ +
+
+ +
+ + ); +} + +/** Keep the chat subtree stable while swapping its summary rail for companion content. */ +export function ChatPanelCompanionLayout({ + activeTab, + topInset = 0, + children, + rail, +}: React.PropsWithChildren<{ + activeTab: ChatPanelTab | null; + topInset?: number; + rail?: React.ReactNode; +}>) { + const [companion, setCompanion] = useAtom(chatPanelCompanionAtom); + const visible = + companion !== null && + activeTab?.type === "session" && + activeTab.id === companion.ownerTabId && + activeTab.sessionId === companion.sessionId; + + // Navigation closes transient content. The render gate hides stale content + // immediately, before this cleanup runs; late source reads cannot cross owners. + useEffect(() => { + if (companion && !visible) setCompanion(null); + }, [companion, visible, setCompanion]); + useEffect(() => () => setCompanion(null), [setCompanion]); + + return ( +
+
+ {children} +
+ {visible ? ( + setCompanion(null)} + /> + ) : ( + rail + )} +
+ ); +} diff --git a/src/engines/ChatPanel/ChatPanelShell.tsx b/src/engines/ChatPanel/ChatPanelShell.tsx index 874aefbd20..c982a21241 100644 --- a/src/engines/ChatPanel/ChatPanelShell.tsx +++ b/src/engines/ChatPanel/ChatPanelShell.tsx @@ -5,6 +5,7 @@ import { VerticalResizeHandle } from "@src/scaffold/Resize"; import { GUIDE_TARGETS } from "@src/scaffold/Tutorials/guideTargets"; import type { ChatPanelTab } from "@src/store/chatPanel/chatPanelTabsModel"; +import { ChatPanelCompanionLayout } from "./ChatPanelCompanionLayout"; import { UnifiedChatPanelTabContent } from "./TabContent/UnifiedChatPanelTabContent"; import { ChatPanelFullScreenContext } from "./chatPanelFullScreenContext"; @@ -17,6 +18,8 @@ interface ChatPanelShellProps { chatPanelOpacityStyle: ChatPanelShellStyle; chatWidth: number; chatWidthStyleValue: string | number; + /** Space occupied by floating pane chrome, shared with transcript and rail. */ + chromeTopInset?: number; focusedWorkstationRail?: React.ReactNode; /** The pane fills the app window; hosted tab content may use compact chrome. */ fullScreen: boolean; @@ -47,6 +50,7 @@ export function ChatPanelShell({ chatPanelOpacityStyle, chatWidth, chatWidthStyleValue, + chromeTopInset = 0, focusedWorkstationRail, fullScreen, hasTabBar, @@ -105,20 +109,21 @@ export function ChatPanelShell({ }} > {headerSection} -
-
- - - -
- {focusedWorkstationRail} -
+ + + + + {panelOverlay}
); diff --git a/src/engines/ChatPanel/index.tsx b/src/engines/ChatPanel/index.tsx index 0959991718..e385a64bb8 100644 --- a/src/engines/ChatPanel/index.tsx +++ b/src/engines/ChatPanel/index.tsx @@ -463,6 +463,7 @@ const ChatPanel: React.FC = memo( chatPanelOpacityStyle={chatPanelOpacityStyle} chatWidth={chatWidth} chatWidthStyleValue={chatWidthStyleValue} + chromeTopInset={chromeTopInsetPx} fullScreen={isChatFocus} focusedWorkstationRail={ showFocusedWorkstationControls ? ( diff --git a/src/engines/ChatPanel/sessionSources/openSessionSources.test.ts b/src/engines/ChatPanel/sessionSources/openSessionSources.test.ts index 7c7cebdfb3..8a8ef7ee6c 100644 --- a/src/engines/ChatPanel/sessionSources/openSessionSources.test.ts +++ b/src/engines/ChatPanel/sessionSources/openSessionSources.test.ts @@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { ROUTES } from "@src/config/routes"; import { EditorTabService } from "@src/services/workStation/EditorTabService"; +import { chatPanelCompanionAtom } from "@src/store/chatPanel/chatPanelCompanionAtom"; import { createSessionTab } from "@src/store/chatPanel/chatPanelTabFactories"; import { chatPanelTabsAtom } from "@src/store/chatPanel/chatPanelTabsState"; import { chatPanelMaximizedAtom } from "@src/store/ui/chatPanel/surfaceAtoms"; @@ -37,7 +38,7 @@ describe("openSessionSources", () => { Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); }); - it("opens and refocuses the real session-bound tab while exposing the workstation", () => { + it("opens and refocuses the real session-bound tab only with an explicit Station destination", () => { const store = createInstrumentedStore(); const chat = createSessionTab({ sessionId: "session-a" }); store.set(chatPanelTabsAtom, { tabs: [chat], activeTabId: chat.id }); @@ -47,7 +48,10 @@ describe("openSessionSources", () => { root.render( React.createElement( "button", - { onClick: () => openSessionSources("session-a", "Sources") }, + { + onClick: () => + openSessionSources("session-a", "Sources", "my-station"), + }, "View all" ) ) @@ -68,6 +72,36 @@ describe("openSessionSources", () => { ).toHaveLength(1); }); + it("defaults to the conversation companion without creating or activating a tab", () => { + const store = createInstrumentedStore(); + const chat = createSessionTab({ sessionId: "session-a" }); + store.set(chatPanelTabsAtom, { tabs: [chat], activeTabId: chat.id }); + store.set(stationModeAtom, "agent-station"); + store.set(chatPanelMaximizedAtom, true); + const before = EditorTabService.getTabs(); + openSessionSources("session-a", "Sources"); + const opened = store.get(chatPanelCompanionAtom); + expect(opened).toEqual({ + type: "session-sources", + sessionId: "session-a", + ownerTabId: chat.id, + }); + openSessionSources("session-a", "Sources"); + expect(store.get(chatPanelCompanionAtom)).toBe(opened); + expect(store.get(chatPanelTabsAtom).activeTabId).toBe(chat.id); + expect(EditorTabService.getTabs()).toEqual(before); + expect(store.get(stationModeAtom)).toBe("agent-station"); + expect(store.get(chatPanelMaximizedAtom)).toBe(true); + }); + + it("rejects a stale action belonging to another conversation", () => { + const store = createInstrumentedStore(); + const chat = createSessionTab({ sessionId: "session-b" }); + store.set(chatPanelTabsAtom, { tabs: [chat], activeTabId: chat.id }); + openSessionSources("session-a", "Sources"); + expect(store.get(chatPanelCompanionAtom)).toBeNull(); + }); + it("does not open a tab or reveal the workstation for an empty session reference", () => { const store = createInstrumentedStore(); store.set(stationModeAtom, "agent-station"); diff --git a/src/engines/ChatPanel/sessionSources/openSessionSources.ts b/src/engines/ChatPanel/sessionSources/openSessionSources.ts index 9b5c191c36..4d13b72c23 100644 --- a/src/engines/ChatPanel/sessionSources/openSessionSources.ts +++ b/src/engines/ChatPanel/sessionSources/openSessionSources.ts @@ -1,10 +1,20 @@ import { EditorTabService } from "@src/services/workStation/EditorTabService"; +import { openChatPanelSourcesAtom } from "@src/store/chatPanel/chatPanelCompanionAtom"; import { createSessionSourcesTab } from "@src/store/workstation/tabs/factories/sessionSources"; +import { getInstrumentedStore } from "@src/util/core/state/instrumentedStore"; import { revealMyStation } from "@src/util/ui/revealMyStation"; -/** Repeated activation focuses the same session-bound tab through the tab owner. */ -export function openSessionSources(sessionId: string, title: string): void { +/** Sources open beside the conversation unless the user explicitly chooses Station. */ +export function openSessionSources( + sessionId: string, + title: string, + destination: "chat-panel" | "my-station" = "chat-panel" +): void { if (!sessionId.trim()) return; + if (destination === "chat-panel") { + getInstrumentedStore().set(openChatPanelSourcesAtom, sessionId); + return; + } EditorTabService.openTab(createSessionSourcesTab(sessionId, title)); revealMyStation(); } diff --git a/src/features/SessionSources/SessionSourceCategory.tsx b/src/features/SessionSources/SessionSourceCategory.tsx new file mode 100644 index 0000000000..ff065df0a0 --- /dev/null +++ b/src/features/SessionSources/SessionSourceCategory.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Button from "@src/components/Button"; +import { SidebarSectionHeader } from "@src/components/SidebarSectionHeader"; +import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; +import { useCollapsible } from "@src/hooks/ui/useCollapsible"; + +import { SessionSourceRow } from "./SessionSourceRow"; +import { SessionToolActivityGroup } from "./SessionToolActivityGroup"; + +const PAGE_SIZE = 30; + +/** Category-local intent survives refresh and collapse; the session-keyed parent + * owns remount/reset. Collapsed rows unmount so thumbnails do no hidden work. */ +export function SessionSourceCategory({ + kind, + title, + items, + onOpenSource, +}: { + kind: SessionSource["kind"]; + title: string; + items: SessionSource[]; + onOpenSource: (source: SessionSource) => void; +}) { + const { t } = useTranslation(); + const { isOpen, toggle } = useCollapsible(); + const [visibleCount, setVisibleCount] = useState(PAGE_SIZE); + return ( +
+ {items.length} + } + /> + {isOpen ? ( + <> +
    + {items.slice(0, visibleCount).map((source) => ( +
  • + {source.kind === "tool-group" ? ( + + ) : ( + + )} +
  • + ))} +
+ {items.length > visibleCount ? ( +
+ +
+ ) : null} + + ) : null} +
+ ); +} diff --git a/src/features/SessionSources/SessionSourcesContent.tsx b/src/features/SessionSources/SessionSourcesContent.tsx new file mode 100644 index 0000000000..aefde94bdd --- /dev/null +++ b/src/features/SessionSources/SessionSourcesContent.tsx @@ -0,0 +1,32 @@ +import { useAtomValue } from "jotai"; +import React from "react"; + +import { SessionSourcesFileScope } from "@src/engines/ChatPanel/sessionSources/SessionSourcesFileScope"; +import { useSessionSourcesState } from "@src/engines/ChatPanel/sessionSources/useSessionSources"; +import { sessionByIdAtom } from "@src/store/session"; + +import { SessionSourcesView } from "./SessionSourcesView"; + +export function SessionSourcesContent({ sessionId }: { sessionId: string }) { + const session = useAtomValue(sessionByIdAtom(sessionId)); + const { sources, loading, error, retry } = useSessionSourcesState( + sessionId, + session?.updated_at + ); + const basePath = + session?.repoRootPath ?? + (session?.importedFrom + ? undefined + : (session?.worktreePath ?? session?.repoPath)); + return ( + + + + ); +} diff --git a/src/features/SessionSources/SessionSourcesView.test.ts b/src/features/SessionSources/SessionSourcesView.test.ts index e473f2f9b5..a819aa5375 100644 --- a/src/features/SessionSources/SessionSourcesView.test.ts +++ b/src/features/SessionSources/SessionSourcesView.test.ts @@ -176,6 +176,86 @@ describe("SessionSourcesView", () => { container.querySelector('[data-source-category="image"]') ).toBeNull(); }); + it.each(["image", "file", "link", "tool-group"] as const)( + "collapses %s independently while retaining its header and count", + async (kind) => { + const mixed: SessionSource[] = [ + sources[0], + { + kind: "file", + key: "file", + path: "/tmp/report.md", + fileName: "report.md", + isDirectory: false, + }, + { + kind: "link", + key: "link", + url: "https://example.com", + label: "Reference", + }, + { kind: "tool-group", key: "tool", group: "exec", operations: [] }, + ]; + await render({ sources: mixed }); + const category = container.querySelector( + `[data-source-category="${kind}"]` + )!; + const header = category.querySelector( + "button[aria-expanded]" + )!; + expect(header.getAttribute("aria-expanded")).toBe("true"); + expect(header.textContent).toContain("1"); + act(() => { + header.focus(); + header.click(); + }); + expect(header.getAttribute("aria-expanded")).toBe("false"); + expect(document.activeElement).toBe(header); + expect(category.querySelector("ul")).toBeNull(); + for (const other of container.querySelectorAll( + "[data-source-category]" + )) { + if (other !== category) + expect(other.querySelector("ul")).not.toBeNull(); + } + act(() => header.click()); + expect(header.getAttribute("aria-expanded")).toBe("true"); + expect(category.querySelector("ul")).not.toBeNull(); + expect(navigation.openSource).not.toHaveBeenCalled(); + } + ); + it("retains collapse and pagination across refresh and retry, resetting for a new session", async () => { + await render(); + click("loadMoreSources"); + click("sourceCategoryImages"); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(0); + expect(container.textContent).not.toContain("loadMoreSources"); + const updated = [ + ...sources, + { ...sources[0], key: "new-image", fileName: "new.png" }, + ]; + await render({ sources: updated, error: true }); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(0); + expect( + container.querySelector("button[aria-expanded]")?.textContent + ).toContain("66"); + click("actions.retry"); + expect(retry).toHaveBeenCalledOnce(); + await render({ sources: updated, loading: true }); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(0); + click("sourceCategoryImages"); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(60); + click("shot-2.png"); + expect(navigation.openSource).toHaveBeenCalledWith(sources[2]); + click("sourceCategoryImages"); + await render({}, "session-b"); + expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(30); + expect( + container + .querySelector("button[aria-expanded]") + ?.getAttribute("aria-expanded") + ).toBe("true"); + }); it("preserves successful rows during failed refresh and offers retry", async () => { await render({ error: true }); expect(container.querySelector('[role="alert"]')?.textContent).toContain( diff --git a/src/features/SessionSources/SessionSourcesView.tsx b/src/features/SessionSources/SessionSourcesView.tsx index 41e13ce599..7a046d6cb3 100644 --- a/src/features/SessionSources/SessionSourcesView.tsx +++ b/src/features/SessionSources/SessionSourcesView.tsx @@ -1,19 +1,15 @@ -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import Button from "@src/components/Button"; import PageNotice from "@src/components/PageNotice"; -import { SidebarSectionHeader } from "@src/components/SidebarSectionHeader"; import { ListPanelScrollArea } from "@src/components/layout/blocks"; import { EDITOR_TAB_CANVAS_BG_CLASS } from "@src/config/workstation/tokens"; import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; +import { SessionSourceCategory } from "./SessionSourceCategory"; import { SessionSourceImagePreview } from "./SessionSourceImagePreview"; -import { SessionSourceRow } from "./SessionSourceRow"; -import { SessionToolActivityGroup } from "./SessionToolActivityGroup"; import { useSessionSourceNavigation } from "./useSessionSourceNavigation"; -const PAGE_SIZE = 30; const CATEGORIES = [ { kind: "image", label: "sourceCategoryImages" }, { kind: "file", label: "sourceCategoryFiles" }, @@ -36,9 +32,6 @@ export function SessionSourcesView({ onRetry: () => void; }) { const { t } = useTranslation(); - const [visibleCounts, setVisibleCounts] = useState< - Partial> - >({}); const categories = useMemo( () => CATEGORIES.map((category) => ({ @@ -84,61 +77,15 @@ export function SessionSourcesView({

) : (
- {categories.map(({ kind, label, items }) => { - const visibleCount = visibleCounts[kind] ?? PAGE_SIZE; - return ( -
- - {items.length} - - } - /> -
    - {items.slice(0, visibleCount).map((source) => ( -
  • - {source.kind === "tool-group" ? ( - - ) : ( - - )} -
  • - ))} -
- {items.length > visibleCount ? ( -
- -
- ) : null} -
- ); - })} + {categories.map(({ kind, label, items }) => ( + + ))}
)} diff --git a/src/modules/WorkStation/TabContent/renderers/sessionSources.tsx b/src/modules/WorkStation/TabContent/renderers/sessionSources.tsx index 909d939b37..6f241e732f 100644 --- a/src/modules/WorkStation/TabContent/renderers/sessionSources.tsx +++ b/src/modules/WorkStation/TabContent/renderers/sessionSources.tsx @@ -1,37 +1,9 @@ -import { useAtomValue } from "jotai"; import React from "react"; -import { SessionSourcesFileScope } from "@src/engines/ChatPanel/sessionSources/SessionSourcesFileScope"; -import { useSessionSourcesState } from "@src/engines/ChatPanel/sessionSources/useSessionSources"; -import { SessionSourcesView } from "@src/features/SessionSources/SessionSourcesView"; -import { sessionByIdAtom } from "@src/store/session"; +import { SessionSourcesContent } from "@src/features/SessionSources/SessionSourcesContent"; import type { UnifiedTabContentProps } from "../types"; -function SessionSourcesContent({ sessionId }: { sessionId: string }) { - const session = useAtomValue(sessionByIdAtom(sessionId)); - const { sources, loading, error, retry } = useSessionSourcesState( - sessionId, - session?.updated_at - ); - const basePath = - session?.repoRootPath ?? - (session?.importedFrom - ? undefined - : (session?.worktreePath ?? session?.repoPath)); - return ( - - - - ); -} - export default function SessionSourcesTabRenderer({ tab, isActive, diff --git a/src/scaffold/Resize/hooks/useColumnResize.test.ts b/src/scaffold/Resize/hooks/useColumnResize.test.ts new file mode 100644 index 0000000000..5168d47e4b --- /dev/null +++ b/src/scaffold/Resize/hooks/useColumnResize.test.ts @@ -0,0 +1,53 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { expect, it, vi } from "vitest"; + +import { useColumnResize } from "./useColumnResize"; + +it("resizes from the CSS-constrained width after a viewport shrink", () => { + Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true); + const host = document.createElement("div"); + document.body.append(host); + const root = createRoot(host); + const commit = vi.fn(); + function Pane() { + const { columnRef, handleMouseDown } = useColumnResize({ + width: 420, + setWidth: commit, + min: 200, + max: 720, + inverted: true, + }); + return React.createElement("div", { + ref: columnRef, + onMouseDown: handleMouseDown, + style: { width: 420, maxWidth: "50%" }, + }); + } + try { + act(() => root.render(React.createElement(Pane))); + const column = host.firstElementChild as HTMLDivElement; + Object.defineProperty(column, "offsetWidth", { value: 300 }); + act(() => + column.dispatchEvent( + new MouseEvent("mousedown", { bubbles: true, clientX: 300, button: 0 }) + ) + ); + act(() => + document.dispatchEvent( + new MouseEvent("mousemove", { bubbles: true, clientX: 320, buttons: 1 }) + ) + ); + act(() => + window.dispatchEvent( + new MouseEvent("mouseup", { bubbles: true, clientX: 320 }) + ) + ); + expect(commit).toHaveBeenCalledWith(280); + } finally { + act(() => root.unmount()); + host.remove(); + Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT"); + } +}); diff --git a/src/scaffold/Resize/hooks/useColumnResize.ts b/src/scaffold/Resize/hooks/useColumnResize.ts index 25cdcfaf14..d2da464599 100644 --- a/src/scaffold/Resize/hooks/useColumnResize.ts +++ b/src/scaffold/Resize/hooks/useColumnResize.ts @@ -76,7 +76,9 @@ export function useColumnResize({ setIsResizing(true); const startX = event.clientX; - const startWidth = width; + // CSS may cap a right pane after a viewport change. Start from its + // rendered layout width so the first drag does not jump or have a dead zone. + const startWidth = columnRef.current?.offsetWidth || width; pendingWidthRef.current = startWidth; const handleMouseMove = (moveEvent: MouseEvent) => { diff --git a/src/store/chatPanel/chatPanelCompanionAtom.ts b/src/store/chatPanel/chatPanelCompanionAtom.ts new file mode 100644 index 0000000000..0d7c3afa8a --- /dev/null +++ b/src/store/chatPanel/chatPanelCompanionAtom.ts @@ -0,0 +1,30 @@ +import { atom } from "jotai"; + +import { chatPanelTabsAtom } from "./chatPanelTabsState"; + +/** Transient, single-slot content beside a conversation; never a Station tab. */ +export type ChatPanelCompanion = { + type: "session-sources"; + sessionId: string; + ownerTabId: string; +}; + +export const chatPanelCompanionAtom = atom(null); + +/** Only the active conversation may open its companion. Repeated opens are inert. */ +export const openChatPanelSourcesAtom = atom( + null, + (get, set, sessionId: string) => { + const id = sessionId.trim(); + const tabs = get(chatPanelTabsAtom); + const owner = tabs.tabs.find((tab) => tab.id === tabs.activeTabId); + if (!id || owner?.type !== "session" || owner.sessionId !== id) return; + const current = get(chatPanelCompanionAtom); + if (current?.ownerTabId === owner.id && current.sessionId === id) return; + set(chatPanelCompanionAtom, { + type: "session-sources", + sessionId: id, + ownerTabId: owner.id, + }); + } +); From c95c85a3e0bc7c57bb8909bbcc5fa607df940f72 Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Tue, 29 Sep 2026 14:33:50 +0800 Subject: [PATCH 6/7] feat(sources): prioritize user-provided references --- .../SessionSources/SessionSourcesView.test.ts | 84 +++++++++++++++++++ .../SessionSources/SessionSourcesView.tsx | 39 ++++++++- .../SessionSources/sortSessionSources.test.ts | 52 ++++++++++++ .../SessionSources/sortSessionSources.ts | 28 +++++++ src/i18n/locales/de/common.json | 6 ++ src/i18n/locales/en/common.json | 6 ++ src/i18n/locales/es/common.json | 6 ++ src/i18n/locales/fr/common.json | 6 ++ src/i18n/locales/hi/common.json | 6 ++ src/i18n/locales/id/common.json | 6 ++ src/i18n/locales/ja/common.json | 6 ++ src/i18n/locales/ko/common.json | 6 ++ src/i18n/locales/pl/common.json | 6 ++ src/i18n/locales/pt/common.json | 6 ++ src/i18n/locales/ru/common.json | 6 ++ src/i18n/locales/tr/common.json | 6 ++ src/i18n/locales/vi/common.json | 6 ++ src/i18n/locales/zh-Hant/common.json | 6 ++ src/i18n/locales/zh/common.json | 6 ++ 19 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 src/features/SessionSources/sortSessionSources.test.ts create mode 100644 src/features/SessionSources/sortSessionSources.ts diff --git a/src/features/SessionSources/SessionSourcesView.test.ts b/src/features/SessionSources/SessionSourcesView.test.ts index a819aa5375..8b3269b2e0 100644 --- a/src/features/SessionSources/SessionSourcesView.test.ts +++ b/src/features/SessionSources/SessionSourcesView.test.ts @@ -22,6 +22,35 @@ vi.mock("./SessionSourceThumbnail", () => ({ vi.mock("./SessionSourceImagePreview", () => ({ SessionSourceImagePreview: () => null, })); +vi.mock("@src/components/Select", () => ({ + default: ({ + value, + onChange, + options, + ariaLabel, + }: { + value: string; + onChange: (value: string) => void; + options: { value: string; label: string }[]; + ariaLabel: string; + }) => + React.createElement( + "select", + { + value, + "aria-label": ariaLabel, + onChange: (event: React.ChangeEvent) => + onChange(event.target.value), + }, + options.map((option) => + React.createElement( + "option", + { key: option.value, value: option.value }, + option.label + ) + ) + ), +})); vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (key: string) => key }), })); @@ -71,6 +100,61 @@ afterEach(() => { }); describe("SessionSourcesView", () => { + it("sorts provided sources first within each category and resets for another session", async () => { + const mixed: SessionSource[] = [ + { + kind: "file", + key: "assistant-file", + path: "/tmp/new.md", + fileName: "new.md", + isDirectory: false, + origin: "assistant-reference", + }, + { + kind: "file", + key: "provided-file", + path: "/tmp/old.md", + fileName: "old.md", + isDirectory: false, + origin: "assistant-reference", + origins: ["assistant-reference", "provided-file"], + }, + { + kind: "link", + key: "assistant-link", + url: "https://example.com/new", + label: "New link", + origin: "assistant-reference", + }, + { + kind: "link", + key: "provided-link", + url: "https://example.com/old", + label: "Old link", + origin: "provided-link", + }, + ]; + await render({ sources: mixed }); + const fileRows = () => + [...container.querySelectorAll('[data-source-category="file"] li')].map( + (row) => row.textContent + ); + expect(fileRows()[0]).toContain("new.md"); + const select = container.querySelector( + 'select[aria-label="common:git.rail.sourceSort"]' + )! as HTMLSelectElement; + await act(async () => { + select.value = "provided-first"; + select.dispatchEvent(new Event("change", { bubbles: true })); + }); + expect(fileRows()[0]).toContain("old.md"); + expect( + container.querySelector('[data-source-category="link"] li')?.textContent + ).toContain("Old link"); + expect(mixed[0].key).toBe("assistant-file"); + await render({ sources: mixed }, "session-b"); + expect(fileRows()[0]).toContain("new.md"); + }); it("bounds image reads, opens source rows and reveals further pages on request", async () => { await render(); expect(container.querySelectorAll("[data-thumbnail]")).toHaveLength(30); diff --git a/src/features/SessionSources/SessionSourcesView.tsx b/src/features/SessionSources/SessionSourcesView.tsx index 7a046d6cb3..06e97a4ade 100644 --- a/src/features/SessionSources/SessionSourcesView.tsx +++ b/src/features/SessionSources/SessionSourcesView.tsx @@ -1,13 +1,19 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import PageNotice from "@src/components/PageNotice"; +import Select from "@src/components/Select"; import { ListPanelScrollArea } from "@src/components/layout/blocks"; import { EDITOR_TAB_CANVAS_BG_CLASS } from "@src/config/workstation/tokens"; import type { SessionSource } from "@src/engines/ChatPanel/sessionSources/extractSessionSources"; import { SessionSourceCategory } from "./SessionSourceCategory"; import { SessionSourceImagePreview } from "./SessionSourceImagePreview"; +import { + SOURCE_SORTS, + type SourceSort, + sortSessionSources, +} from "./sortSessionSources"; import { useSessionSourceNavigation } from "./useSessionSourceNavigation"; const CATEGORIES = [ @@ -32,16 +38,21 @@ export function SessionSourcesView({ onRetry: () => void; }) { const { t } = useTranslation(); + const [sort, setSort] = useState("newest"); + const orderedSources = useMemo( + () => sortSessionSources(sources, sort), + [sources, sort] + ); const categories = useMemo( () => CATEGORIES.map((category) => ({ ...category, - items: sources.filter((source) => source.kind === category.kind), + items: orderedSources.filter((source) => source.kind === category.kind), })).filter((category) => category.items.length > 0), - [sources] + [orderedSources] ); const { openSource, imagePreview, closeImagePreview } = - useSessionSourceNavigation(sources, basePath); + useSessionSourceNavigation(orderedSources, basePath); return (
+ {sources.length > 0 ? ( +
+