From c3f8f4100c3946e71de35ee85a2c7fb5292ac97c Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:45:03 -0800 Subject: [PATCH] refactor(native-chat): organize launch, transcript, and conversation owners (#41643) --- .github/workflows/mobile.yml | 12 ++ .../workflows/zcode-transcript-contract.yml | 5 +- .../build-plugins/main-build-entry-points.ts | 2 +- config/knip.json | 2 +- config/main-blocking-sqlite-baseline.txt | 6 +- config/main-blocking-worker-entries.json | 2 +- config/main-sync-fs-baseline.txt | 4 +- .../process-host-direct-import-baseline.txt | 2 +- .../scripts/ci/sharding/ci-shard-timings.json | 40 ++-- .../transcript-stream-fragments-benchmark.mjs | 2 +- ...sconfig.test.mobile-host-declarations.json | 2 +- .../acp-conversation-copy.ts | 2 +- .../discovery/acp-at-rest-commands.test.ts | 25 +-- .../acp/discovery/acp-at-rest-commands.ts | 4 +- .../discovery/acp-listing-session-models.ts | 2 +- .../acp/discovery/acp-model-catalog-probe.ts | 2 +- .../acp-command-discovery.test-fixture.ts | 2 +- src/main/acp/launch/acp-launch-command.ts | 2 +- src/main/acp/launch/acp-launch-contract.ts | 2 +- .../acp-structured-launch-resolution.ts | 6 +- .../launch/acp-structured-launch-visuals.ts | 2 +- src/main/acp/launch/acp-visuals-skill.ts | 2 +- .../session-search-directory-listings.ts | 2 +- .../ai-vault/discovery/session-json-file.ts | 4 +- .../discovery/session-path-absence.ts | 2 +- .../discovery/session-scanner-discovery.ts | 7 +- .../ai-vault/parsing/session-scan-issues.ts | 2 +- ...sion-scanner-core-parser-wsl-stall.test.ts | 6 +- .../session-scanner-jsonl-reader.test.ts | 2 +- .../parsing/session-scanner-jsonl-reader.ts | 4 +- .../session-scanner-parse-wsl-stall.test.ts | 8 +- .../prompts/session-first-user-prompt-read.ts | 2 +- .../antigravity-transcript-candidates.ts | 4 +- .../session-scanner-antigravity-history.ts | 4 +- .../session-scanner-claude-subagents.ts | 4 +- .../claude/session-scanner-primary-parsers.ts | 2 +- .../session-scanner-subagent-transcripts.ts | 2 +- .../cline/session-scanner-cline-parser.ts | 4 +- .../session-scanner-codebuddy-parser.ts | 2 +- .../codex/session-scanner-codex-index-read.ts | 2 +- .../codex/session-scanner-codex-parser.ts | 4 +- .../session-scanner-codex-title-index.ts | 2 +- .../copilot/session-scanner-copilot-parser.ts | 2 +- .../session-scanner-cursor-chat-meta.test.ts | 89 ++++---- .../session-scanner-cursor-chat-meta.ts | 7 +- .../cursor/session-scanner-cursor-parser.ts | 2 +- .../devin/session-scanner-devin-db.ts | 4 +- .../devin/session-scanner-devin-parser.ts | 2 +- .../droid/session-scanner-droid-parser.ts | 2 +- .../gemini/session-scanner-gemini-parsers.ts | 2 +- .../grok/session-scanner-grok-parser.ts | 4 +- .../hermes/session-scanner-hermes-parser.ts | 2 +- .../jcode/session-scanner-jcode-parser.ts | 2 +- .../kimi/session-scanner-kimi-parser.ts | 4 +- .../kimi/session-scanner-kimi-paths.ts | 4 +- .../kiro/session-scanner-kiro-parser.ts | 4 +- .../muse/session-scanner-muse-parser.ts | 2 +- .../session-scanner-omp-subagent-listing.ts | 7 +- ...ession-scanner-omp-subagent-transcripts.ts | 2 +- .../opencode-wsl-runtime-preparation.test.ts | 34 +-- .../opencode-wsl-runtime-preparation.ts | 2 +- ...ion-scanner-opencode-fork-coverage.test.ts | 42 ++++ ...ession-scanner-opencode-sqlite-dispatch.ts | 4 +- ...scanner-opencode-sqlite-worker-protocol.ts | 2 +- .../session-scanner-opencode-wsl-client.ts | 2 +- .../transcript-opencode2-fork.test-fixture.ts | 50 +++++ .../session-scanner-opencode-parser.ts | 7 +- ...scanner-opencode-sources-wsl-stall.test.ts | 2 +- .../session-scanner-opencode-sources.ts | 2 +- .../qoder/session-scanner-qoder-parser.ts | 2 +- .../scanners/session-scanner-graph-parsers.ts | 2 +- .../zcode/session-scanner-zcode-sources.ts | 2 +- .../scope/session-scanner-scope-discovery.ts | 2 +- .../session-scanner-scope-inventory.test.ts | 2 +- .../scope/session-scanner-scope-inventory.ts | 2 +- src/main/ai-vault/scope/session-scope-cwd.ts | 4 +- src/main/ai-vault/session-handoff-snapshot.ts | 8 +- ...ession-title-file-reader-wsl-stall.test.ts | 2 +- .../session-title-file-reader.ts | 2 +- .../session-title-request-paths.ts | 4 +- .../structured-session-ownership.ts | 2 +- .../remote-session-content-lines.ts | 2 +- .../transcripts/session-transcript-reader.ts | 4 +- .../antigravity-acp-launch-spec.ts | 2 +- .../claude-child-process-environment.ts | 2 +- src/main/claude/claude-cli-conversations.ts | 2 +- .../claude-naming-invocation.ts | 2 +- .../claude-model-catalog-probe.test.ts | 2 +- .../claude-model-catalog-probe.ts | 2 +- .../claude-structured-child-env.test.ts | 2 +- .../launch/claude-structured-child-env.ts | 2 +- .../launch/claude-structured-launch-args.ts | 2 +- .../launch/claude-structured-launch-flags.ts | 2 +- ...laude-structured-launch-resolution.test.ts | 81 ------- .../claude-structured-launch-resolution.ts | 4 +- .../transcripts/claude-conversation-copy.ts | 2 +- .../claude-conversation-fork-boundary.ts | 4 +- .../claude-conversation-fork-worker-entry.ts | 2 +- .../claude-provider-session-rename.test.ts | 2 +- .../claude-provider-session-rename.ts | 5 +- .../claude/transcripts/claude-rewind-fork.ts | 2 +- .../transcripts/claude-rewind-transcript.ts | 2 +- .../transcripts/claude-transcript-prompt.ts | 2 +- .../codex/codex-conversation-copy.test.ts | 23 +- src/main/codex/codex-conversation-copy.ts | 2 +- src/main/codex/codex-fork-history.ts | 6 +- .../journal-views/codex-cli-conversations.ts | 2 +- .../rollouts/codex-rollout-session-meta.ts | 6 +- .../catalog/codex-at-rest-skills-probe.ts | 2 +- .../catalog/codex-model-catalog-probe.ts | 2 +- .../catalog/codex-structured-model-catalog.ts | 2 +- .../launch/codex-cli-installation-error.ts | 2 +- ...codex-structured-child-environment.test.ts | 2 +- .../codex-structured-launch-args.test.ts | 2 +- .../launch/codex-structured-launch-args.ts | 2 +- ...codex-structured-launch-resolution.test.ts | 2 +- .../codex-structured-launch-resolution.ts | 4 +- .../codex-structured-real-cli-visuals.test.ts | 2 +- .../launch/codex-structured-visuals.test.ts | 2 +- .../launch/codex-structured-visuals.ts | 2 +- .../codex-provider-session-rename.ts | 2 +- .../codex-structured-session-start.ts | 2 +- .../codex-structured-session-state.ts | 2 +- src/main/copilot/copilot-acp-launch-spec.ts | 2 +- src/main/grok/grok-session-history.ts | 2 +- src/main/grok/grok-transcript-import.ts | 2 +- .../native-chat-visual-guest-theme.test.ts | 63 +----- .../host/native-chat-visual-guest-theme.ts | 4 +- src/main/ipc/ai-vault/ai-vault-resume.test.ts | 2 +- src/main/ipc/ai-vault/ai-vault-resume.ts | 2 +- src/main/ipc/native-chat.ts | 16 +- .../pty/host-env/saved-resume-admission.ts | 2 +- ...openclaude-0.31.0-local-command-rows.jsonl | 13 -- .../agent-cli-installation-error.ts | 12 +- .../structured-agent-arguments-error.ts | 4 +- ...tructured-agent-command-resolution.test.ts | 0 .../structured-agent-command-resolution.ts | 19 +- .../structured-agent-configured-args.test.ts | 0 .../structured-agent-configured-args.ts | 12 +- ...structured-agent-session-create-support.ts | 6 +- ...model-catalog-account-list-test-support.ts | 2 +- ...-model-catalog-discovery-freshness.test.ts | 5 +- .../agent-model-catalog-discovery.ts | 2 +- .../agent-model-catalog-lister.ts | 2 +- .../agent-model-catalog-scope.ts | 2 +- ...agent-model-catalog-shared-listing.test.ts | 57 +---- .../agent-model-catalog-shared-setup.test.ts | 136 +----------- .../adopted-transcript-source.ts | 2 +- .../history-import/journal-codex-history.ts | 2 +- .../history-import/journal-legacy-import.ts | 4 +- .../journal-legacy-transcript-stream.ts | 15 +- .../journal-pi-active-branch.ts | 2 +- ...red-agent-session-failed-create-refusal.ts | 4 +- .../structured-agent-session-fork-intent.ts | 2 +- .../structured-agent-session-fork-source.ts | 2 +- .../structured-agent-session-fork.ts | 2 +- ...uctured-agent-session-fork.test-fixture.ts | 2 +- .../structured-agent-session-fork.test.ts | 2 +- .../structured-provider-session-ownership.ts | 2 +- ...tured-agent-session-adopted-import.test.ts | 3 +- ...ent-session-create-adoption-budget.test.ts | 3 +- ...n-create-adoption-contract-test-fixture.ts | 3 +- ...ured-agent-session-create-adoption.test.ts | 167 --------------- ...structured-agent-session-host-test-data.ts | 2 +- ...ured-agent-session-provider-naming.test.ts | 81 ------- ...ured-agent-session-continuation-context.ts | 4 +- .../structured-agent-session-agent-start.ts | 2 +- .../structured-agent-session-failure-text.ts | 4 +- .../structured-agent-session-start-failure.ts | 4 +- .../agent-conversation-copy-error.ts | 4 +- .../codex-adoption-preparation.ts | 10 +- .../structured-agent-cli-conversations.ts | 12 +- .../structured-agent-cli-source.ts | 16 +- ...ructured-agent-session-history-adoption.ts | 16 +- .../structured-conversation-key.ts | 12 +- .../agent-session-context-snapshots.test.ts | 0 .../agent-session-context-snapshots.ts | 6 +- .../agent-session-saved-prompt-text.ts | 14 +- .../native-chat-media-path.test.ts | 12 +- .../native-chat-media-path.ts | 28 +-- ...tive-chat-scratch-folder-migration.test.ts | 2 +- .../native-chat-scratch-folder-migration.ts | 4 +- .../native-chat-scratch-folder.ts | 0 .../native-chat-visual-file-read.test.ts | 0 .../native-chat-visual-file-read.ts | 8 +- .../native-chat-visual-guest-location.ts | 6 +- .../native-chat-visuals-delivery.ts | 2 +- .../native-chat-visuals-skill-location.ts | 2 +- .../native-chat-visuals-sweep.test.ts | 2 +- .../native-chat-visuals-sweep.ts | 6 +- .../structured-chat-name-publication.ts | 4 +- .../structured-chat-naming-types.ts | 14 +- .../structured-chat-naming-work.ts | 4 +- .../structured-chat-title-handover.ts | 8 +- ...ession-file-resolver-wsl-scan-gate.test.ts | 22 +- src/main/native-chat/session-file-resolver.ts | 17 +- .../structured-chat-naming-title.test.ts | 97 --------- .../native-chat/structured-chat-naming.ts | 11 +- .../agent-session-record-test-fixture.ts | 4 +- .../transcript-claude-read-image.test.ts | 72 ------- ...line-decoders-claude-control-bytes.test.ts | 12 +- ...ne-decoders-claude-image-companion.test.ts | 58 +++++ ...line-decoders-claude-queued-prompt.test.ts | 0 .../transcript-line-decoders-claude.ts | 24 +-- .../transcript-line-decoders-codex.ts | 12 +- .../transcript-line-decoders-grok.ts | 15 +- .../transcript-line-decoders-omp.ts | 14 +- .../transcript-line-decoders.grok.test.ts | 2 +- .../transcript-line-decoders.omp.test.ts | 2 +- .../transcript-record-blocks.ts | 8 +- .../transcript-turn-lifecycle.test.ts | 0 .../transcript-turn-lifecycle.ts | 18 +- .../transcript-turn-markers.ts | 2 +- .../host-readable-transcript-path.test.ts | 14 +- .../host-readable-transcript-path.ts | 20 +- .../native-chat-open-wake.ts | 16 +- .../session-file-id-layouts.ts | 6 +- .../session-file-resolver-claude-probe.ts | 2 +- .../transcript-path-admission.ts | 7 +- .../wsl-session-path-scan.ts | 4 +- .../wsl-transcript-fs-process-client.ts | 4 +- .../wsl-transcript-fs-process-decode.ts | 0 .../wsl-transcript-fs-process-dispatch.ts | 8 +- .../wsl-transcript-fs-process-entry.ts | 0 .../wsl-transcript-fs-process-handle-owner.ts | 2 +- .../wsl-transcript-fs-process-lane-pool.ts | 2 +- ...l-transcript-fs-process-operations.test.ts | 0 .../wsl-transcript-fs-process-operations.ts | 0 .../wsl-transcript-fs-process-protocol.ts | 0 .../wsl-transcript-fs-process-send.ts | 0 .../wsl-transcript-fs-process-slot.ts | 0 .../wsl-transcript-fs-process-spawn.ts | 2 +- .../ssh-transcript-fs-access.ts | 2 +- .../ssh-transcript-path.ts | 4 +- .../wsl-transcript-fs-access.ts | 12 +- .../wsl-transcript-fs-error.ts | 2 +- .../wsl-transcript-fs-gate.ts | 8 - .../wsl-transcript-fs-route-quarantine.ts | 0 .../wsl-transcript-fs-route.ts | 2 +- .../wsl-transcript-running-admission.test.ts | 6 +- .../wsl-transcript-running-admission.ts | 2 +- .../wsl-transcript-running-observer.ts | 4 +- .../wsl-transcript-watcher-running-guard.ts | 6 +- .../codex}/codex-local-history.ts | 14 +- .../codex}/codex-rollout-history-source.ts | 4 +- .../codex}/codex-rollout-history-stream.ts | 0 .../opencode}/transcript-opencode-database.ts | 14 +- .../transcript-opencode-fingerprint-window.ts | 0 .../transcript-opencode-part-blocks.ts | 10 +- .../transcript-opencode-row-budget.ts | 2 +- .../transcript-opencode-sqlite-query.test.ts | 67 +----- .../transcript-opencode-sqlite-query.ts | 20 +- .../transcript-opencode-subscribe.ts | 8 +- .../opencode}/transcript-opencode.ts | 16 +- .../transcript-opencode2-fork-coverage.ts | 8 +- .../opencode}/transcript-opencode2-sqlite.ts | 14 +- .../opencode}/transcript-zcode.test.ts | 2 +- .../transcript-interruption-message.test.ts | 52 ----- ...ine-decoders-claude-command-output.test.ts | 59 ----- ...ne-decoders-claude-image-companion.test.ts | 108 ---------- ...ipt-line-decoders-claude-stop-hook.test.ts | 65 ------ .../native-chat/transcript-line-decoders.ts | 6 - .../transcript-opencode2-fork.test-fixture.ts | 85 -------- .../transcript-opencode2-sqlite.test.ts | 119 ----------- src/main/native-chat/transcript-read-cache.ts | 6 +- src/main/native-chat/transcript-reader.ts | 18 +- .../transcript-fallback-id.ts | 0 .../transcript-file-order.ts | 2 +- .../transcript-stream-lines.test.ts | 0 .../transcript-stream-lines.ts | 2 +- .../transcript-zstd-decompression.test.ts | 0 .../transcript-zstd-decompression.ts | 0 .../native-chat/transcript-tail-reader.ts | 26 ++- ...nscript-watch-engine-wsl-lifecycle.test.ts | 150 ------------- .../transcript-watch-unflushed-settle.test.ts | 111 ---------- src/main/native-chat/transcript-watch.ts | 24 +-- .../transcript-file-version.test.ts | 0 .../transcript-file-version.ts | 5 +- .../transcript-incremental-reader.ts | 13 +- .../transcript-tail-boundary.ts | 4 +- .../transcript-unavailable-snapshot.ts | 0 .../transcript-watch-contract.ts | 6 +- .../transcript-watch-engine.ts | 12 +- .../transcript-watch-scheduler.ts | 0 .../transcript-watcher-count.ts | 0 .../transcript-watcher-install-probe.ts | 6 +- ...transcript-window-tool-attribution.test.ts | 178 --------------- .../wsl-transcript-running-observer.test.ts | 85 -------- src/main/omp/omp-session-cwd-lookup.ts | 2 +- src/main/omp/omp-session-history.ts | 2 +- src/main/omp/omp-transcript-import.ts | 2 +- .../opencode-database-discovery.ts | 7 +- .../transcripts/opencode-session-history.ts | 2 +- .../transcripts/opencode-transcript-import.ts | 4 +- .../pi/rpc/launch/rpc-launch-resolution.ts | 4 +- src/main/pi/rpc/launch/rpc-launch.ts | 2 +- .../pi/rpc/options/rpc-model-catalog-probe.ts | 2 +- src/main/pi/rpc/rpc-session-rename.test.ts | 2 +- src/main/pi/transcripts/pi-session-history.ts | 2 +- .../pi/transcripts/pi-transcript-import.ts | 2 +- .../runtime-structured-agent-host.ts | 4 +- .../structured-agent-runtime-metadata.ts | 2 +- ...ctured-agent-runtime-registration-types.ts | 4 +- .../structured-agent-runtime-registrations.ts | 2 +- .../structured-agent-session-host-assembly.ts | 2 +- ...ructured-agent-session-runtime-contract.ts | 2 +- ...d-agent-launch-composition.test-support.ts | 4 +- ...tructured-agent-launch-composition.test.ts | 102 --------- .../structured-agent-launch-composition.ts | 4 +- .../launch/structured-agent-launch-support.ts | 4 +- .../structured-session-recovery-options.ts | 2 +- ...t-session-provider-naming-shutdown.test.ts | 2 +- .../naming/structured-chat-naming-runtime.ts | 4 +- ...agent-session-record-store-damaged.test.ts | 2 +- ...tructured-agent-session-create-adoption.ts | 8 +- .../structured-agent-session-runtime.test.ts | 35 +-- .../structured-agent-session-runtime.ts | 2 +- .../construction/runtime-construction.ts | 2 +- .../runtime-file-command-state.ts | 2 +- .../runtime/files/chat-scratch-file-root.ts | 2 +- src/main/runtime/files/file-command-host.ts | 2 +- .../crew/crew-mounted-render.test-fixture.ts | 2 +- .../crew/crew-read-model.test.ts | 2 +- .../crew/crew-recipient-authority.test.ts | 2 +- .../crew-recipient-delta-cost.test-fixture.ts | 2 +- .../crew-recipient-nested-authority.test.ts | 196 ----------------- .../worker-transcript-local-read.ts | 4 +- src/main/runtime/rpc/core.ts | 2 +- .../rpc/dispatch/dispatcher-stream-options.ts | 2 +- .../native-chat-subscription-token.test.ts | 82 ------- .../methods/native-chat/native-chat.test.ts | 50 ----- .../rpc/methods/native-chat/native-chat.ts | 4 +- .../structured-worker-read-cursor.test.ts | 2 +- .../structured-agent-session-visual.test.ts | 4 +- .../structured-agent-session-visual.ts | 4 +- .../structured-agent-session-create.ts | 2 +- ...uctured-agent-session-precommit-refusal.ts | 2 +- ...gent-session-rest-contract-test-fixture.ts | 2 +- .../structured-session-cli-writer-host.ts | 4 +- .../structured-session-create-support.ts | 2 +- .../worktree-retirement-discovery-wsl.test.ts | 176 ++------------- .../worktree-retirement-discovery.ts | 7 +- .../wsl/discovery/wsl-running-distros.test.ts | 30 +-- ...nt-launch-picker-legacy-permission.test.ts | 83 +------ ...chat-tool-viewed-images-shell-read.test.ts | 15 ++ .../e2e/native-chat-live-history.unit.test.ts | 202 ------------------ 346 files changed, 971 insertions(+), 3581 deletions(-) create mode 100644 src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-fork-coverage.test.ts create mode 100644 src/main/ai-vault/scanners/opencode/reader-transport/transcript-opencode2-fork.test-fixture.ts delete mode 100644 src/main/claude/structured/launch/claude-structured-launch-resolution.test.ts delete mode 100644 src/main/native-chat/__fixtures__/openclaude-0.31.0-local-command-rows.jsonl rename src/main/native-chat/{ => agent-launch}/agent-cli-installation-error.ts (62%) rename src/main/native-chat/{ => agent-launch}/structured-agent-arguments-error.ts (83%) rename src/main/native-chat/{ => agent-launch}/structured-agent-command-resolution.test.ts (100%) rename src/main/native-chat/{ => agent-launch}/structured-agent-command-resolution.ts (90%) rename src/main/native-chat/{ => agent-launch}/structured-agent-configured-args.test.ts (100%) rename src/main/native-chat/{ => agent-launch}/structured-agent-configured-args.ts (64%) rename src/main/native-chat/{ => agent-launch}/structured-agent-session-create-support.ts (81%) delete mode 100644 src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/providers/testing/structured-agent-session-provider-naming.test.ts rename src/main/native-chat/{ => conversation-adoption}/agent-conversation-copy-error.ts (76%) rename src/main/native-chat/{ => conversation-adoption}/codex-adoption-preparation.ts (82%) rename src/main/native-chat/{ => conversation-adoption}/structured-agent-cli-conversations.ts (91%) rename src/main/native-chat/{ => conversation-adoption}/structured-agent-cli-source.ts (86%) rename src/main/native-chat/{ => conversation-adoption}/structured-agent-session-history-adoption.ts (85%) rename src/main/native-chat/{ => conversation-adoption}/structured-conversation-key.ts (79%) rename src/main/native-chat/{ => conversation-context}/agent-session-context-snapshots.test.ts (100%) rename src/main/native-chat/{ => conversation-context}/agent-session-context-snapshots.ts (88%) rename src/main/native-chat/{ => conversation-context}/agent-session-saved-prompt-text.ts (78%) rename src/main/native-chat/{ => conversation-files}/native-chat-media-path.test.ts (90%) rename src/main/native-chat/{ => conversation-files}/native-chat-media-path.ts (83%) rename src/main/native-chat/{ => conversation-files}/native-chat-scratch-folder-migration.test.ts (98%) rename src/main/native-chat/{ => conversation-files}/native-chat-scratch-folder-migration.ts (95%) rename src/main/native-chat/{ => conversation-files}/native-chat-scratch-folder.ts (100%) rename src/main/native-chat/{ => conversation-files}/native-chat-visual-file-read.test.ts (100%) rename src/main/native-chat/{ => conversation-files}/native-chat-visual-file-read.ts (95%) rename src/main/native-chat/{ => conversation-files}/native-chat-visual-guest-location.ts (81%) rename src/main/native-chat/{ => conversation-files}/native-chat-visuals-delivery.ts (95%) rename src/main/native-chat/{ => conversation-files}/native-chat-visuals-skill-location.ts (96%) rename src/main/native-chat/{ => conversation-files}/native-chat-visuals-sweep.test.ts (97%) rename src/main/native-chat/{ => conversation-files}/native-chat-visuals-sweep.ts (93%) rename src/main/native-chat/{ => conversation-naming}/structured-chat-name-publication.ts (93%) rename src/main/native-chat/{ => conversation-naming}/structured-chat-naming-types.ts (71%) rename src/main/native-chat/{ => conversation-naming}/structured-chat-naming-work.ts (96%) rename src/main/native-chat/{ => conversation-naming}/structured-chat-title-handover.ts (92%) delete mode 100644 src/main/native-chat/structured-chat-naming-title.test.ts rename src/main/native-chat/{ => testing}/agent-session-record-test-fixture.ts (89%) delete mode 100644 src/main/native-chat/transcript-claude-read-image.test.ts rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders-claude-control-bytes.test.ts (65%) create mode 100644 src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-image-companion.test.ts rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders-claude-queued-prompt.test.ts (100%) rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders-claude.ts (89%) rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders-codex.ts (95%) rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders-grok.ts (94%) rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders-omp.ts (93%) rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders.grok.test.ts (99%) rename src/main/native-chat/{ => transcript-decoding}/transcript-line-decoders.omp.test.ts (99%) rename src/main/native-chat/{ => transcript-decoding}/transcript-record-blocks.ts (94%) rename src/main/native-chat/{ => transcript-decoding}/transcript-turn-lifecycle.test.ts (100%) rename src/main/native-chat/{ => transcript-decoding}/transcript-turn-lifecycle.ts (87%) rename src/main/native-chat/{ => transcript-decoding}/transcript-turn-markers.ts (88%) rename src/main/native-chat/{ => transcript-discovery}/host-readable-transcript-path.test.ts (92%) rename src/main/native-chat/{ => transcript-discovery}/host-readable-transcript-path.ts (94%) rename src/main/native-chat/{ => transcript-discovery}/native-chat-open-wake.ts (78%) rename src/main/native-chat/{ => transcript-discovery}/session-file-id-layouts.ts (82%) rename src/main/native-chat/{ => transcript-discovery}/session-file-resolver-claude-probe.ts (96%) rename src/main/native-chat/{ => transcript-discovery}/transcript-path-admission.ts (91%) rename src/main/native-chat/{ => transcript-discovery}/wsl-session-path-scan.ts (96%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-client.ts (98%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-decode.ts (100%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-dispatch.ts (93%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-entry.ts (100%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-handle-owner.ts (89%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-lane-pool.ts (98%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-operations.test.ts (100%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-operations.ts (100%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-protocol.ts (100%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-send.ts (100%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-slot.ts (100%) rename src/main/native-chat/{ => transcript-filesystem/process}/wsl-transcript-fs-process-spawn.ts (96%) rename src/main/native-chat/{ => transcript-filesystem}/ssh-transcript-fs-access.ts (88%) rename src/main/native-chat/{ => transcript-filesystem}/ssh-transcript-path.ts (91%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-fs-access.ts (96%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-fs-error.ts (97%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-fs-gate.ts (97%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-fs-route-quarantine.ts (100%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-fs-route.ts (91%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-running-admission.test.ts (91%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-running-admission.ts (90%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-running-observer.ts (95%) rename src/main/native-chat/{ => transcript-filesystem}/wsl-transcript-watcher-running-guard.ts (84%) rename src/main/native-chat/{ => transcript-history/codex}/codex-local-history.ts (90%) rename src/main/native-chat/{ => transcript-history/codex}/codex-rollout-history-source.ts (93%) rename src/main/native-chat/{ => transcript-history/codex}/codex-rollout-history-stream.ts (100%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode-database.ts (86%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode-fingerprint-window.ts (100%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode-part-blocks.ts (92%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode-row-budget.ts (94%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode-sqlite-query.test.ts (75%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode-sqlite-query.ts (91%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode-subscribe.ts (97%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode.ts (88%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode2-fork-coverage.ts (91%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-opencode2-sqlite.ts (93%) rename src/main/native-chat/{ => transcript-history/opencode}/transcript-zcode.test.ts (98%) delete mode 100644 src/main/native-chat/transcript-interruption-message.test.ts delete mode 100644 src/main/native-chat/transcript-line-decoders-claude-command-output.test.ts delete mode 100644 src/main/native-chat/transcript-line-decoders-claude-image-companion.test.ts delete mode 100644 src/main/native-chat/transcript-line-decoders-claude-stop-hook.test.ts delete mode 100644 src/main/native-chat/transcript-line-decoders.ts delete mode 100644 src/main/native-chat/transcript-opencode2-fork.test-fixture.ts delete mode 100644 src/main/native-chat/transcript-opencode2-sqlite.test.ts rename src/main/native-chat/{ => transcript-streaming}/transcript-fallback-id.ts (100%) rename src/main/native-chat/{ => transcript-streaming}/transcript-file-order.ts (89%) rename src/main/native-chat/{ => transcript-streaming}/transcript-stream-lines.test.ts (100%) rename src/main/native-chat/{ => transcript-streaming}/transcript-stream-lines.ts (97%) rename src/main/native-chat/{ => transcript-streaming}/transcript-zstd-decompression.test.ts (100%) rename src/main/native-chat/{ => transcript-streaming}/transcript-zstd-decompression.ts (100%) delete mode 100644 src/main/native-chat/transcript-watch-engine-wsl-lifecycle.test.ts delete mode 100644 src/main/native-chat/transcript-watch-unflushed-settle.test.ts rename src/main/native-chat/{ => transcript-watching}/transcript-file-version.test.ts (100%) rename src/main/native-chat/{ => transcript-watching}/transcript-file-version.ts (95%) rename src/main/native-chat/{ => transcript-watching}/transcript-incremental-reader.ts (91%) rename src/main/native-chat/{ => transcript-watching}/transcript-tail-boundary.ts (89%) rename src/main/native-chat/{ => transcript-watching}/transcript-unavailable-snapshot.ts (100%) rename src/main/native-chat/{ => transcript-watching}/transcript-watch-contract.ts (83%) rename src/main/native-chat/{ => transcript-watching}/transcript-watch-engine.ts (94%) rename src/main/native-chat/{ => transcript-watching}/transcript-watch-scheduler.ts (100%) rename src/main/native-chat/{ => transcript-watching}/transcript-watcher-count.ts (100%) rename src/main/native-chat/{ => transcript-watching}/transcript-watcher-install-probe.ts (58%) delete mode 100644 src/main/native-chat/transcript-window-tool-attribution.test.ts delete mode 100644 src/main/native-chat/wsl-transcript-running-observer.test.ts delete mode 100644 src/main/runtime/agent-session/launch/structured-agent-launch-composition.test.ts delete mode 100644 src/main/runtime/orchestration/crew/crew-recipient-nested-authority.test.ts delete mode 100644 src/main/runtime/rpc/methods/native-chat/native-chat-subscription-token.test.ts create mode 100644 src/shared/native-chat/tools/native-chat-tool-viewed-images-shell-read.test.ts delete mode 100644 tests/e2e/native-chat-live-history.unit.test.ts diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index fed925e9b9f..0ba46d5730c 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -41,6 +41,12 @@ on: - 'src/main/native-chat/agent-session-wire/**' # Recorded phone transcripts exercise these host decoders and their runtime dependencies. - 'src/main/native-chat/*transcript*.ts' + - 'src/main/native-chat/transcript-discovery/**' + - 'src/main/native-chat/transcript-filesystem/**' + - 'src/main/native-chat/transcript-decoding/**' + - 'src/main/native-chat/transcript-streaming/**' + - 'src/main/native-chat/transcript-watching/**' + - 'src/main/native-chat/transcript-history/**' - 'src/main/native-chat/subagent-entry-id-bounds.ts' - 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-payload-bounds.ts' - 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-reasoning-row.ts' @@ -177,6 +183,12 @@ on: - 'src/main/native-chat/agent-session-wire/**' # Recorded phone transcripts exercise these host decoders and their runtime dependencies. - 'src/main/native-chat/*transcript*.ts' + - 'src/main/native-chat/transcript-discovery/**' + - 'src/main/native-chat/transcript-filesystem/**' + - 'src/main/native-chat/transcript-decoding/**' + - 'src/main/native-chat/transcript-streaming/**' + - 'src/main/native-chat/transcript-watching/**' + - 'src/main/native-chat/transcript-history/**' - 'src/main/native-chat/subagent-entry-id-bounds.ts' - 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-payload-bounds.ts' - 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-reasoning-row.ts' diff --git a/.github/workflows/zcode-transcript-contract.yml b/.github/workflows/zcode-transcript-contract.yml index 3c1c7b640a8..eacd35634f8 100644 --- a/.github/workflows/zcode-transcript-contract.yml +++ b/.github/workflows/zcode-transcript-contract.yml @@ -7,8 +7,7 @@ on: - 'src/main/ai-vault/scanners/opencode/**' - 'src/main/ai-vault/scanners/opencode2/**' - 'src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime*' - - 'src/main/native-chat/transcript-opencode*' - - 'src/main/native-chat/transcript-zcode*' + - 'src/main/native-chat/transcript-history/opencode/**' - 'src/shared/native-chat/native-chat-types.ts' - 'src/shared/native-chat/transcript/native-chat-transcript-projection*' - '.github/workflows/zcode-transcript-contract.yml' @@ -41,4 +40,4 @@ jobs: bun-version-file: config/.bun-version no-cache: true - name: Verify SQLite ordering, legacy readers, and host routing - run: pnpm test src/main/native-chat/transcript-zcode.test.ts src/main/native-chat/transcript-opencode-sqlite-query.test.ts src/main/ai-vault/scanners/zcode/session-scanner-zcode.test.ts src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts src/shared/native-chat/transcript/native-chat-transcript-projection.test.ts src/renderer/src/components/native-chat/conversation/projection/structured-agent-session-transcript-order.test.ts + run: pnpm test src/main/native-chat/transcript-history/opencode/transcript-zcode.test.ts src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.test.ts src/main/ai-vault/scanners/zcode/session-scanner-zcode.test.ts src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts src/shared/native-chat/transcript/native-chat-transcript-projection.test.ts src/renderer/src/components/native-chat/conversation/projection/structured-agent-session-transcript-order.test.ts diff --git a/config/build-plugins/main-build-entry-points.ts b/config/build-plugins/main-build-entry-points.ts index d68603e8ff5..3431f1a61f1 100644 --- a/config/build-plugins/main-build-entry-points.ts +++ b/config/build-plugins/main-build-entry-points.ts @@ -30,7 +30,7 @@ export const MAIN_BUILD_ENTRY_POINTS: Record = { 'src/main/ai-vault/scan-service/session-scanner-service-entry.ts' ), 'wsl-transcript-fs-process-entry': resolve( - 'src/main/native-chat/wsl-transcript-fs-process-entry.ts' + 'src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts' ), // libuv spawns inline; run port probes on a worker to keep the UI loop free. 'port-scan-command-worker-entry': resolve('src/main/ports/port-scan-command-worker-entry.ts'), diff --git a/config/knip.json b/config/knip.json index 865e36e3823..dcd29f87e1b 100644 --- a/config/knip.json +++ b/config/knip.json @@ -41,7 +41,7 @@ "src/main/plugins/testing/plugin-test-hooks.ts", "src/main/plugins/testing/plugin-test-register.ts", "src/main/ai-vault/scan-service/session-scanner-service-entry.ts", - "src/main/native-chat/wsl-transcript-fs-process-entry.ts", + "src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts", "src/main/usage/scan-worker/usage-scan-worker-entry.ts", "src/main/workspaces/space-analysis/workspace-space-scan-worker-entry.ts", "src/main/claude-accounts/profiles/setup/claude-profile-setup-worker-entry.ts", diff --git a/config/main-blocking-sqlite-baseline.txt b/config/main-blocking-sqlite-baseline.txt index f0dd7179716..1fadfec8350 100644 --- a/config/main-blocking-sqlite-baseline.txt +++ b/config/main-blocking-sqlite-baseline.txt @@ -50,9 +50,9 @@ 1 src/main/native-chat/agent-session-journal/writing/command-receipt-schema.ts 8 src/main/native-chat/agent-session-journal/writing/command-receipt-table.ts 2 src/main/native-chat/agent-session-journal/writing/journal-goal-attachment-inverse.ts -10 src/main/native-chat/transcript-opencode-sqlite-query.ts -14 src/main/native-chat/transcript-opencode2-fork-coverage.ts -10 src/main/native-chat/transcript-opencode2-sqlite.ts +10 src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.ts +14 src/main/native-chat/transcript-history/opencode/transcript-opencode2-fork-coverage.ts +10 src/main/native-chat/transcript-history/opencode/transcript-opencode2-sqlite.ts 10 src/main/opencode-usage/opencode-usage-row-queries.ts 1 src/main/opencode-usage/scanner.ts 12 src/main/persistence/moving-wsl-projects/desktop-state/wsl-move-orchestration-history-rewrite.ts diff --git a/config/main-blocking-worker-entries.json b/config/main-blocking-worker-entries.json index 74f7b41f447..e5cd9bb550b 100644 --- a/config/main-blocking-worker-entries.json +++ b/config/main-blocking-worker-entries.json @@ -18,7 +18,7 @@ "src/main/plugins/workers/plugin-host-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.", "src/main/computer/computer-sidecar.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.", "src/main/ai-vault/scan-service/session-scanner-service-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.", - "src/main/native-chat/wsl-transcript-fs-process-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.", + "src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.", "src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.", "src/main/orcad/orcad-local-serve-selection-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main." } diff --git a/config/main-sync-fs-baseline.txt b/config/main-sync-fs-baseline.txt index 03d03095267..baf5e8317ec 100644 --- a/config/main-sync-fs-baseline.txt +++ b/config/main-sync-fs-baseline.txt @@ -316,8 +316,8 @@ 4 src/main/muse/hook-service.ts 2 src/main/native-chat/agent-session-attachments/agent-session-attachment-claims.ts 1 src/main/native-chat/agent-session-journal/database/journal-host-database.ts -1 src/main/native-chat/host-readable-transcript-path.ts -2 src/main/native-chat/wsl-transcript-fs-process-spawn.ts +1 src/main/native-chat/transcript-discovery/host-readable-transcript-path.ts +2 src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-spawn.ts 1 src/main/node-runtime/appimage-bundled-node-runtime.ts 2 src/main/node-runtime/managed-runtime-release-install.ts 7 src/main/node-runtime/managed-runtime-release.ts diff --git a/config/process-host-direct-import-baseline.txt b/config/process-host-direct-import-baseline.txt index c9e41a2ffa6..960a76522ef 100644 --- a/config/process-host-direct-import-baseline.txt +++ b/config/process-host-direct-import-baseline.txt @@ -50,7 +50,7 @@ src/main/macos-keychain/generic-password.ts src/main/power/macos-system-sleep-assertion.ts src/main/macos-permissions/macos-tcc-prompt-watch.ts src/main/memory/collector.ts -src/main/native-chat/wsl-transcript-fs-process-spawn.ts +src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-spawn.ts src/main/network/macos-system-resolver-health.ts src/main/network/macos-tailscale-dns-diagnostic.ts src/main/plugins/workers/parent-process/plugin-host-process.ts diff --git a/config/scripts/ci/sharding/ci-shard-timings.json b/config/scripts/ci/sharding/ci-shard-timings.json index 286b183e044..512528a8e6c 100644 --- a/config/scripts/ci/sharding/ci-shard-timings.json +++ b/config/scripts/ci/sharding/ci-shard-timings.json @@ -2282,7 +2282,7 @@ "src/main/native-chat/agent-session-attachments/agent-session-attachment-claims.test.ts": 971, "src/main/native-chat/agent-session-attachments/agent-session-attachment-store.test.ts": 340, "src/main/native-chat/agent-session-attachments/agent-session-attachment-sweep.test.ts": 948, - "src/main/native-chat/agent-session-context-snapshots.test.ts": 65, + "src/main/native-chat/conversation-context/agent-session-context-snapshots.test.ts": 65, "src/main/native-chat/agent-session-journal/history-import/adopted-directory-probe.test.ts": 153, "src/main/native-chat/agent-session-journal/database/journal-checkpoint-attachment-sweep.test.ts": 8790, "src/main/native-chat/agent-session-journal/history-import/journal-codex-history.test.ts": 786, @@ -2396,37 +2396,37 @@ "src/main/native-chat/agent-session-wire/host/testing/structured-conversation-command-child-work.test.ts": 1810, "src/main/native-chat/agent-session-wire/host/testing/structured-conversation-command.test.ts": 2989, "src/main/native-chat/agent-session-wire/rewind/testing/structured-rewind-cut.test.ts": 672, - "src/main/native-chat/host-readable-transcript-path.test.ts": 150, + "src/main/native-chat/transcript-discovery/host-readable-transcript-path.test.ts": 150, "src/main/native-chat/native-chat-inline-visuals-setting.test.ts": 751, - "src/main/native-chat/native-chat-scratch-folder-migration.test.ts": 109, - "src/main/native-chat/native-chat-visual-file-read.test.ts": 98, - "src/main/native-chat/native-chat-visuals-sweep.test.ts": 123, + "src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.test.ts": 109, + "src/main/native-chat/conversation-files/native-chat-visual-file-read.test.ts": 98, + "src/main/native-chat/conversation-files/native-chat-visuals-sweep.test.ts": 123, "src/main/native-chat/session-file-resolver-wsl-scan-gate.test.ts": 156, "src/main/native-chat/session-file-resolver.test.ts": 245, - "src/main/native-chat/structured-agent-command-resolution.test.ts": 223, - "src/main/native-chat/structured-agent-configured-args.test.ts": 191, - "src/main/native-chat/transcript-claude-read-image.test.ts": 389, - "src/main/native-chat/transcript-file-version.test.ts": 121, + "src/main/native-chat/agent-launch/structured-agent-command-resolution.test.ts": 223, + "src/main/native-chat/agent-launch/structured-agent-configured-args.test.ts": 191, + "src/shared/native-chat/tools/native-chat-tool-viewed-images-shell-read.test.ts": 389, + "src/main/native-chat/transcript-watching/transcript-file-version.test.ts": 121, "src/main/native-chat/transcript-interruption-message.test.ts": 263, "src/main/native-chat/transcript-line-decoders-claude-command-output.test.ts": 248, - "src/main/native-chat/transcript-line-decoders-claude-control-bytes.test.ts": 237, - "src/main/native-chat/transcript-line-decoders-claude-image-companion.test.ts": 238, - "src/main/native-chat/transcript-line-decoders-claude-queued-prompt.test.ts": 255, + "src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-control-bytes.test.ts": 237, + "src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-image-companion.test.ts": 238, + "src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-queued-prompt.test.ts": 255, "src/main/native-chat/transcript-line-decoders-claude-stop-hook.test.ts": 211, - "src/main/native-chat/transcript-line-decoders.grok.test.ts": 283, - "src/main/native-chat/transcript-line-decoders.omp.test.ts": 297, - "src/main/native-chat/transcript-opencode-sqlite-query.test.ts": 886, + "src/main/native-chat/transcript-decoding/transcript-line-decoders.grok.test.ts": 283, + "src/main/native-chat/transcript-decoding/transcript-line-decoders.omp.test.ts": 297, + "src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.test.ts": 886, "src/main/native-chat/transcript-read-cache.test.ts": 513, "src/main/native-chat/transcript-reader.test.ts": 565, - "src/main/native-chat/transcript-stream-lines.test.ts": 79, - "src/main/native-chat/transcript-turn-lifecycle.test.ts": 258, + "src/main/native-chat/transcript-streaming/transcript-stream-lines.test.ts": 79, + "src/main/native-chat/transcript-decoding/transcript-turn-lifecycle.test.ts": 258, "src/main/native-chat/transcript-watch-engine-wsl-lifecycle.test.ts": 508, "src/main/native-chat/transcript-watch-error.test.ts": 2306, "src/main/native-chat/transcript-watch-unflushed-settle.test.ts": 554, "src/main/native-chat/transcript-window-tool-attribution.test.ts": 523, - "src/main/native-chat/transcript-zcode.test.ts": 814, - "src/main/native-chat/wsl-transcript-fs-process-operations.test.ts": 43, - "src/main/native-chat/wsl-transcript-running-admission.test.ts": 1178, + "src/main/native-chat/transcript-history/opencode/transcript-zcode.test.ts": 814, + "src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-operations.test.ts": 43, + "src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.test.ts": 1178, "src/main/native-chat/wsl-transcript-running-observer.test.ts": 103, "src/main/workspaces/removal/nested-worktree-removal-plan.test.ts": 242, "src/main/node-runtime/managed-runtime-installation.test.ts": 427, diff --git a/config/scripts/performance/session-transcripts/transcript-stream-fragments-benchmark.mjs b/config/scripts/performance/session-transcripts/transcript-stream-fragments-benchmark.mjs index 786d1e8d047..28605ad332e 100644 --- a/config/scripts/performance/session-transcripts/transcript-stream-fragments-benchmark.mjs +++ b/config/scripts/performance/session-transcripts/transcript-stream-fragments-benchmark.mjs @@ -6,7 +6,7 @@ import { performance } from 'node:perf_hooks' import { build } from 'esbuild' import { buildCounterbalancedSchedule } from '../measurement/counterbalanced-benchmark-schedule.mjs' -const modulePath = 'src/main/native-chat/transcript-stream-lines.ts' +const modulePath = 'src/main/native-chat/transcript-streaming/transcript-stream-lines.ts' const baselineSource = readFileSync(0, 'utf8') assert.ok(baselineSource.includes('decodeTranscriptStream'), 'Pipe the baseline module on stdin') const arms = {} diff --git a/config/tsconfig.test.mobile-host-declarations.json b/config/tsconfig.test.mobile-host-declarations.json index cb32d2f2e77..5f5ba25a476 100644 --- a/config/tsconfig.test.mobile-host-declarations.json +++ b/config/tsconfig.test.mobile-host-declarations.json @@ -20,7 +20,7 @@ "../src/main/github/issues/issue-field-options.ts", "../src/main/runtime/workspaces/process-listing/runtime-worktree-ps-summaries.ts", "../src/types/build-constants.d.ts", - "../src/main/native-chat/transcript-line-decoders-claude.ts", + "../src/main/native-chat/transcript-decoding/transcript-line-decoders-claude.ts", "../src/main/codex/structured/items/codex-thread-item-identity.ts", "../src/main/codex/structured/items/codex-structured-item-translation.ts" ], diff --git a/src/main/acp/conversation-copy/acp-conversation-copy.ts b/src/main/acp/conversation-copy/acp-conversation-copy.ts index e40508384a6..e38796a6163 100644 --- a/src/main/acp/conversation-copy/acp-conversation-copy.ts +++ b/src/main/acp/conversation-copy/acp-conversation-copy.ts @@ -1,4 +1,4 @@ -import { agentConversationCopyRefusal } from '../../native-chat/agent-conversation-copy-error' +import { agentConversationCopyRefusal } from '../../native-chat/conversation-adoption/agent-conversation-copy-error' import type { StructuredAgentSessionConversationCopyInput } from '../../native-chat/agent-session-wire/providers/structured-agent-session-conversation-copy-input' import { readOpenCodeForkCoverageViaWorker } from '../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn' import type { NativeCopyCoverage } from '../../native-chat/agent-session-journal/context-epochs/journal-history-origin' diff --git a/src/main/acp/discovery/acp-at-rest-commands.test.ts b/src/main/acp/discovery/acp-at-rest-commands.test.ts index d9088217ac9..c6cc8bd2772 100644 --- a/src/main/acp/discovery/acp-at-rest-commands.test.ts +++ b/src/main/acp/discovery/acp-at-rest-commands.test.ts @@ -1,7 +1,6 @@ -import { readdir } from 'node:fs/promises' import { statSync } from 'node:fs' import type * as fs from 'node:fs/promises' -import { join } from 'node:path' + import { afterEach, expect, it, vi } from 'vitest' import capture from '../../../shared/__fixtures__/omp-native-commands.json' import '../../../shared/priority-semaphore' @@ -12,7 +11,7 @@ import { } from './testing/acp-command-discovery.test-fixture' import { acpLaunchSpecFor } from '../launch/acp-launch-registry' import { readAcpForkSupport } from '../capabilities/acp-fork-support' -import { agentSessionRecordFixture } from '../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../native-chat/testing/agent-session-record-test-fixture' vi.mock('node:fs/promises', async (importOriginal) => { const actual = await importOriginal() @@ -93,26 +92,6 @@ it('observes pre-initialize reports, validates session identity and keeps the la expect(children[0]!.lastCloseResult).toEqual({ root: 'exited', tree: null }) }) -it('handles actual root exit just before listener registration without changing historical evidence', async () => { - const evidence = Object.freeze({ root: 'live' as const, tree: 'unverifiable' as const }) - const { catalog, children, root } = await rig((child) => { - const onExit = child.onExit.bind(child) - vi.spyOn(child, 'onExit').mockImplementation((listener) => { - child.exit() - onExit(listener) - }) - child.proveClose = async () => { - child.evidence = evidence - return false - } - }) - expect(await catalog.settled(ompRecord(), 1_000)).toBeDefined() - await catalog.closeAll() - expect(children[0]!.lastCloseResult).toBe(evidence) - expect(children[0]!.closes).toBe(1) - expect(await readdir(join(root, 'acp-command-sessions', 'omp'))).toEqual([]) -}) - it('lists OpenCode 2 commands from a session it then deletes, and never starts OpenCode 1.x', async () => { const openCodeRecord = (provider: 'opencode' | 'opencode2') => agentSessionRecordFixture({ diff --git a/src/main/acp/discovery/acp-at-rest-commands.ts b/src/main/acp/discovery/acp-at-rest-commands.ts index e018203df24..72d30b83372 100644 --- a/src/main/acp/discovery/acp-at-rest-commands.ts +++ b/src/main/acp/discovery/acp-at-rest-commands.ts @@ -5,7 +5,7 @@ import { AtRestCommandCatalog, type AtRestCommandScan } from '../../native-chat/agent-session-wire/options/at-rest-command-catalog' -import { resolveNativeChatVisualsSkillLocation } from '../../native-chat/native-chat-visuals-skill-location' +import { resolveNativeChatVisualsSkillLocation } from '../../native-chat/conversation-files/native-chat-visuals-skill-location' import type { StructuredAgentSessionLogger } from '../../native-chat/agent-session-wire/host/structured-agent-session-logger' import type { AgentCliVersionReader } from '../../agent-launch/preflight/agent-cli-version-probe' import type { ProviderProcessLaunch } from '../../provider-process/provider-process-launch' @@ -15,7 +15,7 @@ import { STRUCTURED_CHILD_ENV_TO_DELETE } from '../../runtime/agent-session/envi import { LEGACY_NATIVE_CHAT_VISUALS_DIR_ENV, NATIVE_CHAT_SCRATCH_DIR_ENV -} from '../../native-chat/native-chat-visuals-delivery' +} from '../../native-chat/conversation-files/native-chat-visuals-delivery' import { acpListingDependsOnRelease, acpModelListingForRelease } from './acp-model-discovery-policy' import type { AcpLaunchSpec } from '../launch/acp-launch-contract' import { createAcpAgentConnection } from '../process/acp-agent-connection' diff --git a/src/main/acp/discovery/acp-listing-session-models.ts b/src/main/acp/discovery/acp-listing-session-models.ts index 0c35c338adc..3fd072a2000 100644 --- a/src/main/acp/discovery/acp-listing-session-models.ts +++ b/src/main/acp/discovery/acp-listing-session-models.ts @@ -3,7 +3,7 @@ import { observeAcpForkSupport } from '../capabilities/acp-fork-support' import { assertStructuredAgentResolutionCurrent, type StructuredAgentResolutionOperation -} from '../../native-chat/structured-agent-command-resolution' +} from '../../native-chat/agent-launch/structured-agent-command-resolution' import type { AgentSessionModelOption } from '../../../shared/agent-session/agent-session-wire' import type { AcpSessionModelDiscovery } from './acp-model-discovery-policy' import type { AcpLaunchSpec } from '../launch/acp-launch-contract' diff --git a/src/main/acp/discovery/acp-model-catalog-probe.ts b/src/main/acp/discovery/acp-model-catalog-probe.ts index 8b935339fc8..7f8da46c04a 100644 --- a/src/main/acp/discovery/acp-model-catalog-probe.ts +++ b/src/main/acp/discovery/acp-model-catalog-probe.ts @@ -1,5 +1,5 @@ import { observeAcpForkSupport } from '../capabilities/acp-fork-support' -import { assertStructuredAgentResolutionCurrent } from '../../native-chat/structured-agent-command-resolution' +import { assertStructuredAgentResolutionCurrent } from '../../native-chat/agent-launch/structured-agent-command-resolution' import { homedir } from 'node:os' import { join } from 'node:path' import type { AgentCliVersionReader } from '../../agent-launch/preflight/agent-cli-version-probe' diff --git a/src/main/acp/discovery/testing/acp-command-discovery.test-fixture.ts b/src/main/acp/discovery/testing/acp-command-discovery.test-fixture.ts index 094a39a9cca..6fbc0c2d42a 100644 --- a/src/main/acp/discovery/testing/acp-command-discovery.test-fixture.ts +++ b/src/main/acp/discovery/testing/acp-command-discovery.test-fixture.ts @@ -3,7 +3,7 @@ import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import capture from '../../../../shared/__fixtures__/omp-native-commands.json' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import type { ProviderProcessCloseResult } from '../../../provider-process/provider-process-close' import { AcpAtRestCommandCatalog, type AcpAtRestCommandDeps } from '../acp-at-rest-commands' import { acpLaunchSpecFor } from '../../launch/acp-launch-registry' diff --git a/src/main/acp/launch/acp-launch-command.ts b/src/main/acp/launch/acp-launch-command.ts index e23c8bdc6bc..e065a39dc19 100644 --- a/src/main/acp/launch/acp-launch-command.ts +++ b/src/main/acp/launch/acp-launch-command.ts @@ -9,7 +9,7 @@ import { resolveStructuredAgentCommand, type StructuredAgentCommand, type StructuredAgentCommandSettings -} from '../../native-chat/structured-agent-command-resolution' +} from '../../native-chat/agent-launch/structured-agent-command-resolution' import { copyBesidePinnedMembers, resolveInstalledRuntimeRelease diff --git a/src/main/acp/launch/acp-launch-contract.ts b/src/main/acp/launch/acp-launch-contract.ts index 493e8ecea70..2775539ddf6 100644 --- a/src/main/acp/launch/acp-launch-contract.ts +++ b/src/main/acp/launch/acp-launch-contract.ts @@ -10,7 +10,7 @@ import type { AcpVisualsSkillLoader } from './acp-visuals-skill' import type { StructuredAgentSessionHistory, StructuredAgentTranscriptImport -} from '../../native-chat/structured-agent-cli-conversations' +} from '../../native-chat/conversation-adoption/structured-agent-cli-conversations' import type { AcpSignInWatch } from '../process/acp-agent-connection' import type { AcpLaunchGate } from './acp-launch-gate' diff --git a/src/main/acp/launch/acp-structured-launch-resolution.ts b/src/main/acp/launch/acp-structured-launch-resolution.ts index 62e69d6ab32..b0072fc91de 100644 --- a/src/main/acp/launch/acp-structured-launch-resolution.ts +++ b/src/main/acp/launch/acp-structured-launch-resolution.ts @@ -7,7 +7,7 @@ import { type StructuredAgentCommand, type StructuredAgentCommandSettings, type StructuredAgentResolutionOperation -} from '../../native-chat/structured-agent-command-resolution' +} from '../../native-chat/agent-launch/structured-agent-command-resolution' // Launch from durable record values for the working directory, pinned account and resume session. import { @@ -22,14 +22,14 @@ import { agentSessionProviderHandleKey } from '../../../shared/agent-session/lif import type { AgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' import type { resolveCliCommand } from '../../../shared/node-cli-command-resolution' import type { AgentCliVersionReader } from '../../agent-launch/preflight/agent-cli-version-probe' -import { AgentCliInstallationError } from '../../native-chat/agent-cli-installation-error' +import { AgentCliInstallationError } from '../../native-chat/agent-launch/agent-cli-installation-error' import type { JournalLoad } from '../../native-chat/agent-session-journal/replay/journal-open' import type { StructuredAgentLaunchPartResolver } from '../../runtime/agent-session/launch/structured-agent-launch-composition' import { LEGACY_NATIVE_CHAT_VISUALS_DIR_ENV, NATIVE_CHAT_SCRATCH_DIR_ENV, withNativeChatVisualsEnv -} from '../../native-chat/native-chat-visuals-delivery' +} from '../../native-chat/conversation-files/native-chat-visuals-delivery' import type { AcpLaunchSpec } from './acp-launch-contract' import { acpLaunchSpecForAccount, resolveAcpLaunchCommand } from './acp-launch-command' import { nothingExchangedOn } from '../session/acp-structured-resume-history' diff --git a/src/main/acp/launch/acp-structured-launch-visuals.ts b/src/main/acp/launch/acp-structured-launch-visuals.ts index 40286b5a775..d5da05eb474 100644 --- a/src/main/acp/launch/acp-structured-launch-visuals.ts +++ b/src/main/acp/launch/acp-structured-launch-visuals.ts @@ -2,7 +2,7 @@ import type { StructuredAgentSessionLogger } from '../../native-chat/agent-sessi import type { NativeChatVisualsLaunch, PrepareNativeChatVisuals -} from '../../native-chat/native-chat-visuals-delivery' +} from '../../native-chat/conversation-files/native-chat-visuals-delivery' import type { AcpLaunchSpec } from './acp-launch-contract' import type { AcpVisualsSkillLaunch } from './acp-visuals-skill' diff --git a/src/main/acp/launch/acp-visuals-skill.ts b/src/main/acp/launch/acp-visuals-skill.ts index 4c52bb3aa10..331817a6609 100644 --- a/src/main/acp/launch/acp-visuals-skill.ts +++ b/src/main/acp/launch/acp-visuals-skill.ts @@ -1,7 +1,7 @@ // ACP has no standard skill delivery; use each agent's per-process configuration. import { hasReachedAppVersion } from '../../../shared/app-version' -import type { NativeChatVisualsSkillLocation } from '../../native-chat/native-chat-visuals-skill-location' +import type { NativeChatVisualsSkillLocation } from '../../native-chat/conversation-files/native-chat-visuals-skill-location' export type AcpVisualsSkillInput = { skill: NativeChatVisualsSkillLocation diff --git a/src/main/ai-vault-search/discovery/session-search-directory-listings.ts b/src/main/ai-vault-search/discovery/session-search-directory-listings.ts index cac48a04441..a87a1a8aa5f 100644 --- a/src/main/ai-vault-search/discovery/session-search-directory-listings.ts +++ b/src/main/ai-vault-search/discovery/session-search-directory-listings.ts @@ -1,4 +1,4 @@ -import { wslGatedReaddir } from '../../native-chat/wsl-transcript-fs-access' +import { wslGatedReaddir } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' export type SessionSearchDirectoryListing = | { listed: true; names: ReadonlySet } diff --git a/src/main/ai-vault/discovery/session-json-file.ts b/src/main/ai-vault/discovery/session-json-file.ts index 461edd3034d..3e62eae7d06 100644 --- a/src/main/ai-vault/discovery/session-json-file.ts +++ b/src/main/ai-vault/discovery/session-json-file.ts @@ -1,5 +1,5 @@ -import { wslGatedReadFile } from '../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../native-chat/wsl-transcript-fs-error' +import { wslGatedReadFile } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { asRecord } from '../parsing/session-record-fields' import { throwIfAiVaultScanCancelled } from '../scan-lifecycle/ai-vault-scan-cancellation' diff --git a/src/main/ai-vault/discovery/session-path-absence.ts b/src/main/ai-vault/discovery/session-path-absence.ts index ef5aae3969f..4b258bebb94 100644 --- a/src/main/ai-vault/discovery/session-path-absence.ts +++ b/src/main/ai-vault/discovery/session-path-absence.ts @@ -1,4 +1,4 @@ -import { WslTranscriptFsError } from '../../native-chat/wsl-transcript-fs-error' +import { WslTranscriptFsError } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-error' /** A missing file or folder is absence, not a refusal; WSL gate errors are always refusals. */ export function isMissingTranscriptPathError(error: unknown): boolean { diff --git a/src/main/ai-vault/discovery/session-scanner-discovery.ts b/src/main/ai-vault/discovery/session-scanner-discovery.ts index 4af6f505fe9..cdd24a37fc9 100644 --- a/src/main/ai-vault/discovery/session-scanner-discovery.ts +++ b/src/main/ai-vault/discovery/session-scanner-discovery.ts @@ -9,8 +9,11 @@ import type { AiVaultAgent, AiVaultScanIssue } from '../../../shared/ai-vault/contracts/ai-vault-types' -import { wslGatedReaddir, wslGatedStat } from '../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../native-chat/wsl-transcript-fs-error' +import { + wslGatedReaddir, + wslGatedStat +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { recordSessionScanIssue, scanIssueTextFromError } from '../parsing/session-scan-issues' import { aiVaultScanIssueText } from '../../../shared/ai-vault/scanning/ai-vault-scan-issue-copy' import type { diff --git a/src/main/ai-vault/parsing/session-scan-issues.ts b/src/main/ai-vault/parsing/session-scan-issues.ts index 455580ca4ef..cc1e7474675 100644 --- a/src/main/ai-vault/parsing/session-scan-issues.ts +++ b/src/main/ai-vault/parsing/session-scan-issues.ts @@ -3,7 +3,7 @@ import { aiVaultScanIssueText, type AiVaultScanIssueText } from '../../../shared/ai-vault/scanning/ai-vault-scan-issue-copy' -import { WslTranscriptFsError } from '../../native-chat/wsl-transcript-fs-error' +import { WslTranscriptFsError } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { errorMessage } from '../../../shared/error-message' // Bound IPC payloads when an unreachable host produces one issue per transcript. diff --git a/src/main/ai-vault/parsing/session-scanner-core-parser-wsl-stall.test.ts b/src/main/ai-vault/parsing/session-scanner-core-parser-wsl-stall.test.ts index eb3f121a4b6..3f3c168c1e1 100644 --- a/src/main/ai-vault/parsing/session-scanner-core-parser-wsl-stall.test.ts +++ b/src/main/ai-vault/parsing/session-scanner-core-parser-wsl-stall.test.ts @@ -28,10 +28,10 @@ import { parseAgentSessionFileCached } from '../parse-cache/session-scanner-pars import { resetSessionParseCacheForTests } from '../parse-cache/session-parse-cache-store' import { resetWslTranscriptFsGateForTests, - WSL_TRANSCRIPT_FS_ROUTE_QUARANTINE_BASE_MS, WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS -} from '../../native-chat/wsl-transcript-fs-gate' -import { WslTranscriptFsError } from '../../native-chat/wsl-transcript-fs-error' +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-gate' +import { WSL_TRANSCRIPT_FS_ROUTE_QUARANTINE_BASE_MS } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-route-quarantine' +import { WslTranscriptFsError } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-error' function uncPath(distro: string, ...segments: string[]): string { return `\\\\wsl.localhost\\${distro}\\home\\ada\\${segments.join('\\')}` diff --git a/src/main/ai-vault/parsing/session-scanner-jsonl-reader.test.ts b/src/main/ai-vault/parsing/session-scanner-jsonl-reader.test.ts index d6823340aa8..3cbc17c96af 100644 --- a/src/main/ai-vault/parsing/session-scanner-jsonl-reader.test.ts +++ b/src/main/ai-vault/parsing/session-scanner-jsonl-reader.test.ts @@ -10,7 +10,7 @@ type ChunkSource = { } const source = vi.hoisted((): ChunkSource => ({ chunks: [], closed: false, opens: 0 })) -vi.mock('../../native-chat/wsl-transcript-fs-access', () => ({ +vi.mock('../../native-chat/transcript-filesystem/wsl-transcript-fs-access', () => ({ openTranscriptReadStream: async function* () { source.opens += 1 source.closed = false diff --git a/src/main/ai-vault/parsing/session-scanner-jsonl-reader.ts b/src/main/ai-vault/parsing/session-scanner-jsonl-reader.ts index c02e0dbae9f..ad0932c1bf5 100644 --- a/src/main/ai-vault/parsing/session-scanner-jsonl-reader.ts +++ b/src/main/ai-vault/parsing/session-scanner-jsonl-reader.ts @@ -3,11 +3,11 @@ import { waitForAiVaultScan } from '../scan-lifecycle/ai-vault-scan-cancellation' import type { Hash } from 'node:crypto' -import { openTranscriptReadStream } from '../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { isZstdTranscriptPath, zstdTranscriptStream -} from '../../native-chat/transcript-zstd-decompression' +} from '../../native-chat/transcript-streaming/transcript-zstd-decompression' import { MAX_SESSION_TRANSCRIPT_RECORD_BYTES, type SkippedTranscriptRecord diff --git a/src/main/ai-vault/parsing/session-scanner-parse-wsl-stall.test.ts b/src/main/ai-vault/parsing/session-scanner-parse-wsl-stall.test.ts index 08db7cd059d..c134fb0c3a3 100644 --- a/src/main/ai-vault/parsing/session-scanner-parse-wsl-stall.test.ts +++ b/src/main/ai-vault/parsing/session-scanner-parse-wsl-stall.test.ts @@ -13,7 +13,7 @@ vi.mock('node:fs/promises', async (importOriginal) => ({ readdir: mocks.readdir })) -import * as transcriptFs from '../../native-chat/wsl-transcript-fs-access' +import * as transcriptFs from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { parseAgentSessionFileCached } from '../parse-cache/session-scanner-parse-cache' import { registerTranscriptConsumer, @@ -22,10 +22,10 @@ import { import { resetSessionParseCacheForTests } from '../parse-cache/session-parse-cache-store' import { resetWslTranscriptFsGateForTests, - WSL_TRANSCRIPT_FS_ROUTE_QUARANTINE_BASE_MS, WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS -} from '../../native-chat/wsl-transcript-fs-gate' -import { WslTranscriptFsError } from '../../native-chat/wsl-transcript-fs-error' +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-gate' +import { WSL_TRANSCRIPT_FS_ROUTE_QUARANTINE_BASE_MS } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-route-quarantine' +import { WslTranscriptFsError } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-error' type ReadResult = { bytesRead: number; buffer: Buffer } diff --git a/src/main/ai-vault/prompts/session-first-user-prompt-read.ts b/src/main/ai-vault/prompts/session-first-user-prompt-read.ts index 79c27092cde..1eaa8283ff1 100644 --- a/src/main/ai-vault/prompts/session-first-user-prompt-read.ts +++ b/src/main/ai-vault/prompts/session-first-user-prompt-read.ts @@ -4,7 +4,7 @@ import type { AiVaultFirstUserPromptResult as ReadAiVaultFirstUserPromptResult } from '../../../shared/ai-vault/contracts/ai-vault-types' import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../../shared/execution-host' -import { wslGatedStat } from '../../native-chat/wsl-transcript-fs-access' +import { wslGatedStat } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { parseAgentSessionFile } from '../parsing/session-scanner-agent-parser' import { withFullFirstUserPromptCapture } from './session-scanner-first-user-prompt-capture' import { diff --git a/src/main/ai-vault/scanners/antigravity/antigravity-transcript-candidates.ts b/src/main/ai-vault/scanners/antigravity/antigravity-transcript-candidates.ts index c92866d0333..0f9319e653d 100644 --- a/src/main/ai-vault/scanners/antigravity/antigravity-transcript-candidates.ts +++ b/src/main/ai-vault/scanners/antigravity/antigravity-transcript-candidates.ts @@ -1,6 +1,6 @@ import { dirname, join } from 'node:path' -import { wslGatedLstat } from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { wslGatedLstat } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import type { AiVaultScanIssue } from '../../../../shared/ai-vault/contracts/ai-vault-types' import { recordSessionScanIssue, scanIssueTextFromError } from '../../parsing/session-scan-issues' import type { FileWithMtime, SessionFileDiscovery } from '../../session-scanner-types' diff --git a/src/main/ai-vault/scanners/antigravity/session-scanner-antigravity-history.ts b/src/main/ai-vault/scanners/antigravity/session-scanner-antigravity-history.ts index c6bcebbfecc..c2d2fbb5be6 100644 --- a/src/main/ai-vault/scanners/antigravity/session-scanner-antigravity-history.ts +++ b/src/main/ai-vault/scanners/antigravity/session-scanner-antigravity-history.ts @@ -8,11 +8,11 @@ import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-va import { openTranscriptReadStream, wslGatedStat -} from '../../../native-chat/wsl-transcript-fs-access' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { readNodeFileWithinLimit } from '../../../../shared/file-content/node-bounded-file-reader' import { isWslUncPath } from '../../../../shared/wsl-paths' import { throwIfAiVaultScanCancelled } from '../../scan-lifecycle/ai-vault-scan-cancellation' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { parseJsonObject } from '../../parsing/session-json-object' import { timestampMs } from '../../parsing/session-record-time' import { antigravityHistoryPromptHash } from './antigravity-history-prompt' diff --git a/src/main/ai-vault/scanners/claude/session-scanner-claude-subagents.ts b/src/main/ai-vault/scanners/claude/session-scanner-claude-subagents.ts index 0104a423fa9..fabd6abb66d 100644 --- a/src/main/ai-vault/scanners/claude/session-scanner-claude-subagents.ts +++ b/src/main/ai-vault/scanners/claude/session-scanner-claude-subagents.ts @@ -15,8 +15,8 @@ import { wslGatedReaddir, wslGatedReadFile, wslGatedStat -} from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { recordSessionScanIssue, scanIssueTextFromError } from '../../parsing/session-scan-issues' import { sessionIdFromFileName, sessionSortTime } from '../../parsing/session-scanner-accumulator' import { parseClaudeSessionFile } from './session-scanner-primary-parsers' diff --git a/src/main/ai-vault/scanners/claude/session-scanner-primary-parsers.ts b/src/main/ai-vault/scanners/claude/session-scanner-primary-parsers.ts index 998bc9ca936..9ab41e9b1ce 100644 --- a/src/main/ai-vault/scanners/claude/session-scanner-primary-parsers.ts +++ b/src/main/ai-vault/scanners/claude/session-scanner-primary-parsers.ts @@ -2,7 +2,7 @@ import { remoteSessionContentLines, type RemoteSessionContent } from '../../transcripts/remote-session-content-lines' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/main/ai-vault/scanners/claude/session-scanner-subagent-transcripts.ts b/src/main/ai-vault/scanners/claude/session-scanner-subagent-transcripts.ts index a84488a8b41..a23f8454a99 100644 --- a/src/main/ai-vault/scanners/claude/session-scanner-subagent-transcripts.ts +++ b/src/main/ai-vault/scanners/claude/session-scanner-subagent-transcripts.ts @@ -2,7 +2,7 @@ import { throwIfAiVaultScanCancelled, waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' -import { wslGatedReaddir } from '../../../native-chat/wsl-transcript-fs-access' +import { wslGatedReaddir } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { basename, dirname, extname, join } from 'node:path' export const SUBAGENT_DIR_NAME = 'subagents' diff --git a/src/main/ai-vault/scanners/cline/session-scanner-cline-parser.ts b/src/main/ai-vault/scanners/cline/session-scanner-cline-parser.ts index 3e612f46dc8..1aa689a82cf 100644 --- a/src/main/ai-vault/scanners/cline/session-scanner-cline-parser.ts +++ b/src/main/ai-vault/scanners/cline/session-scanner-cline-parser.ts @@ -1,8 +1,8 @@ import { isMissingRemoteSessionPathError } from '../../transcripts/remote-session-file-stat' import { BinarySessionTranscriptError } from '../../transcripts/remote-session-content-lines' import { readStreamedSessionDocument } from '../../transcripts/session-document-stream' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import { clineMessagesPathForMetadata } from '../../../../shared/ai-vault/session-display/ai-vault-session-log-resource' import type { ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/main/ai-vault/scanners/codebuddy/session-scanner-codebuddy-parser.ts b/src/main/ai-vault/scanners/codebuddy/session-scanner-codebuddy-parser.ts index 6906c02590b..3c072206d20 100644 --- a/src/main/ai-vault/scanners/codebuddy/session-scanner-codebuddy-parser.ts +++ b/src/main/ai-vault/scanners/codebuddy/session-scanner-codebuddy-parser.ts @@ -1,5 +1,5 @@ import { createInterface } from 'node:readline' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { ExecutionHostId } from '../../../../shared/execution-host' import { isKnownHarnessInjectedUserTurnText } from '../../../../shared/harness-injected-user-turns' diff --git a/src/main/ai-vault/scanners/codex/session-scanner-codex-index-read.ts b/src/main/ai-vault/scanners/codex/session-scanner-codex-index-read.ts index 3564dd668e1..2a51742b1c6 100644 --- a/src/main/ai-vault/scanners/codex/session-scanner-codex-index-read.ts +++ b/src/main/ai-vault/scanners/codex/session-scanner-codex-index-read.ts @@ -3,7 +3,7 @@ import type { Stats } from 'node:fs' import { openTranscriptReadStream, readTranscriptSlice -} from '../../../native-chat/wsl-transcript-fs-access' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { consumeCompleteJsonlLines } from '../../parsing/session-scanner-jsonl-reader' import { extractString } from '../../../../shared/trimmed-string' import { parseJsonObject } from '../../parsing/session-json-object' diff --git a/src/main/ai-vault/scanners/codex/session-scanner-codex-parser.ts b/src/main/ai-vault/scanners/codex/session-scanner-codex-parser.ts index 23a03759b4a..4ff219883b5 100644 --- a/src/main/ai-vault/scanners/codex/session-scanner-codex-parser.ts +++ b/src/main/ai-vault/scanners/codex/session-scanner-codex-parser.ts @@ -1,9 +1,9 @@ import { waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { isZstdTranscriptPath, zstdTranscriptStream -} from '../../../native-chat/transcript-zstd-decompression' +} from '../../../native-chat/transcript-streaming/transcript-zstd-decompression' import { createInterface } from 'node:readline' import type { AiVaultSession, diff --git a/src/main/ai-vault/scanners/codex/session-scanner-codex-title-index.ts b/src/main/ai-vault/scanners/codex/session-scanner-codex-title-index.ts index 0b8c6968001..b3424555ce2 100644 --- a/src/main/ai-vault/scanners/codex/session-scanner-codex-title-index.ts +++ b/src/main/ai-vault/scanners/codex/session-scanner-codex-title-index.ts @@ -6,7 +6,7 @@ import { InFlightPromiseDedupe, stableInFlightKey } from '../../../../shared/in-flight-promise-dedupe' -import { wslGatedStat } from '../../../native-chat/wsl-transcript-fs-access' +import { wslGatedStat } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' // Thread names may arrive here after the transcript was written. diff --git a/src/main/ai-vault/scanners/copilot/session-scanner-copilot-parser.ts b/src/main/ai-vault/scanners/copilot/session-scanner-copilot-parser.ts index 8d6c77494c5..143616818ea 100644 --- a/src/main/ai-vault/scanners/copilot/session-scanner-copilot-parser.ts +++ b/src/main/ai-vault/scanners/copilot/session-scanner-copilot-parser.ts @@ -1,4 +1,4 @@ -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.test.ts b/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.test.ts index 5a9f833c542..3f051c82c6b 100644 --- a/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.test.ts +++ b/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.test.ts @@ -2,7 +2,7 @@ import { mkdir, mkdtemp, rm, stat, utimes, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' let failNextChatsReaddir = false let failNextChatsRootReaddir = false @@ -10,53 +10,56 @@ let failMetaJsonReads = false let failMetaJsonStats: false | true | 'eacces' = false let cancelMetaJsonReads: AbortController | null = null let chatsRootReads = 0 -vi.mock('../../../native-chat/wsl-transcript-fs-access', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - wslGatedReaddir: ( - ...args: Parameters - ): ReturnType => { - if (args[0].endsWith('chats')) { - chatsRootReads += 1 - if (failNextChatsRootReaddir) { - failNextChatsRootReaddir = false +vi.mock( + '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access', + async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + wslGatedReaddir: ( + ...args: Parameters + ): ReturnType => { + if (args[0].endsWith('chats')) { + chatsRootReads += 1 + if (failNextChatsRootReaddir) { + failNextChatsRootReaddir = false + return Promise.reject(new WslTranscriptFsError('timeout', 'wsl fs timed out')) + } + } + if (failNextChatsReaddir && args[0].includes('workspace-hash')) { + failNextChatsReaddir = false return Promise.reject(new WslTranscriptFsError('timeout', 'wsl fs timed out')) } + return actual.wslGatedReaddir(...args) + }, + wslGatedReadFile: ( + ...args: Parameters + ): ReturnType => { + if (failMetaJsonReads && String(args[0]).endsWith('meta.json')) { + return Promise.reject(new WslTranscriptFsError('timeout', 'wsl fs timed out')) + } + if (cancelMetaJsonReads && String(args[0]).endsWith('meta.json')) { + cancelMetaJsonReads.abort() + return Promise.reject(new DOMException('This operation was aborted', 'AbortError')) + } + return actual.wslGatedReadFile(...args) + }, + wslGatedStat: ( + ...args: Parameters + ): ReturnType => { + if (failMetaJsonStats && String(args[0]).endsWith('meta.json')) { + return Promise.reject( + failMetaJsonStats === 'eacces' + ? Object.assign(new Error('permission denied'), { code: 'EACCES' }) + : new WslTranscriptFsError('timeout', 'wsl fs timed out') + ) + } + return actual.wslGatedStat(...args) } - if (failNextChatsReaddir && args[0].includes('workspace-hash')) { - failNextChatsReaddir = false - return Promise.reject(new WslTranscriptFsError('timeout', 'wsl fs timed out')) - } - return actual.wslGatedReaddir(...args) - }, - wslGatedReadFile: ( - ...args: Parameters - ): ReturnType => { - if (failMetaJsonReads && String(args[0]).endsWith('meta.json')) { - return Promise.reject(new WslTranscriptFsError('timeout', 'wsl fs timed out')) - } - if (cancelMetaJsonReads && String(args[0]).endsWith('meta.json')) { - cancelMetaJsonReads.abort() - return Promise.reject(new DOMException('This operation was aborted', 'AbortError')) - } - return actual.wslGatedReadFile(...args) - }, - wslGatedStat: ( - ...args: Parameters - ): ReturnType => { - if (failMetaJsonStats && String(args[0]).endsWith('meta.json')) { - return Promise.reject( - failMetaJsonStats === 'eacces' - ? Object.assign(new Error('permission denied'), { code: 'EACCES' }) - : new WslTranscriptFsError('timeout', 'wsl fs timed out') - ) - } - return actual.wslGatedStat(...args) } } -}) -import type * as WslTranscriptFsAccess from '../../../native-chat/wsl-transcript-fs-access' +) +import type * as WslTranscriptFsAccess from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { cursorChatMetaPath, resetCursorChatMetaIndexCacheForTests, diff --git a/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.ts b/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.ts index 6b85239146b..7993d69f723 100644 --- a/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.ts +++ b/src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.ts @@ -1,8 +1,11 @@ import { waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' import { AsyncLocalStorage } from 'node:async_hooks' import { basename, dirname, join } from 'node:path' -import { wslGatedReaddir, wslGatedStat } from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { + wslGatedReaddir, + wslGatedStat +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { timestampIso } from '../../parsing/session-scanner-accumulator' import { scanIssueTextFromError } from '../../parsing/session-scan-issues' import type { AiVaultScanIssueText } from '../../../../shared/ai-vault/scanning/ai-vault-scan-issue-copy' diff --git a/src/main/ai-vault/scanners/cursor/session-scanner-cursor-parser.ts b/src/main/ai-vault/scanners/cursor/session-scanner-cursor-parser.ts index 96bfcf4c87d..d859513a8fc 100644 --- a/src/main/ai-vault/scanners/cursor/session-scanner-cursor-parser.ts +++ b/src/main/ai-vault/scanners/cursor/session-scanner-cursor-parser.ts @@ -2,7 +2,7 @@ import { remoteSessionContentLines, type RemoteSessionContent } from '../../transcripts/remote-session-content-lines' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/main/ai-vault/scanners/devin/session-scanner-devin-db.ts b/src/main/ai-vault/scanners/devin/session-scanner-devin-db.ts index eb6efeb779b..a5c3a7c98b2 100644 --- a/src/main/ai-vault/scanners/devin/session-scanner-devin-db.ts +++ b/src/main/ai-vault/scanners/devin/session-scanner-devin-db.ts @@ -1,9 +1,9 @@ import { parseWslUncPath } from '../../../../shared/wsl-paths' import { wasWslDistroLastSeenRunningSettled } from '../../../wsl/discovery/wsl-background-distro-gate' -import { WSL_TRANSCRIPT_RUNNING_LEASE_MS } from '../../../native-chat/wsl-transcript-running-admission' +import { WSL_TRANSCRIPT_RUNNING_LEASE_MS } from '../../../native-chat/transcript-filesystem/wsl-transcript-running-admission' import { AsyncLocalStorage } from 'node:async_hooks' import { dirname, join } from 'node:path' -import { wslGatedStat } from '../../../native-chat/wsl-transcript-fs-access' +import { wslGatedStat } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { columnExists, tableExists } from '../../../opencode-usage/schema-helpers' import { readOpenCodeDatabase } from '../opencode/sqlite/session-scanner-opencode-sqlite-open' import type { SessionSidecarObservation } from '../../parse-cache/session-sidecar-stat' diff --git a/src/main/ai-vault/scanners/devin/session-scanner-devin-parser.ts b/src/main/ai-vault/scanners/devin/session-scanner-devin-parser.ts index 190ed724b1e..0eddb094f14 100644 --- a/src/main/ai-vault/scanners/devin/session-scanner-devin-parser.ts +++ b/src/main/ai-vault/scanners/devin/session-scanner-devin-parser.ts @@ -1,5 +1,5 @@ import { readStreamedSessionDocument } from '../../transcripts/session-document-stream' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { ExecutionHostId } from '../../../../shared/execution-host' import type { FileWithMtime, SessionAccumulator } from '../../session-scanner-types' diff --git a/src/main/ai-vault/scanners/droid/session-scanner-droid-parser.ts b/src/main/ai-vault/scanners/droid/session-scanner-droid-parser.ts index 4163f30dffb..ec5c64ab725 100644 --- a/src/main/ai-vault/scanners/droid/session-scanner-droid-parser.ts +++ b/src/main/ai-vault/scanners/droid/session-scanner-droid-parser.ts @@ -1,4 +1,4 @@ -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/main/ai-vault/scanners/gemini/session-scanner-gemini-parsers.ts b/src/main/ai-vault/scanners/gemini/session-scanner-gemini-parsers.ts index 4a4d3c88b7f..7fdbbabc8e1 100644 --- a/src/main/ai-vault/scanners/gemini/session-scanner-gemini-parsers.ts +++ b/src/main/ai-vault/scanners/gemini/session-scanner-gemini-parsers.ts @@ -1,6 +1,6 @@ import { readStreamedSessionDocument } from '../../transcripts/session-document-stream' import { remoteSessionContentLines } from '../../transcripts/remote-session-content-lines' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { diff --git a/src/main/ai-vault/scanners/grok/session-scanner-grok-parser.ts b/src/main/ai-vault/scanners/grok/session-scanner-grok-parser.ts index f904cc2877d..8f376f3b6f6 100644 --- a/src/main/ai-vault/scanners/grok/session-scanner-grok-parser.ts +++ b/src/main/ai-vault/scanners/grok/session-scanner-grok-parser.ts @@ -5,8 +5,8 @@ import { import { openTranscriptReadStream, wslGatedReadFile -} from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { dirname } from 'node:path' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' diff --git a/src/main/ai-vault/scanners/hermes/session-scanner-hermes-parser.ts b/src/main/ai-vault/scanners/hermes/session-scanner-hermes-parser.ts index dbde0e4957e..952934f5bbb 100644 --- a/src/main/ai-vault/scanners/hermes/session-scanner-hermes-parser.ts +++ b/src/main/ai-vault/scanners/hermes/session-scanner-hermes-parser.ts @@ -1,5 +1,5 @@ import { readStreamedSessionDocument } from '../../transcripts/session-document-stream' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { ExecutionHostId } from '../../../../shared/execution-host' import type { FileWithMtime, SessionAccumulator } from '../../session-scanner-types' diff --git a/src/main/ai-vault/scanners/jcode/session-scanner-jcode-parser.ts b/src/main/ai-vault/scanners/jcode/session-scanner-jcode-parser.ts index a1868042814..acd80343835 100644 --- a/src/main/ai-vault/scanners/jcode/session-scanner-jcode-parser.ts +++ b/src/main/ai-vault/scanners/jcode/session-scanner-jcode-parser.ts @@ -1,5 +1,5 @@ import { waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' -import { wslGatedReadFile } from '../../../native-chat/wsl-transcript-fs-access' +import { wslGatedReadFile } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { ExecutionHostId } from '../../../../shared/execution-host' import type { FileWithMtime } from '../../session-scanner-types' diff --git a/src/main/ai-vault/scanners/kimi/session-scanner-kimi-parser.ts b/src/main/ai-vault/scanners/kimi/session-scanner-kimi-parser.ts index bb2a2651929..f2513afb8fa 100644 --- a/src/main/ai-vault/scanners/kimi/session-scanner-kimi-parser.ts +++ b/src/main/ai-vault/scanners/kimi/session-scanner-kimi-parser.ts @@ -5,8 +5,8 @@ import { import { openTranscriptReadStream, wslGatedReadFile -} from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import { diff --git a/src/main/ai-vault/scanners/kimi/session-scanner-kimi-paths.ts b/src/main/ai-vault/scanners/kimi/session-scanner-kimi-paths.ts index 224620d2d36..1f530c74b36 100644 --- a/src/main/ai-vault/scanners/kimi/session-scanner-kimi-paths.ts +++ b/src/main/ai-vault/scanners/kimi/session-scanner-kimi-paths.ts @@ -9,8 +9,8 @@ import { resolveAbsoluteDirOverride } from '../../../../shared/absolute-dir-over import { openTranscriptReadStream, wslGatedStat -} from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { asRecord } from '../../parsing/session-record-fields' import { extractString } from '../../../../shared/trimmed-string' import { KimiSessionIndexCache } from './session-scanner-kimi-index-cache' diff --git a/src/main/ai-vault/scanners/kiro/session-scanner-kiro-parser.ts b/src/main/ai-vault/scanners/kiro/session-scanner-kiro-parser.ts index e5e13484052..a6a787b5668 100644 --- a/src/main/ai-vault/scanners/kiro/session-scanner-kiro-parser.ts +++ b/src/main/ai-vault/scanners/kiro/session-scanner-kiro-parser.ts @@ -1,7 +1,7 @@ import { waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' import { basename } from 'node:path' -import { wslGatedReadFile } from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { wslGatedReadFile } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import { kiroTranscriptPathForMetadata } from '../../../../shared/ai-vault/session-display/ai-vault-session-log-resource' import type { ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/main/ai-vault/scanners/muse/session-scanner-muse-parser.ts b/src/main/ai-vault/scanners/muse/session-scanner-muse-parser.ts index f5c09005016..04ecd55d2ed 100644 --- a/src/main/ai-vault/scanners/muse/session-scanner-muse-parser.ts +++ b/src/main/ai-vault/scanners/muse/session-scanner-muse-parser.ts @@ -1,5 +1,5 @@ import { waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' -import { wslGatedReadFile } from '../../../native-chat/wsl-transcript-fs-access' +import { wslGatedReadFile } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import { unwrapMuseLogRecords } from '../../../../shared/muse-session-log' import type { ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-listing.ts b/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-listing.ts index 8e6341d5d20..bb550920c52 100644 --- a/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-listing.ts +++ b/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-listing.ts @@ -4,8 +4,11 @@ import type { AiVaultSession, AiVaultSubagentListResult } from '../../../../shared/ai-vault/contracts/ai-vault-types' -import { wslGatedReaddir, wslGatedStat } from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { + wslGatedReaddir, + wslGatedStat +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { recordSessionScanIssue, scanIssueTextFromError } from '../../parsing/session-scan-issues' import { sessionIdFromFileName, sessionSortTime } from '../../parsing/session-scanner-accumulator' import { parseMessageGraphSessionFile } from '../session-scanner-graph-parsers' diff --git a/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-transcripts.ts b/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-transcripts.ts index eb88e796fff..dc86457f257 100644 --- a/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-transcripts.ts +++ b/src/main/ai-vault/scanners/omp/session-scanner-omp-subagent-transcripts.ts @@ -2,7 +2,7 @@ import { throwIfAiVaultScanCancelled, waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' -import { wslGatedReaddir } from '../../../native-chat/wsl-transcript-fs-access' +import { wslGatedReaddir } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { basename, dirname, extname, join } from 'node:path' import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' import type { SubagentTranscriptPartition } from '../claude/session-scanner-subagent-transcripts' diff --git a/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts b/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts index b21d69cf8b6..e4c55153c3a 100644 --- a/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts +++ b/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' +import { mkdtemp, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { runProcess } from '@orca/process-host' @@ -23,7 +23,7 @@ vi.mock('../../../../../shared/app-environment', () => ({ })) vi.mock('../../../../wsl/execution/wsl-runner', () => ({ runWslProcess: mocks.run })) // The admission gate asks the running list for the path's distro; `running` stands in for it. -vi.mock('../../../../native-chat/wsl-transcript-running-admission', () => ({ +vi.mock('../../../../native-chat/transcript-filesystem/wsl-transcript-running-admission', () => ({ admitWslTranscriptPath: async (path: string) => { if ((await mocks.running([path], { requireConfirmed: true })).length === 0) { throw new Error('WSL distro is not running.') @@ -105,34 +105,4 @@ describe('WSL SQLite runtime preparation', () => { await rm(directory, { recursive: true, force: true }) } }) - - it.skipIf(process.platform === 'win32').each(['opencode', 'zcode'] as const)( - 'presence script finds %s-only data despite unrelated OpenCode overrides', - async (agent) => { - await prepared() - const script = mocks.run.mock.calls[0]?.[0].script - const directory = await mkdtemp(join(tmpdir(), 'orca-wsl-presence-')) - try { - const data = - agent === 'zcode' - ? join(directory, '.zcode', 'cli', 'db') - : join(directory, '.local', 'share', 'opencode') - await mkdir(data, { recursive: true }) - await writeFile(join(data, agent === 'zcode' ? 'db.sqlite' : 'opencode.db'), '') - const result = await runProcess({ - program: '/bin/sh', - args: ['-c', script], - env: { - HOME: directory, - XDG_DATA_HOME: join(directory, 'empty'), - OPENCODE_DB: ':memory:' - } - }) - expect(result.code).toBe(0) - expect(result.stdout).toBe('present') - } finally { - await rm(directory, { recursive: true, force: true }) - } - } - ) }) diff --git a/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.ts b/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.ts index 1ee40f4475c..d0c03574c1d 100644 --- a/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.ts +++ b/src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.ts @@ -20,7 +20,7 @@ import { REMOTE_NODE_RUNTIME_READY } from '../../../../managed-server/install/or import { getRemoteHostPlatform } from '../../../../ssh/remote-host/ssh-remote-platform' import { runWslProcess, type WslSpec } from '../../../../wsl/execution/wsl-runner' import { describeWslProcessFailure } from '../../../../wsl/execution/wsl-process-failure' -import { admitWslTranscriptPath } from '../../../../native-chat/wsl-transcript-running-admission' +import { admitWslTranscriptPath } from '../../../../native-chat/transcript-filesystem/wsl-transcript-running-admission' import { AI_VAULT_SCAN_ISSUE_COPY } from '../../../../../shared/ai-vault/scanning/ai-vault-scan-issue-copy' import type { OpenCodeWslRuntime } from './session-scanner-opencode-wsl-runtime' diff --git a/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-fork-coverage.test.ts b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-fork-coverage.test.ts new file mode 100644 index 00000000000..0ecabf312ae --- /dev/null +++ b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-fork-coverage.test.ts @@ -0,0 +1,42 @@ +import { openCode2ForkStore } from './transcript-opencode2-fork.test-fixture' +import { handleOpenCodeSqliteRequest } from './session-scanner-opencode-sqlite-dispatch' +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, expect, it } from 'vitest' +import type Database from '../../../../sqlite/sync-database' + +const fixtures: { db: Database.Database; root: string }[] = [] +afterEach(() => { + for (const { db, root } of fixtures.splice(0)) { + db.close() + rmSync(root, { recursive: true, force: true }) + } +}) + +it('captures the M1 copied roots without events, excludes a racing CLI root, and honors before', async () => { + const root = mkdtempSync(join(tmpdir(), 'orca-native-opencode2-fork-')) + const path = join(root, 'opencode.db') + const db = openCode2ForkStore(path) + fixtures.push({ db, root }) + const request = { + id: 1, + kind: 'fork-coverage' as const, + dbPath: path, + sessionId: 'ses_e4d797526ffee7BYM6ri3gpmbg', + sourceSessionId: 'ses_e4d7a0c31ffejclsdjiiDszHMB' + } + expect(await handleOpenCodeSqliteRequest(request)).toEqual({ + id: 1, + ok: true, + value: ['msg_1b2868ada001HhsJYcY0OtPOjp_4'] + }) + expect( + await handleOpenCodeSqliteRequest({ ...request, sourceSessionId: 'wrong-source' }) + ).toMatchObject({ ok: false }) + db.exec(`UPDATE session_v2 SET fork_boundary = + '{"type":"before","messageID":"msg_1b285f9e10016X0Hu1rKOHAACe"}' + WHERE id = 'ses_e4d797526ffee7BYM6ri3gpmbg'; + DELETE FROM session_message WHERE session_id = 'ses_e4d797526ffee7BYM6ri3gpmbg' AND seq < 90;`) + expect(await handleOpenCodeSqliteRequest(request)).toEqual({ id: 1, ok: true, value: [] }) +}) diff --git a/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-dispatch.ts b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-dispatch.ts index 717a9b1a2b2..7886f59e510 100644 --- a/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-dispatch.ts +++ b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-dispatch.ts @@ -1,9 +1,9 @@ -import { readOpenCode2ForkCoverage } from '../../../../native-chat/transcript-opencode2-fork-coverage' +import { readOpenCode2ForkCoverage } from '../../../../native-chat/transcript-history/opencode/transcript-opencode2-fork-coverage' import { readOpenCodeDatabase } from '../sqlite/session-scanner-opencode-sqlite-open' import { readOpenCodeTranscriptPage, readOpenCodeTranscriptSignal -} from '../../../../native-chat/transcript-opencode-sqlite-query' +} from '../../../../native-chat/transcript-history/opencode/transcript-opencode-sqlite-query' import type { AiVaultScanIssue } from '../../../../../shared/ai-vault/contracts/ai-vault-types' import { captureOpenCodeSqliteSession } from '../sqlite/session-scanner-opencode-sqlite-capture' import { listOpenCodeSqliteSessions } from '../sqlite/session-scanner-opencode-sqlite-list' diff --git a/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-protocol.ts b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-protocol.ts index 98a82e1886f..84a81a0a3ec 100644 --- a/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-protocol.ts +++ b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-protocol.ts @@ -1,7 +1,7 @@ import type { OpenCodeTranscriptPage, OpenCodeTranscriptSignal -} from '../../../../native-chat/transcript-opencode-sqlite-query' +} from '../../../../native-chat/transcript-history/opencode/transcript-opencode-sqlite-query' import type { AiVaultScanIssue, AiVaultSession diff --git a/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-wsl-client.ts b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-wsl-client.ts index 0e9a68c1ef7..e0508517a5d 100644 --- a/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-wsl-client.ts +++ b/src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-wsl-client.ts @@ -7,7 +7,7 @@ import { buildWslExecArgs } from '../../../../../shared/wsl-login-shell-command' import { parseWslUncPath } from '../../../../../shared/wsl-paths' import { resolveWslExecutablePath } from '../../../../wsl/execution/wsl-executable-path' import { resolveWslInteropSpawnCwd } from '../../../../wsl/execution/wsl-interop-spawn-directory' -import { admitWslTranscriptPath } from '../../../../native-chat/wsl-transcript-running-admission' +import { admitWslTranscriptPath } from '../../../../native-chat/transcript-filesystem/wsl-transcript-running-admission' import { noteWslDistroMayHaveStopped } from '../../../../wsl/discovery/wsl-background-distro-gate' import { createOpenCodeSqliteProcessClient } from './session-scanner-opencode-sqlite-process-client' import type { OpenCodeSqliteWorkerClient } from './session-scanner-opencode-sqlite-worker-client' diff --git a/src/main/ai-vault/scanners/opencode/reader-transport/transcript-opencode2-fork.test-fixture.ts b/src/main/ai-vault/scanners/opencode/reader-transport/transcript-opencode2-fork.test-fixture.ts new file mode 100644 index 00000000000..564830610ae --- /dev/null +++ b/src/main/ai-vault/scanners/opencode/reader-transport/transcript-opencode2-fork.test-fixture.ts @@ -0,0 +1,50 @@ +import Database from '../../../../sqlite/sync-database' + +// OpenCode 2.0.21 M1: copied seqs keep a fresh prefix; the event table is empty. +export function openCode2ForkStore(path: string) { + const db = new Database(path) + db.exec(`CREATE TABLE session_v2 (id TEXT PRIMARY KEY, parent_id TEXT, fork_session_id TEXT, fork_boundary TEXT); + CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, type TEXT, + seq INTEGER, data TEXT, time_created INTEGER, time_updated INTEGER); + CREATE TABLE event (id TEXT PRIMARY KEY, aggregate_id TEXT, type TEXT, seq INTEGER); + INSERT INTO session_v2 VALUES ('ses_e4d7a0c31ffejclsdjiiDszHMB', NULL, NULL, NULL); + INSERT INTO session_v2 VALUES ('ses_e4d797526ffee7BYM6ri3gpmbg', NULL, + 'ses_e4d7a0c31ffejclsdjiiDszHMB', '{"type":"through","messageID":"msg_1b2868a8e0026IQTGMmj77bBFi"}');`) + const insert = db.prepare('INSERT INTO session_message VALUES (?, ?, ?, ?, ?, ?, ?)') + for (const [session, user, idle] of [ + [ + 'ses_e4d7a0c31ffejclsdjiiDszHMB', + 'msg_1b285f9e10016X0Hu1rKOHAACe', + 'msg_1b2868a8e0026IQTGMmj77bBFi' + ], + [ + 'ses_e4d797526ffee7BYM6ri3gpmbg', + 'msg_1b2868ada001HhsJYcY0OtPOjp_4', + 'msg_1b2868ada001HhsJYcY0OtPOjp_87' + ] + ]) { + insert.run( + user!, + session!, + 'user', + 4, + JSON.stringify({ files: [{ uri: 'file:///image.png' }] }), + 1, + 1 + ) + insert.run(idle!, session!, 'idle', 87, '{"outcome":"succeeded"}', 1, 1) + } + // Match copied timestamps deliberately: time is not provenance. + insert.run( + 'msg_1b2868b430019orGpEpk0E3liv', + 'ses_e4d797526ffee7BYM6ri3gpmbg', + 'user', + 90, + '{"text":"CLI after fork"}', + 1, + 1 + ) + return db +} + +// OpenCode 1.19.5 M1: regenerated message IDs and empty newer-schema tables. diff --git a/src/main/ai-vault/scanners/opencode/session-scanner-opencode-parser.ts b/src/main/ai-vault/scanners/opencode/session-scanner-opencode-parser.ts index 9068302f9c6..431e219211b 100644 --- a/src/main/ai-vault/scanners/opencode/session-scanner-opencode-parser.ts +++ b/src/main/ai-vault/scanners/opencode/session-scanner-opencode-parser.ts @@ -2,8 +2,11 @@ import { throwIfAiVaultScanCancelled, waitForAiVaultScan } from '../../scan-lifecycle/ai-vault-scan-cancellation' -import { wslGatedReaddir, wslGatedReadFile } from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { + wslGatedReaddir, + wslGatedReadFile +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { basename, dirname, join } from 'node:path' import type { AiVaultSession } from '../../../../shared/ai-vault/contracts/ai-vault-types' import type { FileWithMtime, SessionAccumulator } from '../../session-scanner-types' diff --git a/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources-wsl-stall.test.ts b/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources-wsl-stall.test.ts index 973d8d24ce2..6ac8171eb91 100644 --- a/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources-wsl-stall.test.ts +++ b/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources-wsl-stall.test.ts @@ -23,7 +23,7 @@ import { opencodeDiscoveries } from './session-scanner-opencode-sources' import { resetWslTranscriptFsGateForTests, WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS -} from '../../../native-chat/wsl-transcript-fs-gate' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-gate' let releaseStall: (() => void) | undefined diff --git a/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources.ts b/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources.ts index 7a6401d4605..4152f68f1dc 100644 --- a/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources.ts +++ b/src/main/ai-vault/scanners/opencode/session-scanner-opencode-sources.ts @@ -1,6 +1,6 @@ import { basename, dirname, join } from 'node:path' import type { AiVaultScanIssue } from '../../../../shared/ai-vault/contracts/ai-vault-types' -import type { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import type { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { resolveOpenCodeStorageDirectory } from '../../../opencode/accounts/opencode-data-directory' import { listOpenCodeDatabases, diff --git a/src/main/ai-vault/scanners/qoder/session-scanner-qoder-parser.ts b/src/main/ai-vault/scanners/qoder/session-scanner-qoder-parser.ts index a0245b3759a..462bda85e2e 100644 --- a/src/main/ai-vault/scanners/qoder/session-scanner-qoder-parser.ts +++ b/src/main/ai-vault/scanners/qoder/session-scanner-qoder-parser.ts @@ -1,6 +1,6 @@ import { createInterface } from 'node:readline' import type { ExecutionHostId } from '../../../../shared/execution-host' -import { openTranscriptReadStream } from '../../../native-chat/wsl-transcript-fs-access' +import { openTranscriptReadStream } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { accumulatorSessionIdentity, updateTimeline diff --git a/src/main/ai-vault/scanners/session-scanner-graph-parsers.ts b/src/main/ai-vault/scanners/session-scanner-graph-parsers.ts index 3a781b834ca..767eb196a9f 100644 --- a/src/main/ai-vault/scanners/session-scanner-graph-parsers.ts +++ b/src/main/ai-vault/scanners/session-scanner-graph-parsers.ts @@ -10,7 +10,7 @@ import { import { openTranscriptReadStream, wslGatedReadFile -} from '../../native-chat/wsl-transcript-fs-access' +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { basename, dirname } from 'node:path' import { createInterface } from 'node:readline' import type { AiVaultSession } from '../../../shared/ai-vault/contracts/ai-vault-types' diff --git a/src/main/ai-vault/scanners/zcode/session-scanner-zcode-sources.ts b/src/main/ai-vault/scanners/zcode/session-scanner-zcode-sources.ts index ee7d615a4f7..f84ea7724ed 100644 --- a/src/main/ai-vault/scanners/zcode/session-scanner-zcode-sources.ts +++ b/src/main/ai-vault/scanners/zcode/session-scanner-zcode-sources.ts @@ -2,7 +2,7 @@ import { homedir } from 'node:os' import { join } from 'node:path' import type { AiVaultScanIssue } from '../../../../shared/ai-vault/contracts/ai-vault-types' import { throwIfSignalAborted } from '../../../../shared/abort-signal-reason' -import { wslGatedAccess } from '../../../native-chat/wsl-transcript-fs-access' +import { wslGatedAccess } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { listZcodeSqliteSessionsViaWorker } from '../opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn' import { scanIssueTextFromError } from '../../parsing/session-scan-issues' import type { AiVaultScanOptions, SessionFileDiscovery } from '../../session-scanner-types' diff --git a/src/main/ai-vault/scope/session-scanner-scope-discovery.ts b/src/main/ai-vault/scope/session-scanner-scope-discovery.ts index b07dbab1aac..f38f478658e 100644 --- a/src/main/ai-vault/scope/session-scanner-scope-discovery.ts +++ b/src/main/ai-vault/scope/session-scanner-scope-discovery.ts @@ -4,7 +4,7 @@ import { openTranscriptReadStream, wslGatedReaddir, wslGatedStat -} from '../../native-chat/wsl-transcript-fs-access' +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import type { createAiVaultScopeCwdMatcher } from '../../../shared/ai-vault/scanning/ai-vault-scope-cwd-match' import type { CwdBucketLayout } from './session-cwd-bucket-layouts' import type { diff --git a/src/main/ai-vault/scope/session-scanner-scope-inventory.test.ts b/src/main/ai-vault/scope/session-scanner-scope-inventory.test.ts index 4f0dbce92bf..4909b96049e 100644 --- a/src/main/ai-vault/scope/session-scanner-scope-inventory.test.ts +++ b/src/main/ai-vault/scope/session-scanner-scope-inventory.test.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, expect, it, vi } from 'vitest' -import * as transcriptFs from '../../native-chat/wsl-transcript-fs-access' +import * as transcriptFs from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { aiVaultScanWork, trackAiVaultScanProgress } from '../scan-lifecycle/ai-vault-scan-progress' diff --git a/src/main/ai-vault/scope/session-scanner-scope-inventory.ts b/src/main/ai-vault/scope/session-scanner-scope-inventory.ts index 8eef850e6c8..394809ddc77 100644 --- a/src/main/ai-vault/scope/session-scanner-scope-inventory.ts +++ b/src/main/ai-vault/scope/session-scanner-scope-inventory.ts @@ -1,7 +1,7 @@ import { extname, join } from 'node:path' import { yieldToEventLoop } from '../../../shared/event-loop-yield' import type { AiVaultScanIssue } from '../../../shared/ai-vault/contracts/ai-vault-types' -import { wslGatedReaddir } from '../../native-chat/wsl-transcript-fs-access' +import { wslGatedReaddir } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { throwIfAiVaultScanCancelled } from '../scan-lifecycle/ai-vault-scan-cancellation' import { openAiVaultScanSource } from '../scan-lifecycle/ai-vault-scan-progress' import { SCAN_ISSUE_LIMIT, recordSessionScanIssue } from '../parsing/session-scan-issues' diff --git a/src/main/ai-vault/scope/session-scope-cwd.ts b/src/main/ai-vault/scope/session-scope-cwd.ts index 9a7639870b5..be76091dbd5 100644 --- a/src/main/ai-vault/scope/session-scope-cwd.ts +++ b/src/main/ai-vault/scope/session-scope-cwd.ts @@ -3,12 +3,12 @@ import { openTranscriptReadStream, readTranscriptSlice, wslGatedReadFile -} from '../../native-chat/wsl-transcript-fs-access' +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { isZstdTranscriptPath, readZstdTranscriptSlice, zstdTranscriptStream -} from '../../native-chat/transcript-zstd-decompression' +} from '../../native-chat/transcript-streaming/transcript-zstd-decompression' import { CLAUDE_CWD_BUCKET_LAYOUT, CODEBUDDY_CWD_BUCKET_LAYOUT, diff --git a/src/main/ai-vault/session-handoff-snapshot.ts b/src/main/ai-vault/session-handoff-snapshot.ts index 7c574de15b1..39f925bb743 100644 --- a/src/main/ai-vault/session-handoff-snapshot.ts +++ b/src/main/ai-vault/session-handoff-snapshot.ts @@ -10,14 +10,14 @@ import { } from '../../shared/ai-vault/contracts/ai-vault-session-handoff' import { formatWorkerTranscriptMessage } from '../../shared/worker-transcript-text' import { isWslUncPath, toLinuxPath } from '../../shared/wsl-paths' -import { readOpenCodeNativeChatTranscriptFull } from '../native-chat/transcript-opencode' -import { writeAgentSessionContextSnapshot } from '../native-chat/agent-session-context-snapshots' -import { wakeNativeChatTranscriptDistro } from '../native-chat/native-chat-open-wake' +import { readOpenCodeNativeChatTranscriptFull } from '../native-chat/transcript-history/opencode/transcript-opencode' +import { writeAgentSessionContextSnapshot } from '../native-chat/conversation-context/agent-session-context-snapshots' +import { wakeNativeChatTranscriptDistro } from '../native-chat/transcript-discovery/native-chat-open-wake' import { getProfileUserDataPath } from '../orca-profiles/profile-storage-paths' import { wslDistroForPath } from '../wsl/filesystem/wsl-unc-access-gate' import { getAiVaultWslScanHomeDirs } from './discovery/ai-vault-scan-roots' import { openCodeWslPath } from './scanners/opencode/reader-transport/session-scanner-opencode-wsl-client' -import { configureOpenCodeTranscriptWslReaders } from '../native-chat/transcript-opencode-database' +import { configureOpenCodeTranscriptWslReaders } from '../native-chat/transcript-history/opencode/transcript-opencode-database' /** Copies one database-backed conversation into its own hand-off snapshot on this host, * so the new agent never gets a path to the account-wide database. */ diff --git a/src/main/ai-vault/session-titles/session-title-file-reader-wsl-stall.test.ts b/src/main/ai-vault/session-titles/session-title-file-reader-wsl-stall.test.ts index 2ee037fc239..27b72a10404 100644 --- a/src/main/ai-vault/session-titles/session-title-file-reader-wsl-stall.test.ts +++ b/src/main/ai-vault/session-titles/session-title-file-reader-wsl-stall.test.ts @@ -26,7 +26,7 @@ import { resetSessionParseCacheForTests } from '../parse-cache/session-parse-cac import { resetWslTranscriptFsGateForTests, WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS -} from '../../native-chat/wsl-transcript-fs-gate' +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-gate' let releaseStall: (() => void) | undefined let tempRoot: string | undefined diff --git a/src/main/ai-vault/session-titles/session-title-file-reader.ts b/src/main/ai-vault/session-titles/session-title-file-reader.ts index 993d3f7f0b4..d81eda28a90 100644 --- a/src/main/ai-vault/session-titles/session-title-file-reader.ts +++ b/src/main/ai-vault/session-titles/session-title-file-reader.ts @@ -1,4 +1,4 @@ -import { wslGatedLstat } from '../../native-chat/wsl-transcript-fs-access' +import { wslGatedLstat } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { AI_VAULT_SESSION_TITLE_REQUEST_MAX_COUNT, type AiVaultSessionTitle, diff --git a/src/main/ai-vault/session-titles/session-title-request-paths.ts b/src/main/ai-vault/session-titles/session-title-request-paths.ts index 47161e5a545..fed9584a516 100644 --- a/src/main/ai-vault/session-titles/session-title-request-paths.ts +++ b/src/main/ai-vault/session-titles/session-title-request-paths.ts @@ -1,6 +1,6 @@ import type { AiVaultSessionTitleRequest } from '../../../shared/ai-vault/contracts/ai-vault-session-title' -import { toHostReadableTranscriptPath } from '../../native-chat/host-readable-transcript-path' -import { wslTranscriptFsRefusal } from '../../native-chat/wsl-transcript-fs-error' +import { toHostReadableTranscriptPath } from '../../native-chat/transcript-discovery/host-readable-transcript-path' +import { wslTranscriptFsRefusal } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-error' export function resolveHostReadableAiVaultTitleRequests( requests: AiVaultSessionTitleRequest[], diff --git a/src/main/ai-vault/structured-sessions/structured-session-ownership.ts b/src/main/ai-vault/structured-sessions/structured-session-ownership.ts index e2252c72393..3a60eed1538 100644 --- a/src/main/ai-vault/structured-sessions/structured-session-ownership.ts +++ b/src/main/ai-vault/structured-sessions/structured-session-ownership.ts @@ -32,7 +32,7 @@ import { sessionHistoryBinaryNames, type StructuredAgentResumeInvocation, type StructuredAgentSessionHistory -} from '../../native-chat/structured-agent-cli-conversations' +} from '../../native-chat/conversation-adoption/structured-agent-cli-conversations' import { STRUCTURED_AGENT_RUNTIME_METADATA } from '../../runtime/agent-session/installation/structured-agent-runtime-metadata' import type { StructuredAgentRuntimeMetadata } from '../../runtime/agent-session/installation/structured-agent-runtime-registration-types' import { commandName, commandProgramIndex, shellCommands } from './shell-command-words' diff --git a/src/main/ai-vault/transcripts/remote-session-content-lines.ts b/src/main/ai-vault/transcripts/remote-session-content-lines.ts index 84ec6cbf2fa..84a48ed03c4 100644 --- a/src/main/ai-vault/transcripts/remote-session-content-lines.ts +++ b/src/main/ai-vault/transcripts/remote-session-content-lines.ts @@ -1,4 +1,4 @@ -import { splitTranscriptStreamLines } from '../../native-chat/transcript-stream-lines' +import { splitTranscriptStreamLines } from '../../native-chat/transcript-streaming/transcript-stream-lines' import { setImmediate as yieldToEventLoop } from 'node:timers/promises' import { throwIfAiVaultScanCancelled } from '../scan-lifecycle/ai-vault-scan-cancellation' import { MAX_SESSION_TRANSCRIPT_RECORD_BYTES } from './session-transcript-record-budget' diff --git a/src/main/ai-vault/transcripts/session-transcript-reader.ts b/src/main/ai-vault/transcripts/session-transcript-reader.ts index 07689a706d3..e0fcbe2d354 100644 --- a/src/main/ai-vault/transcripts/session-transcript-reader.ts +++ b/src/main/ai-vault/transcripts/session-transcript-reader.ts @@ -3,11 +3,11 @@ import { waitForAiVaultScan } from '../scan-lifecycle/ai-vault-scan-cancellation' import { createHash } from 'node:crypto' -import { readTranscriptSlice } from '../../native-chat/wsl-transcript-fs-access' +import { readTranscriptSlice } from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import type { AiVaultSession } from '../../../shared/ai-vault/contracts/ai-vault-types' import { parseAgentSessionFile } from '../parsing/session-scanner-agent-parser' import { consumeCompleteJsonlLines } from '../parsing/session-scanner-jsonl-reader' -import { isZstdTranscriptPath } from '../../native-chat/transcript-zstd-decompression' +import { isZstdTranscriptPath } from '../../native-chat/transcript-streaming/transcript-zstd-decompression' import type { ResumableSessionParseState, SessionFileCandidate } from '../session-scanner-types' import { invalidateSessionParseCacheEntry, diff --git a/src/main/antigravity/antigravity-acp-launch-spec.ts b/src/main/antigravity/antigravity-acp-launch-spec.ts index 08bd78c65ef..0fcf40a9de8 100644 --- a/src/main/antigravity/antigravity-acp-launch-spec.ts +++ b/src/main/antigravity/antigravity-acp-launch-spec.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { homedir } from 'node:os' import type { AcpLaunchSpec } from '../acp/launch/acp-launch-contract' import { ANTIGRAVITY_ACP_DIALECT } from '../acp/acp-dialects/antigravity-dialect' -import type { StructuredAgentSessionHistory } from '../native-chat/structured-agent-cli-conversations' +import type { StructuredAgentSessionHistory } from '../native-chat/conversation-adoption/structured-agent-cli-conversations' import { AntigravityChatAuthConfigSchema, resolveAntigravityChatAuthConfig diff --git a/src/main/claude/child-process/claude-child-process-environment.ts b/src/main/claude/child-process/claude-child-process-environment.ts index c05fd804cb6..5272d7e00a1 100644 --- a/src/main/claude/child-process/claude-child-process-environment.ts +++ b/src/main/claude/child-process/claude-child-process-environment.ts @@ -6,7 +6,7 @@ import { stripInheritedAgentSessionEnv } from '../../../shared/agent-launch/star import { LEGACY_NATIVE_CHAT_VISUALS_DIR_ENV, NATIVE_CHAT_SCRATCH_DIR_ENV -} from '../../native-chat/native-chat-visuals-delivery' +} from '../../native-chat/conversation-files/native-chat-visuals-delivery' function cloneProcessEnv(source: NodeJS.ProcessEnv): Record { const env: Record = {} diff --git a/src/main/claude/claude-cli-conversations.ts b/src/main/claude/claude-cli-conversations.ts index 6a83df22ef2..3932bd830e0 100644 --- a/src/main/claude/claude-cli-conversations.ts +++ b/src/main/claude/claude-cli-conversations.ts @@ -8,7 +8,7 @@ import { type StructuredAgentResumeInvocation, type StructuredAgentCliConversations, type StructuredAgentSessionHistory -} from '../native-chat/structured-agent-cli-conversations' +} from '../native-chat/conversation-adoption/structured-agent-cli-conversations' const CLAUDE_TRANSCRIPT_IMPORT = directoryTranscriptImport({ conversationStore: ({ accountHomePath }) => join(accountHomePath, 'projects'), diff --git a/src/main/claude/conversation-naming/claude-naming-invocation.ts b/src/main/claude/conversation-naming/claude-naming-invocation.ts index 5d7c574a815..5c60d24d90a 100644 --- a/src/main/claude/conversation-naming/claude-naming-invocation.ts +++ b/src/main/claude/conversation-naming/claude-naming-invocation.ts @@ -4,7 +4,7 @@ import { getClaudeProfileRouter } from '../../claude-accounts/profiles/routing/c import { applyClaudeEnvPatch } from '../../claude-accounts/runtime-auth/environment' import { agentSessionPinnedLaunchDirectory } from '../../runtime/agent-session/records/agent-session-record-launch-directory' import type { StructuredAgentLaunchSources } from '../../runtime/agent-session/launch/structured-agent-launch-composition' -import { resolveStructuredAgentCommand } from '../../native-chat/structured-agent-command-resolution' +import { resolveStructuredAgentCommand } from '../../native-chat/agent-launch/structured-agent-command-resolution' import { claudeChildEnv, resolveClaudeChildEnvSources, diff --git a/src/main/claude/model-catalog/claude-model-catalog-probe.test.ts b/src/main/claude/model-catalog/claude-model-catalog-probe.test.ts index 5ea1984b47d..667762251d4 100644 --- a/src/main/claude/model-catalog/claude-model-catalog-probe.test.ts +++ b/src/main/claude/model-catalog/claude-model-catalog-probe.test.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { describe, expect, it } from 'vitest' import { createClaudeModelCatalogProbe } from './claude-model-catalog-probe' -import { resolveStructuredAgentCommand } from '../../native-chat/structured-agent-command-resolution' +import { resolveStructuredAgentCommand } from '../../native-chat/agent-launch/structured-agent-command-resolution' const AUTH_POLICY = { account: 'system' } as const diff --git a/src/main/claude/model-catalog/claude-model-catalog-probe.ts b/src/main/claude/model-catalog/claude-model-catalog-probe.ts index dc130eedcac..fee0027363d 100644 --- a/src/main/claude/model-catalog/claude-model-catalog-probe.ts +++ b/src/main/claude/model-catalog/claude-model-catalog-probe.ts @@ -3,7 +3,7 @@ import { waitForPromiseWithSignal } from '../../../shared/abort-signal-reason' import { assertStructuredAgentResolutionCurrent, type StructuredAgentResolutionOperation -} from '../../native-chat/structured-agent-command-resolution' +} from '../../native-chat/agent-launch/structured-agent-command-resolution' import { buildClaudeModelListArgs, CLAUDE_MODEL_LIST_STDIN diff --git a/src/main/claude/structured/launch/claude-structured-child-env.test.ts b/src/main/claude/structured/launch/claude-structured-child-env.test.ts index b9c63b68c70..427888c2430 100644 --- a/src/main/claude/structured/launch/claude-structured-child-env.test.ts +++ b/src/main/claude/structured/launch/claude-structured-child-env.test.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { afterEach, expect, it, vi } from 'vitest' import { runProcess } from '@orca/process-host' import { claudeChildEnv, resolveClaudeChildEnvSources } from './claude-structured-child-env' -import { resolveStructuredAgentCommand } from '../../../native-chat/structured-agent-command-resolution' +import { resolveStructuredAgentCommand } from '../../../native-chat/agent-launch/structured-agent-command-resolution' afterEach(() => { vi.unstubAllEnvs() diff --git a/src/main/claude/structured/launch/claude-structured-child-env.ts b/src/main/claude/structured/launch/claude-structured-child-env.ts index e6be2562fb9..51f0b1cfcbe 100644 --- a/src/main/claude/structured/launch/claude-structured-child-env.ts +++ b/src/main/claude/structured/launch/claude-structured-child-env.ts @@ -5,7 +5,7 @@ import { resolveAgainAfterRefreshOnMissingProgram, type StructuredAgentCommand, type StructuredAgentResolutionOperation -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' // What a structured Claude child's environment is built from, shared by its launch and the // `--version` probe that runs the same binary. diff --git a/src/main/claude/structured/launch/claude-structured-launch-args.ts b/src/main/claude/structured/launch/claude-structured-launch-args.ts index 69ec4b82e4c..0dba1385778 100644 --- a/src/main/claude/structured/launch/claude-structured-launch-args.ts +++ b/src/main/claude/structured/launch/claude-structured-launch-args.ts @@ -1,4 +1,4 @@ -import { StructuredAgentArgumentsError } from '../../../native-chat/structured-agent-arguments-error' +import { StructuredAgentArgumentsError } from '../../../native-chat/agent-launch/structured-agent-arguments-error' /** Flags supplied by the SDK or owned by Orca's structured transport. */ const OWNED_FLAGS = new Set([ diff --git a/src/main/claude/structured/launch/claude-structured-launch-flags.ts b/src/main/claude/structured/launch/claude-structured-launch-flags.ts index ae4813827a7..de39f664019 100644 --- a/src/main/claude/structured/launch/claude-structured-launch-flags.ts +++ b/src/main/claude/structured/launch/claude-structured-launch-flags.ts @@ -1,7 +1,7 @@ import type { NativeChatVisualsLaunch, PrepareNativeChatVisuals -} from '../../../native-chat/native-chat-visuals-delivery' +} from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import { CLAUDE_CACHED_FLAG_CHECK_BUDGET_MS, CLAUDE_CLI_FLAG_PROBE_KILL_AFTER_MS, diff --git a/src/main/claude/structured/launch/claude-structured-launch-resolution.test.ts b/src/main/claude/structured/launch/claude-structured-launch-resolution.test.ts deleted file mode 100644 index 819f9564cf3..00000000000 --- a/src/main/claude/structured/launch/claude-structured-launch-resolution.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' -import { createClaudeStructuredLaunchResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition.test-support' -import { describe, expect, it, vi } from 'vitest' -import type { AgentSessionRecord } from '../../../../shared/agent-session/lifecycle/agent-session-record' -import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' -import { claudeProviderHandle } from '../../../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' - -const SESSION_ID = 'orca-session-1' -const IDENTITY = { sessionId: SESSION_ID } as Parameters< - ReturnType ->[0]['identity'] - -function record(overrides: Partial = {}): AgentSessionRecord { - return agentSessionRecordFixture({ - sessionId: SESSION_ID, - provider: 'claude', - location: { - executionHostId: LOCAL_EXECUTION_HOST_ID, - wslDistro: null, - workspaceId: 'workspace-1', - workspaceKind: 'folder' - }, - accountHome: { variable: 'CLAUDE_CONFIG_DIR', path: '/home/work/.claude' }, - providerHandleChain: [], - ...overrides - }) -} - -function identityAt(leafUuid: string | null): typeof IDENTITY { - return { - ...IDENTITY, - providerHandle: claudeProviderHandle('provider-current', leafUuid) - } -} - -function resolverFor( - value: AgentSessionRecord | null, - resolveEnv?: () => Record, - account: 'managed' | 'system' = 'system', - hasTranscript: () => Promise = async () => true, - resolveLaunchArgs?: () => string[], - attachmentDirectory?: string -) { - return createClaudeStructuredLaunchResolver({ - store: { getRecord: () => value, pinLaunchDirectory: vi.fn() }, - resolveWorkspacePath: async (id) => `/repos/${id}`, - resolveCommand: () => ({ command: '/usr/local/bin/claude', prefixArgs: [] }), - resolveAuthPolicy: () => ({ account }), - hasTranscript, - resolveLaunchArgs: resolveLaunchArgs ?? (() => value?.launchArgs ?? []), - ...(resolveEnv ? { resolveEnv } : {}), - ...(attachmentDirectory ? { attachmentDirectory } : {}) - }) -} - -const RESUMABLE = record({ - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resolver reads only each link's handle, so the link's other fields stay unset. - providerHandleChain: [ - { handle: claudeProviderHandle('provider-current', 'leaf-current') } - ] as AgentSessionRecord['providerHandleChain'] -}) - -describe('claude structured launch resolution', () => { - it('launches when only the bookkeeping leaf moved, and refuses a changed session', async () => { - const resolve = resolverFor(RESUMABLE) - - // A failed turn-end or exit write leaves the identity's leaf behind the record's. - await expect(resolve({ identity: identityAt('leaf-stale') })).resolves.toMatchObject({ - providerSessionId: 'provider-current', - resumeLeafUuid: 'leaf-current' - }) - await expect( - resolve({ - identity: { - ...IDENTITY, - providerHandle: claudeProviderHandle('provider-other', 'leaf-current') - } - }) - ).rejects.toThrow('durable resume identity changed before spawn') - }) -}) diff --git a/src/main/claude/structured/launch/claude-structured-launch-resolution.ts b/src/main/claude/structured/launch/claude-structured-launch-resolution.ts index 3f7f4495393..84ccd0f8eb2 100644 --- a/src/main/claude/structured/launch/claude-structured-launch-resolution.ts +++ b/src/main/claude/structured/launch/claude-structured-launch-resolution.ts @@ -1,6 +1,6 @@ import type { ClaudeAccountRecipe } from '../../../../shared/agent-session/lifecycle/agent-session-acquired-account' import { prepareClaudeLaunchAccount } from './claude-launch-account' -import { assertStructuredAgentResolutionCurrent } from '../../../native-chat/structured-agent-command-resolution' +import { assertStructuredAgentResolutionCurrent } from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { activeProviderContext } from '../../../../shared/agent-session/agent-session-provider-context' import { getClaudeProfileRouter } from '../../../claude-accounts/profiles/routing/claude-profile-installed-router' import { requireLegacyAgentSessionAccountHome } from '../../../../shared/agent-session/lifecycle/agent-session-account-home' @@ -31,7 +31,7 @@ import type { ClaudeCliFlagSupport } from '../../cli-capabilities/claude-cli-flag-support' import { resolveClaudeLaunchFlags } from './claude-structured-launch-flags' -import { withNativeChatVisualsEnv } from '../../../native-chat/native-chat-visuals-delivery' +import { withNativeChatVisualsEnv } from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import { claudeLaunchResumesTranscript } from './claude-structured-launch-home' import type { StructuredAgentLaunchPartResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition' import { claudeSessionIdForOrcaSession } from '../claude-structured-session-id' diff --git a/src/main/claude/transcripts/claude-conversation-copy.ts b/src/main/claude/transcripts/claude-conversation-copy.ts index 93a74b80733..52fde8eb027 100644 --- a/src/main/claude/transcripts/claude-conversation-copy.ts +++ b/src/main/claude/transcripts/claude-conversation-copy.ts @@ -1,7 +1,7 @@ import { basename, dirname, join } from 'node:path' import { requireLegacyAgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' import { getClaudeProfileRouter } from '../../claude-accounts/profiles/routing/claude-profile-installed-router' -import { AgentConversationCopyError } from '../../native-chat/agent-conversation-copy-error' +import { AgentConversationCopyError } from '../../native-chat/conversation-adoption/agent-conversation-copy-error' import type { StructuredAgentSessionAdapter } from '../../native-chat/agent-session-wire/providers/structured-agent-session-adapter' import { resolveSessionFilePath } from '../../native-chat/session-file-resolver' import { materializeClaudeConversationTranscript } from './claude-rewind-transcript' diff --git a/src/main/claude/transcripts/claude-conversation-fork-boundary.ts b/src/main/claude/transcripts/claude-conversation-fork-boundary.ts index 0f318e3fa65..019a661cfae 100644 --- a/src/main/claude/transcripts/claude-conversation-fork-boundary.ts +++ b/src/main/claude/transcripts/claude-conversation-fork-boundary.ts @@ -1,7 +1,7 @@ import type { SessionStoreEntry } from '@anthropic-ai/claude-agent-sdk' -import { AgentConversationCopyError } from '../../native-chat/agent-conversation-copy-error' +import { AgentConversationCopyError } from '../../native-chat/conversation-adoption/agent-conversation-copy-error' import type { StructuredAgentSessionAdapter } from '../../native-chat/agent-session-wire/providers/structured-agent-session-adapter' -import { decodeClaudeTranscriptLine } from '../../native-chat/transcript-line-decoders-claude' +import { decodeClaudeTranscriptLine } from '../../native-chat/transcript-decoding/transcript-line-decoders-claude' import { isLocalCommandCaveatRow } from './claude-rewind-fork' import { claudeSourceBlockId } from '../structured/journal/claude-streamed-block-identity' import { isClaudeTranscriptRootPrompt } from './claude-transcript-root-prompt' diff --git a/src/main/claude/transcripts/claude-conversation-fork-worker-entry.ts b/src/main/claude/transcripts/claude-conversation-fork-worker-entry.ts index 19099291b14..f11eeb3ac96 100644 --- a/src/main/claude/transcripts/claude-conversation-fork-worker-entry.ts +++ b/src/main/claude/transcripts/claude-conversation-fork-worker-entry.ts @@ -8,7 +8,7 @@ import { createInterface } from 'node:readline' import { parentPort, workerData } from 'node:worker_threads' import { publishClaudeConversationTranscript } from './claude-conversation-transcript-publication' -import { AgentConversationCopyError } from '../../native-chat/agent-conversation-copy-error' +import { AgentConversationCopyError } from '../../native-chat/conversation-adoption/agent-conversation-copy-error' import { claudeConversationForkBoundary, isClaudeConversationEntry, diff --git a/src/main/claude/transcripts/claude-provider-session-rename.test.ts b/src/main/claude/transcripts/claude-provider-session-rename.test.ts index d274d3e270a..e4f5aaffbcd 100644 --- a/src/main/claude/transcripts/claude-provider-session-rename.test.ts +++ b/src/main/claude/transcripts/claude-provider-session-rename.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import * as transcriptFs from '../../native-chat/wsl-transcript-fs-access' +import * as transcriptFs from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { renameClaudeTranscript } from './claude-provider-session-rename' let home: string diff --git a/src/main/claude/transcripts/claude-provider-session-rename.ts b/src/main/claude/transcripts/claude-provider-session-rename.ts index 28fbf384c65..5276737cb47 100644 --- a/src/main/claude/transcripts/claude-provider-session-rename.ts +++ b/src/main/claude/transcripts/claude-provider-session-rename.ts @@ -2,7 +2,10 @@ import type { SessionStoreEntry } from '@anthropic-ai/claude-agent-sdk' import { appendFile, open } from 'node:fs/promises' import { join } from 'node:path' import { isDefinitiveAbsence } from '../../../shared/definitive-filesystem-absence' -import { wslGatedReaddir, wslGatedStat } from '../../native-chat/wsl-transcript-fs-access' +import { + wslGatedReaddir, + wslGatedStat +} from '../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { loadClaudeAgentSdk } from '../claude-stream-json-connection' export async function renameClaudeTranscript( diff --git a/src/main/claude/transcripts/claude-rewind-fork.ts b/src/main/claude/transcripts/claude-rewind-fork.ts index 765fd763bcb..b2d28ad6989 100644 --- a/src/main/claude/transcripts/claude-rewind-fork.ts +++ b/src/main/claude/transcripts/claude-rewind-fork.ts @@ -9,7 +9,7 @@ import type { AgentSessionRewindFork } from '../../../shared/agent-session/lifec import { getClaudeProfileRouter } from '../../claude-accounts/profiles/routing/claude-profile-installed-router' import type { StructuredAgentSessionAdapter } from '../../native-chat/agent-session-wire/providers/structured-agent-session-adapter' import { resolveSessionFilePath } from '../../native-chat/session-file-resolver' -import { splitTranscriptStreamLines } from '../../native-chat/transcript-stream-lines' +import { splitTranscriptStreamLines } from '../../native-chat/transcript-streaming/transcript-stream-lines' import { claudeSessionIdForRewind } from '../structured/claude-structured-session-id' import { materializeClaudeRewindTranscript } from './claude-rewind-transcript' diff --git a/src/main/claude/transcripts/claude-rewind-transcript.ts b/src/main/claude/transcripts/claude-rewind-transcript.ts index 598cfb8bd1e..4fa975ff5f3 100644 --- a/src/main/claude/transcripts/claude-rewind-transcript.ts +++ b/src/main/claude/transcripts/claude-rewind-transcript.ts @@ -1,4 +1,4 @@ -import { AgentConversationCopyError } from '../../native-chat/agent-conversation-copy-error' +import { AgentConversationCopyError } from '../../native-chat/conversation-adoption/agent-conversation-copy-error' import { existsSync } from 'node:fs' import { rm } from 'node:fs/promises' import { dirname, join } from 'node:path' diff --git a/src/main/claude/transcripts/claude-transcript-prompt.ts b/src/main/claude/transcripts/claude-transcript-prompt.ts index a10383e886d..fa2417f7ef4 100644 --- a/src/main/claude/transcripts/claude-transcript-prompt.ts +++ b/src/main/claude/transcripts/claude-transcript-prompt.ts @@ -1,5 +1,5 @@ import { isClaudeToolResultContent } from '../../../shared/claude-tool-content' -import { decodeClaudeTranscriptLine } from '../../native-chat/transcript-line-decoders-claude' +import { decodeClaudeTranscriptLine } from '../../native-chat/transcript-decoding/transcript-line-decoders-claude' import { claudeEnvelopeStopHookFeedbackRow } from '../structured/journal/claude-stop-hook-feedback' import { claudeRecord, diff --git a/src/main/codex/codex-conversation-copy.test.ts b/src/main/codex/codex-conversation-copy.test.ts index 255afde830d..579f5e6560b 100644 --- a/src/main/codex/codex-conversation-copy.test.ts +++ b/src/main/codex/codex-conversation-copy.test.ts @@ -1,14 +1,14 @@ import type * as AppServerSession from './app-server/codex-app-server-session' -import type * as LocalHistory from '../native-chat/codex-local-history' -import type * as RolloutStream from '../native-chat/codex-rollout-history-stream' -import { readCodexLocalHistory } from '../native-chat/codex-local-history' -import { readCodexRolloutBytes } from '../native-chat/codex-rollout-history-stream' +import type * as LocalHistory from '../native-chat/transcript-history/codex/codex-local-history' +import type * as RolloutStream from '../native-chat/transcript-history/codex/codex-rollout-history-stream' +import { readCodexLocalHistory } from '../native-chat/transcript-history/codex/codex-local-history' +import { readCodexRolloutBytes } from '../native-chat/transcript-history/codex/codex-rollout-history-stream' import { mkdir, mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { agentJournalItemKey } from '../../shared/agent-session/journal/agent-session-journal-item-key' -import { agentSessionRecordFixture } from '../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../native-chat/testing/agent-session-record-test-fixture' import { copyCodexConversation } from './codex-conversation-copy' import { CodexAppServerTimeoutError } from './app-server/codex-app-server-session' import type { CodexAppServerRpc } from './app-server/codex-app-server-session' @@ -26,14 +26,17 @@ const state = vi.hoisted(() => { session } }) -vi.mock('../native-chat/codex-local-history', async (importOriginal) => { +vi.mock('../native-chat/transcript-history/codex/codex-local-history', async (importOriginal) => { const original = await importOriginal() return { ...original, readCodexLocalHistory: vi.fn(original.readCodexLocalHistory) } }) -vi.mock('../native-chat/codex-rollout-history-stream', async (importOriginal) => { - const original = await importOriginal() - return { ...original, readCodexRolloutBytes: vi.fn(original.readCodexRolloutBytes) } -}) +vi.mock( + '../native-chat/transcript-history/codex/codex-rollout-history-stream', + async (importOriginal) => { + const original = await importOriginal() + return { ...original, readCodexRolloutBytes: vi.fn(original.readCodexRolloutBytes) } + } +) vi.mock('../native-chat/session-file-resolver', () => ({ resolveSessionFilePath: async (_agent: string, id: string) => state.paths.get(id) ?? null })) diff --git a/src/main/codex/codex-conversation-copy.ts b/src/main/codex/codex-conversation-copy.ts index 56210bad650..0feafb610c2 100644 --- a/src/main/codex/codex-conversation-copy.ts +++ b/src/main/codex/codex-conversation-copy.ts @@ -4,7 +4,7 @@ import { AgentConversationCopyError, AgentConversationCopyStartError, agentConversationCopyRefusal -} from '../native-chat/agent-conversation-copy-error' +} from '../native-chat/conversation-adoption/agent-conversation-copy-error' import { isAgentSessionRefusalError } from '../../shared/agent-session/failures/agent-session-wire-refusals' import { join } from 'node:path' import { requireLegacyAgentSessionAccountHome } from '../../shared/agent-session/lifecycle/agent-session-account-home' diff --git a/src/main/codex/codex-fork-history.ts b/src/main/codex/codex-fork-history.ts index e985e3658b8..194112e0cf3 100644 --- a/src/main/codex/codex-fork-history.ts +++ b/src/main/codex/codex-fork-history.ts @@ -1,10 +1,10 @@ -import { AgentConversationCopyError } from '../native-chat/agent-conversation-copy-error' +import { AgentConversationCopyError } from '../native-chat/conversation-adoption/agent-conversation-copy-error' import { parseAgentJournalItemKey } from '../../shared/agent-session/journal/agent-session-journal-item-key' import { asRecord } from '../ai-vault/parsing/session-record-fields' import { parseJsonObject } from '../ai-vault/parsing/session-json-object' import { createLegacyIdentityTracker } from '../native-chat/agent-session-journal/history-import/journal-legacy-identity' -import { readCodexLocalHistory } from '../native-chat/codex-local-history' -import { decodeCodexTranscriptLine } from '../native-chat/transcript-line-decoders-codex' +import { readCodexLocalHistory } from '../native-chat/transcript-history/codex/codex-local-history' +import { decodeCodexTranscriptLine } from '../native-chat/transcript-decoding/transcript-line-decoders-codex' type Root = { id?: string; terminal: boolean; prompt: boolean } diff --git a/src/main/codex/history/journal-views/codex-cli-conversations.ts b/src/main/codex/history/journal-views/codex-cli-conversations.ts index 6a958b853dd..ba47a93a8b1 100644 --- a/src/main/codex/history/journal-views/codex-cli-conversations.ts +++ b/src/main/codex/history/journal-views/codex-cli-conversations.ts @@ -6,7 +6,7 @@ import { resumeInvocationAfterMarker, type StructuredAgentCliConversations, type StructuredAgentSessionHistory -} from '../../../native-chat/structured-agent-cli-conversations' +} from '../../../native-chat/conversation-adoption/structured-agent-cli-conversations' import { getOrcaManagedCodexHomePath, getSystemCodexHomePath diff --git a/src/main/codex/history/rollouts/codex-rollout-session-meta.ts b/src/main/codex/history/rollouts/codex-rollout-session-meta.ts index 74317f0f5de..a4c87ee826e 100644 --- a/src/main/codex/history/rollouts/codex-rollout-session-meta.ts +++ b/src/main/codex/history/rollouts/codex-rollout-session-meta.ts @@ -1,13 +1,13 @@ import { openTranscriptReadStream, readTranscriptSlice -} from '../../../native-chat/wsl-transcript-fs-access' +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' import { isZstdTranscriptPath, readZstdTranscriptSlice, zstdTranscriptStream -} from '../../../native-chat/transcript-zstd-decompression' -import type { WslTranscriptFsTaskPriority } from '../../../native-chat/wsl-transcript-fs-gate' +} from '../../../native-chat/transcript-streaming/transcript-zstd-decompression' +import type { WslTranscriptFsTaskPriority } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-gate' const ROLLOUT_READ_LIMIT = 64 * 1024 diff --git a/src/main/codex/structured/catalog/codex-at-rest-skills-probe.ts b/src/main/codex/structured/catalog/codex-at-rest-skills-probe.ts index cee82300f28..2d8b3f72445 100644 --- a/src/main/codex/structured/catalog/codex-at-rest-skills-probe.ts +++ b/src/main/codex/structured/catalog/codex-at-rest-skills-probe.ts @@ -9,7 +9,7 @@ import { codexStructuredLaunchArgs } from '../launch/codex-structured-launch-arg import { LEGACY_NATIVE_CHAT_VISUALS_DIR_ENV, NATIVE_CHAT_SCRATCH_DIR_ENV -} from '../../../native-chat/native-chat-visuals-delivery' +} from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import { AtRestCommandCatalog, type AtRestCommandScan diff --git a/src/main/codex/structured/catalog/codex-model-catalog-probe.ts b/src/main/codex/structured/catalog/codex-model-catalog-probe.ts index 94c26ee190c..2bc912e2a13 100644 --- a/src/main/codex/structured/catalog/codex-model-catalog-probe.ts +++ b/src/main/codex/structured/catalog/codex-model-catalog-probe.ts @@ -1,7 +1,7 @@ import { assertStructuredAgentResolutionCurrent, type StructuredAgentResolutionOperation -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { codexStructuredLaunchArgs } from '../launch/codex-structured-launch-args' import { overlayEnvironmentVariables } from '../../../../shared/environment-overlay' import { diff --git a/src/main/codex/structured/catalog/codex-structured-model-catalog.ts b/src/main/codex/structured/catalog/codex-structured-model-catalog.ts index b15e3e58291..55f15f01174 100644 --- a/src/main/codex/structured/catalog/codex-structured-model-catalog.ts +++ b/src/main/codex/structured/catalog/codex-structured-model-catalog.ts @@ -8,7 +8,7 @@ import type { CodexAppServerConnection } from '../../app-server/codex-app-server import { assertStructuredAgentResolutionCurrent, type StructuredAgentResolutionOperation -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { structuredAgentSessionOptionModels } from '../../../native-chat/agent-session-wire/options/structured-agent-session-option-models' import { CODEX_DEFAULT_SERVICE_TIER, diff --git a/src/main/codex/structured/launch/codex-cli-installation-error.ts b/src/main/codex/structured/launch/codex-cli-installation-error.ts index f462c1454cd..273e54fdbdf 100644 --- a/src/main/codex/structured/launch/codex-cli-installation-error.ts +++ b/src/main/codex/structured/launch/codex-cli-installation-error.ts @@ -7,7 +7,7 @@ import { readAgentCliVersion, type AgentCliVersionReader } from '../../../agent-launch/preflight/agent-cli-version-probe' -import { AgentCliInstallationError } from '../../../native-chat/agent-cli-installation-error' +import { AgentCliInstallationError } from '../../../native-chat/agent-launch/agent-cli-installation-error' import type { StructuredAgentLaunchResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition' import { resolveProviderChildEnv, diff --git a/src/main/codex/structured/launch/codex-structured-child-environment.test.ts b/src/main/codex/structured/launch/codex-structured-child-environment.test.ts index dd75c5b7e2c..160895eae9e 100644 --- a/src/main/codex/structured/launch/codex-structured-child-environment.test.ts +++ b/src/main/codex/structured/launch/codex-structured-child-environment.test.ts @@ -5,7 +5,7 @@ import { delimiter, join } from 'node:path' import { openCodexAppServerConnection } from '../../app-server/codex-app-server-connection' import { sealStructuredSessionChild } from '../../../runtime/agent-session/environment/structured-session-child-env' import { createCodexStructuredLaunchResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition.test-support' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import type { AgentSessionJournalIdentity } from '../../../../shared/agent-session/journal/agent-session-journal-identity' const DEV_CLI_BIN_FIRST = /^[^:;]*[\\/]cli[\\/]bin[:;]/ diff --git a/src/main/codex/structured/launch/codex-structured-launch-args.test.ts b/src/main/codex/structured/launch/codex-structured-launch-args.test.ts index bb33770e903..8dd64f65c43 100644 --- a/src/main/codex/structured/launch/codex-structured-launch-args.test.ts +++ b/src/main/codex/structured/launch/codex-structured-launch-args.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { codexStructuredLaunchArgs } from './codex-structured-launch-args' -import { StructuredAgentArgumentsError } from '../../../native-chat/structured-agent-arguments-error' +import { StructuredAgentArgumentsError } from '../../../native-chat/agent-launch/structured-agent-arguments-error' describe('codexStructuredLaunchArgs', () => { it('drops permission options and config overrides owned by Orca', () => { diff --git a/src/main/codex/structured/launch/codex-structured-launch-args.ts b/src/main/codex/structured/launch/codex-structured-launch-args.ts index 16a48d53662..17cfc15b19f 100644 --- a/src/main/codex/structured/launch/codex-structured-launch-args.ts +++ b/src/main/codex/structured/launch/codex-structured-launch-args.ts @@ -1,6 +1,6 @@ import { CODEX_PERMISSION_CONFIG_ROOTS } from '../../../../shared/agent-launch/permissions/agent-permission-args' import { parseTomlKeyPath } from '../../configuration/toml/config-toml-key-path' -import { StructuredAgentArgumentsError } from '../../../native-chat/structured-agent-arguments-error' +import { StructuredAgentArgumentsError } from '../../../native-chat/agent-launch/structured-agent-arguments-error' const VALUE_OPTIONS = new Set([ '-c', diff --git a/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts b/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts index e512dab1eba..a5bbb6d9c74 100644 --- a/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts +++ b/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts @@ -1,7 +1,7 @@ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import { createCodexStructuredLaunchResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition.test-support' import { describe, expect, it, vi } from 'vitest' import type { AgentSessionRecord } from '../../../../shared/agent-session/lifecycle/agent-session-record' diff --git a/src/main/codex/structured/launch/codex-structured-launch-resolution.ts b/src/main/codex/structured/launch/codex-structured-launch-resolution.ts index 3e6cc7fc1e4..b560bb8bc8f 100644 --- a/src/main/codex/structured/launch/codex-structured-launch-resolution.ts +++ b/src/main/codex/structured/launch/codex-structured-launch-resolution.ts @@ -5,7 +5,7 @@ import { resolveAgainAfterRefreshOnMissingProgram, type StructuredAgentCommand, type StructuredAgentResolutionOperation -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' // How a durable session record becomes a Codex process launch. // // Every input except the account is read back from the record the store already made durable, @@ -35,7 +35,7 @@ import { LEGACY_NATIVE_CHAT_VISUALS_DIR_ENV, NATIVE_CHAT_SCRATCH_DIR_ENV, withNativeChatVisualsEnv -} from '../../../native-chat/native-chat-visuals-delivery' +} from '../../../native-chat/conversation-files/native-chat-visuals-delivery' export type CodexStructuredLaunchPartDeps = { resolveLaunchArgs: () => Promise | string[] diff --git a/src/main/codex/structured/launch/codex-structured-real-cli-visuals.test.ts b/src/main/codex/structured/launch/codex-structured-real-cli-visuals.test.ts index 2c6f0c48070..861d30181df 100644 --- a/src/main/codex/structured/launch/codex-structured-real-cli-visuals.test.ts +++ b/src/main/codex/structured/launch/codex-structured-real-cli-visuals.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { describe, expect, it } from 'vitest' import { resolveCodexCommand } from '../../../codex-cli/command' -import { NATIVE_CHAT_VISUALS_SKILL_NAME } from '../../../native-chat/native-chat-visuals-skill-location' +import { NATIVE_CHAT_VISUALS_SKILL_NAME } from '../../../native-chat/conversation-files/native-chat-visuals-skill-location' import { openCodexAppServerConnection } from '../../app-server/codex-app-server-connection' import { withCodexVisualsThreadConfig } from './codex-structured-visuals' import { codexStructuredPermissionPolicy } from '../../../../shared/codex-permission-policy' diff --git a/src/main/codex/structured/launch/codex-structured-visuals.test.ts b/src/main/codex/structured/launch/codex-structured-visuals.test.ts index d368ef26092..8f722c12e55 100644 --- a/src/main/codex/structured/launch/codex-structured-visuals.test.ts +++ b/src/main/codex/structured/launch/codex-structured-visuals.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import type { NativeChatVisualsLaunch } from '../../../native-chat/native-chat-visuals-delivery' +import type { NativeChatVisualsLaunch } from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import { CodexAppServerTimeoutError } from '../../app-server/codex-app-server-session' import { prepareCodexThreadForVisuals } from './codex-structured-visuals' diff --git a/src/main/codex/structured/launch/codex-structured-visuals.ts b/src/main/codex/structured/launch/codex-structured-visuals.ts index e98a2dad7f8..0595e96e70e 100644 --- a/src/main/codex/structured/launch/codex-structured-visuals.ts +++ b/src/main/codex/structured/launch/codex-structured-visuals.ts @@ -1,4 +1,4 @@ -import type { NativeChatVisualsLaunch } from '../../../native-chat/native-chat-visuals-delivery' +import type { NativeChatVisualsLaunch } from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import type { StructuredAgentSessionLogger } from '../../../native-chat/agent-session-wire/host/structured-agent-session-logger' import type { CodexAppServerConnection } from '../../app-server/codex-app-server-connection-types' import { isCodexAppServerRequestError } from '../../app-server/codex-app-server-request-error' diff --git a/src/main/codex/structured/session-lifecycle/codex-provider-session-rename.ts b/src/main/codex/structured/session-lifecycle/codex-provider-session-rename.ts index db55f095538..5a039df6e15 100644 --- a/src/main/codex/structured/session-lifecycle/codex-provider-session-rename.ts +++ b/src/main/codex/structured/session-lifecycle/codex-provider-session-rename.ts @@ -20,7 +20,7 @@ import { import { NATIVE_CHAT_SCRATCH_DIR_ENV, LEGACY_NATIVE_CHAT_VISUALS_DIR_ENV -} from '../../../native-chat/native-chat-visuals-delivery' +} from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import { CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS } from '../../../codex-cli/codex-read-only-app-server-args' import type { CodexSession } from './codex-structured-session-state' diff --git a/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts b/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts index c52d281af5c..af6652bbb46 100644 --- a/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts +++ b/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts @@ -21,7 +21,7 @@ import { codexProviderHandleLink } from '../acquisition/codex-structured-owner-i import { openCodexThread } from '../threads/codex-structured-thread-open' import { catchUpCodexThread } from '../threads/codex-structured-thread-catch-up' import { withCodexVisualsThreadConfig } from '../launch/codex-structured-visuals' -import { NATIVE_CHAT_SCRATCH_DIR_ENV } from '../../../native-chat/native-chat-visuals-delivery' +import { NATIVE_CHAT_SCRATCH_DIR_ENV } from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import { restoredCodexSessionOptions, reportedCodexSessionOptions diff --git a/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts b/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts index 6c7f91f90e3..e247973e02d 100644 --- a/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts +++ b/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts @@ -28,7 +28,7 @@ import type { StructuredLaunchRequest } from '../../../runtime/agent-session/lau import type { AgentModelCatalogStore } from '../../../native-chat/agent-model-catalog/agent-model-catalog-store' import type { AgentModelCatalogSessionAccess } from '../../../native-chat/agent-model-catalog/agent-model-catalog-lister' import type { StructuredAgentSessionProviderOpen } from '../../../native-chat/agent-session-wire/startup/structured-agent-session-provider-open-contract' -import type { NativeChatVisualsLaunch } from '../../../native-chat/native-chat-visuals-delivery' +import type { NativeChatVisualsLaunch } from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import type { AgentChatPermissionChoice } from '../../../../shared/agent-launch/permissions/agent-chat-permission-mode' import type { CodexSandboxPolicy } from '../launch/codex-structured-sandbox-policy' import type { StructuredAgentSessionEventSink } from '../../../native-chat/agent-session-wire/journal-ingress/structured-agent-session-event-sink' diff --git a/src/main/copilot/copilot-acp-launch-spec.ts b/src/main/copilot/copilot-acp-launch-spec.ts index 58526ad2795..668310ec331 100644 --- a/src/main/copilot/copilot-acp-launch-spec.ts +++ b/src/main/copilot/copilot-acp-launch-spec.ts @@ -7,7 +7,7 @@ import { resumeInvocationFromOptions, sessionFileResumeTargetMatches, type StructuredAgentSessionHistory -} from '../native-chat/structured-agent-cli-conversations' +} from '../native-chat/conversation-adoption/structured-agent-cli-conversations' // Chats and terminals share Copilot's session store; `--resume` also takes ID prefixes. const COPILOT_SESSION_HISTORY: StructuredAgentSessionHistory = { diff --git a/src/main/grok/grok-session-history.ts b/src/main/grok/grok-session-history.ts index 52dbecd6203..925e3ef53f5 100644 --- a/src/main/grok/grok-session-history.ts +++ b/src/main/grok/grok-session-history.ts @@ -2,7 +2,7 @@ import { ownOptionsInclude, resumeInvocationFromOptions, type StructuredAgentSessionHistory -} from '../native-chat/structured-agent-cli-conversations' +} from '../native-chat/conversation-adoption/structured-agent-cli-conversations' const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i diff --git a/src/main/grok/grok-transcript-import.ts b/src/main/grok/grok-transcript-import.ts index 5a40aa6744b..927e6505ce1 100644 --- a/src/main/grok/grok-transcript-import.ts +++ b/src/main/grok/grok-transcript-import.ts @@ -1,7 +1,7 @@ import { join } from 'node:path' import { findGrokChatHistoryByCwd, resolveGrokHomeDir } from '../../shared/grok-session-paths' import { resolveSessionFilePath } from '../native-chat/session-file-resolver' -import { directoryTranscriptImport } from '../native-chat/structured-agent-cli-conversations' +import { directoryTranscriptImport } from '../native-chat/conversation-adoption/structured-agent-cli-conversations' export const GROK_TRANSCRIPT_IMPORT = directoryTranscriptImport({ conversationStore: ({ accountHomePath }) => join(accountHomePath, 'sessions'), diff --git a/src/main/host/native-chat-visual-guest-theme.test.ts b/src/main/host/native-chat-visual-guest-theme.test.ts index da4fe5cd0d2..5fbbb7ec2e7 100644 --- a/src/main/host/native-chat-visual-guest-theme.test.ts +++ b/src/main/host/native-chat-visual-guest-theme.test.ts @@ -1,14 +1,8 @@ import { EventEmitter } from 'node:events' -import { mkdtempSync, rmSync } from 'node:fs' -import { join } from 'node:path' -import { tmpdir } from 'node:os' + import { beforeEach, describe, expect, it, vi } from 'vitest' import type { BrowserWindow, IpcMainEvent } from 'electron' -import type * as SiteDisplayController from '../browser/site-settings/display/browser-site-display-controller' -import { - configureBrowserSiteDisplayStore, - updateBrowserSiteDisplay -} from '../browser/site-settings/display/browser-site-display-store' + import { NATIVE_CHAT_VISUAL_GUEST_THEME_PUBLISH as PUBLISH, NATIVE_CHAT_VISUAL_GUEST_THEME_READ as READ @@ -30,10 +24,10 @@ vi.mock('electron', () => ({ ipcMain: ipc })) vi.mock('../orca-profiles/profile-storage-paths', () => ({ getProfileUserDataPath: () => '/profile' })) -vi.mock('../native-chat/native-chat-scratch-folder', () => ({ +vi.mock('../native-chat/conversation-files/native-chat-scratch-folder', () => ({ nativeChatScratchRootFor: () => '/profile/chat-scratch' })) -vi.mock('../native-chat/native-chat-visual-guest-location', () => ({ +vi.mock('../native-chat/conversation-files/native-chat-visual-guest-location', () => ({ isNativeChatVisualGuestLocation: classify })) vi.mock('../browser/site-settings/display/browser-site-display-controller', () => ({ @@ -47,9 +41,7 @@ vi.mock('../browser/routing/local-host-partitions/local-ssh-browser-partitions', })) import { installNativeChatVisualGuestTheme } from './native-chat-visual-guest-theme' -const { BrowserSiteDisplayController } = await vi.importActual( - '../browser/site-settings/display/browser-site-display-controller' -) +await vi.importActual('../browser/site-settings/display/browser-site-display-controller') function contents(id: number, hostWebContents: EventEmitter | null = null) { return Object.assign(new EventEmitter(), { @@ -118,49 +110,4 @@ describe('visual guest theme handoff', () => { read(ordinary) expect(ordinary.returnValue).toBeNull() }) - - it('applies saved media at the authenticated frame URL before every synchronous reply', () => { - const directory = mkdtempSync(join(tmpdir(), 'orca-visual-media-')) - configureBrowserSiteDisplayStore(directory) - updateBrowserSiteDisplay('work', 'file://', { colorScheme: 'light' }) - const controller = new BrowserSiteDisplayController() - display.onGuestDocumentStart.mockImplementation( - controller.onGuestDocumentStart.bind(controller) - ) - const { host, guest, event, publish, read } = setup() - const sent: string[] = [] - const debuggerEvents = new EventEmitter() - Object.assign(guest, { - getURL: () => '', - debugger: Object.assign(debuggerEvents, { - isAttached: () => true, - sendCommand: (_method: string, params: { features: { value: string }[] }) => { - sent.push(params.features[0].value) - return Promise.resolve({}) - } - }) - }) - profileForSession.mockReturnValue('work') - try { - publish(event(host), dark) - for (let document = 1; document <= 2; document++) { - read( - event(guest, guest.mainFrame, () => expect(sent).toEqual(Array(document).fill('light'))) - ) - } - } finally { - controller.onGuestRetired(guest.id) - rmSync(directory, { recursive: true, force: true }) - } - }) - - it('does not overwrite another window’s synchronous answer', () => { - const first = setup() - const second = setup() - first.publish(first.event(first.host), dark) - const request = first.event() - first.read(request) - second.read(request) - expect(request.returnValue).toMatchObject({ colorScheme: 'dark' }) - }) }) diff --git a/src/main/host/native-chat-visual-guest-theme.ts b/src/main/host/native-chat-visual-guest-theme.ts index 944ff287bcc..46379f90791 100644 --- a/src/main/host/native-chat-visual-guest-theme.ts +++ b/src/main/host/native-chat-visual-guest-theme.ts @@ -8,9 +8,9 @@ import { type NativeChatVisualGuestTheme } from '../../shared/native-chat/native-chat-visual-shell' import { getProfileUserDataPath } from '../orca-profiles/profile-storage-paths' -import { nativeChatScratchRootFor } from '../native-chat/native-chat-scratch-folder' +import { nativeChatScratchRootFor } from '../native-chat/conversation-files/native-chat-scratch-folder' import { browserSiteDisplayController } from '../browser/site-settings/display/browser-site-display-controller' -import { isNativeChatVisualGuestLocation } from '../native-chat/native-chat-visual-guest-location' +import { isNativeChatVisualGuestLocation } from '../native-chat/conversation-files/native-chat-visual-guest-location' import { browserSessionRegistry } from '../browser/sessions/profiles/browser-session-registry' import { localSshBrowserProfileIdForSession } from '../browser/routing/local-host-partitions/local-ssh-browser-partitions' diff --git a/src/main/ipc/ai-vault/ai-vault-resume.test.ts b/src/main/ipc/ai-vault/ai-vault-resume.test.ts index 71b39a1c419..ae130438f9b 100644 --- a/src/main/ipc/ai-vault/ai-vault-resume.test.ts +++ b/src/main/ipc/ai-vault/ai-vault-resume.test.ts @@ -8,7 +8,7 @@ import { prepareAiVaultSessionResume } from './ai-vault-resume' vi.mock('electron', () => ({ ipcMain: { handle: vi.fn() } })) const wakeMock = vi.hoisted(() => vi.fn(async (_distro: string | null) => {})) -vi.mock('../../native-chat/native-chat-open-wake', () => ({ +vi.mock('../../native-chat/transcript-discovery/native-chat-open-wake', () => ({ wakeNativeChatTranscriptDistro: wakeMock })) afterEach(() => setStructuredAgentSessionHost(null)) diff --git a/src/main/ipc/ai-vault/ai-vault-resume.ts b/src/main/ipc/ai-vault/ai-vault-resume.ts index d598526e895..2b5c4ffe7de 100644 --- a/src/main/ipc/ai-vault/ai-vault-resume.ts +++ b/src/main/ipc/ai-vault/ai-vault-resume.ts @@ -11,7 +11,7 @@ import { isWslUncPath } from '../../../shared/wsl-paths' import { assertLegacyAiVaultResumeAllowed } from '../../ai-vault/structured-sessions/structured-session-ownership' import { tryEnsureStructuredAgentSessionHostForResume } from '../../runtime/agent-session/structured-agent-session-host-refusal' import { copyStructuredChatConversation } from '../../ai-vault/structured-sessions/structured-chat-conversation-copy' -import { wakeNativeChatTranscriptDistro } from '../../native-chat/native-chat-open-wake' +import { wakeNativeChatTranscriptDistro } from '../../native-chat/transcript-discovery/native-chat-open-wake' import { wslDistroForPath } from '../../wsl/filesystem/wsl-unc-access-gate' import { prepareAiVaultSessionHandoff } from './ai-vault-handoff' import type { AiVaultSessionHandoffArgs } from '../../../shared/ai-vault/contracts/ai-vault-session-handoff' diff --git a/src/main/ipc/native-chat.ts b/src/main/ipc/native-chat.ts index f5f8461e647..1ea5a8d749b 100644 --- a/src/main/ipc/native-chat.ts +++ b/src/main/ipc/native-chat.ts @@ -1,4 +1,4 @@ -import { DESKTOP_READ_WINDOW } from '../native-chat/transcript-watch-contract' +import { DESKTOP_READ_WINDOW } from '../native-chat/transcript-watching/transcript-watch-contract' import { ipcMain, type IpcMainEvent, type WebContents } from 'electron' import type { AgentType } from '../../shared/agent-status/contracts/agent-status-types' import type { @@ -7,15 +7,15 @@ import type { } from '../../shared/native-chat/native-chat-types' import { clearNativeChatTranscriptCache } from '../native-chat/transcript-read-cache' import type { ReadTranscriptResult } from '../native-chat/transcript-reader' -import { - subscribeNativeChatTranscript, - readNativeChatTranscriptTail, - type NativeChatTranscriptSubscription, - type SubscribeNativeChatTranscriptArgs -} from '../native-chat/transcript-watch' +import { subscribeNativeChatTranscript } from '../native-chat/transcript-watch' +import { readNativeChatTranscriptTail } from '../native-chat/transcript-tail-reader' +import type { + NativeChatTranscriptSubscription, + SubscribeNativeChatTranscriptArgs +} from '../native-chat/transcript-watching/transcript-watch-contract' import { abortWhenRendererGone } from './renderer-lifetime-abort' import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire/host/structured-agent-session-registry' -import { openNativeChatTranscript } from '../native-chat/native-chat-open-wake' +import { openNativeChatTranscript } from '../native-chat/transcript-discovery/native-chat-open-wake' export { clearNativeChatTranscriptCache } diff --git a/src/main/ipc/pty/host-env/saved-resume-admission.ts b/src/main/ipc/pty/host-env/saved-resume-admission.ts index a96c8a5548b..ea38fcd398f 100644 --- a/src/main/ipc/pty/host-env/saved-resume-admission.ts +++ b/src/main/ipc/pty/host-env/saved-resume-admission.ts @@ -21,7 +21,7 @@ import type { WorkspaceSessionState } from '../../../../shared/workspace-session import { keysMayMatch, terminalConversationKey -} from '../../../native-chat/structured-conversation-key' +} from '../../../native-chat/conversation-adoption/structured-conversation-key' export type ResumeSpawnArgs = { connectionId?: string | null diff --git a/src/main/native-chat/__fixtures__/openclaude-0.31.0-local-command-rows.jsonl b/src/main/native-chat/__fixtures__/openclaude-0.31.0-local-command-rows.jsonl deleted file mode 100644 index 78307deee6c..00000000000 --- a/src/main/native-chat/__fixtures__/openclaude-0.31.0-local-command-rows.jsonl +++ /dev/null @@ -1,13 +0,0 @@ -{"type":"mode","mode":"normal","sessionId":"00000000-0000-4000-8000-000000000000"} -{"type":"file-history-snapshot","messageId":"b57b5064-f405-496e-972e-a8f0401f5195","snapshot":{"messageId":"b57b5064-f405-496e-972e-a8f0401f5195","trackedFileBackups":{},"timestamp":"2026-09-23T00:21:16.966Z"},"isSnapshotUpdate":false} -{"parentUuid":null,"isSidechain":false,"type":"user","message":{"role":"user","content":"Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to."},"isMeta":true,"uuid":"77469cbb-626a-48d7-a43c-c15c7614a95f","timestamp":"2026-09-23T00:21:16.965Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"parentUuid":"77469cbb-626a-48d7-a43c-c15c7614a95f","isSidechain":false,"type":"user","message":{"role":"user","content":"/context\n context\n "},"uuid":"b57b5064-f405-496e-972e-a8f0401f5195","timestamp":"2026-09-23T00:21:16.965Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"parentUuid":"b57b5064-f405-496e-972e-a8f0401f5195","isSidechain":false,"type":"user","message":{"role":"user","content":" \u001b[1mContext Usage\u001b[22m\n\u001b[38;5;244m⛁ ⛁ ⛁ ⛁ ⛁ ⛁ \u001b[38;5;246m⛁ ⛁ ⛁ ⛁ \u001b[39m \u001b[38;5;246mgpt-4o · 18.3k/128k tokens (14%)\u001b[39m\n\n\u001b[38;5;246m⛁ ⛁ ⛁ ⛁ \u001b[38;5;220m⛀ \u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m \u001b[38;5;246m\u001b[3mEstimated usage by category\u001b[23m\u001b[39m\n \u001b[38;5;244m⛁\u001b[39m System prompt: \u001b[38;5;246m8k tokens (6.2%)\u001b[39m\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m \u001b[38;5;246m⛁\u001b[39m System tools: \u001b[38;5;246m10k tokens (7.8%)\u001b[39m\n \u001b[38;5;220m⛁\u001b[39m Skills: \u001b[38;5;246m304 tokens (0.2%)\u001b[39m\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m \u001b[38;5;246m⛶\u001b[39m Free space: \u001b[38;5;246m63.3k (49.5%)\u001b[39m\n \u001b[38;5;246m⛝ Autocompact buffer: 46.4k tokens (36.2%)\u001b[39m\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m\n\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m\n\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\u001b[38;5;246m⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\u001b[38;5;246m⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\u001b[38;5;246m⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\n\u001b[1mSkills\u001b[22m\u001b[38;5;246m · /skills\u001b[39m"},"uuid":"ed4aeadc-1c29-4cd6-a074-fc179a168707","timestamp":"2026-09-23T00:21:16.965Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"type":"file-history-snapshot","messageId":"46776637-ece8-482c-b346-65c2938f80aa","snapshot":{"messageId":"46776637-ece8-482c-b346-65c2938f80aa","trackedFileBackups":{},"timestamp":"2026-09-23T00:21:28.270Z"},"isSnapshotUpdate":false} -{"parentUuid":"ed4aeadc-1c29-4cd6-a074-fc179a168707","isSidechain":false,"type":"user","message":{"role":"user","content":"Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to."},"isMeta":true,"uuid":"b42e5475-0947-44ae-a15e-24f1e74d6a72","timestamp":"2026-09-23T00:21:28.270Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"parentUuid":"b42e5475-0947-44ae-a15e-24f1e74d6a72","isSidechain":false,"type":"user","message":{"role":"user","content":"/cost\n cost\n "},"uuid":"46776637-ece8-482c-b346-65c2938f80aa","timestamp":"2026-09-23T00:21:28.269Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"parentUuid":"46776637-ece8-482c-b346-65c2938f80aa","isSidechain":false,"type":"system","subtype":"local_command","content":"\u001b[2mTotal cost: $0.0000\u001b[22m\n\u001b[2mTotal duration (API): 0s\u001b[22m\n\u001b[2mTotal duration (wall): 24s\u001b[22m\n\u001b[2mTotal code changes: 0 lines added, 0 lines removed\u001b[22m\n\nUsage: 0 input, 0 output","level":"info","timestamp":"2026-09-23T00:21:28.270Z","uuid":"a1ea4717-298a-4f3e-b5d7-f8a6b2936f10","isMeta":false,"userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"type":"file-history-snapshot","messageId":"4bb33790-bbbf-4b6d-8701-a3e7135805b0","snapshot":{"messageId":"4bb33790-bbbf-4b6d-8701-a3e7135805b0","trackedFileBackups":{},"timestamp":"2026-09-23T00:21:39.716Z"},"isSnapshotUpdate":false} -{"parentUuid":"a1ea4717-298a-4f3e-b5d7-f8a6b2936f10","isSidechain":false,"type":"user","message":{"role":"user","content":"Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to."},"isMeta":true,"uuid":"a3492e9f-3fd7-4c15-a04f-dd6b170326ff","timestamp":"2026-09-23T00:21:39.715Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"parentUuid":"a3492e9f-3fd7-4c15-a04f-dd6b170326ff","isSidechain":false,"type":"user","message":{"role":"user","content":"/context\n context\n "},"uuid":"4bb33790-bbbf-4b6d-8701-a3e7135805b0","timestamp":"2026-09-23T00:21:39.715Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} -{"parentUuid":"4bb33790-bbbf-4b6d-8701-a3e7135805b0","isSidechain":false,"type":"user","message":{"role":"user","content":" \u001b[1mContext Usage\u001b[22m\n\u001b[38;5;244m⛁ ⛁ ⛁ ⛁ ⛁ ⛁ \u001b[38;5;246m⛁ ⛁ ⛁ ⛁ \u001b[39m \u001b[38;5;246mgpt-4o · 18.9k/128k tokens (15%)\u001b[39m\n\n\u001b[38;5;246m⛁ ⛁ ⛁ ⛁ \u001b[38;5;220m⛀ \u001b[38;5;135m⛀ \u001b[38;5;246m⛶ ⛶ ⛶ ⛶ \u001b[39m \u001b[38;5;246m\u001b[3mEstimated usage by category\u001b[23m\u001b[39m\n \u001b[38;5;244m⛁\u001b[39m System prompt: \u001b[38;5;246m8k tokens (6.2%)\u001b[39m\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m \u001b[38;5;246m⛁\u001b[39m System tools: \u001b[38;5;246m10k tokens (7.8%)\u001b[39m\n \u001b[38;5;220m⛁\u001b[39m Skills: \u001b[38;5;246m304 tokens (0.2%)\u001b[39m\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m \u001b[38;5;135m⛁\u001b[39m Messages: \u001b[38;5;246m607 tokens (0.5%)\u001b[39m\n \u001b[38;5;246m⛶\u001b[39m Free space: \u001b[38;5;246m62.7k (49.0%)\u001b[39m\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m \u001b[38;5;246m⛝ Autocompact buffer: 46.4k tokens (36.2%)\u001b[39m\n\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ \u001b[39m\n\n\u001b[38;5;246m⛶ ⛶ ⛶ ⛶ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\u001b[38;5;246m⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\u001b[38;5;246m⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\u001b[38;5;246m⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ \u001b[39m\n\n\n\u001b[1mSkills\u001b[22m\u001b[38;5;246m · /skills\u001b[39m"},"uuid":"2b989a6e-c7d6-45b6-8367-f656471e4e2b","timestamp":"2026-09-23T00:21:39.715Z","userType":"external","entrypoint":"cli","cwd":"/tmp/project","sessionId":"00000000-0000-4000-8000-000000000000","version":"unknown","gitBranch":"main"} diff --git a/src/main/native-chat/agent-cli-installation-error.ts b/src/main/native-chat/agent-launch/agent-cli-installation-error.ts similarity index 62% rename from src/main/native-chat/agent-cli-installation-error.ts rename to src/main/native-chat/agent-launch/agent-cli-installation-error.ts index 682a291ad33..dd6fc5a975b 100644 --- a/src/main/native-chat/agent-cli-installation-error.ts +++ b/src/main/native-chat/agent-launch/agent-cli-installation-error.ts @@ -1,12 +1,12 @@ -import type { AgentCliInstallationProblem } from '../../shared/agent-launch/catalog/agent-cli-installation-problem' +import type { AgentCliInstallationProblem } from '../../../shared/agent-launch/catalog/agent-cli-installation-problem' import { agentCliTooOldWords, sayAgentSessionFailureEnglish -} from '../../shared/agent-session/failures/agent-session-failure-copy' -import type { TuiAgent } from '../../shared/agent-launch/catalog/tui-agent' -import './agent-session-wire/providers/structured-agent-session-adapter' -import { AgentSessionPreSpawnError } from './agent-session-wire/providers/structured-agent-session-adapter-errors' -import { findInErrorChain } from '../provider-process/provider-error-chain' +} from '../../../shared/agent-session/failures/agent-session-failure-copy' +import type { TuiAgent } from '../../../shared/agent-launch/catalog/tui-agent' +import '../agent-session-wire/providers/structured-agent-session-adapter' +import { AgentSessionPreSpawnError } from '../agent-session-wire/providers/structured-agent-session-adapter-errors' +import { findInErrorChain } from '../../provider-process/provider-error-chain' export class AgentCliInstallationError extends AgentSessionPreSpawnError { readonly installation: AgentCliInstallationProblem diff --git a/src/main/native-chat/structured-agent-arguments-error.ts b/src/main/native-chat/agent-launch/structured-agent-arguments-error.ts similarity index 83% rename from src/main/native-chat/structured-agent-arguments-error.ts rename to src/main/native-chat/agent-launch/structured-agent-arguments-error.ts index eba0cf8bd13..ce954e3029e 100644 --- a/src/main/native-chat/structured-agent-arguments-error.ts +++ b/src/main/native-chat/agent-launch/structured-agent-arguments-error.ts @@ -1,5 +1,5 @@ -import type { AgentSessionArgumentProblem } from '../../shared/agent-session/agent-session-argument-problem' -import { findInErrorChain } from '../provider-process/provider-error-chain' +import type { AgentSessionArgumentProblem } from '../../../shared/agent-session/agent-session-argument-problem' +import { findInErrorChain } from '../../provider-process/provider-error-chain' /** Expose only the option name; saved values may contain secrets. */ export class StructuredAgentArgumentsError extends Error { diff --git a/src/main/native-chat/structured-agent-command-resolution.test.ts b/src/main/native-chat/agent-launch/structured-agent-command-resolution.test.ts similarity index 100% rename from src/main/native-chat/structured-agent-command-resolution.test.ts rename to src/main/native-chat/agent-launch/structured-agent-command-resolution.test.ts diff --git a/src/main/native-chat/structured-agent-command-resolution.ts b/src/main/native-chat/agent-launch/structured-agent-command-resolution.ts similarity index 90% rename from src/main/native-chat/structured-agent-command-resolution.ts rename to src/main/native-chat/agent-launch/structured-agent-command-resolution.ts index 73fb7d4b407..57f831094a8 100644 --- a/src/main/native-chat/structured-agent-command-resolution.ts +++ b/src/main/native-chat/agent-launch/structured-agent-command-resolution.ts @@ -1,22 +1,25 @@ -import { waitForPromiseWithSignal } from '../../shared/abort-signal-reason' +import { waitForPromiseWithSignal } from '../../../shared/abort-signal-reason' import { homedir } from 'node:os' import { delimiter, isAbsolute, resolve as resolvePath } from 'node:path' -import type { GlobalSettings } from '../../shared/global-settings-types' -import type { TuiAgent } from '../../shared/agent-launch/catalog/tui-agent' +import type { GlobalSettings } from '../../../shared/global-settings-types' +import type { TuiAgent } from '../../../shared/agent-launch/catalog/tui-agent' import { resolveCliCommand, resolveExecutableCommand -} from '../../shared/node-cli-command-resolution' +} from '../../../shared/node-cli-command-resolution' import { hasExplicitTuiLaunchCommand, structuredAgentCommandPlan -} from '../../shared/agent-launch/startup/tui-agent-launch-command-override' -import './agent-session-wire/providers/structured-agent-session-adapter' +} from '../../../shared/agent-launch/startup/tui-agent-launch-command-override' +import '../agent-session-wire/providers/structured-agent-session-adapter' import { AgentSessionPreSpawnError, isAgentSessionPreSpawnError -} from './agent-session-wire/providers/structured-agent-session-adapter-errors' -import { guestNativePathEnv, resolveGuestNativeProgram } from '../ipc/wsl-guest-windows-drive-path' +} from '../agent-session-wire/providers/structured-agent-session-adapter-errors' +import { + guestNativePathEnv, + resolveGuestNativeProgram +} from '../../ipc/wsl-guest-windows-drive-path' export type StructuredAgentResolutionOperation = { deadline?: number diff --git a/src/main/native-chat/structured-agent-configured-args.test.ts b/src/main/native-chat/agent-launch/structured-agent-configured-args.test.ts similarity index 100% rename from src/main/native-chat/structured-agent-configured-args.test.ts rename to src/main/native-chat/agent-launch/structured-agent-configured-args.test.ts diff --git a/src/main/native-chat/structured-agent-configured-args.ts b/src/main/native-chat/agent-launch/structured-agent-configured-args.ts similarity index 64% rename from src/main/native-chat/structured-agent-configured-args.ts rename to src/main/native-chat/agent-launch/structured-agent-configured-args.ts index 376e9fb2643..9e8971943c0 100644 --- a/src/main/native-chat/structured-agent-configured-args.ts +++ b/src/main/native-chat/agent-launch/structured-agent-configured-args.ts @@ -1,12 +1,12 @@ -import { projectAgentLaunchProfile } from '../../shared/agent-launch/choices/agent-launch-profile' -import { isTuiAgent } from '../../shared/agent-launch/catalog/tui-agent-config' -import type { GlobalSettings } from '../../shared/global-settings-types' +import { projectAgentLaunchProfile } from '../../../shared/agent-launch/choices/agent-launch-profile' +import { isTuiAgent } from '../../../shared/agent-launch/catalog/tui-agent-config' +import type { GlobalSettings } from '../../../shared/global-settings-types' import { resolveTuiAgentLaunchArgs, type AgentLaunchProfileSettings -} from '../../shared/agent-launch/choices/tui-agent-launch-defaults' -import { tokenizeStartupCommand } from '../../shared/agent-launch/startup/tui-agent-startup-shell' -import { resolveLocalWindowsAgentStartupShell } from '../../shared/windows-terminal-shell' +} from '../../../shared/agent-launch/choices/tui-agent-launch-defaults' +import { tokenizeStartupCommand } from '../../../shared/agent-launch/startup/tui-agent-startup-shell' +import { resolveLocalWindowsAgentStartupShell } from '../../../shared/windows-terminal-shell' /** Match terminal argument grouping before filtering provider-owned flags. */ export function structuredAgentConfiguredArgs( diff --git a/src/main/native-chat/structured-agent-session-create-support.ts b/src/main/native-chat/agent-launch/structured-agent-session-create-support.ts similarity index 81% rename from src/main/native-chat/structured-agent-session-create-support.ts rename to src/main/native-chat/agent-launch/structured-agent-session-create-support.ts index fd11db71714..97329c466f1 100644 --- a/src/main/native-chat/structured-agent-session-create-support.ts +++ b/src/main/native-chat/agent-launch/structured-agent-session-create-support.ts @@ -1,6 +1,6 @@ -import type { AgentSessionExecutionLocation } from '../../shared/agent-session/lifecycle/agent-session-record' -import type { StructuredAgentId } from '../../shared/agent-session/lifecycle/agent-session-provider-handle' -import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host' +import type { AgentSessionExecutionLocation } from '../../../shared/agent-session/lifecycle/agent-session-record' +import type { StructuredAgentId } from '../../../shared/agent-session/lifecycle/agent-session-provider-handle' +import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host' export type StructuredAgentSessionCreateSupport = { supported: boolean diff --git a/src/main/native-chat/agent-model-catalog/agent-model-catalog-account-list-test-support.ts b/src/main/native-chat/agent-model-catalog/agent-model-catalog-account-list-test-support.ts index 3d9d9165701..f3b9d9ac14d 100644 --- a/src/main/native-chat/agent-model-catalog/agent-model-catalog-account-list-test-support.ts +++ b/src/main/native-chat/agent-model-catalog/agent-model-catalog-account-list-test-support.ts @@ -2,7 +2,7 @@ import { resolveHiddenProbeCwd } from '../../lib/hidden-probe-cwd' import { onTestFinished, vi } from 'vitest' import { agentSessionConversationView } from '../../../shared/agent-session/lifecycle/agent-session-conversation-view' import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' -import { agentSessionRecordFixture } from '../agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../testing/agent-session-record-test-fixture' import { agentModelCatalogFingerprint, agentModelCatalogScopeFingerprint diff --git a/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery-freshness.test.ts b/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery-freshness.test.ts index a7f444f692f..e49f7e3ad20 100644 --- a/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery-freshness.test.ts +++ b/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery-freshness.test.ts @@ -1,11 +1,12 @@ -import { agentSessionRecordFixture } from '../agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../testing/agent-session-record-test-fixture' import { agentSessionConversationView } from '../../../shared/agent-session/lifecycle/agent-session-conversation-view' import { describe, expect, it, vi } from 'vitest' import type { AgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' import type { AgentSessionModelOption } from '../../../shared/agent-session/agent-session-wire' import { agentModelCatalogFingerprintForRecord } from './agent-model-catalog-fingerprint' import { createAgentModelCatalogService } from './agent-model-catalog-service' -import { AgentModelCatalogStore, type AgentModelCatalogSuccess } from './agent-model-catalog-store' +import { AgentModelCatalogStore } from './agent-model-catalog-store' +import type { AgentModelCatalogSuccess } from './agent-model-catalog-entry' const HOME: AgentSessionAccountHome = { variable: 'GROK_HOME', path: '/homes/grok' } diff --git a/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery.ts b/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery.ts index 311f1d95f1b..358cd99b082 100644 --- a/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery.ts +++ b/src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery.ts @@ -1,4 +1,4 @@ -import type { StructuredAgentResolutionOperation } from '../structured-agent-command-resolution' +import type { StructuredAgentResolutionOperation } from '../agent-launch/structured-agent-command-resolution' import type { AgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' import type { AgentModelCatalogSuccess } from './agent-model-catalog-entry' diff --git a/src/main/native-chat/agent-model-catalog/agent-model-catalog-lister.ts b/src/main/native-chat/agent-model-catalog/agent-model-catalog-lister.ts index 6aebf3e875e..c070ecf3eb0 100644 --- a/src/main/native-chat/agent-model-catalog/agent-model-catalog-lister.ts +++ b/src/main/native-chat/agent-model-catalog/agent-model-catalog-lister.ts @@ -1,4 +1,4 @@ -import type { StructuredAgentResolutionOperation } from '../structured-agent-command-resolution' +import type { StructuredAgentResolutionOperation } from '../agent-launch/structured-agent-command-resolution' import type { AgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' import type { PrioritySemaphore } from '../../../shared/priority-semaphore' import { startSpan } from '../../observability/tracer' diff --git a/src/main/native-chat/agent-model-catalog/agent-model-catalog-scope.ts b/src/main/native-chat/agent-model-catalog/agent-model-catalog-scope.ts index 37122867666..90a4cedaef7 100644 --- a/src/main/native-chat/agent-model-catalog/agent-model-catalog-scope.ts +++ b/src/main/native-chat/agent-model-catalog/agent-model-catalog-scope.ts @@ -1,4 +1,4 @@ -import type { StructuredAgentResolutionOperation } from '../structured-agent-command-resolution' +import type { StructuredAgentResolutionOperation } from '../agent-launch/structured-agent-command-resolution' import { resolveHiddenProbeCwd } from '../../lib/hidden-probe-cwd' import type { AgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' import { isNativeLocalLocation } from '../../../shared/execution-host' diff --git a/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-listing.test.ts b/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-listing.test.ts index 31c3202e239..836bb19aec6 100644 --- a/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-listing.test.ts +++ b/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-listing.test.ts @@ -3,8 +3,9 @@ import { createAgentModelCatalogService } from './agent-model-catalog-service' import { assertStructuredAgentResolutionCurrent, type StructuredAgentResolutionOperation -} from '../structured-agent-command-resolution' -import { AgentModelCatalogStore, type AgentModelCatalogSuccess } from './agent-model-catalog-store' +} from '../agent-launch/structured-agent-command-resolution' +import { AgentModelCatalogStore } from './agent-model-catalog-store' +import type { AgentModelCatalogSuccess } from './agent-model-catalog-entry' function listing(id: string): AgentModelCatalogSuccess { return { @@ -74,29 +75,6 @@ function sharedListingHarness() { return { discovery, listingFor, runs, run, store, service } } -it('finishes a shared listing for a folder that joined before the first reader gave up', async () => { - vi.useFakeTimers() - onTestFinished(() => { - vi.useRealTimers() - }) - const h = sharedListingHarness() - void h.service.demand({ agent: 'codex', workspacePath: '/a', deadline: Date.now() + 1_000 }) - await vi.waitFor(() => expect(h.run).toHaveBeenCalledOnce()) - const b = h.service.read({ - agent: 'codex', - workspacePath: '/b', - waitForListing: true, - deadline: Date.now() + 60_000 - }) - await vi.advanceTimersByTimeAsync(2_000) - // The first reader is gone, but the listing it started is still running for the folder that joined. - expect(h.runs[0]?.signal?.aborted).toBe(false) - h.discovery.resolve({ ...listing('shared'), complete: true, configured: {} }) - await vi.advanceTimersByTimeAsync(0) - expect(await b).toMatchObject({ models: [{ id: 'shared' }] }) - expect(h.run).toHaveBeenCalledOnce() -}) - it("never answers a newly picked folder with the previous folder's late listing", async () => { const h = sharedListingHarness() const old = h.service.read({ agent: 'codex', workspacePath: '/own-old', waitForListing: true }) @@ -113,32 +91,3 @@ it("never answers a newly picked folder with the previous folder's late listing" models: [{ id: 'old' }] }) }) - -it('keeps a shared listing for an earlier-ending reader of the same folder when the first is cancelled', async () => { - vi.useFakeTimers() - onTestFinished(() => { - vi.useRealTimers() - }) - const h = sharedListingHarness() - const first = h.service.captureRead({ - agent: 'codex', - workspacePath: '/a', - deadline: Date.now() + 60_000 - }) - void h.service.demand({ agent: 'codex', workspacePath: '/a', operation: first }) - await vi.waitFor(() => expect(h.run).toHaveBeenCalledOnce()) - const second = h.service.read({ - agent: 'codex', - workspacePath: '/a', - waitForListing: true, - deadline: Date.now() + 30_000 - }) - await vi.advanceTimersByTimeAsync(0) - first.controller.abort() - await vi.advanceTimersByTimeAsync(1_000) - expect(h.runs[0]?.signal?.aborted).toBe(false) - h.discovery.resolve({ ...listing('shared'), complete: true, configured: {} }) - await vi.advanceTimersByTimeAsync(0) - expect(await second).toMatchObject({ models: [{ id: 'shared' }] }) - expect(h.run).toHaveBeenCalledOnce() -}) diff --git a/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-setup.test.ts b/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-setup.test.ts index b7e0e509a72..19c2f6f17f2 100644 --- a/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-setup.test.ts +++ b/src/main/native-chat/agent-model-catalog/agent-model-catalog-shared-setup.test.ts @@ -1,9 +1,10 @@ import { expect, it, onTestFinished, vi } from 'vitest' import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' import { agentSessionConversationView } from '../../../shared/agent-session/lifecycle/agent-session-conversation-view' -import { agentSessionRecordFixture } from '../agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../testing/agent-session-record-test-fixture' import { createAgentModelCatalogService } from './agent-model-catalog-service' -import { AgentModelCatalogStore, type AgentModelCatalogSuccess } from './agent-model-catalog-store' +import { AgentModelCatalogStore } from './agent-model-catalog-store' +import type { AgentModelCatalogSuccess } from './agent-model-catalog-entry' const HOME = { variable: 'CODEX_HOME', path: '/account' } // Its picker follows a project's own settings, unlike Claude's and Codex's. @@ -23,25 +24,6 @@ function chat(sessionId: string, workspaceId = 'ws', provider = 'codex'): AgentS }) } -/** As when its tab closes: the owner is released with evidence that its process ended. */ -function released(record: AgentSessionRecord): AgentSessionRecord { - return { - ...record, - lease: { - ...record.lease, - claimStatus: 'released', - deathEvidence: { - kind: 'kill-delivered', - detail: 'closed', - observedAt: 2_000, - ownerFence: record.lease.runtimeFence, - lastProvenAliveAt: 1_000, - runtimeEnd: 'quit' - } - } - } -} - const listing = (id: string): AgentModelCatalogSuccess => ({ origin: 'probe', complete: true, @@ -123,89 +105,6 @@ function harness(agent = 'codex') { } } -it.each(['listing', 'setup'] as const)( - "keeps chat b's listing when chat a closes during its %s and an unrelated folder changes", - async (stage) => { - const h = harness() - const gate = stage === 'setup' ? Promise.withResolvers() : undefined - h.folder.gate = gate - void h.service.demand({ agent: 'codex', sessionId: 'a', deadline: Date.now() + 60_000 }) - const b = h.service.read({ - agent: 'codex', - sessionId: 'b', - waitForListing: true, - deadline: Date.now() + 30_000 - }) - if (stage === 'listing') { - await vi.waitFor(() => expect(h.run).toHaveBeenCalledOnce()) - } - h.records.a = released(h.records.a) - h.service.retireContext('another-folder') - gate?.resolve() - await vi.waitFor(() => expect(h.run).toHaveBeenCalledOnce()) - h.discovery.resolve(listing('shared')) - expect(await b).toMatchObject({ models: [{ id: 'shared' }] }) - expect(h.run).toHaveBeenCalledOnce() - } -) - -it.each( - (['gate', 'cli', 'settings'] as const).flatMap((stage) => - (['agent', 'workspace'] as const).map((retired) => [stage, retired] as const) - ) -)( - 'settles a waiting reader at once when its %s stage is stalled and its %s retires', - async (stage, retired) => { - vi.useFakeTimers() - onTestFinished(() => { - vi.useRealTimers() - }) - // Only agents whose picker follows a project's settings check them. - const listed = stage === 'settings' ? agent : 'codex' - const h = harness(listed) - const stalled = Promise.withResolvers() - h.folder[stage] = stalled - let settled = false - void h.service - .read({ agent: listed, sessionId: 'b', waitForListing: true, deadline: Date.now() + 30_000 }) - .then(() => { - settled = true - }) - await vi.advanceTimersByTimeAsync(0) - if (retired === 'agent') { - h.store.expireAgent(listed) - } else { - h.service.retireContext('ws') - } - await vi.advanceTimersByTimeAsync(0) - expect(settled).toBe(true) - stalled.resolve() - h.discovery.resolve(listing('retired')) - await vi.advanceTimersByTimeAsync(0) - expect(h.service.snapshot({ agent: listed, sessionId: 'b' })).not.toHaveProperty('models') - } -) - -it("still lists for chat b when chat a's record goes away while their shared setup runs", async () => { - const h = harness() - // The CLI is found only once the login shell is read again. - h.resolve.mockImplementation(async (_home, _cwd, lookup, _operation, at) => { - if (lookup !== 'recover') { - throw new Error('not on the saved PATH') - } - return { identity: `codex in ${at}`, run: h.run } - }) - const gate = Promise.withResolvers() - h.folder.gate = gate - void h.service.demand({ agent: 'codex', sessionId: 'a' }) - const b = h.service.read({ agent: 'codex', sessionId: 'b', waitForListing: true }) - delete h.records.a - gate.resolve() - await vi.waitFor(() => expect(h.run).toHaveBeenCalledOnce()) - h.discovery.resolve(listing('shared')) - expect(await b).toMatchObject({ models: [{ id: 'shared' }] }) -}) - it('keeps one listing for a workspace on the same folder when another workspace there moves', async () => { const h = harness() h.records.a = chat('a', 'ws-a') @@ -303,35 +202,6 @@ it('holds readers of a stalled refresh only to their own deadlines, and starts n expect(await c).toMatchObject({ models: [{ id: 'fresh' }] }) }) -it.each(['cancelled', 'stopped'] as const)( - 'settles a reader at once when it is %s while its setup is stalled', - async (how) => { - vi.useFakeTimers() - onTestFinished(() => { - vi.useRealTimers() - }) - const h = harness() - h.folder.gate = Promise.withResolvers() - const reader = h.service.captureRead({ - agent: 'codex', - sessionId: 'b', - deadline: Date.now() + 30_000 - }) - let settled = false - void h.service.demand({ agent: 'codex', sessionId: 'b', operation: reader }).then(() => { - settled = true - }) - await vi.advanceTimersByTimeAsync(0) - if (how === 'cancelled') { - reader.controller.abort() - } else { - void h.service.stop() - } - await vi.advanceTimersByTimeAsync(0) - expect(settled).toBe(true) - } -) - it("keeps the folder's defaults when they arrive after a shorter last reader left", async () => { vi.useFakeTimers() onTestFinished(() => { diff --git a/src/main/native-chat/agent-session-journal/history-import/adopted-transcript-source.ts b/src/main/native-chat/agent-session-journal/history-import/adopted-transcript-source.ts index 6c16bc0ad54..ce3fe54b2d7 100644 --- a/src/main/native-chat/agent-session-journal/history-import/adopted-transcript-source.ts +++ b/src/main/native-chat/agent-session-journal/history-import/adopted-transcript-source.ts @@ -9,7 +9,7 @@ import { asRecord } from '../../../ai-vault/parsing/session-record-fields' import { readOpenCodeTranscriptSignalViaWorker } from '../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn' import { samePiDirectory } from '../../../pi/rpc/launch/rpc-launch-resolution' -import { admitTranscriptPath } from '../../transcript-path-admission' +import { admitTranscriptPath } from '../../transcript-discovery/transcript-path-admission' import { adoptedDirectoryProbes } from './adopted-directory-probe' export type AdoptedTranscriptSource = { diff --git a/src/main/native-chat/agent-session-journal/history-import/journal-codex-history.ts b/src/main/native-chat/agent-session-journal/history-import/journal-codex-history.ts index cf556f8f6d9..0bb71fd3008 100644 --- a/src/main/native-chat/agent-session-journal/history-import/journal-codex-history.ts +++ b/src/main/native-chat/agent-session-journal/history-import/journal-codex-history.ts @@ -1,4 +1,4 @@ -import type { CodexHistoryRecord } from '../../codex-local-history' +import type { CodexHistoryRecord } from '../../transcript-history/codex/codex-local-history' import { readRecord, readString } from '../../../codex/app-server/codex-item-field-readers' import { codexRolloutItem } from '../../../codex/history/rollouts/codex-rollout-item-projection' import { diff --git a/src/main/native-chat/agent-session-journal/history-import/journal-legacy-import.ts b/src/main/native-chat/agent-session-journal/history-import/journal-legacy-import.ts index bfc020a501c..f8fd82fb1b3 100644 --- a/src/main/native-chat/agent-session-journal/history-import/journal-legacy-import.ts +++ b/src/main/native-chat/agent-session-journal/history-import/journal-legacy-import.ts @@ -15,8 +15,8 @@ import type { NativeChatMessage } from '../../../../shared/native-chat/native-ch import { resolveNativeChatTranscriptAgent } from '../../../../shared/native-chat/native-chat-agent-support' import { resolveSessionFilePath, type ResolveSessionFileOptions } from '../../session-file-resolver' import { readLegacyImportTranscript, TRANSCRIPT_DECODERS } from './journal-legacy-transcript-stream' -import { readOpenCodeNativeChatTranscriptFull } from '../../transcript-opencode' -import { mayBeClaudeConversationRecord } from '../../transcript-line-decoders-claude' +import { readOpenCodeNativeChatTranscriptFull } from '../../transcript-history/opencode/transcript-opencode' +import { mayBeClaudeConversationRecord } from '../../transcript-decoding/transcript-line-decoders-claude' import { createLegacyIdentityTracker } from './journal-legacy-identity' import { createPiTranscriptCollector } from './journal-pi-active-branch' import { createNewestItemWindow } from './journal-newest-item-window' diff --git a/src/main/native-chat/agent-session-journal/history-import/journal-legacy-transcript-stream.ts b/src/main/native-chat/agent-session-journal/history-import/journal-legacy-transcript-stream.ts index ab53fc45502..5ebd7eccd16 100644 --- a/src/main/native-chat/agent-session-journal/history-import/journal-legacy-transcript-stream.ts +++ b/src/main/native-chat/agent-session-journal/history-import/journal-legacy-transcript-stream.ts @@ -1,10 +1,13 @@ -import { decodeClaudeTranscriptLine } from '../../transcript-line-decoders-claude' -import { decodeCodexTranscriptLine } from '../../transcript-line-decoders-codex' -import { decodeGrokTranscriptLine } from '../../transcript-line-decoders-grok' -import { decodeOmpTranscriptLine } from '../../transcript-line-decoders-omp' +import { decodeClaudeTranscriptLine } from '../../transcript-decoding/transcript-line-decoders-claude' +import { decodeCodexTranscriptLine } from '../../transcript-decoding/transcript-line-decoders-codex' +import { decodeGrokTranscriptLine } from '../../transcript-decoding/transcript-line-decoders-grok' +import { decodeOmpTranscriptLine } from '../../transcript-decoding/transcript-line-decoders-omp' import { createReadStream } from 'node:fs' -import { decodeTranscriptStream } from '../../transcript-stream-lines' -import { readCodexLocalHistory, type CodexHistoryRecord } from '../../codex-local-history' +import { decodeTranscriptStream } from '../../transcript-streaming/transcript-stream-lines' +import { + readCodexLocalHistory, + type CodexHistoryRecord +} from '../../transcript-history/codex/codex-local-history' export async function readLegacyImportTranscript( input: { filePath: string; codex: boolean; roots?: readonly string[] }, diff --git a/src/main/native-chat/agent-session-journal/history-import/journal-pi-active-branch.ts b/src/main/native-chat/agent-session-journal/history-import/journal-pi-active-branch.ts index 35749eb0da1..a1bffb4a39f 100644 --- a/src/main/native-chat/agent-session-journal/history-import/journal-pi-active-branch.ts +++ b/src/main/native-chat/agent-session-journal/history-import/journal-pi-active-branch.ts @@ -6,7 +6,7 @@ import { timestampMs } from '../../../ai-vault/parsing/session-record-time' import { asRecord } from '../../../ai-vault/parsing/session-record-fields' import Database from '../../../sqlite/sync-database' -import { decodeOmpTranscriptLine } from '../../transcript-line-decoders-omp' +import { decodeOmpTranscriptLine } from '../../transcript-decoding/transcript-line-decoders-omp' function closePiImportDatabase(db: Database.Database | null): void { try { diff --git a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-failed-create-refusal.ts b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-failed-create-refusal.ts index 51df40c8a1d..5c4e98bf2e3 100644 --- a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-failed-create-refusal.ts +++ b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-failed-create-refusal.ts @@ -39,8 +39,8 @@ import { providerExecutableMissing, providerLaunchFolderMissing } from '../../../provider-process/provider-executable-missing' -import { argumentProblemOf } from '../../structured-agent-arguments-error' -import { agentCliInstallationProblemOf } from '../../agent-cli-installation-error' +import { argumentProblemOf } from '../../agent-launch/structured-agent-arguments-error' +import { agentCliInstallationProblemOf } from '../../agent-launch/agent-cli-installation-error' import { agentCliInstallationFacts } from '../../../../shared/agent-launch/catalog/agent-cli-installation-problem' export type FailedAcquisitionWording = { diff --git a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-intent.ts b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-intent.ts index b1ae5005d03..0821a65d69d 100644 --- a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-intent.ts +++ b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-intent.ts @@ -1,6 +1,6 @@ import { prepareStructuredAgentSessionFork } from './structured-agent-session-fork' import { resolveCommittedStructuredAgentSessionAdoptionIntent } from '../../../runtime/agent-session/structured-agent-session-create-adoption' -import { agentConversationCopyRefusal } from '../../agent-conversation-copy-error' +import { agentConversationCopyRefusal } from '../../conversation-adoption/agent-conversation-copy-error' import { acpAgentName } from '../../../acp/session/acp-structured-agent-definitions' import type { AgentSessionExecutionLocation } from '../../../../shared/agent-session/lifecycle/agent-session-record' import type { StructuredAgentId } from '../../../../shared/agent-session/lifecycle/agent-session-provider-handle' diff --git a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-source.ts b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-source.ts index dba1bd926a7..4d4d74a6169 100644 --- a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-source.ts +++ b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork-source.ts @@ -22,7 +22,7 @@ import { AgentConversationCopyError, AgentConversationCopyStartError, agentConversationCopyRefusal -} from '../../agent-conversation-copy-error' +} from '../../conversation-adoption/agent-conversation-copy-error' import { acpAgentName } from '../../../acp/session/acp-structured-agent-definitions' import { structuredAgentRuntimeMetadata } from '../../../runtime/agent-session/installation/structured-agent-runtime-metadata' import type { StructuredAgentSessionHost } from '../host/structured-agent-session-host' diff --git a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork.ts b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork.ts index 184a31f819a..e97a112a69c 100644 --- a/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork.ts +++ b/src/main/native-chat/agent-session-wire/acquisition/structured-agent-session-fork.ts @@ -10,7 +10,7 @@ import { AGENT_SESSION_CONVERSATION_NAME_MAX_LENGTH } from '../../../../shared/agent-session/agent-session-conversation-name' import { sliceAtCodeUnitLimit } from '../../../../shared/surrogate-safe-text-slice' -import { AgentConversationCopyError } from '../../agent-conversation-copy-error' +import { AgentConversationCopyError } from '../../conversation-adoption/agent-conversation-copy-error' import { acpAgentName } from '../../../acp/session/acp-structured-agent-definitions' import { AGENT_CHAT_PERMISSION_MODE_OPTION_ID } from '../../../../shared/agent-launch/permissions/agent-chat-permission-mode' import { isAgentSessionRefusalError } from '../../../../shared/agent-session/failures/agent-session-wire-refusals' diff --git a/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test-fixture.ts b/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test-fixture.ts index 1b2317f83df..4d42f397280 100644 --- a/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test-fixture.ts +++ b/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test-fixture.ts @@ -1,7 +1,7 @@ import { vi } from 'vitest' import { mkdir, writeFile } from 'node:fs/promises' import { join } from 'node:path' -import { agentSessionRecordFixture } from '../../../agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../testing/agent-session-record-test-fixture' import { claudeProviderHandle, codexProviderHandle diff --git a/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test.ts b/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test.ts index b23257f47a6..d374162499e 100644 --- a/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test.ts +++ b/src/main/native-chat/agent-session-wire/acquisition/testing/structured-agent-session-fork.test.ts @@ -3,7 +3,7 @@ import { forkTestSource } from './structured-agent-session-fork.test-fixture' import { resolveUncommittedStructuredCreate } from '../../../../runtime/rpc/methods/structured-sessions/creation/structured-agent-session-precommit-refusal' import { handleStructuredAgentSessionCreate } from '../../../../runtime/rpc/methods/structured-sessions/creation/structured-agent-session-create-handler' import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { agentSessionRecordFixture } from '../../../agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../testing/agent-session-record-test-fixture' import { codexProviderHandle } from '../../../../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' import { prepareStructuredAgentSessionFork } from '../structured-agent-session-fork' import { diff --git a/src/main/native-chat/agent-session-wire/host/structured-provider-session-ownership.ts b/src/main/native-chat/agent-session-wire/host/structured-provider-session-ownership.ts index 7f750f5ce43..2ca88498ba3 100644 --- a/src/main/native-chat/agent-session-wire/host/structured-provider-session-ownership.ts +++ b/src/main/native-chat/agent-session-wire/host/structured-provider-session-ownership.ts @@ -9,7 +9,7 @@ import { keysMayMatch, recordConversationKey, type ConversationKey -} from '../../structured-conversation-key' +} from '../../conversation-adoption/structured-conversation-key' export type StructuredProviderSessionOwnership = { wslDistro: string | null diff --git a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-adopted-import.test.ts b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-adopted-import.test.ts index 4c06752ec35..d48943d6304 100644 --- a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-adopted-import.test.ts +++ b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-adopted-import.test.ts @@ -23,7 +23,8 @@ import { NO_STRUCTURED_AGENTS } from '../../providers/testing/structured-agent-s vi.mock( '../../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn', async (importOriginal) => { - const query = await import('../../../transcript-opencode-sqlite-query') + const query = + await import('../../../transcript-history/opencode/transcript-opencode-sqlite-query') return { ...(await importOriginal()), readOpenCodeTranscriptPageViaWorker: async ( diff --git a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-budget.test.ts b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-budget.test.ts index e1b8aabf7aa..e6eed3372a4 100644 --- a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-budget.test.ts +++ b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-budget.test.ts @@ -2,7 +2,8 @@ import { mkdir, realpath, writeFile, rm, symlink } from 'node:fs/promises' vi.mock( '../../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn', async (original) => { - const query = await import('../../../transcript-opencode-sqlite-query') + const query = + await import('../../../transcript-history/opencode/transcript-opencode-sqlite-query') return { ...(await original()), readOpenCodeTranscriptPageViaWorker: query.readOpenCodeTranscriptPage, diff --git a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-contract-test-fixture.ts b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-contract-test-fixture.ts index 9049efc288a..68e6aa7f116 100644 --- a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-contract-test-fixture.ts +++ b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption-contract-test-fixture.ts @@ -2,7 +2,8 @@ import { writeFile } from 'node:fs/promises' vi.mock( '../../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn', async (original) => { - const query = await import('../../../transcript-opencode-sqlite-query') + const query = + await import('../../../transcript-history/opencode/transcript-opencode-sqlite-query') return { ...(await original()), readOpenCodeTranscriptPageViaWorker: query.readOpenCodeTranscriptPage, diff --git a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption.test.ts b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption.test.ts deleted file mode 100644 index e6c8557a1c5..00000000000 --- a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-create-adoption.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { mkdir, realpath, rename, writeFile } from 'node:fs/promises' -vi.mock( - '../../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn', - async (original) => { - const query = await import('../../../transcript-opencode-sqlite-query') - return { - ...(await original()), - readOpenCodeTranscriptPageViaWorker: query.readOpenCodeTranscriptPage, - readOpenCodeTranscriptSignalViaWorker: async (args: { dbPath: string; sessionId: string }) => - query.readOpenCodeTranscriptSignal(args.dbPath, args.sessionId) - } - } -) -import { join } from 'node:path' -import { expect, it, vi } from 'vitest' -// Adoption forks the AI Vault service for the chat's title; naming is covered elsewhere. -vi.mock('../../../../ai-vault/session-titles/session-title-resolver', () => ({ - resolveLocalAiVaultSessionTitles: async () => ({ titles: [] }) -})) -import { - createTestParams, - CREATE_TEST_CALLER as CALLER -} from './structured-agent-session-create-test-fixture' -import { - HOST_TEST_SESSION as SESSION, - HOST_TEST_THREAD as THREAD -} from './structured-agent-session-host-test-data' -import { - codexLegacyMirrorRecords, - writeCodexHistoryFixture -} from '../../../agent-session-journal/history-import/codex-history-test-fixture' -import { structuredAgentRuntimeMetadata } from '../../../../runtime/agent-session/installation/structured-agent-runtime-metadata' -import { - resolveStructuredAgentSessionAdoptionForCreate, - structuredAgentSessionAdoptAttach -} from '../../../../runtime/agent-session/structured-agent-session-create-adoption' -import { prepareCodexAiVaultSessionResume } from '../../../../codex/history/resume/codex-ai-vault-session-resume' - -import { fixture } from './structured-agent-session-create-adoption-contract-test-fixture' - -it.each([ - ['matching', THREAD], - ['legacy', THREAD], - ['mismatched', '019fd532-7c11-7a90-b6de-4e1a2c3d5f61'] -] as const)( - 'resolves a selected-account rollout with %s metadata before at-rest publication', - async (_kind, metadataId) => { - vi.stubEnv('ORCA_USER_DATA_PATH', join(fixture.rig.root, 'host-data')) - const accountHome = { variable: 'CODEX_HOME', path: join(fixture.rig.root, 'selected-account') } - const sessions = join(accountHome.path, 'sessions') - await mkdir(sessions, { recursive: true }) - const rollout = await writeCodexHistoryFixture( - sessions, - THREAD, - ['saved CLI turn'], - undefined, - metadataId - ) - if (_kind === 'legacy') { - await writeFile( - rollout.filePath, - [ - { type: 'session_meta', payload: { id: THREAD, cwd: fixture.rig.root } }, - ...codexLegacyMirrorRecords() - ] - .map((record) => JSON.stringify(record)) - .join('\n') - ) - } - if (metadataId !== THREAD) { - // Keep filename lookup valid so the imported metadata is what refuses the mismatched source. - await rename(rollout.filePath, join(sessions, `rollout-2026-10-27T00-00-00-${THREAD}.jsonl`)) - } - const metadata = structuredAgentRuntimeMetadata('codex') - if (!metadata?.transcriptImport) { - throw new Error('Codex transcript importer is not registered') - } - const adoption = await resolveStructuredAgentSessionAdoptionForCreate({ - settings: {}, - agent: 'codex', - transcriptImport: metadata.transcriptImport, - providerSessionId: THREAD, - selectedAccountHome: accountHome, - launchEnv: {}, - workspaceCwd: fixture.rig.root, - prepareCodexResume: (args) => - prepareCodexAiVaultSessionResume(args, { - runtimeHome: null, - systemCodexHomePath: join(fixture.rig.root, 'system-account'), - preparePinnedLaunchHome: async () => { - throw new Error('At-rest adoption must not ready a fork launch') - } - }) - }) - expect(adoption.accountHome).toEqual(accountHome) - expect(adoption.transcriptPath).toBe( - await realpath(join(sessions, `rollout-2026-10-27T00-00-00-${THREAD}.jsonl`)) - ) - const result = await fixture.rig.host.create( - CALLER, - createTestParams(undefined, { - accountHome: adoption.accountHome, - adopt: structuredAgentSessionAdoptAttach({ - definition: metadata.definition, - handleNamesTranscript: metadata.transcriptImport.handleNamesTranscript, - conversationStore: - metadata.transcriptImport.conversationStore({ - accountHome: adoption.accountHome, - launchEnv: {} - }) ?? undefined, - transcriptPath: adoption.transcriptPath, - providerSessionId: THREAD, - workspaceCwd: fixture.rig.root, - conversationName: 'Fix the flaky login test' - }) - }) - ) - expect(fixture.rig.adapter.acquire).not.toHaveBeenCalled() - expect(fixture.rig.adapter.dispatch).not.toHaveBeenCalled() - if (metadataId !== THREAD) { - expect(result).toMatchObject({ - ok: false, - refusal: { - code: 'agent_session_identity_required', - details: { reason: 'transcriptUnreadable' }, - createAttemptRefusedBeforeCommit: true - } - }) - expect(fixture.rig.store.listRecords()).toEqual([]) - expect(fixture.rig.store.listOperationRows()).toEqual([]) - expect(fixture.rig.store.listVisibleSessionIds()).toEqual([]) - return - } - expect(result.ok).toBe(true) - if (!result.ok) { - throw new Error('Real selected-account adoption failed') - } - const snapshot = await fixture.rig.host.journalSnapshot(SESSION) - expect(snapshot.items.map(({ body }) => body)).toEqual( - _kind === 'legacy' - ? [ - { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'Prompt' }] }, - { - kind: 'message', - role: 'system', - blocks: [{ type: 'text', text: 'Context compacted', presentation: 'compaction' }] - }, - { kind: 'message', role: 'assistant', blocks: [{ type: 'text', text: 'Response' }] } - ] - : [ - { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'saved CLI turn' }] }, - { - kind: 'message', - role: 'assistant', - blocks: [{ type: 'text', text: 'saved CLI turn answer' }] - } - ] - ) - expect(result.value.page.items).toEqual(snapshot.items) - expect(fixture.rig.store.getRecord(SESSION)?.accountHome).toEqual(accountHome) - expect(fixture.rig.store.getRecord(SESSION)?.conversationName).toBe('Fix the flaky login test') - expect(fixture.rig.store.getRecord(SESSION)?.providerHandleChain).toMatchObject([ - { origin: 'adopted', mintedAtFence: 1, handle: { nativeId: THREAD } } - ]) - expect(fixture.rig.store.listVisibleSessionIds()).toEqual([SESSION]) - } -) diff --git a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-host-test-data.ts b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-host-test-data.ts index d9bd29d2c5c..511a21da204 100644 --- a/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-host-test-data.ts +++ b/src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-host-test-data.ts @@ -1,5 +1,5 @@ import { codexProviderHandle } from '../../../../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' -import { agentSessionRecordFixture } from '../../../agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../testing/agent-session-record-test-fixture' import { agentSessionConversationView } from '../../../../../shared/agent-session/lifecycle/agent-session-conversation-view' import type { AgentJournalSnapshot, diff --git a/src/main/native-chat/agent-session-wire/providers/testing/structured-agent-session-provider-naming.test.ts b/src/main/native-chat/agent-session-wire/providers/testing/structured-agent-session-provider-naming.test.ts deleted file mode 100644 index 5d48c6e24aa..00000000000 --- a/src/main/native-chat/agent-session-wire/providers/testing/structured-agent-session-provider-naming.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { expect, it, vi } from 'vitest' -import { codexProviderHandle } from '../../../../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' -import type { AgentSessionRecord } from '../../../../../shared/agent-session/lifecycle/agent-session-record' -import { agentSessionRecordFixture } from '../../../agent-session-record-test-fixture' -import type { StructuredAgentSessionAdapter } from '../structured-agent-session-adapter' -import { createStructuredChatNamingHandler } from '../../../structured-chat-naming' -import { setAgentSessionRecordUserName } from '../../../../runtime/agent-session/records/agent-session-record-conversation-name' -import { - createStructuredAgentSessionProviderNaming, - withProviderNaming -} from '../structured-agent-session-provider-naming' - -type Rename = NonNullable - -function chat(fields: Partial = {}): AgentSessionRecord { - return agentSessionRecordFixture({ - sessionId: 'chat-1', - provider: 'codex', - accountHome: { variable: 'CODEX_HOME', path: '/home/work/.codex' }, - providerHandleChain: [ - { - linkId: 'link-1', - handle: codexProviderHandle('thread-1'), - origin: 'created', - mintedAtFence: 1, - observedAt: 0 - } - ], - conversationName: 'Release notes', - conversationNameSource: 'user', - ...fields - }) -} - -function naming(rename: Rename) { - const warn = vi.fn() - const queue = createStructuredAgentSessionProviderNaming({ - adapter: { renameProviderSession: rename }, - logger: { warn, error: vi.fn() } - }) - return { queue, warn } -} - -it.each([10, 100])('coalesces %i saved renames into two ordered pushes', async (count) => { - const gates = [Promise.withResolvers(), Promise.withResolvers()] - const rename = vi.fn(() => gates[rename.mock.calls.length - 1]!.promise) - let record = chat({ conversationName: 'First' }) - const { queue } = naming(rename) - const handler = createStructuredChatNamingHandler({ - getStore: () => ({ - getRecord: () => record, - compareAndSetConversationName: async () => record, - setUserConversationName: async (_id, name, write) => { - const result = setAgentSessionRecordUserName(record, name, write, 1_700_000_000_000) - record = result.record - return result - } - }), - getSettings: () => ({ nativeChatAutoName: false }), - workerTaskTitle: () => null, - readFirstPrompt: async () => '', - readJournalFirstPrompt: async () => '', - generate: async () => null, - onNamed: () => {}, - logger: { warn: vi.fn(), error: vi.fn() } - }) - const wrapped = withProviderNaming(handler, (committed) => void queue.push(committed)) - await wrapped.setUserName('chat-1', 'First', 'rename') - for (let i = 1; i <= count; i += 1) { - await wrapped.setUserName('chat-1', `Name ${i}`, 'rename') - } - expect(rename).toHaveBeenCalledTimes(1) - const done = queue.push(record) - gates[0]!.resolve() - await vi.waitFor(() => expect(rename).toHaveBeenCalledTimes(2)) - gates[1]!.resolve() - await done - expect(rename.mock.calls.map(([input]) => input.name)).toEqual(['First', `Name ${count}`]) - await wrapped.setUserName('chat-1', 'Ignored alias', 'legacy-alias') - expect(rename).toHaveBeenCalledTimes(2) -}) diff --git a/src/main/native-chat/agent-session-wire/recovery/structured-agent-session-continuation-context.ts b/src/main/native-chat/agent-session-wire/recovery/structured-agent-session-continuation-context.ts index 136100ca281..a7528381dda 100644 --- a/src/main/native-chat/agent-session-wire/recovery/structured-agent-session-continuation-context.ts +++ b/src/main/native-chat/agent-session-wire/recovery/structured-agent-session-continuation-context.ts @@ -1,4 +1,4 @@ -import { projectSavedChatContextItems } from '../../agent-session-saved-prompt-text' +import { projectSavedChatContextItems } from '../../conversation-context/agent-session-saved-prompt-text' import type { AgentJournalCursor } from '../../../../shared/agent-session/journal/agent-session-journal-identity' import type { AgentSessionContinuationContext } from '../../../../shared/agent-session/agent-session-wire' import { agentSessionCurrentContextRows } from '../../../../shared/agent-session/agent-session-context-clear' @@ -7,7 +7,7 @@ import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' import { projectStructuredAgentSessionMessages } from '../../../../shared/agent-session/view/structured-agent-session-message-projection' import { formatWorkerTranscriptMessage } from '../../../../shared/worker-transcript-text' import { toLinuxPath } from '../../../../shared/wsl-paths' -import { writeAgentSessionContextSnapshot } from '../../agent-session-context-snapshots' +import { writeAgentSessionContextSnapshot } from '../../conversation-context/agent-session-context-snapshots' import type { StructuredAgentSessionHostDeps, StructuredAgentSessionHostSession diff --git a/src/main/native-chat/agent-session-wire/startup/structured-agent-session-agent-start.ts b/src/main/native-chat/agent-session-wire/startup/structured-agent-session-agent-start.ts index 5a421e7d208..0bf1c96e9bc 100644 --- a/src/main/native-chat/agent-session-wire/startup/structured-agent-session-agent-start.ts +++ b/src/main/native-chat/agent-session-wire/startup/structured-agent-session-agent-start.ts @@ -6,7 +6,7 @@ import type { ProviderDiagnostic } from '../../../../shared/agent-session/failures/agent-session-failure' import type { AgentSessionArgumentProblem } from '../../../../shared/agent-session/agent-session-argument-problem' -import { argumentProblemOf } from '../../structured-agent-arguments-error' +import { argumentProblemOf } from '../../agent-launch/structured-agent-arguments-error' import { agentSessionRefusalFromReference, isAgentSessionRefusalError, diff --git a/src/main/native-chat/agent-session-wire/startup/structured-agent-session-failure-text.ts b/src/main/native-chat/agent-session-wire/startup/structured-agent-session-failure-text.ts index 39efcf77a76..810efd87fb3 100644 --- a/src/main/native-chat/agent-session-wire/startup/structured-agent-session-failure-text.ts +++ b/src/main/native-chat/agent-session-wire/startup/structured-agent-session-failure-text.ts @@ -28,8 +28,8 @@ import { } from '../../../provider-process/provider-executable-missing' import { providerRequestTimeoutOf } from '../../../provider-process/provider-request-timeout' import { findInErrorChain } from '../../../provider-process/provider-error-chain' -import { argumentProblemOf } from '../../structured-agent-arguments-error' -import { agentCliInstallationProblemOf } from '../../agent-cli-installation-error' +import { argumentProblemOf } from '../../agent-launch/structured-agent-arguments-error' +import { agentCliInstallationProblemOf } from '../../agent-launch/agent-cli-installation-error' import { agentCliInstallationFacts } from '../../../../shared/agent-launch/catalog/agent-cli-installation-problem' const TYPED_START_REFUSALS = [ diff --git a/src/main/native-chat/agent-session-wire/startup/structured-agent-session-start-failure.ts b/src/main/native-chat/agent-session-wire/startup/structured-agent-session-start-failure.ts index 3043bb10cbe..8f02eb0997b 100644 --- a/src/main/native-chat/agent-session-wire/startup/structured-agent-session-start-failure.ts +++ b/src/main/native-chat/agent-session-wire/startup/structured-agent-session-start-failure.ts @@ -8,8 +8,8 @@ import { providerLaunchFolderMissing } from '../../../provider-process/provider-executable-missing' import { providerRequestTimeoutOf } from '../../../provider-process/provider-request-timeout' -import { argumentProblemOf } from '../../structured-agent-arguments-error' -import { agentCliInstallationProblemOf } from '../../agent-cli-installation-error' +import { argumentProblemOf } from '../../agent-launch/structured-agent-arguments-error' +import { agentCliInstallationProblemOf } from '../../agent-launch/agent-cli-installation-error' import { AgentSessionAcquisitionRefusal, isAgentSessionPreSpawnError diff --git a/src/main/native-chat/agent-conversation-copy-error.ts b/src/main/native-chat/conversation-adoption/agent-conversation-copy-error.ts similarity index 76% rename from src/main/native-chat/agent-conversation-copy-error.ts rename to src/main/native-chat/conversation-adoption/agent-conversation-copy-error.ts index 64fb08a76f8..a3baa47bc57 100644 --- a/src/main/native-chat/agent-conversation-copy-error.ts +++ b/src/main/native-chat/conversation-adoption/agent-conversation-copy-error.ts @@ -2,8 +2,8 @@ import { AgentSessionRefusalError, refuse, type AgentSessionWireRefusalCode -} from '../../shared/agent-session/failures/agent-session-wire-refusals' -import type { AgentSessionRefusalDetailsByCode } from '../../shared/agent-session/failures/agent-session-refusal-details' +} from '../../../shared/agent-session/failures/agent-session-wire-refusals' +import type { AgentSessionRefusalDetailsByCode } from '../../../shared/agent-session/failures/agent-session-refusal-details' /** Only copy errors authored here may pass through the host's generic provider failure. */ export class AgentConversationCopyError extends Error {} diff --git a/src/main/native-chat/codex-adoption-preparation.ts b/src/main/native-chat/conversation-adoption/codex-adoption-preparation.ts similarity index 82% rename from src/main/native-chat/codex-adoption-preparation.ts rename to src/main/native-chat/conversation-adoption/codex-adoption-preparation.ts index 37c5736d408..34731aaa9c4 100644 --- a/src/main/native-chat/codex-adoption-preparation.ts +++ b/src/main/native-chat/conversation-adoption/codex-adoption-preparation.ts @@ -3,12 +3,12 @@ import { join } from 'node:path' import { isLegacyAgentSessionAccountHome, type AgentSessionAccountHome -} from '../../shared/agent-session/lifecycle/agent-session-account-home' -import type { NativeCodexResumePreparation } from '../codex/history/resume/codex-ai-vault-session-resume' -import { getSystemCodexHomePath } from '../codex/configuration/homes/codex-home-paths' +} from '../../../shared/agent-session/lifecycle/agent-session-account-home' +import type { NativeCodexResumePreparation } from '../../codex/history/resume/codex-ai-vault-session-resume' +import { getSystemCodexHomePath } from '../../codex/configuration/homes/codex-home-paths' import type { StructuredAgentSessionAdoption } from './structured-agent-session-history-adoption' -import { admitTranscriptPath } from './transcript-path-admission' -import { normalizeRuntimePathForComparison } from '../../shared/cross-platform-path' +import { admitTranscriptPath } from '../transcript-discovery/transcript-path-admission' +import { normalizeRuntimePathForComparison } from '../../../shared/cross-platform-path' export async function prepareCodexAdoption( source: StructuredAgentSessionAdoption, diff --git a/src/main/native-chat/structured-agent-cli-conversations.ts b/src/main/native-chat/conversation-adoption/structured-agent-cli-conversations.ts similarity index 91% rename from src/main/native-chat/structured-agent-cli-conversations.ts rename to src/main/native-chat/conversation-adoption/structured-agent-cli-conversations.ts index dafa0f747c0..427d20c5102 100644 --- a/src/main/native-chat/structured-agent-cli-conversations.ts +++ b/src/main/native-chat/conversation-adoption/structured-agent-cli-conversations.ts @@ -1,13 +1,13 @@ import { isAbsolute } from 'node:path' -import { admitTranscriptPath } from './transcript-path-admission' +import { admitTranscriptPath } from '../transcript-discovery/transcript-path-admission' import { isLegacyAgentSessionAccountHome, type AgentSessionAccountHome -} from '../../shared/agent-session/lifecycle/agent-session-account-home' -import type { AgentSessionProviderHandleLink } from '../../shared/agent-session/lifecycle/agent-session-provider-handle' -import type { AiVaultAgent } from '../../shared/ai-vault/contracts/ai-vault-types' -import { TUI_AGENT_CONFIG } from '../../shared/agent-launch/catalog/tui-agent-config' -import { sessionIdFromFileName } from '../ai-vault/parsing/session-scanner-accumulator' +} from '../../../shared/agent-session/lifecycle/agent-session-account-home' +import type { AgentSessionProviderHandleLink } from '../../../shared/agent-session/lifecycle/agent-session-provider-handle' +import type { AiVaultAgent } from '../../../shared/ai-vault/contracts/ai-vault-types' +import { TUI_AGENT_CONFIG } from '../../../shared/agent-launch/catalog/tui-agent-config' +import { sessionIdFromFileName } from '../../ai-vault/parsing/session-scanner-accumulator' export type StructuredAgentTranscriptImportSettings = { codexManagedAccounts?: readonly { managedHomePath: string }[] diff --git a/src/main/native-chat/structured-agent-cli-source.ts b/src/main/native-chat/conversation-adoption/structured-agent-cli-source.ts similarity index 86% rename from src/main/native-chat/structured-agent-cli-source.ts rename to src/main/native-chat/conversation-adoption/structured-agent-cli-source.ts index 15343324bfc..a0845bb6e3b 100644 --- a/src/main/native-chat/structured-agent-cli-source.ts +++ b/src/main/native-chat/conversation-adoption/structured-agent-cli-source.ts @@ -1,15 +1,15 @@ -import type { AgentStatusIpcPayload } from '../../shared/agent-status/contracts/agent-status-ipc-payload' -import type { StructuredAgentSessionResumeSource } from '../../shared/agent-session/structured-agent-session-create' -import type { StructuredAgentId } from '../../shared/agent-session/lifecycle/agent-session-provider-handle' -import { agentSessionRefusalError } from '../../shared/agent-session/failures/agent-session-wire-refusals' +import type { AgentStatusIpcPayload } from '../../../shared/agent-status/contracts/agent-status-ipc-payload' +import type { StructuredAgentSessionResumeSource } from '../../../shared/agent-session/structured-agent-session-create' +import type { StructuredAgentId } from '../../../shared/agent-session/lifecycle/agent-session-provider-handle' +import { agentSessionRefusalError } from '../../../shared/agent-session/failures/agent-session-wire-refusals' import { adoptsCliConversations, chatAgentsShareConversations, CLI_CONVERSATION_ADOPTING_AGENTS -} from '../../shared/structured-agent-cli-conversation-adoption' -import { isWslUncPath } from '../../shared/wsl-paths' -import { needsWslHostResolution } from './host-readable-transcript-path' -import { parseSshTranscriptPath } from './ssh-transcript-path' +} from '../../../shared/structured-agent-cli-conversation-adoption' +import { isWslUncPath } from '../../../shared/wsl-paths' +import { needsWslHostResolution } from '../transcript-discovery/host-readable-transcript-path' +import { parseSshTranscriptPath } from '../transcript-filesystem/ssh-transcript-path' type CliSource = NonNullable export type StructuredAgentCliSourceRoute = 'native-local' | 'unsupported' | 'unknown' diff --git a/src/main/native-chat/structured-agent-session-history-adoption.ts b/src/main/native-chat/conversation-adoption/structured-agent-session-history-adoption.ts similarity index 85% rename from src/main/native-chat/structured-agent-session-history-adoption.ts rename to src/main/native-chat/conversation-adoption/structured-agent-session-history-adoption.ts index bc092efe416..3a49005bc1b 100644 --- a/src/main/native-chat/structured-agent-session-history-adoption.ts +++ b/src/main/native-chat/conversation-adoption/structured-agent-session-history-adoption.ts @@ -1,20 +1,20 @@ // Resolve credential homes and transcripts from host-owned facts, never client-supplied paths. -import { chatAgentsShareConversations } from '../../shared/structured-agent-cli-conversation-adoption' -import { agentSessionRefusalError } from '../../shared/agent-session/failures/agent-session-wire-refusals' -import type { AgentSessionOperationRow } from '../../shared/agent-session/lifecycle/agent-session-operation-ledger' +import { chatAgentsShareConversations } from '../../../shared/structured-agent-cli-conversation-adoption' +import { agentSessionRefusalError } from '../../../shared/agent-session/failures/agent-session-wire-refusals' +import type { AgentSessionOperationRow } from '../../../shared/agent-session/lifecycle/agent-session-operation-ledger' import type { AgentSessionProviderHandle, AgentSessionProviderHandleLink, StructuredAgentId -} from '../../shared/agent-session/lifecycle/agent-session-provider-handle' -import type { AgentSessionRecord } from '../../shared/agent-session/lifecycle/agent-session-record' -import type { AgentSessionOwnerObservation } from '../../shared/agent-session/lifecycle/agent-session-conversation-view' -import { agentSessionOwnerObservationIsLive } from '../../shared/agent-session/lifecycle/agent-session-lease-adjudication' +} from '../../../shared/agent-session/lifecycle/agent-session-provider-handle' +import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' +import type { AgentSessionOwnerObservation } from '../../../shared/agent-session/lifecycle/agent-session-conversation-view' +import { agentSessionOwnerObservationIsLive } from '../../../shared/agent-session/lifecycle/agent-session-lease-adjudication' import { agentSessionAccountHomesEqual, type AgentSessionAccountHome -} from '../../shared/agent-session/lifecycle/agent-session-account-home' +} from '../../../shared/agent-session/lifecycle/agent-session-account-home' export type StructuredAgentSessionAdoptionOwnership = { sessionId: string diff --git a/src/main/native-chat/structured-conversation-key.ts b/src/main/native-chat/conversation-adoption/structured-conversation-key.ts similarity index 79% rename from src/main/native-chat/structured-conversation-key.ts rename to src/main/native-chat/conversation-adoption/structured-conversation-key.ts index 48f39815538..3086f1d374e 100644 --- a/src/main/native-chat/structured-conversation-key.ts +++ b/src/main/native-chat/conversation-adoption/structured-conversation-key.ts @@ -1,15 +1,15 @@ import { chatAgentsShareConversations, CLI_CONVERSATION_ADOPTING_AGENTS -} from '../../shared/structured-agent-cli-conversation-adoption' +} from '../../../shared/structured-agent-cli-conversation-adoption' import { agentSessionAccountHomesEqual, type AgentSessionAccountHome -} from '../../shared/agent-session/lifecycle/agent-session-account-home' -import type { AgentSessionProviderHandleLink } from '../../shared/agent-session/lifecycle/agent-session-provider-handle' -import type { AgentSessionRecord } from '../../shared/agent-session/lifecycle/agent-session-record' -import { structuredAgentRuntimeMetadata } from '../runtime/agent-session/installation/structured-agent-runtime-metadata' -import { sessionIdFromFileName } from '../ai-vault/parsing/session-scanner-accumulator' +} from '../../../shared/agent-session/lifecycle/agent-session-account-home' +import type { AgentSessionProviderHandleLink } from '../../../shared/agent-session/lifecycle/agent-session-provider-handle' +import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' +import { structuredAgentRuntimeMetadata } from '../../runtime/agent-session/installation/structured-agent-runtime-metadata' +import { sessionIdFromFileName } from '../../ai-vault/parsing/session-scanner-accumulator' /** One conversation wherever it is named: the agents sharing its ids, its id (null: a targetless * resume that may pick any of the family's), and its account home (null: the namer can't tell). */ diff --git a/src/main/native-chat/agent-session-context-snapshots.test.ts b/src/main/native-chat/conversation-context/agent-session-context-snapshots.test.ts similarity index 100% rename from src/main/native-chat/agent-session-context-snapshots.test.ts rename to src/main/native-chat/conversation-context/agent-session-context-snapshots.test.ts diff --git a/src/main/native-chat/agent-session-context-snapshots.ts b/src/main/native-chat/conversation-context/agent-session-context-snapshots.ts similarity index 88% rename from src/main/native-chat/agent-session-context-snapshots.ts rename to src/main/native-chat/conversation-context/agent-session-context-snapshots.ts index 6fef5293ecb..8eb6f6796ea 100644 --- a/src/main/native-chat/agent-session-context-snapshots.ts +++ b/src/main/native-chat/conversation-context/agent-session-context-snapshots.ts @@ -2,12 +2,12 @@ import { createHash, randomUUID } from 'node:crypto' import { rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join, resolve } from 'node:path' -import { runKeyedSerializedOperation } from '../../shared/concurrency/keyed-serialized-operations' -import { renameFileWithWindowsRetryAsync } from '../../shared/windows-retry-file-operations' +import { runKeyedSerializedOperation } from '../../../shared/concurrency/keyed-serialized-operations' +import { renameFileWithWindowsRetryAsync } from '../../../shared/windows-retry-file-operations' import { ensureOwnedTempStagingRoot, getOwnedTempStagingRoot -} from '../filesystem/owned-temp-staging-root' +} from '../../filesystem/owned-temp-staging-root' const claims = new Map() const publications = new Map>() diff --git a/src/main/native-chat/agent-session-saved-prompt-text.ts b/src/main/native-chat/conversation-context/agent-session-saved-prompt-text.ts similarity index 78% rename from src/main/native-chat/agent-session-saved-prompt-text.ts rename to src/main/native-chat/conversation-context/agent-session-saved-prompt-text.ts index 42d144bcd91..798db1df28a 100644 --- a/src/main/native-chat/agent-session-saved-prompt-text.ts +++ b/src/main/native-chat/conversation-context/agent-session-saved-prompt-text.ts @@ -1,16 +1,16 @@ import type { AgentJournalRenderItem, AgentJournalQuestionItem -} from '../../shared/agent-session/journal/agent-session-journal-types' -import { agentJournalItemRowOrigin } from '../../shared/agent-session/journal/agent-session-journal-position' -import { agentJournalLinkageFields } from '../../shared/agent-session/journal/agent-session-journal-producer' +} from '../../../shared/agent-session/journal/agent-session-journal-types' +import { agentJournalItemRowOrigin } from '../../../shared/agent-session/journal/agent-session-journal-position' +import { agentJournalLinkageFields } from '../../../shared/agent-session/journal/agent-session-journal-producer' import { agentSessionPromptQuestions, legacyAgentSessionQuestionAnswers -} from '../../shared/agent-session/agent-session-question-answer' -import { nativeChatReceiptAnswers } from '../../shared/native-chat/native-chat-resolution-receipt' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { projectStructuredItemToNativeChat } from '../../shared/agent-session/view/structured-agent-session-projection' +} from '../../../shared/agent-session/agent-session-question-answer' +import { nativeChatReceiptAnswers } from '../../../shared/native-chat/native-chat-resolution-receipt' +import type { NativeChatMessage } from '../../../shared/native-chat/native-chat-types' +import { projectStructuredItemToNativeChat } from '../../../shared/agent-session/view/structured-agent-session-projection' function questionInterpretation(body: AgentJournalQuestionItem): string[] { if (body.resolution.state !== 'resolved') { diff --git a/src/main/native-chat/native-chat-media-path.test.ts b/src/main/native-chat/conversation-files/native-chat-media-path.test.ts similarity index 90% rename from src/main/native-chat/native-chat-media-path.test.ts rename to src/main/native-chat/conversation-files/native-chat-media-path.test.ts index 9c737cf047b..b6fc677790a 100644 --- a/src/main/native-chat/native-chat-media-path.test.ts +++ b/src/main/native-chat/conversation-files/native-chat-media-path.test.ts @@ -2,20 +2,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' -import { agentSessionConversationView } from '../../shared/agent-session/lifecycle/agent-session-conversation-view' -import { agentSessionRecordFixture } from './agent-session-record-test-fixture' +import { agentSessionConversationView } from '../../../shared/agent-session/lifecycle/agent-session-conversation-view' +import { agentSessionRecordFixture } from '../testing/agent-session-record-test-fixture' import { clearGrokSessionPathLookupCacheForTests, grokEncodedCwdDirName -} from '../../shared/grok-session-paths' -import type { AgentStatusIpcPayload } from '../../shared/agent-status/contracts/agent-status-ipc-payload' +} from '../../../shared/grok-session-paths' +import type { AgentStatusIpcPayload } from '../../../shared/agent-status/contracts/agent-status-ipc-payload' import { resolveNativeChatMediaPath, type NativeChatMediaPathRequest } from './native-chat-media-path' -vi.mock('../telemetry/client', () => ({ track: vi.fn() })) -vi.mock('../telemetry/cohort-classifier', () => ({ getCohortAtEmit: () => ({}) })) +vi.mock('../../telemetry/client', () => ({ track: vi.fn() })) +vi.mock('../../telemetry/cohort-classifier', () => ({ getCohortAtEmit: () => ({}) })) let root: string let home: string diff --git a/src/main/native-chat/native-chat-media-path.ts b/src/main/native-chat/conversation-files/native-chat-media-path.ts similarity index 83% rename from src/main/native-chat/native-chat-media-path.ts rename to src/main/native-chat/conversation-files/native-chat-media-path.ts index 90c9a763bb9..ebdaf0796c9 100644 --- a/src/main/native-chat/native-chat-media-path.ts +++ b/src/main/native-chat/conversation-files/native-chat-media-path.ts @@ -1,25 +1,25 @@ import { lstat } from 'node:fs/promises' import { basename, dirname, isAbsolute, join } from 'node:path' -import type { AgentStatusIpcPayload } from '../../shared/agent-status/contracts/agent-status-ipc-payload' -import type { AgentSessionConversationView } from '../../shared/agent-session/lifecycle/agent-session-conversation-view' -import { isLegacyAgentSessionAccountHome } from '../../shared/agent-session/lifecycle/agent-session-account-home' -import { MAX_AGENT_SESSION_PROVIDER_HANDLE_LINKS } from '../../shared/agent-session/lifecycle/agent-session-provider-handle' -import { structuredAgentSessionTabId } from '../../shared/agent-session/view/structured-agent-session-projection' -import type { RuntimeNativeChatFileContext } from '../../shared/native-chat/native-chat-file-context' -import type { ExecutionHostId } from '../../shared/execution-host' -import { parsePaneKey } from '../../shared/workspace-layout/stable-pane-id' +import type { AgentStatusIpcPayload } from '../../../shared/agent-status/contracts/agent-status-ipc-payload' +import type { AgentSessionConversationView } from '../../../shared/agent-session/lifecycle/agent-session-conversation-view' +import { isLegacyAgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' +import { MAX_AGENT_SESSION_PROVIDER_HANDLE_LINKS } from '../../../shared/agent-session/lifecycle/agent-session-provider-handle' +import { structuredAgentSessionTabId } from '../../../shared/agent-session/view/structured-agent-session-projection' +import type { RuntimeNativeChatFileContext } from '../../../shared/native-chat/native-chat-file-context' +import type { ExecutionHostId } from '../../../shared/execution-host' +import { parsePaneKey } from '../../../shared/workspace-layout/stable-pane-id' import { findGrokChatHistoryByCwd, findGrokChatHistoryBySessionId, GROK_CHAT_HISTORY_FILE, isSafeGrokSessionId, resolveGrokSessionsDir -} from '../../shared/grok-session-paths' -import { readNodeFileWithinLimit } from '../../shared/file-content/node-bounded-file-reader' -import { isENOENT } from '../ipc/filesystem/access/filesystem-path-containment' -import { needsWslHostResolution } from './host-readable-transcript-path' -import { parseWslPath } from '../wsl/filesystem/unc-path-access' -import { admitTranscriptPath } from './transcript-path-admission' +} from '../../../shared/grok-session-paths' +import { readNodeFileWithinLimit } from '../../../shared/file-content/node-bounded-file-reader' +import { isENOENT } from '../../ipc/filesystem/access/filesystem-path-containment' +import { needsWslHostResolution } from '../transcript-discovery/host-readable-transcript-path' +import { parseWslPath } from '../../wsl/filesystem/unc-path-access' +import { admitTranscriptPath } from '../transcript-discovery/transcript-path-admission' export type NativeChatMediaPathResolution = | { kind: 'matched'; absolutePath: string } diff --git a/src/main/native-chat/native-chat-scratch-folder-migration.test.ts b/src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.test.ts similarity index 98% rename from src/main/native-chat/native-chat-scratch-folder-migration.test.ts rename to src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.test.ts index ff7be62703e..9cd6fb6ffb6 100644 --- a/src/main/native-chat/native-chat-scratch-folder-migration.test.ts +++ b/src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.test.ts @@ -40,7 +40,7 @@ vi.mock('node:fs/promises', async (importOriginal) => { } } }) -import type { StructuredAgentSessionLogger } from './agent-session-wire/host/structured-agent-session-logger' +import type { StructuredAgentSessionLogger } from '../agent-session-wire/host/structured-agent-session-logger' import { nativeChatScratchFolderFor, nativeChatScratchRootFor } from './native-chat-scratch-folder' import { migrateLegacyNativeChatVisualsFolders } from './native-chat-scratch-folder-migration' import { readNativeChatVisualFile } from './native-chat-visual-file-read' diff --git a/src/main/native-chat/native-chat-scratch-folder-migration.ts b/src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.ts similarity index 95% rename from src/main/native-chat/native-chat-scratch-folder-migration.ts rename to src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.ts index 107d0c3859c..baab3168329 100644 --- a/src/main/native-chat/native-chat-scratch-folder-migration.ts +++ b/src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.ts @@ -4,8 +4,8 @@ import { constants } from 'node:fs' import { copyFile, lstat, mkdir, mkdtemp, readdir, rename, rmdir, unlink } from 'node:fs/promises' import { join } from 'node:path' -import { isDefinitiveAbsence } from '../../shared/definitive-filesystem-absence' -import type { StructuredAgentSessionLogger } from './agent-session-wire/host/structured-agent-session-logger' +import { isDefinitiveAbsence } from '../../../shared/definitive-filesystem-absence' +import type { StructuredAgentSessionLogger } from '../agent-session-wire/host/structured-agent-session-logger' import { isNativeChatScratchFolderName, nativeChatScratchRootFor diff --git a/src/main/native-chat/native-chat-scratch-folder.ts b/src/main/native-chat/conversation-files/native-chat-scratch-folder.ts similarity index 100% rename from src/main/native-chat/native-chat-scratch-folder.ts rename to src/main/native-chat/conversation-files/native-chat-scratch-folder.ts diff --git a/src/main/native-chat/native-chat-visual-file-read.test.ts b/src/main/native-chat/conversation-files/native-chat-visual-file-read.test.ts similarity index 100% rename from src/main/native-chat/native-chat-visual-file-read.test.ts rename to src/main/native-chat/conversation-files/native-chat-visual-file-read.test.ts diff --git a/src/main/native-chat/native-chat-visual-file-read.ts b/src/main/native-chat/conversation-files/native-chat-visual-file-read.ts similarity index 95% rename from src/main/native-chat/native-chat-visual-file-read.ts rename to src/main/native-chat/conversation-files/native-chat-visual-file-read.ts index b91968a7768..aa5acb807a7 100644 --- a/src/main/native-chat/native-chat-visual-file-read.ts +++ b/src/main/native-chat/conversation-files/native-chat-visual-file-read.ts @@ -7,13 +7,13 @@ import { basename, dirname, join } from 'node:path' import { NATIVE_CHAT_VISUAL_MAX_BYTES, isNativeChatVisualFileName -} from '../../shared/native-chat/native-chat-visual-directive' +} from '../../../shared/native-chat/native-chat-visual-directive' import type { AgentSessionReadVisualResult, AgentSessionVisualReadError -} from '../../shared/rpc-contract/agent-session-visual-params' -import { isENOENT } from '../ipc/filesystem/access/filesystem-path-containment' -import { readLocalFileBounded } from '../ipc/filesystem/local-regular-file-read' +} from '../../../shared/rpc-contract/agent-session-visual-params' +import { isENOENT } from '../../ipc/filesystem/access/filesystem-path-containment' +import { readLocalFileBounded } from '../../ipc/filesystem/local-regular-file-read' // O_NOFOLLOW prevents final-component swaps; Windows relies on lstat and post-open identity checks. const VISUAL_OPEN_FLAGS = diff --git a/src/main/native-chat/native-chat-visual-guest-location.ts b/src/main/native-chat/conversation-files/native-chat-visual-guest-location.ts similarity index 81% rename from src/main/native-chat/native-chat-visual-guest-location.ts rename to src/main/native-chat/conversation-files/native-chat-visual-guest-location.ts index 11e97d17162..0f5152e1897 100644 --- a/src/main/native-chat/native-chat-visual-guest-location.ts +++ b/src/main/native-chat/conversation-files/native-chat-visual-guest-location.ts @@ -1,8 +1,8 @@ import { basename, dirname } from 'node:path' import { fileURLToPath } from 'node:url' -import { isNativeChatVisualFileName } from '../../shared/native-chat/native-chat-visual-directive' -import { parseDocPreviewUrl } from '../../shared/document-preview/doc-preview-scheme' -import { getDocPreviewGrant } from '../browser/document-preview/doc-preview-grant-registry' +import { isNativeChatVisualFileName } from '../../../shared/native-chat/native-chat-visual-directive' +import { parseDocPreviewUrl } from '../../../shared/document-preview/doc-preview-scheme' +import { getDocPreviewGrant } from '../../browser/document-preview/doc-preview-grant-registry' /** Presentation identity only; existing file/grant policies still own every read. */ export function isNativeChatVisualGuestLocation(url: string, scratchRoot: string): boolean { diff --git a/src/main/native-chat/native-chat-visuals-delivery.ts b/src/main/native-chat/conversation-files/native-chat-visuals-delivery.ts similarity index 95% rename from src/main/native-chat/native-chat-visuals-delivery.ts rename to src/main/native-chat/conversation-files/native-chat-visuals-delivery.ts index da9309efbd6..22a111552e4 100644 --- a/src/main/native-chat/native-chat-visuals-delivery.ts +++ b/src/main/native-chat/conversation-files/native-chat-visuals-delivery.ts @@ -1,7 +1,7 @@ // Best-effort visual setup must never prevent chat startup. import { mkdir } from 'node:fs/promises' -import type { StructuredAgentSessionLogger } from './agent-session-wire/host/structured-agent-session-logger' +import type { StructuredAgentSessionLogger } from '../agent-session-wire/host/structured-agent-session-logger' import { nativeChatScratchFolderFor } from './native-chat-scratch-folder' import { resolveNativeChatVisualsSkillLocation, diff --git a/src/main/native-chat/native-chat-visuals-skill-location.ts b/src/main/native-chat/conversation-files/native-chat-visuals-skill-location.ts similarity index 96% rename from src/main/native-chat/native-chat-visuals-skill-location.ts rename to src/main/native-chat/conversation-files/native-chat-visuals-skill-location.ts index 05b3c6e2489..5afc5b201af 100644 --- a/src/main/native-chat/native-chat-visuals-skill-location.ts +++ b/src/main/native-chat/conversation-files/native-chat-visuals-skill-location.ts @@ -2,7 +2,7 @@ import { access } from 'node:fs/promises' import { join } from 'node:path' -import { getAppEnvironment, hasAppEnvironment } from '../../shared/app-environment' +import { getAppEnvironment, hasAppEnvironment } from '../../../shared/app-environment' const PLUGIN_DIR_NAME = 'native-chat-visuals' export const NATIVE_CHAT_VISUALS_SKILL_NAME = 'orca-chat-visuals' diff --git a/src/main/native-chat/native-chat-visuals-sweep.test.ts b/src/main/native-chat/conversation-files/native-chat-visuals-sweep.test.ts similarity index 97% rename from src/main/native-chat/native-chat-visuals-sweep.test.ts rename to src/main/native-chat/conversation-files/native-chat-visuals-sweep.test.ts index 0b7375771d2..4309e9d291c 100644 --- a/src/main/native-chat/native-chat-visuals-sweep.test.ts +++ b/src/main/native-chat/conversation-files/native-chat-visuals-sweep.test.ts @@ -14,7 +14,7 @@ import { import { tmpdir } from 'node:os' import { basename, join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' -import type { StructuredAgentSessionLogger } from './agent-session-wire/host/structured-agent-session-logger' +import type { StructuredAgentSessionLogger } from '../agent-session-wire/host/structured-agent-session-logger' import { nativeChatScratchFolderFor, nativeChatScratchRootFor } from './native-chat-scratch-folder' import { sweepNativeChatVisualsFolders, diff --git a/src/main/native-chat/native-chat-visuals-sweep.ts b/src/main/native-chat/conversation-files/native-chat-visuals-sweep.ts similarity index 93% rename from src/main/native-chat/native-chat-visuals-sweep.ts rename to src/main/native-chat/conversation-files/native-chat-visuals-sweep.ts index f91b0fac9cd..f85aa9b33f5 100644 --- a/src/main/native-chat/native-chat-visuals-sweep.ts +++ b/src/main/native-chat/conversation-files/native-chat-visuals-sweep.ts @@ -4,9 +4,9 @@ import type { Dirent } from 'node:fs' import { lstat, readdir, rm } from 'node:fs/promises' import { join } from 'node:path' -import { isDefinitiveAbsence } from '../../shared/definitive-filesystem-absence' -import { yieldToEventLoop } from '../../shared/event-loop-yield' -import type { StructuredAgentSessionLogger } from './agent-session-wire/host/structured-agent-session-logger' +import { isDefinitiveAbsence } from '../../../shared/definitive-filesystem-absence' +import { yieldToEventLoop } from '../../../shared/event-loop-yield' +import type { StructuredAgentSessionLogger } from '../agent-session-wire/host/structured-agent-session-logger' import { isNativeChatScratchFolderName, nativeChatScratchFolderName, diff --git a/src/main/native-chat/structured-chat-name-publication.ts b/src/main/native-chat/conversation-naming/structured-chat-name-publication.ts similarity index 93% rename from src/main/native-chat/structured-chat-name-publication.ts rename to src/main/native-chat/conversation-naming/structured-chat-name-publication.ts index f344d0fc605..ee3d3c22ce7 100644 --- a/src/main/native-chat/structured-chat-name-publication.ts +++ b/src/main/native-chat/conversation-naming/structured-chat-name-publication.ts @@ -1,5 +1,5 @@ -import type { AgentSessionRecord } from '../../shared/agent-session/lifecycle/agent-session-record' -import type { StructuredChatNamingDeps } from './structured-chat-naming' +import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' +import type { StructuredChatNamingDeps } from './structured-chat-naming-types' export async function runStructuredChatNameGeneration(input: { automaticTitle?: Parameters[3] diff --git a/src/main/native-chat/structured-chat-naming-types.ts b/src/main/native-chat/conversation-naming/structured-chat-naming-types.ts similarity index 71% rename from src/main/native-chat/structured-chat-naming-types.ts rename to src/main/native-chat/conversation-naming/structured-chat-naming-types.ts index 9f8b4c8fb34..c50f67ab337 100644 --- a/src/main/native-chat/structured-chat-naming-types.ts +++ b/src/main/native-chat/conversation-naming/structured-chat-naming-types.ts @@ -1,10 +1,10 @@ -import type { AgentSessionRecord } from '../../shared/agent-session/lifecycle/agent-session-record' -import type { GlobalSettings } from '../../shared/global-settings-types' -import type { AgentSessionRecordStore } from '../runtime/agent-session/records/agent-session-record-store' -import type { AgentGenerationFailureOutput } from '../text-generation/agent-failure-output' -import type { StructuredAgentSessionLogger } from './agent-session-wire/host/structured-agent-session-logger' -import type { StructuredAgentSessionAdapter } from './agent-session-wire/providers/structured-agent-session-adapter' -import type { AgentSessionJournal } from './agent-session-journal/journal-store' +import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' +import type { GlobalSettings } from '../../../shared/global-settings-types' +import type { AgentSessionRecordStore } from '../../runtime/agent-session/records/agent-session-record-store' +import type { AgentGenerationFailureOutput } from '../../text-generation/agent-failure-output' +import type { StructuredAgentSessionLogger } from '../agent-session-wire/host/structured-agent-session-logger' +import type { StructuredAgentSessionAdapter } from '../agent-session-wire/providers/structured-agent-session-adapter' +import type { AgentSessionJournal } from '../agent-session-journal/journal-store' export type AutomaticChatTitle = { owner: ReturnType> diff --git a/src/main/native-chat/structured-chat-naming-work.ts b/src/main/native-chat/conversation-naming/structured-chat-naming-work.ts similarity index 96% rename from src/main/native-chat/structured-chat-naming-work.ts rename to src/main/native-chat/conversation-naming/structured-chat-naming-work.ts index c3a9ab0f345..bcbcdf63c2e 100644 --- a/src/main/native-chat/structured-chat-naming-work.ts +++ b/src/main/native-chat/conversation-naming/structured-chat-naming-work.ts @@ -1,6 +1,6 @@ -import type { AgentSessionRecord } from '../../shared/agent-session/lifecycle/agent-session-record' +import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' import type { AutomaticChatTitle, StructuredChatNamingDeps } from './structured-chat-naming-types' -import type { AgentSessionJournal } from './agent-session-journal/journal-store' +import type { AgentSessionJournal } from '../agent-session-journal/journal-store' export type NamingRequest = { record: AgentSessionRecord diff --git a/src/main/native-chat/structured-chat-title-handover.ts b/src/main/native-chat/conversation-naming/structured-chat-title-handover.ts similarity index 92% rename from src/main/native-chat/structured-chat-title-handover.ts rename to src/main/native-chat/conversation-naming/structured-chat-title-handover.ts index ecd72d6daad..9054e7aa278 100644 --- a/src/main/native-chat/structured-chat-title-handover.ts +++ b/src/main/native-chat/conversation-naming/structured-chat-title-handover.ts @@ -1,7 +1,7 @@ -import type { AgentSessionRecord } from '../../shared/agent-session/lifecycle/agent-session-record' -import { sessionCrewChanges } from '../runtime/orchestration/crew/crew-changes' -import type { StructuredAgentSessionHost } from './agent-session-wire/host/structured-agent-session-host' -import type { JournalPublication } from './agent-session-wire/host/structured-agent-session-conversations' +import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' +import { sessionCrewChanges } from '../../runtime/orchestration/crew/crew-changes' +import type { StructuredAgentSessionHost } from '../agent-session-wire/host/structured-agent-session-host' +import type { JournalPublication } from '../agent-session-wire/host/structured-agent-session-conversations' import type { AutomaticChatTitle } from './structured-chat-naming-types' export function deferredStructuredChatTitle(input: { diff --git a/src/main/native-chat/session-file-resolver-wsl-scan-gate.test.ts b/src/main/native-chat/session-file-resolver-wsl-scan-gate.test.ts index bbfda1b0eaf..6b6fba4f727 100644 --- a/src/main/native-chat/session-file-resolver-wsl-scan-gate.test.ts +++ b/src/main/native-chat/session-file-resolver-wsl-scan-gate.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type * as WslRunningPathFilterModule from '../wsl/filesystem/wsl-running-path-filter' -import type * as WslTranscriptFsGateModule from './wsl-transcript-fs-gate' +import type * as WslTranscriptFsGateModule from './transcript-filesystem/wsl-transcript-fs-gate' const WSL_SESSIONS_DIR = '\\\\wsl.localhost\\Ubuntu\\home\\ada\\.codex\\sessions' const DEBIAN_SESSIONS_DIR = '\\\\wsl.localhost\\Debian\\home\\ada\\.codex\\sessions' @@ -22,7 +22,7 @@ const mocks = vi.hoisted(() => ({ ) })) -vi.mock('./wsl-transcript-fs-gate', async (importOriginal) => ({ +vi.mock('./transcript-filesystem/wsl-transcript-fs-gate', async (importOriginal) => ({ ...(await importOriginal()), runWslTranscriptFsTask: mocks.gate })) @@ -41,7 +41,9 @@ vi.mock('../ai-vault/discovery/session-scanner-discovery', () => ({ } })) // These cases cover root and path policy; the E9 gate has its own suite. -vi.mock('./wsl-transcript-running-admission', () => ({ admitWslTranscriptPath: async () => {} })) +vi.mock('./transcript-filesystem/wsl-transcript-running-admission', () => ({ + admitWslTranscriptPath: async () => {} +})) vi.mock('../wsl/discovery/wsl-home-unless-stopped', async () => { const wsl = await import('../wsl/discovery/distro-catalog') return { @@ -60,7 +62,7 @@ vi.mock('../wsl/filesystem/wsl-running-path-filter', async (importOriginal) => ( })) import { resolveSessionFilePath } from './session-file-resolver' -import { WslTranscriptFsError } from './wsl-transcript-fs-error' +import { WslTranscriptFsError } from './transcript-filesystem/wsl-transcript-fs-error' const realPlatform = process.platform @@ -128,18 +130,6 @@ describe('Codex WSL scan gate', () => { expect(mocks.walk).not.toHaveBeenCalled() }) - it('skips running-distro roots when a host hook path is not flushed yet', async () => { - await expect( - resolveSessionFilePath('codex', 'session-id', { - transcriptPath: `${LOCAL_SESSIONS_DIR}\\2026\\rollout-1-session-id.jsonl` - }) - ).resolves.toBeNull() - - expect(mocks.walk).toHaveBeenCalled() - expect(mocks.walk.mock.calls.some(([dir]) => dir.startsWith('\\\\wsl'))).toBe(false) - expect(mocks.gate).not.toHaveBeenCalled() - }) - it('reports the abort, not a refusal, when the hook-path probe races a caller abort', async () => { const controller = new AbortController() const abortReason = new Error('caller went away') diff --git a/src/main/native-chat/session-file-resolver.ts b/src/main/native-chat/session-file-resolver.ts index 02568647602..b878a53497c 100644 --- a/src/main/native-chat/session-file-resolver.ts +++ b/src/main/native-chat/session-file-resolver.ts @@ -24,13 +24,16 @@ import { needsWslHostResolution, toHostReadableTranscriptPath, wslAgentSessionsDirs -} from './host-readable-transcript-path' -import { SESSION_FILE_ID_LAYOUTS, type SessionIdLookupAgent } from './session-file-id-layouts' -import { findWslSessionPath } from './wsl-session-path-scan' -import { findClaudeSessionFile } from './session-file-resolver-claude-probe' -import { parseSshTranscriptPath } from './ssh-transcript-path' -import { wslTranscriptFsRefusal } from './wsl-transcript-fs-gate' -import type { WslTranscriptFsError } from './wsl-transcript-fs-error' +} from './transcript-discovery/host-readable-transcript-path' +import { + SESSION_FILE_ID_LAYOUTS, + type SessionIdLookupAgent +} from './transcript-discovery/session-file-id-layouts' +import { findWslSessionPath } from './transcript-discovery/wsl-session-path-scan' +import { findClaudeSessionFile } from './transcript-discovery/session-file-resolver-claude-probe' +import { parseSshTranscriptPath } from './transcript-filesystem/ssh-transcript-path' +import { wslTranscriptFsRefusal } from './transcript-filesystem/wsl-transcript-fs-error' +import type { WslTranscriptFsError } from './transcript-filesystem/wsl-transcript-fs-error' // Read shared discovery roots per call; preference order prevents cross-account matches. function claudeProjectsDirs(): string[] { diff --git a/src/main/native-chat/structured-chat-naming-title.test.ts b/src/main/native-chat/structured-chat-naming-title.test.ts deleted file mode 100644 index 48d8da27fc3..00000000000 --- a/src/main/native-chat/structured-chat-naming-title.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { expect, it, vi } from 'vitest' -import { - createStructuredChatNamingHandler, - type StructuredChatNamingDeps -} from './structured-chat-naming' -import { agentSessionRecordFixture } from './agent-session-record-test-fixture' -import { - compareAndSetAgentSessionRecordName, - setAgentSessionRecordUserName -} from '../runtime/agent-session/records/agent-session-record-conversation-name' - -function rig( - generate: StructuredChatNamingDeps['generate'], - captureAutomaticTitle: StructuredChatNamingDeps['captureAutomaticTitle'] = () => ({ owner: null }) -) { - let record = agentSessionRecordFixture({ sessionId: 'chat-1' }) - let beforeWrite: (() => Promise) | undefined - const handler = createStructuredChatNamingHandler({ - getStore: () => ({ - getRecord: () => record, - compareAndSetConversationName: async (_id, name, expected, context, applicable) => - compareAndSetAgentSessionRecordName( - async (apply) => { - if (expected !== null) { - await beforeWrite?.() - } - record = apply(record) - return record - }, - name, - expected, - context, - applicable - ), - setUserConversationName: async (_id, name, write) => { - const result = setAgentSessionRecordUserName(record, name, write, 1) - record = result.record - return result - } - }), - getSettings: () => ({ nativeChatAutoName: true }), - readFirstPrompt: async () => 'Repair login', - readJournalFirstPrompt: async () => 'Repair login', - workerTaskTitle: () => null, - generate, - captureAutomaticTitle, - wait: async () => {}, - onNamed: () => {}, - logger: { warn: vi.fn(), error: vi.fn() } - }) - return { - handler, - record: () => record, - beforeWrite: (run: () => Promise) => { - beforeWrite = run - }, - start: () => - handler( - { - sessionId: 'chat-1', - workspaceId: 'workspace-1', - agent: 'claude', - status: 'working', - latestPrompt: 'Repair login', - updatedAt: 1 - }, - { replay: false, firstInputSubmissionKey: 'input-1' } - ), - done: () => vi.waitFor(() => expect(handler.activeSessionIds()).toEqual([])) - } -} - -it.each(['owner', 'same-text rename'] as const)( - 'rejects a queued title write after %s changes', - async (change) => { - let owned = true - const state = rig( - async (_record, _prompt, _current, automatic) => - automatic ? { name: 'CLI title', shouldApply: () => owned } : { name: 'Independent title' }, - () => ({ - owner: { isCurrent: () => owned, generate: async () => null } - }) - ) - state.beforeWrite(async () => { - if (change === 'owner') { - owned = false - } else { - await state.handler.setUserName('chat-1', 'Repair login', 'rename') - } - }) - state.start() - await state.done() - expect(state.record().conversationName).toBe( - change === 'owner' ? 'Independent title' : 'Repair login' - ) - } -) diff --git a/src/main/native-chat/structured-chat-naming.ts b/src/main/native-chat/structured-chat-naming.ts index ec4e230b256..3a44c2fe1b5 100644 --- a/src/main/native-chat/structured-chat-naming.ts +++ b/src/main/native-chat/structured-chat-naming.ts @@ -1,19 +1,16 @@ import type { StructuredChatNameGeneration, StructuredChatNamingDeps -} from './structured-chat-naming-types' -export type { - StructuredChatNameGeneration, - StructuredChatNamingDeps -} from './structured-chat-naming-types' -import { runStructuredChatNameGeneration } from './structured-chat-name-publication' +} from './conversation-naming/structured-chat-naming-types' + +import { runStructuredChatNameGeneration } from './conversation-naming/structured-chat-name-publication' import { chatNamingFailure } from '../../shared/chat-naming-failure-copy' import { automaticChatNamingRequest, createStructuredChatNamingWork, namingRequestOwnsRecord, type NamingRequest -} from './structured-chat-naming-work' +} from './conversation-naming/structured-chat-naming-work' import type { StructuredAgentSessionStatusObserverOptions } from './agent-session-wire/observations/structured-agent-session-status-observation' import type { AgentSessionRecord } from '../../shared/agent-session/lifecycle/agent-session-record' import type { AgentSessionStatusSummary } from '../../shared/agent-session/agent-session-wire' diff --git a/src/main/native-chat/agent-session-record-test-fixture.ts b/src/main/native-chat/testing/agent-session-record-test-fixture.ts similarity index 89% rename from src/main/native-chat/agent-session-record-test-fixture.ts rename to src/main/native-chat/testing/agent-session-record-test-fixture.ts index 5a5952f46b0..8da8e22f672 100644 --- a/src/main/native-chat/agent-session-record-test-fixture.ts +++ b/src/main/native-chat/testing/agent-session-record-test-fixture.ts @@ -2,8 +2,8 @@ import { AGENT_SESSION_RECORD_SCHEMA_VERSION, type AgentSessionExecutionLocation, type AgentSessionRecord -} from '../../shared/agent-session/lifecycle/agent-session-record' -import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host' +} from '../../../shared/agent-session/lifecycle/agent-session-record' +import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host' export function agentSessionRecordFixture( overrides: Partial> & { diff --git a/src/main/native-chat/transcript-claude-read-image.test.ts b/src/main/native-chat/transcript-claude-read-image.test.ts deleted file mode 100644 index 658c6d71bff..00000000000 --- a/src/main/native-chat/transcript-claude-read-image.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { foldToolMessages } from '../../shared/native-chat/tools/native-chat-tool-fold' -import { nativeChatToolRunViewedImages } from '../../shared/native-chat/tools/native-chat-tool-viewed-images' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' - -// Claude 2.x Read of an image: the result holds only base64, the path is on the call. -function readTurn(filePath: string, result: Record): string[] { - return [ - JSON.stringify({ - type: 'assistant', - uuid: 'call-row', - timestamp: '2026-10-29T14:09:20.000Z', - message: { - role: 'assistant', - content: [{ type: 'tool_use', id: 'toolu_1', name: 'Read', input: { file_path: filePath } }] - } - }), - JSON.stringify({ - type: 'user', - uuid: 'result-row', - timestamp: '2026-10-29T14:09:23.939Z', - message: { - role: 'user', - content: [{ tool_use_id: 'toolu_1', type: 'tool_result', ...result }] - }, - toolUseResult: { type: 'image', file: { base64: 'iVBORw0KGgo=', type: 'image/png' } } - }) - ] -} - -const IMAGE_RESULT = { - content: [ - { type: 'image', source: { type: 'base64', media_type: 'image/png', data: 'iVBORw0KGgo=' } } - ] -} - -function viewedImages(lines: string[]): ReturnType { - const decoded = lines - .map((line, index) => decodeClaudeTranscriptLine(line, `fallback-${index}`)) - .filter((message): message is NativeChatMessage => message !== null) - const folded = foldToolMessages(decoded) - expect(folded).toHaveLength(1) - return nativeChatToolRunViewedImages(folded[0]!.blocks) -} - -describe('Claude Read of an image', () => { - it('shows the screenshot the agent read', () => { - expect(viewedImages(readTurn('/tmp/orca-annot/s2.png', IMAGE_RESULT))).toEqual([ - { type: 'image-ref', path: '/tmp/orca-annot/s2.png' } - ]) - }) - - it('shows nothing for a read that failed or returned text', () => { - const failed = { content: 'File does not exist.', is_error: true } - expect(viewedImages(readTurn('/tmp/gone.png', failed))).toEqual([]) - expect(viewedImages(readTurn('/repo/src/a.ts', { content: '1\tconst a = 1' }))).toEqual([]) - expect(viewedImages(readTurn('/repo/icons/logo.svg', { content: '1\t' }))).toEqual([]) - }) -}) - -it('shows nothing for a shell read, which gave the agent bytes as text', () => { - const codexCat = { - type: 'tool-call', - name: 'read', - callId: 'c1', - input: { command: 'cat /repo/images/shot.png', cwd: '/repo', path: '/repo/images/shot.png' } - } as const - expect( - nativeChatToolRunViewedImages([codexCat, { type: 'tool-result', callId: 'c1', output: '' }]) - ).toEqual([]) -}) diff --git a/src/main/native-chat/transcript-line-decoders-claude-control-bytes.test.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-control-bytes.test.ts similarity index 65% rename from src/main/native-chat/transcript-line-decoders-claude-control-bytes.test.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-control-bytes.test.ts index 404c3b403ce..b16a5befda3 100644 --- a/src/main/native-chat/transcript-line-decoders-claude-control-bytes.test.ts +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-control-bytes.test.ts @@ -1,8 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - normalizeNativeChatUserText, - normalizedNativeChatUserMessageText -} from '../../shared/native-chat/transcript/native-chat-image-transcript-markers' + import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' const COMPOSER_TEXT = 'line 000 xxxxxxxxxx\nline 001 xxxxxxxxxx\nline 002 xxxxxxxxxx' @@ -23,11 +20,4 @@ describe('decodeClaudeTranscriptLine on a mobile-sent prompt', () => { text: `\u0015${COMPOSER_TEXT}` }) }) - - it('normalizes to the composer text so the optimistic echo can retire', () => { - const message = decodeClaudeTranscriptLine(MOBILE_SEND_LINE, 'fallback')! - expect(normalizedNativeChatUserMessageText(message)).toBe( - normalizeNativeChatUserText(COMPOSER_TEXT) - ) - }) }) diff --git a/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-image-companion.test.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-image-companion.test.ts new file mode 100644 index 00000000000..fc0a6aac2d6 --- /dev/null +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-image-companion.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest' + +import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' + +// Claude 2.1.237 stores source paths in separate isMeta/turnCompanion rows; prompt base64 has no path. + +describe('Claude pasted-image companion row', () => { + it('still drops an ordinary isMeta injected turn', () => { + const injected = JSON.stringify({ + type: 'user', + uuid: 'injected', + isMeta: true, + message: { + role: 'user', + content: [{ type: 'text', text: 'Base directory for this skill: /x' }] + } + }) + expect(decodeClaudeTranscriptLine(injected, 'fallback')).toBeNull() + }) + + it('does not turn a mixed marker-and-prose meta row into an image source turn', () => { + const mixed = JSON.stringify({ + type: 'user', + uuid: 'mixed', + isMeta: true, + message: { + role: 'user', + content: [ + { type: 'text', text: '[Image: source: /tmp/a.png]' }, + { type: 'text', text: 'metadata prose' } + ] + } + }) + + expect(decodeClaudeTranscriptLine(mixed, 'fallback')).toBeNull() + }) + + it('retains tool results from a mixed meta row while dropping its marker text', () => { + const mixed = JSON.stringify({ + type: 'user', + uuid: 'mixed-tool', + isMeta: true, + message: { + role: 'user', + content: [ + { type: 'text', text: '[Image: source: /tmp/a.png]' }, + { type: 'tool_result', content: 'tool output' } + ] + } + }) + + expect(decodeClaudeTranscriptLine(mixed, 'fallback')).toMatchObject({ + id: 'mixed-tool', + role: 'tool', + blocks: [{ type: 'tool-result', output: 'tool output' }] + }) + }) +}) diff --git a/src/main/native-chat/transcript-line-decoders-claude-queued-prompt.test.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-queued-prompt.test.ts similarity index 100% rename from src/main/native-chat/transcript-line-decoders-claude-queued-prompt.test.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-queued-prompt.test.ts diff --git a/src/main/native-chat/transcript-line-decoders-claude.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude.ts similarity index 89% rename from src/main/native-chat/transcript-line-decoders-claude.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders-claude.ts index 41e127f05ef..716ae6ae4fe 100644 --- a/src/main/native-chat/transcript-line-decoders-claude.ts +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders-claude.ts @@ -4,23 +4,23 @@ import { type NativeChatEditPatch, type NativeChatEditPatchHunk, type NativeChatMessage -} from '../../shared/native-chat/native-chat-types' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' -import { timestampMs } from '../ai-vault/parsing/session-record-time' -import { imageSourcePathFromText } from '../../shared/native-chat/transcript/native-chat-image-transcript-markers' +} from '../../../shared/native-chat/native-chat-types' +import { asRecord } from '../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../shared/trimmed-string' +import { parseJsonObject } from '../../ai-vault/parsing/session-json-object' +import { timestampMs } from '../../ai-vault/parsing/session-record-time' +import { imageSourcePathFromText } from '../../../shared/native-chat/transcript/native-chat-image-transcript-markers' import { claudeContentBlocks } from './transcript-record-blocks' -import { isKnownRasterImageMimeType } from '../../shared/file-preview/raster-image-preview-limits' -import { unwrapClaudePastedContentBlock } from '../../shared/claude-pasted-content' -import { unwrapClaudeBashInputBlock } from '../../shared/claude-bash-input' +import { isKnownRasterImageMimeType } from '../../../shared/file-preview/raster-image-preview-limits' +import { unwrapClaudePastedContentBlock } from '../../../shared/claude-pasted-content' +import { unwrapClaudeBashInputBlock } from '../../../shared/claude-bash-input' import { claudeInterruptedMessageId } from './transcript-turn-markers' -import { claudeStopHookFeedbackRow } from '../claude/structured/journal/claude-stop-hook-feedback' -import { structuredAgentSessionStatusBlock } from '../../shared/agent-session/view/structured-agent-session-status-block' +import { claudeStopHookFeedbackRow } from '../../claude/structured/journal/claude-stop-hook-feedback' +import { structuredAgentSessionStatusBlock } from '../../../shared/agent-session/view/structured-agent-session-status-block' import { claudeMessageWithoutSyntheticText, isClaudeInjectedUserMessage -} from '../claude/structured/journal/claude-internal-message-content' +} from '../../claude/structured/journal/claude-internal-message-content' const MAX_EDIT_PATCH_HUNKS = 40 const MAX_EDIT_PATCH_HUNK_LINES = 400 diff --git a/src/main/native-chat/transcript-line-decoders-codex.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders-codex.ts similarity index 95% rename from src/main/native-chat/transcript-line-decoders-codex.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders-codex.ts index 25a356f851b..45712b07ada 100644 --- a/src/main/native-chat/transcript-line-decoders-codex.ts +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders-codex.ts @@ -2,12 +2,12 @@ import { NATIVE_CHAT_INTERRUPTED_STATUS_TEXT, type NativeChatBlock, type NativeChatMessage -} from '../../shared/native-chat/native-chat-types' -import { NATIVE_CHAT_NOTICE_COPY } from '../../shared/native-chat/native-chat-notice' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' -import { timestampMs } from '../ai-vault/parsing/session-record-time' +} from '../../../shared/native-chat/native-chat-types' +import { NATIVE_CHAT_NOTICE_COPY } from '../../../shared/native-chat/native-chat-notice' +import { asRecord } from '../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../shared/trimmed-string' +import { parseJsonObject } from '../../ai-vault/parsing/session-json-object' +import { timestampMs } from '../../ai-vault/parsing/session-record-time' import { claudeContentBlocks, toolResultOutput } from './transcript-record-blocks' import { CODEX_EVENT_TURN_ABORTED } from './transcript-turn-markers' diff --git a/src/main/native-chat/transcript-line-decoders-grok.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders-grok.ts similarity index 94% rename from src/main/native-chat/transcript-line-decoders-grok.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders-grok.ts index 447168e005b..30bd7612e31 100644 --- a/src/main/native-chat/transcript-line-decoders-grok.ts +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders-grok.ts @@ -1,10 +1,13 @@ -import type { NativeChatBlock, NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' -import { timestampMs } from '../ai-vault/parsing/session-record-time' +import type { + NativeChatBlock, + NativeChatMessage +} from '../../../shared/native-chat/native-chat-types' +import { asRecord } from '../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../shared/trimmed-string' +import { parseJsonObject } from '../../ai-vault/parsing/session-json-object' +import { timestampMs } from '../../ai-vault/parsing/session-record-time' import { claudeContentBlocks, toolResultOutput } from './transcript-record-blocks' -import { AGENT_SESSION_PASTED_IMAGE_SUFFIX_SOURCE } from '../../shared/agent-session/agent-session-attachments' +import { AGENT_SESSION_PASTED_IMAGE_SUFFIX_SOURCE } from '../../../shared/agent-session/agent-session-attachments' export function decodeGrokTranscriptLine( line: string, diff --git a/src/main/native-chat/transcript-line-decoders-omp.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders-omp.ts similarity index 93% rename from src/main/native-chat/transcript-line-decoders-omp.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders-omp.ts index f90c20cf8a2..69112f2d197 100644 --- a/src/main/native-chat/transcript-line-decoders-omp.ts +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders-omp.ts @@ -5,13 +5,13 @@ import { NATIVE_CHAT_INTERRUPTED_STATUS_TEXT, type NativeChatBlock, type NativeChatMessage -} from '../../shared/native-chat/native-chat-types' -import type { AgentSessionTokenUsage } from '../../shared/agent-session/agent-session-context-usage' -import { buildImageDataUri } from '../../shared/file-preview/image-data-uri' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' -import { timestampMs } from '../ai-vault/parsing/session-record-time' +} from '../../../shared/native-chat/native-chat-types' +import type { AgentSessionTokenUsage } from '../../../shared/agent-session/agent-session-context-usage' +import { buildImageDataUri } from '../../../shared/file-preview/image-data-uri' +import { asRecord } from '../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../shared/trimmed-string' +import { parseJsonObject } from '../../ai-vault/parsing/session-json-object' +import { timestampMs } from '../../ai-vault/parsing/session-record-time' import { toolResultOutput } from './transcript-record-blocks' export function decodeOmpTranscriptLine( diff --git a/src/main/native-chat/transcript-line-decoders.grok.test.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders.grok.test.ts similarity index 99% rename from src/main/native-chat/transcript-line-decoders.grok.test.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders.grok.test.ts index 4fce5911220..ea4b30f9141 100644 --- a/src/main/native-chat/transcript-line-decoders.grok.test.ts +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders.grok.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { decodeGrokTranscriptLine } from './transcript-line-decoders' +import { decodeGrokTranscriptLine } from './transcript-line-decoders-grok' describe('decodeGrokTranscriptLine', () => { it('decodes assistant tool calls on empty content rows', () => { diff --git a/src/main/native-chat/transcript-line-decoders.omp.test.ts b/src/main/native-chat/transcript-decoding/transcript-line-decoders.omp.test.ts similarity index 99% rename from src/main/native-chat/transcript-line-decoders.omp.test.ts rename to src/main/native-chat/transcript-decoding/transcript-line-decoders.omp.test.ts index 20d71adbf9c..6b58847389e 100644 --- a/src/main/native-chat/transcript-line-decoders.omp.test.ts +++ b/src/main/native-chat/transcript-decoding/transcript-line-decoders.omp.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { decodeOmpTranscriptLine } from './transcript-line-decoders' +import { decodeOmpTranscriptLine } from './transcript-line-decoders-omp' const line = (record: unknown): string => JSON.stringify(record) diff --git a/src/main/native-chat/transcript-record-blocks.ts b/src/main/native-chat/transcript-decoding/transcript-record-blocks.ts similarity index 94% rename from src/main/native-chat/transcript-record-blocks.ts rename to src/main/native-chat/transcript-decoding/transcript-record-blocks.ts index 19a8cb0a282..3a7c7a896f3 100644 --- a/src/main/native-chat/transcript-record-blocks.ts +++ b/src/main/native-chat/transcript-decoding/transcript-record-blocks.ts @@ -2,15 +2,15 @@ import type { NativeChatBlock, NativeChatImageRefBlock, NativeChatToolResultBlock -} from '../../shared/native-chat/native-chat-types' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' +} from '../../../shared/native-chat/native-chat-types' +import { asRecord } from '../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../shared/trimmed-string' import { isClaudeToolUseContent, isClaudeToolResultContent, claudeToolResultText, claudeToolResultFailed -} from '../../shared/claude-tool-content' +} from '../../../shared/claude-tool-content' export function toolResultOutput(value: unknown): string { if (typeof value === 'string') { diff --git a/src/main/native-chat/transcript-turn-lifecycle.test.ts b/src/main/native-chat/transcript-decoding/transcript-turn-lifecycle.test.ts similarity index 100% rename from src/main/native-chat/transcript-turn-lifecycle.test.ts rename to src/main/native-chat/transcript-decoding/transcript-turn-lifecycle.test.ts diff --git a/src/main/native-chat/transcript-turn-lifecycle.ts b/src/main/native-chat/transcript-decoding/transcript-turn-lifecycle.ts similarity index 87% rename from src/main/native-chat/transcript-turn-lifecycle.ts rename to src/main/native-chat/transcript-decoding/transcript-turn-lifecycle.ts index 342c71e941d..e5a1d663459 100644 --- a/src/main/native-chat/transcript-turn-lifecycle.ts +++ b/src/main/native-chat/transcript-decoding/transcript-turn-lifecycle.ts @@ -1,12 +1,12 @@ -import type { AgentType } from '../../shared/agent-status/contracts/agent-status-types' -import type { NativeChatTurnLifecycle } from '../../shared/native-chat/native-chat-types' -import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat/native-chat-agent-support' -import { isNoiseMessage } from '../../shared/native-chat/native-chat-noise' -import { claudeBashInputAsTypedCommand } from '../../shared/claude-bash-input' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' -import { timestampMs } from '../ai-vault/parsing/session-record-time' +import type { AgentType } from '../../../shared/agent-status/contracts/agent-status-types' +import type { NativeChatTurnLifecycle } from '../../../shared/native-chat/native-chat-types' +import { resolveNativeChatTranscriptAgent } from '../../../shared/native-chat/native-chat-agent-support' +import { isNoiseMessage } from '../../../shared/native-chat/native-chat-noise' +import { claudeBashInputAsTypedCommand } from '../../../shared/claude-bash-input' +import { asRecord } from '../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../shared/trimmed-string' +import { parseJsonObject } from '../../ai-vault/parsing/session-json-object' +import { timestampMs } from '../../ai-vault/parsing/session-record-time' import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' import { claudeInterruptedMessageId, diff --git a/src/main/native-chat/transcript-turn-markers.ts b/src/main/native-chat/transcript-decoding/transcript-turn-markers.ts similarity index 88% rename from src/main/native-chat/transcript-turn-markers.ts rename to src/main/native-chat/transcript-decoding/transcript-turn-markers.ts index 4ce336eb918..4a5e48b343d 100644 --- a/src/main/native-chat/transcript-turn-markers.ts +++ b/src/main/native-chat/transcript-decoding/transcript-turn-markers.ts @@ -1,6 +1,6 @@ // Share provider boundary predicates so visible interruption rows and lifecycle settlement agree. -import { extractString } from '../../shared/trimmed-string' +import { extractString } from '../../../shared/trimmed-string' export function claudeInterruptedMessageId(record: Record): string | undefined { if (record.type !== 'user') { diff --git a/src/main/native-chat/host-readable-transcript-path.test.ts b/src/main/native-chat/transcript-discovery/host-readable-transcript-path.test.ts similarity index 92% rename from src/main/native-chat/host-readable-transcript-path.test.ts rename to src/main/native-chat/transcript-discovery/host-readable-transcript-path.test.ts index 0aed607bed1..751aced6dd3 100644 --- a/src/main/native-chat/host-readable-transcript-path.test.ts +++ b/src/main/native-chat/transcript-discovery/host-readable-transcript-path.test.ts @@ -8,30 +8,30 @@ const wslMocks = vi.hoisted(() => ({ })) // These cases cover root and path policy; the E9 gate has its own suite. -vi.mock('./wsl-transcript-running-admission', () => ({ +vi.mock('../transcript-filesystem/wsl-transcript-running-admission', () => ({ admitWslTranscriptPath: wslMocks.admitWslTranscriptPath })) -vi.mock('../wsl/discovery/wsl-home-unless-stopped', async () => { - const wsl = await import('../wsl/discovery/distro-catalog') +vi.mock('../../wsl/discovery/wsl-home-unless-stopped', async () => { + const wsl = await import('../../wsl/discovery/distro-catalog') return { wslHomeUnlessStopped: (distro: string) => wsl.getWslHomeAsync(distro), listRunningWslHomeDirsUnlessStopped: () => wsl.listRunningWslHomeDirsAsync() } }) -vi.mock('../wsl/discovery/distro-catalog', () => ({ +vi.mock('../../wsl/discovery/distro-catalog', () => ({ listRunningWslHomeDirsAsync: wslMocks.listRunningWslHomeDirsAsync })) -vi.mock('../wsl/filesystem/unc-path-access', () => ({ +vi.mock('../../wsl/filesystem/unc-path-access', () => ({ resolveWslGuestRealDirAsync: wslMocks.resolveWslGuestRealDirAsync })) -vi.mock('../wsl/filesystem/wsl-running-path-filter', () => ({ +vi.mock('../../wsl/filesystem/wsl-running-path-filter', () => ({ filterPathsToRunningWslDistrosAsync: wslMocks.filterPathsToRunningWslDistrosAsync })) import { resetHostReadableTranscriptPathCacheForTests, toHostReadableTranscriptPath } from './host-readable-transcript-path' -import { WslTranscriptFsError } from './wsl-transcript-fs-gate' +import { WslTranscriptFsError } from '../transcript-filesystem/wsl-transcript-fs-error' const UBUNTU_HOME = '\\\\wsl.localhost\\Ubuntu\\home\\ada' const DEBIAN_HOME = '\\\\wsl.localhost\\Debian\\home\\other' diff --git a/src/main/native-chat/host-readable-transcript-path.ts b/src/main/native-chat/transcript-discovery/host-readable-transcript-path.ts similarity index 94% rename from src/main/native-chat/host-readable-transcript-path.ts rename to src/main/native-chat/transcript-discovery/host-readable-transcript-path.ts index 93711287b7f..a089788c4e1 100644 --- a/src/main/native-chat/host-readable-transcript-path.ts +++ b/src/main/native-chat/transcript-discovery/host-readable-transcript-path.ts @@ -1,24 +1,26 @@ import { existsSync } from 'node:fs' import { isAbsolute, posix } from 'node:path' -import { isWslUncPath, parseWslUncPath, toWindowsWslPath } from '../../shared/wsl-paths' +import { isWslUncPath, parseWslUncPath, toWindowsWslPath } from '../../../shared/wsl-paths' import { uniqueDirs, wslHomeSessionDirs, type SessionHomeAgent -} from '../ai-vault/discovery/session-scanner-roots' -import { resolveWslGuestRealDirAsync } from '../wsl/filesystem/unc-path-access' +} from '../../ai-vault/discovery/session-scanner-roots' +import { resolveWslGuestRealDirAsync } from '../../wsl/filesystem/unc-path-access' import { listRunningWslHomeDirsUnlessStopped, wslHomeUnlessStopped -} from '../wsl/discovery/wsl-home-unless-stopped' -import { admitWslTranscriptPath } from './wsl-transcript-running-admission' +} from '../../wsl/discovery/wsl-home-unless-stopped' +import { admitWslTranscriptPath } from '../transcript-filesystem/wsl-transcript-running-admission' import { filterPathsToRunningWslDistrosAsync, filterPathsToWslDistros -} from '../wsl/filesystem/wsl-running-path-filter' -import { wslGatedAccess } from './wsl-transcript-fs-access' -import { WslTranscriptFsError } from './wsl-transcript-fs-error' -import { wslTranscriptFsRefusal } from './wsl-transcript-fs-gate' +} from '../../wsl/filesystem/wsl-running-path-filter' +import { wslGatedAccess } from '../transcript-filesystem/wsl-transcript-fs-access' +import { + WslTranscriptFsError, + wslTranscriptFsRefusal +} from '../transcript-filesystem/wsl-transcript-fs-error' export function isGuestAbsoluteLinuxPath(path: string): boolean { if (!path.startsWith('/') || path.startsWith('//')) { diff --git a/src/main/native-chat/native-chat-open-wake.ts b/src/main/native-chat/transcript-discovery/native-chat-open-wake.ts similarity index 78% rename from src/main/native-chat/native-chat-open-wake.ts rename to src/main/native-chat/transcript-discovery/native-chat-open-wake.ts index ad8367aa90c..161c767560d 100644 --- a/src/main/native-chat/native-chat-open-wake.ts +++ b/src/main/native-chat/transcript-discovery/native-chat-open-wake.ts @@ -1,18 +1,18 @@ -import type { AgentStatusIpcPayload } from '../../shared/agent-status/contracts/agent-status-ipc-payload' +import type { AgentStatusIpcPayload } from '../../../shared/agent-status/contracts/agent-status-ipc-payload' import { isWslHookRelayConnectionId, WSL_HOOK_RELAY_CONNECTION_PREFIX -} from '../../shared/wsl-hook-relay-contract' -import { toWindowsWslPath } from '../../shared/wsl-paths' +} from '../../../shared/wsl-hook-relay-contract' +import { toWindowsWslPath } from '../../../shared/wsl-paths' import { blockedWslDistroForPath, runAsWslUserAct, wslDistroForPath -} from '../wsl/filesystem/wsl-unc-access-gate' -import { wslGatedAccess } from './wsl-transcript-fs-access' -import { WSL_TRANSCRIPT_RUNNING_LEASE_MS } from './wsl-transcript-running-admission' -import { agentHookServer } from '../agent-hooks/server/agent-hook-server' -import { nativeChatTranscriptPathOnExecutionHost } from './ssh-transcript-path' +} from '../../wsl/filesystem/wsl-unc-access-gate' +import { wslGatedAccess } from '../transcript-filesystem/wsl-transcript-fs-access' +import { WSL_TRANSCRIPT_RUNNING_LEASE_MS } from '../transcript-filesystem/wsl-transcript-running-admission' +import { agentHookServer } from '../../agent-hooks/server/agent-hook-server' +import { nativeChatTranscriptPathOnExecutionHost } from '../transcript-filesystem/ssh-transcript-path' type HookRow = Pick diff --git a/src/main/native-chat/session-file-id-layouts.ts b/src/main/native-chat/transcript-discovery/session-file-id-layouts.ts similarity index 82% rename from src/main/native-chat/session-file-id-layouts.ts rename to src/main/native-chat/transcript-discovery/session-file-id-layouts.ts index 205fe7861f6..2e581c54a1f 100644 --- a/src/main/native-chat/session-file-id-layouts.ts +++ b/src/main/native-chat/transcript-discovery/session-file-id-layouts.ts @@ -1,7 +1,7 @@ import { win32 } from 'node:path' -import { GROK_CHAT_HISTORY_FILE } from '../../shared/grok-session-paths' -import { isClaudeProjectBucketDirectory } from '../ai-vault/scanners/claude/claude-project-dir-encoding' -import { isOmpConversationDirectory } from '../ai-vault/scanners/omp/session-scanner-omp-subagent-transcripts' +import { GROK_CHAT_HISTORY_FILE } from '../../../shared/grok-session-paths' +import { isClaudeProjectBucketDirectory } from '../../ai-vault/scanners/claude/claude-project-dir-encoding' +import { isOmpConversationDirectory } from '../../ai-vault/scanners/omp/session-scanner-omp-subagent-transcripts' export type SessionIdLookupAgent = 'claude' | 'codex' | 'grok' | 'omp' diff --git a/src/main/native-chat/session-file-resolver-claude-probe.ts b/src/main/native-chat/transcript-discovery/session-file-resolver-claude-probe.ts similarity index 96% rename from src/main/native-chat/session-file-resolver-claude-probe.ts rename to src/main/native-chat/transcript-discovery/session-file-resolver-claude-probe.ts index 9e07e12699e..bd91d1261df 100644 --- a/src/main/native-chat/session-file-resolver-claude-probe.ts +++ b/src/main/native-chat/transcript-discovery/session-file-resolver-claude-probe.ts @@ -1,6 +1,6 @@ import { lstat, readdir } from 'node:fs/promises' import { join } from 'node:path' -import { encodeClaudeProjectPaths } from '../ai-vault/scanners/claude/claude-project-dir-encoding' +import { encodeClaudeProjectPaths } from '../../ai-vault/scanners/claude/claude-project-dir-encoding' // Ids become path segments here; a separator or dot segment would escape the bucket. function isSafeClaudeSessionId(sessionId: string): boolean { diff --git a/src/main/native-chat/transcript-path-admission.ts b/src/main/native-chat/transcript-discovery/transcript-path-admission.ts similarity index 91% rename from src/main/native-chat/transcript-path-admission.ts rename to src/main/native-chat/transcript-discovery/transcript-path-admission.ts index 1da92501b0f..a61280c70d1 100644 --- a/src/main/native-chat/transcript-path-admission.ts +++ b/src/main/native-chat/transcript-discovery/transcript-path-admission.ts @@ -1,7 +1,10 @@ import { lstat, realpath } from 'node:fs/promises' import { basename, dirname, isAbsolute, relative, resolve } from 'node:path' -import { isDescendantOrEqual, isENOENT } from '../ipc/filesystem/access/filesystem-path-containment' -import { isWslUncPath } from '../../shared/wsl-paths' +import { + isDescendantOrEqual, + isENOENT +} from '../../ipc/filesystem/access/filesystem-path-containment' +import { isWslUncPath } from '../../../shared/wsl-paths' import { needsWslHostResolution } from './host-readable-transcript-path' /** Account containment must survive symlinks and host-native path translation. */ diff --git a/src/main/native-chat/wsl-session-path-scan.ts b/src/main/native-chat/transcript-discovery/wsl-session-path-scan.ts similarity index 96% rename from src/main/native-chat/wsl-session-path-scan.ts rename to src/main/native-chat/transcript-discovery/wsl-session-path-scan.ts index e69de189501..bd8709c2dc4 100644 --- a/src/main/native-chat/wsl-session-path-scan.ts +++ b/src/main/native-chat/transcript-discovery/wsl-session-path-scan.ts @@ -1,7 +1,7 @@ import type { Dirent } from 'node:fs' -import { walkSessionFiles } from '../ai-vault/discovery/session-scanner-discovery' +import { walkSessionFiles } from '../../ai-vault/discovery/session-scanner-discovery' import { SESSION_FILE_ID_LAYOUTS, type SessionIdLookupAgent } from './session-file-id-layouts' -import { wslGatedReaddir } from './wsl-transcript-fs-access' +import { wslGatedReaddir } from '../transcript-filesystem/wsl-transcript-fs-access' type ScanWaiter = { sessionId: string diff --git a/src/main/native-chat/wsl-transcript-fs-process-client.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-client.ts similarity index 98% rename from src/main/native-chat/wsl-transcript-fs-process-client.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-client.ts index 567c381f267..77def126afb 100644 --- a/src/main/native-chat/wsl-transcript-fs-process-client.ts +++ b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-client.ts @@ -7,7 +7,7 @@ import { decodeWslTranscriptFsProcessValue } from './wsl-transcript-fs-process-decode' // Classify transport faults as unavailable so discovery cannot mistake unconsulted mounts for missing paths. -import { wslTranscriptFsProcessFailureError } from './wsl-transcript-fs-error' +import { wslTranscriptFsProcessFailureError } from '../wsl-transcript-fs-error' import { attachSlotChild, WSL_TRANSCRIPT_FS_PROCESS_CLOSE_TIMEOUT_MS, @@ -24,8 +24,6 @@ import { wslTranscriptFsHandleOwners } from './wsl-transcript-fs-process-handle-owner' -export type { WslTranscriptFsProcessHandle } from './wsl-transcript-fs-process-slot' - export class WslTranscriptFsProcessClient { private readonly pool: WslTranscriptFsProcessLanePool private readonly handles = new WeakMap() diff --git a/src/main/native-chat/wsl-transcript-fs-process-decode.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-decode.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-process-decode.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-decode.ts diff --git a/src/main/native-chat/wsl-transcript-fs-process-dispatch.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-dispatch.ts similarity index 93% rename from src/main/native-chat/wsl-transcript-fs-process-dispatch.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-dispatch.ts index b3f5e7dd9af..63fe3d5e89e 100644 --- a/src/main/native-chat/wsl-transcript-fs-process-dispatch.ts +++ b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-dispatch.ts @@ -4,16 +4,12 @@ import { type WslTranscriptFsReusableProcessCall } from './wsl-transcript-fs-process-protocol' import { decodeWslTranscriptFsProcessValue } from './wsl-transcript-fs-process-decode' -import { - WslTranscriptFsProcessClient, - type WslTranscriptFsProcessHandle -} from './wsl-transcript-fs-process-client' +import { WslTranscriptFsProcessClient } from './wsl-transcript-fs-process-client' +import type { WslTranscriptFsProcessHandle } from './wsl-transcript-fs-process-slot' import { wslTranscriptFsHandleOwners } from './wsl-transcript-fs-process-handle-owner' import { WslTranscriptFsProcessOperations } from './wsl-transcript-fs-process-operations' import { forkWslTranscriptFsProcess } from './wsl-transcript-fs-process-spawn' -export type { WslTranscriptFsProcessHandle } from './wsl-transcript-fs-process-client' - // Require the worker global too: leaked VITEST=true must never enable in-process UNC I/O in real apps. function inVitestWorker(): boolean { return process.env.VITEST === 'true' && '__vitest_worker__' in globalThis diff --git a/src/main/native-chat/wsl-transcript-fs-process-entry.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-process-entry.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts diff --git a/src/main/native-chat/wsl-transcript-fs-process-handle-owner.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-handle-owner.ts similarity index 89% rename from src/main/native-chat/wsl-transcript-fs-process-handle-owner.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-handle-owner.ts index 5b11fc208b6..6c4bb7d0971 100644 --- a/src/main/native-chat/wsl-transcript-fs-process-handle-owner.ts +++ b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-handle-owner.ts @@ -1,4 +1,4 @@ -import { wslTranscriptFsProcessFailureError } from './wsl-transcript-fs-error' +import { wslTranscriptFsProcessFailureError } from '../wsl-transcript-fs-error' import { invalidTranscriptHandleError } from './wsl-transcript-fs-process-protocol' import type { WslTranscriptFsProcessClient } from './wsl-transcript-fs-process-client' import type { WslTranscriptFsProcessHandle } from './wsl-transcript-fs-process-slot' diff --git a/src/main/native-chat/wsl-transcript-fs-process-lane-pool.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-lane-pool.ts similarity index 98% rename from src/main/native-chat/wsl-transcript-fs-process-lane-pool.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-lane-pool.ts index 31f060c9498..4d51e2c962a 100644 --- a/src/main/native-chat/wsl-transcript-fs-process-lane-pool.ts +++ b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-lane-pool.ts @@ -2,7 +2,7 @@ import { WSL_TRANSCRIPT_FS_PROCESS_IDLE_REAP_MS, type ProcessSlot } from './wsl-transcript-fs-process-slot' -import { wslTranscriptFsProcessFailureError } from './wsl-transcript-fs-error' +import { wslTranscriptFsProcessFailureError } from '../wsl-transcript-fs-error' type SlotWaiter = { resolve: (slot: ProcessSlot) => void diff --git a/src/main/native-chat/wsl-transcript-fs-process-operations.test.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-operations.test.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-process-operations.test.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-operations.test.ts diff --git a/src/main/native-chat/wsl-transcript-fs-process-operations.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-operations.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-process-operations.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-operations.ts diff --git a/src/main/native-chat/wsl-transcript-fs-process-protocol.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-protocol.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-process-protocol.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-protocol.ts diff --git a/src/main/native-chat/wsl-transcript-fs-process-send.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-send.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-process-send.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-send.ts diff --git a/src/main/native-chat/wsl-transcript-fs-process-slot.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-slot.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-process-slot.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-slot.ts diff --git a/src/main/native-chat/wsl-transcript-fs-process-spawn.ts b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-spawn.ts similarity index 96% rename from src/main/native-chat/wsl-transcript-fs-process-spawn.ts rename to src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-spawn.ts index ba604aa0287..431cee9353f 100644 --- a/src/main/native-chat/wsl-transcript-fs-process-spawn.ts +++ b/src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-spawn.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { pickAllowedEnv, RUNTIME_ENV_ALLOWLIST -} from '../ai-vault/scan-service/session-scanner-service-env' +} from '../../../ai-vault/scan-service/session-scanner-service-env' const PROCESS_ENTRY_FILENAME = 'wsl-transcript-fs-process-entry.js' diff --git a/src/main/native-chat/ssh-transcript-fs-access.ts b/src/main/native-chat/transcript-filesystem/ssh-transcript-fs-access.ts similarity index 88% rename from src/main/native-chat/ssh-transcript-fs-access.ts rename to src/main/native-chat/transcript-filesystem/ssh-transcript-fs-access.ts index 71d41f88ba4..fcc333a53e0 100644 --- a/src/main/native-chat/ssh-transcript-fs-access.ts +++ b/src/main/native-chat/transcript-filesystem/ssh-transcript-fs-access.ts @@ -1,4 +1,4 @@ -import { SshHostNotServedError } from '../providers/ssh-host-not-served-error' +import { SshHostNotServedError } from '../../providers/ssh-host-not-served-error' import type { SshTranscriptLocation } from './ssh-transcript-path' export type SshTranscriptHandle = { sshTranscript: SshTranscriptLocation } diff --git a/src/main/native-chat/ssh-transcript-path.ts b/src/main/native-chat/transcript-filesystem/ssh-transcript-path.ts similarity index 91% rename from src/main/native-chat/ssh-transcript-path.ts rename to src/main/native-chat/transcript-filesystem/ssh-transcript-path.ts index 75a98a0c9bd..e6b20c69036 100644 --- a/src/main/native-chat/ssh-transcript-path.ts +++ b/src/main/native-chat/transcript-filesystem/ssh-transcript-path.ts @@ -1,5 +1,5 @@ -import type { AgentStatusIpcPayload } from '../../shared/agent-status/contracts/agent-status-ipc-payload' -import { isWslHookRelayConnectionId } from '../../shared/wsl-hook-relay-contract' +import type { AgentStatusIpcPayload } from '../../../shared/agent-status/contracts/agent-status-ipc-payload' +import { isWslHookRelayConnectionId } from '../../../shared/wsl-hook-relay-contract' // Encode connection and file in the engine’s single path string so local files cannot answer remote reads. const SSH_TRANSCRIPT_PATH_PREFIX = 'orca-ssh-transcript:' diff --git a/src/main/native-chat/wsl-transcript-fs-access.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-access.ts similarity index 96% rename from src/main/native-chat/wsl-transcript-fs-access.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-fs-access.ts index 31e20cb81c0..6d4df955e98 100644 --- a/src/main/native-chat/wsl-transcript-fs-access.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-access.ts @@ -2,18 +2,18 @@ import { createReadStream, type Dirent, type Stats } from 'node:fs' import { access, lstat, open, readdir, readFile, stat, type FileHandle } from 'node:fs/promises' import { Readable } from 'node:stream' import { StringDecoder } from 'node:string_decoder' -import { throwIfSignalAborted, waitForPromiseWithSignal } from '../../shared/abort-signal-reason' -import { isWslUncPath } from '../../shared/wsl-paths' +import { throwIfSignalAborted, waitForPromiseWithSignal } from '../../../shared/abort-signal-reason' +import { isWslUncPath } from '../../../shared/wsl-paths' import { runWslTranscriptFsTask, type WslTranscriptFsTaskPriority } from './wsl-transcript-fs-gate' import { closeWslTranscriptFsProcess, isWslTranscriptFsProcessHandle, openWslTranscriptFsProcess, readWslTranscriptFsProcess, - runWslTranscriptFsProcess, - type WslTranscriptFsProcessHandle -} from './wsl-transcript-fs-process-dispatch' -import type { WslTranscriptFsReusableProcessCall } from './wsl-transcript-fs-process-protocol' + runWslTranscriptFsProcess +} from './process/wsl-transcript-fs-process-dispatch' +import type { WslTranscriptFsProcessHandle } from './process/wsl-transcript-fs-process-slot' +import type { WslTranscriptFsReusableProcessCall } from './process/wsl-transcript-fs-process-protocol' import { wslTranscriptFsLaneKey } from './wsl-transcript-fs-route' import { admitWslTranscriptPath } from './wsl-transcript-running-admission' import { diff --git a/src/main/native-chat/wsl-transcript-fs-error.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-error.ts similarity index 97% rename from src/main/native-chat/wsl-transcript-fs-error.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-fs-error.ts index 854f46f6565..fcc1808a1cd 100644 --- a/src/main/native-chat/wsl-transcript-fs-error.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-error.ts @@ -2,7 +2,7 @@ import { AI_VAULT_SCAN_ISSUE_COPY, aiVaultScanIssueText, type AiVaultScanIssueText -} from '../../shared/ai-vault/scanning/ai-vault-scan-issue-copy' +} from '../../../shared/ai-vault/scanning/ai-vault-scan-issue-copy' export const WSL_TRANSCRIPT_FS_SLOW_MESSAGE = AI_VAULT_SCAN_ISSUE_COPY.wslTranscriptFsSlow export const WSL_TRANSCRIPT_FS_CAPACITY_MESSAGE = AI_VAULT_SCAN_ISSUE_COPY.wslTranscriptFsCapacity diff --git a/src/main/native-chat/wsl-transcript-fs-gate.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-gate.ts similarity index 97% rename from src/main/native-chat/wsl-transcript-fs-gate.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-fs-gate.ts index 6854ce6ad4f..7f6707df30c 100644 --- a/src/main/native-chat/wsl-transcript-fs-gate.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-gate.ts @@ -18,14 +18,6 @@ export const WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS = 60_000 // Burst bounds keep polling fan-out from growing retained tasks or callers indefinitely. export const WSL_TRANSCRIPT_FS_MAX_PENDING_TASKS = 64 export const WSL_TRANSCRIPT_FS_MAX_WAITERS_PER_TASK = 64 -export { - WSL_TRANSCRIPT_FS_CAPACITY_MESSAGE, - WSL_TRANSCRIPT_FS_SLOW_MESSAGE, - WslTranscriptFsError, - wslTranscriptFsRefusal, - type WslTranscriptFsFailureCode -} from './wsl-transcript-fs-error' -export { WSL_TRANSCRIPT_FS_ROUTE_QUARANTINE_BASE_MS } from './wsl-transcript-fs-route-quarantine' export type WslTranscriptFsTaskPriority = 'exact' | 'scan' diff --git a/src/main/native-chat/wsl-transcript-fs-route-quarantine.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-route-quarantine.ts similarity index 100% rename from src/main/native-chat/wsl-transcript-fs-route-quarantine.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-fs-route-quarantine.ts diff --git a/src/main/native-chat/wsl-transcript-fs-route.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-route.ts similarity index 91% rename from src/main/native-chat/wsl-transcript-fs-route.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-fs-route.ts index e5454133d76..bb92ec11b6a 100644 --- a/src/main/native-chat/wsl-transcript-fs-route.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-fs-route.ts @@ -1,4 +1,4 @@ -import { parseWslUncPath } from '../../shared/wsl-paths' +import { parseWslUncPath } from '../../../shared/wsl-paths' /** Isolate distro/spelling consistently across gate and process ownership. * wsl$ and wsl.localhost remain distinct: provider behavior may differ, so each has its own deadlines. */ diff --git a/src/main/native-chat/wsl-transcript-running-admission.test.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.test.ts similarity index 91% rename from src/main/native-chat/wsl-transcript-running-admission.test.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.test.ts index 27e83d645b8..2479696564a 100644 --- a/src/main/native-chat/wsl-transcript-running-admission.test.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.test.ts @@ -8,7 +8,7 @@ const mocks = vi.hoisted(() => ({ })) vi.mock('@orca/process-host', () => ({ runProcess: mocks.runProcess })) -vi.mock('./wsl-transcript-fs-process-dispatch', async (importOriginal) => ({ +vi.mock('./process/wsl-transcript-fs-process-dispatch', async (importOriginal) => ({ ...(await importOriginal()), runWslTranscriptFsProcess: mocks.runFsProcess, openWslTranscriptFsProcess: async () => ({ wslTranscriptFsProcessHandle: 1 }), @@ -17,11 +17,11 @@ vi.mock('./wsl-transcript-fs-process-dispatch', async (importOriginal) => ({ closeWslTranscriptFsProcess: async () => {} })) -import { _resetWslCachesForTests } from '../wsl/discovery/distro-catalog' +import { _resetWslCachesForTests } from '../../wsl/discovery/distro-catalog' import { _resetWslBackgroundDistroGateForTests, noteWslDistroMayHaveStopped -} from '../wsl/discovery/wsl-background-distro-gate' +} from '../../wsl/discovery/wsl-background-distro-gate' import { openTranscriptFile, readTranscriptFile } from './wsl-transcript-fs-access' import { resetWslTranscriptFsGateForTests } from './wsl-transcript-fs-gate' diff --git a/src/main/native-chat/wsl-transcript-running-admission.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.ts similarity index 90% rename from src/main/native-chat/wsl-transcript-running-admission.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.ts index 9a42f952e0d..486236f91c8 100644 --- a/src/main/native-chat/wsl-transcript-running-admission.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.ts @@ -1,4 +1,4 @@ -import { blockedWslDistroForPath } from '../wsl/filesystem/wsl-unc-access-gate' +import { blockedWslDistroForPath } from '../../wsl/filesystem/wsl-unc-access-gate' import { wslTranscriptFsDistroNotRunningError } from './wsl-transcript-fs-error' /** diff --git a/src/main/native-chat/wsl-transcript-running-observer.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-running-observer.ts similarity index 95% rename from src/main/native-chat/wsl-transcript-running-observer.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-running-observer.ts index f90fdeae789..14363883a70 100644 --- a/src/main/native-chat/wsl-transcript-running-observer.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-running-observer.ts @@ -1,5 +1,5 @@ -import { listRunningWslDistrosAsync } from '../wsl/discovery/distro-catalog' -import { filterPathsToWslDistros } from '../wsl/filesystem/wsl-running-path-filter' +import { listRunningWslDistrosAsync } from '../../wsl/discovery/distro-catalog' +import { filterPathsToWslDistros } from '../../wsl/filesystem/wsl-running-path-filter' /** Every watched WSL transcript shares one running-list read on this cadence, and reuses any * read another caller started within it, so watching never adds a `wsl.exe` per watcher. */ diff --git a/src/main/native-chat/wsl-transcript-watcher-running-guard.ts b/src/main/native-chat/transcript-filesystem/wsl-transcript-watcher-running-guard.ts similarity index 84% rename from src/main/native-chat/wsl-transcript-watcher-running-guard.ts rename to src/main/native-chat/transcript-filesystem/wsl-transcript-watcher-running-guard.ts index 7d98f4a9c03..f64e327068d 100644 --- a/src/main/native-chat/wsl-transcript-watcher-running-guard.ts +++ b/src/main/native-chat/transcript-filesystem/wsl-transcript-watcher-running-guard.ts @@ -1,9 +1,9 @@ -import { isWslUncPath } from '../../shared/wsl-paths' -import { filterPathsToRunningWslDistrosAsync } from '../wsl/filesystem/wsl-running-path-filter' +import { isWslUncPath } from '../../../shared/wsl-paths' +import { filterPathsToRunningWslDistrosAsync } from '../../wsl/filesystem/wsl-running-path-filter' import { createTranscriptNativeWatcher, type TranscriptNativeWatcher -} from '../../shared/transcript-native-watcher' +} from '../../../shared/transcript-native-watcher' import { parseSshTranscriptPath } from './ssh-transcript-path' export function isWslTranscriptWatcherPath(filePath: string): boolean { diff --git a/src/main/native-chat/codex-local-history.ts b/src/main/native-chat/transcript-history/codex/codex-local-history.ts similarity index 90% rename from src/main/native-chat/codex-local-history.ts rename to src/main/native-chat/transcript-history/codex/codex-local-history.ts index 10b95015145..dfd470055b0 100644 --- a/src/main/native-chat/codex-local-history.ts +++ b/src/main/native-chat/transcript-history/codex/codex-local-history.ts @@ -1,13 +1,13 @@ import { createHash } from 'node:crypto' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' -import { sessionIdFromFileName } from '../ai-vault/parsing/session-scanner-accumulator' +import { asRecord } from '../../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../../shared/trimmed-string' +import { parseJsonObject } from '../../../ai-vault/parsing/session-json-object' +import { sessionIdFromFileName } from '../../../ai-vault/parsing/session-scanner-accumulator' import { codexHistorySources, codexPhysicalRolloutId } from './codex-rollout-history-source' import { readCodexRolloutBytes } from './codex-rollout-history-stream' -import { splitTranscriptStreamLines } from './transcript-stream-lines' -import { isCodexVisibleHistoryRecord } from './transcript-line-decoders-codex' -import { transcriptFallbackId } from './transcript-fallback-id' +import { splitTranscriptStreamLines } from '../../transcript-streaming/transcript-stream-lines' +import { isCodexVisibleHistoryRecord } from '../../transcript-decoding/transcript-line-decoders-codex' +import { transcriptFallbackId } from '../../transcript-streaming/transcript-fallback-id' type HistoryPosition = { thread_id: string diff --git a/src/main/native-chat/codex-rollout-history-source.ts b/src/main/native-chat/transcript-history/codex/codex-rollout-history-source.ts similarity index 93% rename from src/main/native-chat/codex-rollout-history-source.ts rename to src/main/native-chat/transcript-history/codex/codex-rollout-history-source.ts index 6beb4da8d66..28af5c17c24 100644 --- a/src/main/native-chat/codex-rollout-history-source.ts +++ b/src/main/native-chat/transcript-history/codex/codex-rollout-history-source.ts @@ -1,6 +1,6 @@ import { basename, dirname, join } from 'node:path' -import { forEachSessionFile } from '../ai-vault/discovery/session-scanner-discovery' -import { admitTranscriptPath } from './transcript-path-admission' +import { forEachSessionFile } from '../../../ai-vault/discovery/session-scanner-discovery' +import { admitTranscriptPath } from '../../transcript-discovery/transcript-path-admission' export function codexPhysicalRolloutId(path: string): string | null { const match = basename(path).match( diff --git a/src/main/native-chat/codex-rollout-history-stream.ts b/src/main/native-chat/transcript-history/codex/codex-rollout-history-stream.ts similarity index 100% rename from src/main/native-chat/codex-rollout-history-stream.ts rename to src/main/native-chat/transcript-history/codex/codex-rollout-history-stream.ts diff --git a/src/main/native-chat/transcript-opencode-database.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode-database.ts similarity index 86% rename from src/main/native-chat/transcript-opencode-database.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode-database.ts index 608d9c01442..ff40857e82c 100644 --- a/src/main/native-chat/transcript-opencode-database.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode-database.ts @@ -1,19 +1,19 @@ import { homedir } from 'node:os' -import { wslGatedAccess } from './wsl-transcript-fs-access' +import { wslGatedAccess } from '../../transcript-filesystem/wsl-transcript-fs-access' import { join } from 'node:path' -import { waitForPromiseWithSignal } from '../../shared/abort-signal-reason' -import { getAiVaultWslScanHomeDirs } from '../ai-vault/discovery/ai-vault-scan-roots' -import { prepareOpenCodeWslReaders } from '../ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation' +import { waitForPromiseWithSignal } from '../../../../shared/abort-signal-reason' +import { getAiVaultWslScanHomeDirs } from '../../../ai-vault/discovery/ai-vault-scan-roots' +import { prepareOpenCodeWslReaders } from '../../../ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation' import { configureOpenCodeWslReaders, openCodeWslPath -} from '../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-wsl-client' -import { readOpenCodeTranscriptSignalViaWorker } from '../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn' +} from '../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-wsl-client' +import { readOpenCodeTranscriptSignalViaWorker } from '../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn' import { compareOpenCodeClaimPriority, listOpenCodeDatabases, listOpenCodeDatabasesInDirectory -} from '../opencode-usage/opencode-database-discovery' +} from '../../../opencode-usage/opencode-database-discovery' // ZCode sessions belong only to its own store. function zcodeTranscriptDatabasePaths(wslHomeDirs: readonly string[]): string[] { diff --git a/src/main/native-chat/transcript-opencode-fingerprint-window.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode-fingerprint-window.ts similarity index 100% rename from src/main/native-chat/transcript-opencode-fingerprint-window.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode-fingerprint-window.ts diff --git a/src/main/native-chat/transcript-opencode-part-blocks.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode-part-blocks.ts similarity index 92% rename from src/main/native-chat/transcript-opencode-part-blocks.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode-part-blocks.ts index 2bec558bfb9..1f14d6afb63 100644 --- a/src/main/native-chat/transcript-opencode-part-blocks.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode-part-blocks.ts @@ -4,11 +4,11 @@ import type { NativeChatImageRefBlock, NativeChatMessage, NativeChatTextBlock -} from '../../shared/native-chat/native-chat-types' -import { agentSessionHostStatusBody } from '../../shared/agent-session/view/agent-session-host-status-rows' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' +} from '../../../../shared/native-chat/native-chat-types' +import { agentSessionHostStatusBody } from '../../../../shared/agent-session/view/agent-session-host-status-rows' +import { asRecord } from '../../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../../shared/trimmed-string' +import { parseJsonObject } from '../../../ai-vault/parsing/session-json-object' // Electron-free: imported by the OpenCode SQLite worker. export type OpenCodePartRow = { diff --git a/src/main/native-chat/transcript-opencode-row-budget.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode-row-budget.ts similarity index 94% rename from src/main/native-chat/transcript-opencode-row-budget.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode-row-budget.ts index 1060ebe1277..10cc244e82f 100644 --- a/src/main/native-chat/transcript-opencode-row-budget.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode-row-budget.ts @@ -1,4 +1,4 @@ -import type SyncDatabase from '../sqlite/sync-database' +import type SyncDatabase from '../../../sqlite/sync-database' type BindValue = SyncDatabase.BindValue type SqliteStatement = SyncDatabase.Statement diff --git a/src/main/native-chat/transcript-opencode-sqlite-query.test.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.test.ts similarity index 75% rename from src/main/native-chat/transcript-opencode-sqlite-query.test.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.test.ts index 44baf1819a9..bec22c45201 100644 --- a/src/main/native-chat/transcript-opencode-sqlite-query.test.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.test.ts @@ -1,9 +1,8 @@ -import { readOpenCodeNativeChatTranscriptFull } from './transcript-opencode' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { afterEach, describe, expect, it, vi } from 'vitest' -import Database from '../sqlite/sync-database' +import { afterEach, describe, expect, it } from 'vitest' +import Database from '../../../sqlite/sync-database' import { readOpenCodeTranscriptPage, readOpenCodeTranscriptSignal @@ -235,65 +234,3 @@ describe('readOpenCodeTranscriptSignal', () => { }) }) }) - -it.each(['stop', 'tool-calls', undefined])( - 'uses native v1 completion evidence without treating prose or tool work as terminal: %s', - async (finish) => { - const { db, path } = createTempDb() - applySchema(db) - insertMessage(db, { id: 'u1', time: 1 }) - insertPart(db, { id: 'pu', messageId: 'u1', time: 1, data: { type: 'text', text: 'Prompt' } }) - insertMessage(db, { id: 'a1', time: 2, role: 'assistant' }) - insertPart(db, { id: 'pa', messageId: 'a1', time: 2, data: { type: 'text', text: 'Response' } }) - if (finish) { - db.prepare('UPDATE message SET data = ? WHERE id = ?').run( - JSON.stringify({ role: 'assistant', time: { completed: 3 }, finish }), - 'a1' - ) - } - const imported = await readOpenCodeNativeChatTranscriptFull('ses-1', { - resolveDbPath: async () => path, - readPage: async (args) => readOpenCodeTranscriptPage(args) - }) - expect(imported).toMatchObject({ forkTipItemIds: finish === 'stop' ? ['u1', 'a1'] : [] }) - } -) - -it.each([100, 1000])( - 'decodes each of %i v1 messages once for content and fork evidence', - (count) => { - const { db, path } = createTempDb() - applySchema(db) - const raw = JSON.stringify({ role: 'assistant', time: { completed: 2 }, finish: 'stop' }) - db.exec('BEGIN') - for (let index = 1; index <= count; index++) { - insertMessage(db, { id: `msg-${index}`, time: index, role: 'assistant' }) - insertPart(db, { - id: `part-${index}`, - messageId: `msg-${index}`, - time: index, - data: { type: 'text', text: 'response' } - }) - } - db.prepare('UPDATE message SET data = ?').run(raw) - db.exec('COMMIT') - const parse = vi.spyOn(JSON, 'parse') - let page - let decodes - try { - page = readOpenCodeTranscriptPage({ dbPath: path, sessionId: 'ses-1', limit: count }) - decodes = parse.mock.calls.filter(([input]) => input === raw).length - } finally { - parse.mockRestore() - } - expect(decodes).toBe(count) - expect(page?.items).toHaveLength(count) - expect(page?.items[0].message).toMatchObject({ - role: 'assistant', - blocks: [{ type: 'text', text: 'response' }] - }) - expect(page?.forkTip).toHaveLength(count) - expect(page?.forkTip?.[0]).toEqual({ id: 'msg-1', role: 'assistant', terminal: true }) - expect(page?.hasMore).toBe(false) - } -) diff --git a/src/main/native-chat/transcript-opencode-sqlite-query.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.ts similarity index 91% rename from src/main/native-chat/transcript-opencode-sqlite-query.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.ts index b28b905d50a..cf032b8bf5b 100644 --- a/src/main/native-chat/transcript-opencode-sqlite-query.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.ts @@ -1,17 +1,17 @@ -import { openOpenCodeDatabaseReadonly } from '../ai-vault/scanners/opencode/sqlite/session-scanner-opencode-sqlite-open' -import { zcodeVisibleMessageFilter } from '../ai-vault/scanners/zcode/session-scanner-zcode-visibility' -import { zcodeTranscriptOrder } from '../ai-vault/scanners/zcode/session-scanner-zcode-order' +import { openOpenCodeDatabaseReadonly } from '../../../ai-vault/scanners/opencode/sqlite/session-scanner-opencode-sqlite-open' +import { zcodeVisibleMessageFilter } from '../../../ai-vault/scanners/zcode/session-scanner-zcode-visibility' +import { zcodeTranscriptOrder } from '../../../ai-vault/scanners/zcode/session-scanner-zcode-order' import { readOpenCode2TranscriptPage, readOpenCode2TranscriptSignal } from './transcript-opencode2-sqlite' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { openCodeTranscriptPageLimit } from '../../shared/opencode-transcript-page-limit' -import { columnExists, tableExists } from '../opencode-usage/schema-helpers' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' -import type SyncDatabase from '../sqlite/sync-database' +import type { NativeChatMessage } from '../../../../shared/native-chat/native-chat-types' +import { openCodeTranscriptPageLimit } from '../../../../shared/opencode-transcript-page-limit' +import { columnExists, tableExists } from '../../../opencode-usage/schema-helpers' +import { asRecord } from '../../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../../shared/trimmed-string' +import { parseJsonObject } from '../../../ai-vault/parsing/session-json-object' +import type SyncDatabase from '../../../sqlite/sync-database' import { rowsOf, rowsWithinBudget } from './transcript-opencode-row-budget' import { opencodeMessages, diff --git a/src/main/native-chat/transcript-opencode-subscribe.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode-subscribe.ts similarity index 97% rename from src/main/native-chat/transcript-opencode-subscribe.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode-subscribe.ts index 3f7fddbdb2f..ac6f89fb523 100644 --- a/src/main/native-chat/transcript-opencode-subscribe.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode-subscribe.ts @@ -1,18 +1,18 @@ import { pruneOpenCodeFingerprintCache } from './transcript-opencode-fingerprint-window' -export { pruneOpenCodeFingerprintCache as pruneOpenCodeFingerprintCacheForTest } from './transcript-opencode-fingerprint-window' -import { errorMessage } from '../../shared/error-message' + +import { errorMessage } from '../../../../shared/error-message' import { DESKTOP_READ_WINDOW, UNFLUSHED_SETTLE_MS, type NativeChatTranscriptSubscription, type SubscribeNativeChatTranscriptArgs -} from './transcript-watch-contract' +} from '../../transcript-watching/transcript-watch-contract' import type { OpenCodeTranscriptItem } from './transcript-opencode-sqlite-query' import { openCodeTranscriptDefaultDeps, type OpenCodeTranscriptDeps } from './transcript-opencode' import { openCodeTranscriptPageLimit, OPENCODE_TRANSCRIPT_MAX_WINDOW -} from '../../shared/opencode-transcript-page-limit' +} from '../../../../shared/opencode-transcript-page-limit' const OPENCODE_POLL_MS = 1_000 diff --git a/src/main/native-chat/transcript-opencode.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode.ts similarity index 88% rename from src/main/native-chat/transcript-opencode.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode.ts index f0de1e707a5..473f8113f4e 100644 --- a/src/main/native-chat/transcript-opencode.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode.ts @@ -1,4 +1,4 @@ -import { createImportedForkTip } from './agent-session-journal/history-import/imported-fork-tip' +import { createImportedForkTip } from '../../agent-session-journal/history-import/imported-fork-tip' import type { OpenCodeForkTipEvent, OpenCodeTranscriptItem, @@ -8,17 +8,17 @@ import type { import { OPENCODE_CAPTURE_RECORD_LIMIT, OPENCODE_CAPTURE_TEXT_LIMIT -} from '../ai-vault/scanners/opencode/sqlite/opencode-transcript-capture-limits' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { errorMessage } from '../../shared/error-message' -import type { ReadTranscriptResult } from './transcript-reader' +} from '../../../ai-vault/scanners/opencode/sqlite/opencode-transcript-capture-limits' +import type { NativeChatMessage } from '../../../../shared/native-chat/native-chat-types' +import { errorMessage } from '../../../../shared/error-message' +import type { ReadTranscriptResult } from '../../transcript-reader' import { readOpenCodeTranscriptPageViaWorker, readOpenCodeTranscriptSignalViaWorker -} from '../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn' +} from '../../../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-worker-spawn' import { discoverOpenCodeTranscriptDatabase } from './transcript-opencode-database' -import { DESKTOP_READ_WINDOW } from './transcript-watch-contract' -import { openCodeTranscriptPageLimit } from '../../shared/opencode-transcript-page-limit' +import { DESKTOP_READ_WINDOW } from '../../transcript-watching/transcript-watch-contract' +import { openCodeTranscriptPageLimit } from '../../../../shared/opencode-transcript-page-limit' export type OpenCodeTranscriptDeps = { resolveDbPath?: ( diff --git a/src/main/native-chat/transcript-opencode2-fork-coverage.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode2-fork-coverage.ts similarity index 91% rename from src/main/native-chat/transcript-opencode2-fork-coverage.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode2-fork-coverage.ts index f401a762068..a6902f95535 100644 --- a/src/main/native-chat/transcript-opencode2-fork-coverage.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode2-fork-coverage.ts @@ -1,7 +1,7 @@ -import type SyncDatabase from '../sqlite/sync-database' -import { tableExists } from '../opencode-usage/schema-helpers' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' +import type SyncDatabase from '../../../sqlite/sync-database' +import { tableExists } from '../../../opencode-usage/schema-helpers' +import { asRecord } from '../../../ai-vault/parsing/session-record-fields' +import { parseJsonObject } from '../../../ai-vault/parsing/session-json-object' import { hasOpenCode2Session } from './transcript-opencode2-sqlite' export function readOpenCode2ForkCoverage( diff --git a/src/main/native-chat/transcript-opencode2-sqlite.ts b/src/main/native-chat/transcript-history/opencode/transcript-opencode2-sqlite.ts similarity index 93% rename from src/main/native-chat/transcript-opencode2-sqlite.ts rename to src/main/native-chat/transcript-history/opencode/transcript-opencode2-sqlite.ts index 2f0b3e1852f..fee3e84a89a 100644 --- a/src/main/native-chat/transcript-opencode2-sqlite.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-opencode2-sqlite.ts @@ -1,15 +1,15 @@ import { createHash } from 'node:crypto' -import type SyncDatabase from '../sqlite/sync-database' -import { openCodeTranscriptPageLimit } from '../../shared/opencode-transcript-page-limit' -import { columnExists, tableExists } from '../opencode-usage/schema-helpers' -import { asRecord } from '../ai-vault/parsing/session-record-fields' -import { extractString } from '../../shared/trimmed-string' -import { parseJsonObject } from '../ai-vault/parsing/session-json-object' +import type SyncDatabase from '../../../sqlite/sync-database' +import { openCodeTranscriptPageLimit } from '../../../../shared/opencode-transcript-page-limit' +import { columnExists, tableExists } from '../../../opencode-usage/schema-helpers' +import { asRecord } from '../../../ai-vault/parsing/session-record-fields' +import { extractString } from '../../../../shared/trimmed-string' +import { parseJsonObject } from '../../../ai-vault/parsing/session-json-object' import { opencodeMessages, OPENCODE_TRANSCRIPT_MAX_ROW_BYTES } from './transcript-opencode-part-blocks' -import type { NativeChatBlock } from '../../shared/native-chat/native-chat-types' +import type { NativeChatBlock } from '../../../../shared/native-chat/native-chat-types' import type { OpenCodeTranscriptPage, OpenCodeTranscriptSignal, diff --git a/src/main/native-chat/transcript-zcode.test.ts b/src/main/native-chat/transcript-history/opencode/transcript-zcode.test.ts similarity index 98% rename from src/main/native-chat/transcript-zcode.test.ts rename to src/main/native-chat/transcript-history/opencode/transcript-zcode.test.ts index d8ac97ba68e..855aedd24a3 100644 --- a/src/main/native-chat/transcript-zcode.test.ts +++ b/src/main/native-chat/transcript-history/opencode/transcript-zcode.test.ts @@ -2,7 +2,7 @@ import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, expect, it, vi } from 'vitest' -import Database from '../sqlite/sync-database' +import Database from '../../../sqlite/sync-database' import { readOpenCodeTranscriptPage, readOpenCodeTranscriptSignal diff --git a/src/main/native-chat/transcript-interruption-message.test.ts b/src/main/native-chat/transcript-interruption-message.test.ts deleted file mode 100644 index 5d2d2c3ccbe..00000000000 --- a/src/main/native-chat/transcript-interruption-message.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - NATIVE_CHAT_INTERRUPTED_STATUS_TEXT, - type NativeChatMessage -} from '../../shared/native-chat/native-chat-types' -import { stripNoiseMessages } from '../../shared/native-chat/native-chat-noise' -import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' -import { decodeCodexTranscriptLine } from './transcript-line-decoders-codex' - -function expectNormalizedInterruption(message: NativeChatMessage | null): void { - expect(message).toMatchObject({ - role: 'system', - blocks: [{ type: 'text', text: NATIVE_CHAT_INTERRUPTED_STATUS_TEXT }], - source: 'transcript' - }) - expect(stripNoiseMessages(message ? [message] : [])).toEqual([message]) -} - -describe('native chat transcript interruption messages', () => { - it('normalizes Claude interruption boilerplate into one visible status row', () => { - const message = decodeClaudeTranscriptLine( - JSON.stringify({ - type: 'user', - uuid: 'interrupt-row', - interruptedMessageId: 'assistant-request-1', - timestamp: '2026-07-16T23:46:01.000Z', - message: { - role: 'user', - content: [{ type: 'text', text: '[Request interrupted by user]' }] - } - }), - 'fallback' - ) - - expectNormalizedInterruption(message) - expect(message?.id).toBe('interrupt-row') - }) - - it('normalizes Codex turn_aborted into one visible status row', () => { - const message = decodeCodexTranscriptLine( - JSON.stringify({ - type: 'event_msg', - timestamp: '2026-07-16T23:46:01.000Z', - payload: { type: 'turn_aborted', reason: 'interrupted', turn_id: 'turn-2' } - }), - 'fallback' - ) - - expectNormalizedInterruption(message) - expect(message?.id).toBe('fallback') - }) -}) diff --git a/src/main/native-chat/transcript-line-decoders-claude-command-output.test.ts b/src/main/native-chat/transcript-line-decoders-claude-command-output.test.ts deleted file mode 100644 index 09a6cf28e19..00000000000 --- a/src/main/native-chat/transcript-line-decoders-claude-command-output.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { readFileSync } from 'node:fs' -import { join } from 'node:path' -import { describe, expect, it } from 'vitest' -import { surfaceNativeChatCommandOutputs } from '../../shared/native-chat/tools/native-chat-command-output' -import { stripNoiseMessages } from '../../shared/native-chat/native-chat-noise' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' - -// OpenClaude 0.31.0: /context, /cost, /context without model calls; session ID/cwd scrubbed. -const FIXTURE_LINES = readFileSync( - join(__dirname, '__fixtures__', 'openclaude-0.31.0-local-command-rows.jsonl'), - 'utf8' -) - .split('\n') - .filter((line) => line.trim() !== '') - -function decoded(lines: readonly string[]): NativeChatMessage[] { - return lines.flatMap((line, index) => decodeClaudeTranscriptLine(line, `f${index}`) ?? []) -} - -function commandRow(messages: NativeChatMessage[], index: number): NativeChatMessage | undefined { - return messages.filter((message) => - message.blocks.some((block) => block.type === 'text' && block.text.includes('')) - )[index] -} - -describe("OpenClaude's local command rows through the transcript decoder", () => { - const messages = decoded(FIXTURE_LINES) - - it("links each reply row to its command row's id", () => { - const replies = messages.filter((message) => - message.blocks.some( - (block) => block.type === 'text' && block.text.includes('') - ) - ) - expect(replies.map(({ parentId }) => parentId)).toEqual([ - commandRow(messages, 0)?.id, - commandRow(messages, 2)?.id - ]) - }) - - it('shows both /context reports as plain command output and nothing else', () => { - const visible = stripNoiseMessages(surfaceNativeChatCommandOutputs(messages, 'openclaude')) - expect(visible.map(({ role }) => role)).toEqual(['system', 'system']) - const totals = visible.map(({ blocks: [block] }) => { - expect(block).toMatchObject({ type: 'text', presentation: 'command-output' }) - const text = block?.type === 'text' ? block.text : '' - expect(text.split('\n')[0]).toBe('Context Usage') - expect(text).not.toContain('\u001b') - return /gpt-4o · \S+ tokens \(\d+%\)/.exec(text)?.[0] - }) - expect(totals).toEqual(['gpt-4o · 18.3k/128k tokens (14%)', 'gpt-4o · 18.9k/128k tokens (15%)']) - }) - - it('keeps the reply hidden for a host that sends no row link', () => { - const unlinked = messages.map(({ parentId: _parentId, ...message }) => message) - expect(stripNoiseMessages(surfaceNativeChatCommandOutputs(unlinked, 'openclaude'))).toEqual([]) - }) -}) diff --git a/src/main/native-chat/transcript-line-decoders-claude-image-companion.test.ts b/src/main/native-chat/transcript-line-decoders-claude-image-companion.test.ts deleted file mode 100644 index 0e6e6641309..00000000000 --- a/src/main/native-chat/transcript-line-decoders-claude-image-companion.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { normalizeImageTranscriptMessages } from '../../shared/native-chat/transcript/native-chat-image-transcript-markers' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' - -// Claude 2.1.237 stores source paths in separate isMeta/turnCompanion rows; prompt base64 has no path. -const PROMPT_ROW = JSON.stringify({ - type: 'user', - uuid: 'prompt-uuid', - timestamp: '2026-09-01T01:33:04.608Z', - message: { - role: 'user', - content: [ - { type: 'text', text: 'look at this\r[Image #1] [Image #2] does it repro?' }, - { type: 'image', source: { type: 'base64', media_type: 'image/jpeg', data: 'AAAA' } }, - { type: 'image', source: { type: 'base64', media_type: 'image/jpeg', data: 'BBBB' } } - ] - } -}) -const COMPANION_ROW = JSON.stringify({ - type: 'user', - uuid: 'companion-uuid', - isMeta: true, - turnCompanion: true, - timestamp: '2026-09-01T01:33:04.608Z', - message: { - role: 'user', - content: [ - { type: 'text', text: '[Image: source: /tmp/orca-paste-a.png]' }, - { type: 'text', text: '[Image: source: /tmp/orca-paste-b.png]' } - ] - } -}) - -function decode(): NativeChatMessage[] { - return [COMPANION_ROW, PROMPT_ROW] - .map((line, index) => decodeClaudeTranscriptLine(line, `fallback-${index}`)) - .filter((message): message is NativeChatMessage => message !== null) -} - -describe('Claude pasted-image companion row', () => { - it('folds a multi-image companion into the prompt as image refs', () => { - const folded = normalizeImageTranscriptMessages(decode()) - expect(folded).toHaveLength(1) - expect(folded[0]!.blocks.filter((b) => b.type === 'image-ref')).toEqual([ - { type: 'image-ref', path: '/tmp/orca-paste-a.png' }, - { type: 'image-ref', path: '/tmp/orca-paste-b.png' } - ]) - const text = folded[0]!.blocks - .filter((b) => b.type === 'text') - .map((b) => b.text) - .join('') - expect(text).toContain('look at this') - expect(text).toContain('does it repro?') - expect(text).not.toContain('[Image #') - }) - - it('still drops an ordinary isMeta injected turn', () => { - const injected = JSON.stringify({ - type: 'user', - uuid: 'injected', - isMeta: true, - message: { - role: 'user', - content: [{ type: 'text', text: 'Base directory for this skill: /x' }] - } - }) - expect(decodeClaudeTranscriptLine(injected, 'fallback')).toBeNull() - }) - - it('does not turn a mixed marker-and-prose meta row into an image source turn', () => { - const mixed = JSON.stringify({ - type: 'user', - uuid: 'mixed', - isMeta: true, - message: { - role: 'user', - content: [ - { type: 'text', text: '[Image: source: /tmp/a.png]' }, - { type: 'text', text: 'metadata prose' } - ] - } - }) - - expect(decodeClaudeTranscriptLine(mixed, 'fallback')).toBeNull() - }) - - it('retains tool results from a mixed meta row while dropping its marker text', () => { - const mixed = JSON.stringify({ - type: 'user', - uuid: 'mixed-tool', - isMeta: true, - message: { - role: 'user', - content: [ - { type: 'text', text: '[Image: source: /tmp/a.png]' }, - { type: 'tool_result', content: 'tool output' } - ] - } - }) - - expect(decodeClaudeTranscriptLine(mixed, 'fallback')).toMatchObject({ - id: 'mixed-tool', - role: 'tool', - blocks: [{ type: 'tool-result', output: 'tool output' }] - }) - }) -}) diff --git a/src/main/native-chat/transcript-line-decoders-claude-stop-hook.test.ts b/src/main/native-chat/transcript-line-decoders-claude-stop-hook.test.ts deleted file mode 100644 index 733baa0602b..00000000000 --- a/src/main/native-chat/transcript-line-decoders-claude-stop-hook.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { nativeChatRowTurnKeys } from '../../shared/native-chat/transcript/native-chat-turn-grouping' -import { - nativeChatTurnFold, - nativeChatTurnFoldRow -} from '../../shared/native-chat/transcript/native-chat-turn-fold' -import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' - -// Text rows of a Claude Code 2.1.316 turn whose Stop hook blocked once. -function stopHookTurn(feedback: string): string[] { - return [ - { type: 'user', uuid: 'prompt', message: { role: 'user', content: 'Say: the sky is blue' } }, - { - type: 'assistant', - uuid: 'reply', - message: { role: 'assistant', content: [{ type: 'text', text: 'The sky is blue.' }] } - }, - { - type: 'user', - uuid: 'feedback', - isMeta: true, - message: { role: 'user', content: `Stop hook feedback:\n${feedback}` } - }, - { - type: 'assistant', - uuid: 'continuation', - message: { role: 'assistant', content: [{ type: 'text', text: 'Evidence: I checked.' }] } - } - ].map((record) => JSON.stringify({ timestamp: '2026-11-01T05:33:22.035Z', ...record })) -} - -describe('a Claude turn its Stop hook sent back', () => { - it.each([ - ['a JSON block decision', 'Verify your statement with evidence.'], - ['an exit-2 hook', '[/tmp/hook.sh]: Verify your statement with evidence.\n'] - ])('keeps the reply and the hook row visible once settled (%s)', (_, feedback) => { - const messages = stopHookTurn(feedback).flatMap( - (line) => decodeClaudeTranscriptLine(line, 'fallback') ?? [] - ) - const turnKeys = nativeChatRowTurnKeys(messages) - const { foldedRows } = nativeChatTurnFold({ - rows: messages.map((message, index) => - nativeChatTurnFoldRow(message, turnKeys[index], false) - ), - settledTurnKeys: new Set(['prompt']), - expandedTurnKeys: new Set() - }) - expect( - messages.flatMap((message, index) => - foldedRows.has(index) ? [] : [[message.role, message.blocks[0]]] - ) - ).toEqual([ - ['user', { type: 'text', text: 'Say: the sky is blue' }], - ['assistant', { type: 'text', text: 'The sky is blue.' }], - [ - 'system', - expect.objectContaining({ - text: 'Stop hook feedback: Verify your statement with evidence.', - tone: 'warning' - }) - ], - ['assistant', { type: 'text', text: 'Evidence: I checked.' }] - ]) - }) -}) diff --git a/src/main/native-chat/transcript-line-decoders.ts b/src/main/native-chat/transcript-line-decoders.ts deleted file mode 100644 index 9e646485db8..00000000000 --- a/src/main/native-chat/transcript-line-decoders.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Stateless line decoders serve full reads and tailing; callers supply stable per-line fallback IDs. - -export { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' -export { decodeCodexTranscriptLine } from './transcript-line-decoders-codex' -export { decodeGrokTranscriptLine } from './transcript-line-decoders-grok' -export { decodeOmpTranscriptLine } from './transcript-line-decoders-omp' diff --git a/src/main/native-chat/transcript-opencode2-fork.test-fixture.ts b/src/main/native-chat/transcript-opencode2-fork.test-fixture.ts deleted file mode 100644 index 429f749df27..00000000000 --- a/src/main/native-chat/transcript-opencode2-fork.test-fixture.ts +++ /dev/null @@ -1,85 +0,0 @@ -import Database from '../sqlite/sync-database' - -// OpenCode 2.0.21 M1: copied seqs keep a fresh prefix; the event table is empty. -export function openCode2ForkStore(path: string) { - const db = new Database(path) - db.exec(`CREATE TABLE session_v2 (id TEXT PRIMARY KEY, parent_id TEXT, fork_session_id TEXT, fork_boundary TEXT); - CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, type TEXT, - seq INTEGER, data TEXT, time_created INTEGER, time_updated INTEGER); - CREATE TABLE event (id TEXT PRIMARY KEY, aggregate_id TEXT, type TEXT, seq INTEGER); - INSERT INTO session_v2 VALUES ('ses_e4d7a0c31ffejclsdjiiDszHMB', NULL, NULL, NULL); - INSERT INTO session_v2 VALUES ('ses_e4d797526ffee7BYM6ri3gpmbg', NULL, - 'ses_e4d7a0c31ffejclsdjiiDszHMB', '{"type":"through","messageID":"msg_1b2868a8e0026IQTGMmj77bBFi"}');`) - const insert = db.prepare('INSERT INTO session_message VALUES (?, ?, ?, ?, ?, ?, ?)') - for (const [session, user, idle] of [ - [ - 'ses_e4d7a0c31ffejclsdjiiDszHMB', - 'msg_1b285f9e10016X0Hu1rKOHAACe', - 'msg_1b2868a8e0026IQTGMmj77bBFi' - ], - [ - 'ses_e4d797526ffee7BYM6ri3gpmbg', - 'msg_1b2868ada001HhsJYcY0OtPOjp_4', - 'msg_1b2868ada001HhsJYcY0OtPOjp_87' - ] - ]) { - insert.run( - user!, - session!, - 'user', - 4, - JSON.stringify({ files: [{ uri: 'file:///image.png' }] }), - 1, - 1 - ) - insert.run(idle!, session!, 'idle', 87, '{"outcome":"succeeded"}', 1, 1) - } - // Match copied timestamps deliberately: time is not provenance. - insert.run( - 'msg_1b2868b430019orGpEpk0E3liv', - 'ses_e4d797526ffee7BYM6ri3gpmbg', - 'user', - 90, - '{"text":"CLI after fork"}', - 1, - 1 - ) - return db -} - -// OpenCode 1.19.5 M1: regenerated message IDs and empty newer-schema tables. -export function openCode1ForkStore(path: string) { - const db = new Database(path) - db.exec(`CREATE TABLE session (id TEXT PRIMARY KEY, parent_id TEXT); - CREATE TABLE message (id TEXT PRIMARY KEY, session_id TEXT NOT NULL, - time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL, data TEXT NOT NULL); - CREATE TABLE part (id TEXT PRIMARY KEY, message_id TEXT NOT NULL, session_id TEXT NOT NULL, - time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL, data TEXT NOT NULL); - CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, type TEXT, - seq INTEGER, data TEXT, time_created INTEGER, time_updated INTEGER); - INSERT INTO session VALUES ('ses_e4d773f6affe3HGZP6SZo2NjZp', NULL); - INSERT INTO session VALUES ('ses_e4d76d713ffe98vxcM3WaxWjVI', NULL);`) - const insert = db.prepare('INSERT INTO message VALUES (?, ?, 1, 1, ?)') - insert.run('msg_1b288c688001YdJeVFY2Yyte5V', 'ses_e4d773f6affe3HGZP6SZo2NjZp', '{"role":"user"}') - for (const [id, role] of [ - ['msg_1b28928ed001PfHNbepa2ju4PH', 'user'], - ['msg_1b28928ee001wHBhq6QZzVGJ3f', 'assistant'], - ['msg_1b28928f5001P7iHSIEED9Iq2p', 'user'], - ['msg_1b28928f6001CkhDQSpYI3ByI4', 'user'], - ['msg_1b28928f80012WtB5Unb6QBY2Y', 'assistant'], - // Deliberately before capture: the accepted 1.x interval limitation covers this CLI root too. - ['msg_1b2892ade0019IigeE4JneP9j3', 'user'] - ]) { - insert.run(id!, 'ses_e4d76d713ffe98vxcM3WaxWjVI', JSON.stringify({ role })) - } - const part = db.prepare( - "INSERT INTO part VALUES (?, ?, 'ses_e4d76d713ffe98vxcM3WaxWjVI', 1, 1, ?)" - ) - part.run( - 'file-part', - 'msg_1b28928ed001PfHNbepa2ju4PH', - '{"type":"file","url":"file:///image.png"}' - ) - part.run('compaction-part', 'msg_1b28928f6001CkhDQSpYI3ByI4', '{"type":"compaction","auto":true}') - return db -} diff --git a/src/main/native-chat/transcript-opencode2-sqlite.test.ts b/src/main/native-chat/transcript-opencode2-sqlite.test.ts deleted file mode 100644 index c9f9163691e..00000000000 --- a/src/main/native-chat/transcript-opencode2-sqlite.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { openCode2ForkStore } from './transcript-opencode2-fork.test-fixture' -import { handleOpenCodeSqliteRequest } from '../ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-dispatch' -import { mkdtempSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, expect, it, vi } from 'vitest' -import Database from '../sqlite/sync-database' -import { readOpenCodeNativeChatTranscriptFull } from './transcript-opencode' -import { readOpenCodeTranscriptPage } from './transcript-opencode-sqlite-query' - -const fixtures: { db: Database.Database; root: string }[] = [] -afterEach(() => { - for (const { db, root } of fixtures.splice(0)) { - db.close() - rmSync(root, { recursive: true, force: true }) - } -}) -function fixture() { - const root = mkdtempSync(join(tmpdir(), 'orca-native-opencode2-')) - const path = join(root, 'opencode.db') - const db = new Database(path) - fixtures.push({ db, root }) - db.exec(`CREATE TABLE session_v2 (id TEXT PRIMARY KEY); - CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, type TEXT, - seq INTEGER, data TEXT, time_created INTEGER, time_updated INTEGER); - INSERT INTO session_v2 VALUES ('session'); - ALTER TABLE session_v2 ADD COLUMN directory TEXT; - UPDATE session_v2 SET directory = '/folder';`) - const insertMessage = db.prepare('INSERT INTO session_message VALUES (?, ?, ?, ?, ?, ?, ?)') - let sequence = 0 - const insert = (id: string, type: string, data: unknown, time = 1) => - insertMessage.run(id, 'session', type, ++sequence, JSON.stringify(data), time, time) - return { path, insert, db } -} - -it.each([true, false])( - 'uses genuine v2 idle evidence for the imported latest root, completed: %s', - async (completed) => { - const { path, insert } = fixture() - insert('u1', 'user', { text: 'First prompt' }) - insert('a1', 'assistant', { content: [{ type: 'text', text: 'First response' }] }) - insert('idle1', 'idle', { outcome: 'succeeded' }) - insert('u2', 'user', { text: 'Latest prompt' }) - insert('a2', 'assistant', { content: [{ type: 'text', text: 'Latest prose' }] }) - if (completed) { - insert('idle2', 'idle', { outcome: 'succeeded' }) - } - const imported = await readOpenCodeNativeChatTranscriptFull('session', { - resolveDbPath: async () => path, - readPage: async (args) => readOpenCodeTranscriptPage(args) - }) - expect(imported).toMatchObject({ - forkTipItemIds: completed ? ['opencode:u2', 'opencode:a2', 'opencode:idle2'] : [] - }) - } -) - -it('captures the M1 copied roots without events, excludes a racing CLI root, and honors before', async () => { - const root = mkdtempSync(join(tmpdir(), 'orca-native-opencode2-fork-')) - const path = join(root, 'opencode.db') - const db = openCode2ForkStore(path) - fixtures.push({ db, root }) - const request = { - id: 1, - kind: 'fork-coverage' as const, - dbPath: path, - sessionId: 'ses_e4d797526ffee7BYM6ri3gpmbg', - sourceSessionId: 'ses_e4d7a0c31ffejclsdjiiDszHMB' - } - expect(await handleOpenCodeSqliteRequest(request)).toEqual({ - id: 1, - ok: true, - value: ['msg_1b2868ada001HhsJYcY0OtPOjp_4'] - }) - expect( - await handleOpenCodeSqliteRequest({ ...request, sourceSessionId: 'wrong-source' }) - ).toMatchObject({ ok: false }) - db.exec(`UPDATE session_v2 SET fork_boundary = - '{"type":"before","messageID":"msg_1b285f9e10016X0Hu1rKOHAACe"}' - WHERE id = 'ses_e4d797526ffee7BYM6ri3gpmbg'; - DELETE FROM session_message WHERE session_id = 'ses_e4d797526ffee7BYM6ri3gpmbg' AND seq < 90;`) - expect(await handleOpenCodeSqliteRequest(request)).toEqual({ id: 1, ok: true, value: [] }) -}) - -it.each([100, 1000])( - 'decodes each of %i v2 messages once for content and fork evidence', - (count) => { - const { path, insert, db } = fixture() - const data = { content: [{ type: 'text', text: 'response' }], outcome: 'succeeded' } - const raw = JSON.stringify(data) - db.exec('BEGIN') - for (let index = 1; index <= count; index++) { - insert(`msg-${index}`, index === count ? 'idle' : 'assistant', data) - } - db.exec('COMMIT') - const parse = vi.spyOn(JSON, 'parse') - let page - let decodes - try { - page = readOpenCodeTranscriptPage({ dbPath: path, sessionId: 'session', limit: count }) - decodes = parse.mock.calls.filter(([input]) => input === raw).length - } finally { - parse.mockRestore() - } - expect(decodes).toBe(count) - expect(page?.items).toHaveLength(count) - expect(page?.items[0].message).toMatchObject({ - role: 'assistant', - blocks: [{ type: 'text', text: 'response' }] - }) - expect(page?.forkTip).toHaveLength(count) - expect(page?.forkTip?.at(-1)).toEqual({ - id: `opencode:msg-${count}`, - role: 'terminal', - terminal: true - }) - expect(page?.hasMore).toBe(false) - } -) diff --git a/src/main/native-chat/transcript-read-cache.ts b/src/main/native-chat/transcript-read-cache.ts index 01f6b425545..20d886c758e 100644 --- a/src/main/native-chat/transcript-read-cache.ts +++ b/src/main/native-chat/transcript-read-cache.ts @@ -1,15 +1,15 @@ import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat/native-chat-agent-support' -import { readOpenCodeNativeChatTranscriptFull } from './transcript-opencode' +import { readOpenCodeNativeChatTranscriptFull } from './transcript-history/opencode/transcript-opencode' import type { AgentType } from '../../shared/agent-status/contracts/agent-status-types' import { InFlightPromiseDedupe, stableInFlightKey } from '../../shared/in-flight-promise-dedupe' import { resolveSessionFilePath } from './session-file-resolver' import { readNativeChatTranscript, type ReadTranscriptResult } from './transcript-reader' -import { wslGatedStat } from './wsl-transcript-fs-access' +import { wslGatedStat } from './transcript-filesystem/wsl-transcript-fs-access' import { isWslTranscriptFsRefusalMessage, WslTranscriptFsError, wslTranscriptFsRefusal -} from './wsl-transcript-fs-error' +} from './transcript-filesystem/wsl-transcript-fs-error' // Share one unwindowed parse per resolved path across desktop and paired clients. // Session IDs can resolve to different worktree files; connection keys would duplicate memory. diff --git a/src/main/native-chat/transcript-reader.ts b/src/main/native-chat/transcript-reader.ts index 7baa9ce98e3..f13420f58f9 100644 --- a/src/main/native-chat/transcript-reader.ts +++ b/src/main/native-chat/transcript-reader.ts @@ -1,4 +1,4 @@ -import { readOpenCodeNativeChatTranscriptFull } from './transcript-opencode' +import { readOpenCodeNativeChatTranscriptFull } from './transcript-history/opencode/transcript-opencode' import { isENOENT } from '../ipc/filesystem/access/filesystem-path-containment' import type { AgentType } from '../../shared/agent-status/contracts/agent-status-types' import type { @@ -8,15 +8,13 @@ import type { import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat/native-chat-agent-support' import { errorMessage } from '../../shared/error-message' import { resolveSessionFilePath, type ResolveSessionFileOptions } from './session-file-resolver' -import { openTranscriptReadStream } from './wsl-transcript-fs-access' -import { wslTranscriptFsRefusal } from './wsl-transcript-fs-gate' -import { - decodeClaudeTranscriptLine, - decodeCodexTranscriptLine, - decodeGrokTranscriptLine, - decodeOmpTranscriptLine -} from './transcript-line-decoders' -import { decodeTranscriptStream } from './transcript-stream-lines' +import { openTranscriptReadStream } from './transcript-filesystem/wsl-transcript-fs-access' +import { wslTranscriptFsRefusal } from './transcript-filesystem/wsl-transcript-fs-error' +import { decodeClaudeTranscriptLine } from './transcript-decoding/transcript-line-decoders-claude' +import { decodeCodexTranscriptLine } from './transcript-decoding/transcript-line-decoders-codex' +import { decodeGrokTranscriptLine } from './transcript-decoding/transcript-line-decoders-grok' +import { decodeOmpTranscriptLine } from './transcript-decoding/transcript-line-decoders-omp' +import { decodeTranscriptStream } from './transcript-streaming/transcript-stream-lines' export type ReadTranscriptResult = | { diff --git a/src/main/native-chat/transcript-fallback-id.ts b/src/main/native-chat/transcript-streaming/transcript-fallback-id.ts similarity index 100% rename from src/main/native-chat/transcript-fallback-id.ts rename to src/main/native-chat/transcript-streaming/transcript-fallback-id.ts diff --git a/src/main/native-chat/transcript-file-order.ts b/src/main/native-chat/transcript-streaming/transcript-file-order.ts similarity index 89% rename from src/main/native-chat/transcript-file-order.ts rename to src/main/native-chat/transcript-streaming/transcript-file-order.ts index 75ba5ce62ec..5d567324d91 100644 --- a/src/main/native-chat/transcript-file-order.ts +++ b/src/main/native-chat/transcript-streaming/transcript-file-order.ts @@ -1,4 +1,4 @@ -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' +import type { NativeChatMessage } from '../../../shared/native-chat/native-chat-types' /** Modern readers use record position; legacy readers retain their historical backward-clock clamp. */ export function inTranscriptFileOrder( diff --git a/src/main/native-chat/transcript-stream-lines.test.ts b/src/main/native-chat/transcript-streaming/transcript-stream-lines.test.ts similarity index 100% rename from src/main/native-chat/transcript-stream-lines.test.ts rename to src/main/native-chat/transcript-streaming/transcript-stream-lines.test.ts diff --git a/src/main/native-chat/transcript-stream-lines.ts b/src/main/native-chat/transcript-streaming/transcript-stream-lines.ts similarity index 97% rename from src/main/native-chat/transcript-stream-lines.ts rename to src/main/native-chat/transcript-streaming/transcript-stream-lines.ts index 20ce58d5fe7..8d96b627ab0 100644 --- a/src/main/native-chat/transcript-stream-lines.ts +++ b/src/main/native-chat/transcript-streaming/transcript-stream-lines.ts @@ -1,6 +1,6 @@ import type { Readable } from 'node:stream' import { StringDecoder } from 'node:string_decoder' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' +import type { NativeChatMessage } from '../../../shared/native-chat/native-chat-types' import { transcriptFallbackId } from './transcript-fallback-id' import { inTranscriptFileOrder } from './transcript-file-order' diff --git a/src/main/native-chat/transcript-zstd-decompression.test.ts b/src/main/native-chat/transcript-streaming/transcript-zstd-decompression.test.ts similarity index 100% rename from src/main/native-chat/transcript-zstd-decompression.test.ts rename to src/main/native-chat/transcript-streaming/transcript-zstd-decompression.test.ts diff --git a/src/main/native-chat/transcript-zstd-decompression.ts b/src/main/native-chat/transcript-streaming/transcript-zstd-decompression.ts similarity index 100% rename from src/main/native-chat/transcript-zstd-decompression.ts rename to src/main/native-chat/transcript-streaming/transcript-zstd-decompression.ts diff --git a/src/main/native-chat/transcript-tail-reader.ts b/src/main/native-chat/transcript-tail-reader.ts index 83c3e6f9f0a..e6f32d41d6c 100644 --- a/src/main/native-chat/transcript-tail-reader.ts +++ b/src/main/native-chat/transcript-tail-reader.ts @@ -1,4 +1,4 @@ -import { readOpenCodeNativeChatTranscriptTail } from './transcript-opencode' +import { readOpenCodeNativeChatTranscriptTail } from './transcript-history/opencode/transcript-opencode' import { isENOENT } from '../ipc/filesystem/access/filesystem-path-containment' import type { AgentType } from '../../shared/agent-status/contracts/agent-status-types' import type { @@ -7,31 +7,29 @@ import type { } from '../../shared/native-chat/native-chat-types' import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat/native-chat-agent-support' import { resolveSessionFilePath, type ResolveSessionFileOptions } from './session-file-resolver' -import { - decodeClaudeTranscriptLine, - decodeCodexTranscriptLine, - decodeGrokTranscriptLine, - decodeOmpTranscriptLine -} from './transcript-line-decoders' -import { transcriptFallbackId } from './transcript-fallback-id' -import { inTranscriptFileOrder } from './transcript-file-order' -import { extendTranscriptBoundary } from './transcript-file-version' +import { decodeClaudeTranscriptLine } from './transcript-decoding/transcript-line-decoders-claude' +import { decodeCodexTranscriptLine } from './transcript-decoding/transcript-line-decoders-codex' +import { decodeGrokTranscriptLine } from './transcript-decoding/transcript-line-decoders-grok' +import { decodeOmpTranscriptLine } from './transcript-decoding/transcript-line-decoders-omp' +import { transcriptFallbackId } from './transcript-streaming/transcript-fallback-id' +import { inTranscriptFileOrder } from './transcript-streaming/transcript-file-order' +import { extendTranscriptBoundary } from './transcript-watching/transcript-file-version' import { nativeChatTurnLifecycleDecoderForAgent, type NativeChatTurnLifecycleDecoder -} from './transcript-turn-lifecycle' +} from './transcript-decoding/transcript-turn-lifecycle' import { findLastCompleteLineEnd, readTranscriptByteAt, TAIL_CHUNK_BYTES -} from './transcript-tail-boundary' +} from './transcript-watching/transcript-tail-boundary' import { closeTranscriptHandle, openTranscriptFile, readTranscriptFile, transcriptFileStat -} from './wsl-transcript-fs-access' -import { wslTranscriptFsRefusal } from './wsl-transcript-fs-gate' +} from './transcript-filesystem/wsl-transcript-fs-access' +import { wslTranscriptFsRefusal } from './transcript-filesystem/wsl-transcript-fs-error' export const MAX_NATIVE_CHAT_TRANSCRIPT_RECORD_BYTES = 2 * 1024 * 1024 diff --git a/src/main/native-chat/transcript-watch-engine-wsl-lifecycle.test.ts b/src/main/native-chat/transcript-watch-engine-wsl-lifecycle.test.ts deleted file mode 100644 index 75d4117cd36..00000000000 --- a/src/main/native-chat/transcript-watch-engine-wsl-lifecycle.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as NodeFsPromisesModule from 'node:fs/promises' - -const UNC_PATH = '\\\\wsl.localhost\\Ubuntu\\home\\ada\\.codex\\sessions\\rollout.jsonl' -const EMPTY_STATS = { size: 0, mtimeMs: 1, ctimeMs: 1, ino: 1, dev: 1 } - -const mocks = vi.hoisted(() => ({ - bind: vi.fn(), - dispose: vi.fn(), - filterRunning: vi.fn(), - invalidate: vi.fn(), - observe: vi.fn(), - observation: undefined as ((running: boolean) => Promise | void) | undefined, - rebindNeeded: true, - stat: vi.fn() -})) - -vi.mock('../wsl/filesystem/wsl-running-path-filter', () => ({ - filterPathsToRunningWslDistrosAsync: mocks.filterRunning -})) -vi.mock('node:fs/promises', async (importOriginal) => ({ - ...(await importOriginal()), - stat: mocks.stat -})) -vi.mock('../../shared/transcript-native-watcher', () => ({ - createTranscriptNativeWatcher: () => ({ - bind: mocks.bind, - dispose: mocks.dispose, - invalidate: mocks.invalidate, - needsRebind: () => mocks.rebindNeeded - }) -})) -vi.mock('./wsl-transcript-running-observer', () => ({ - observeWslTranscriptRunningState: mocks.observe -})) - -import { installTranscriptWatcher } from './transcript-watch-engine' - -describe('installed WSL transcript watcher lifecycle', () => { - beforeEach(() => { - vi.useFakeTimers() - mocks.rebindNeeded = true - mocks.bind.mockReset().mockImplementation(() => { - mocks.rebindNeeded = false - return true - }) - mocks.dispose.mockReset() - mocks.filterRunning.mockReset().mockResolvedValue([UNC_PATH]) - mocks.invalidate.mockReset().mockImplementation(() => { - mocks.rebindNeeded = true - }) - mocks.observation = undefined - mocks.observe.mockReset().mockImplementation((_path, onRunning, onStopped) => { - mocks.observation = (running) => (running ? onRunning() : onStopped()) - return vi.fn() - }) - mocks.stat.mockReset().mockResolvedValue(EMPTY_STATS) - }) - - afterEach(() => { - vi.useRealTimers() - }) - - it('settles the guarded initial drain when the distro stops after install', async () => { - const onInitialSnapshot = vi.fn() - const subscription = await installTranscriptWatcher(UNC_PATH, () => null, { - agent: 'codex', - sessionId: 'wsl-session', - reconciliationIntervalMs: 100, - onAppend: () => {}, - onInitialSnapshot - }) - expect(subscription).not.toBeNull() - - mocks.filterRunning.mockResolvedValue([]) - const statsAfterInstall = mocks.stat.mock.calls.length - await vi.advanceTimersByTimeAsync(50) - - expect(mocks.stat).toHaveBeenCalledTimes(statsAfterInstall) - expect(onInitialSnapshot).toHaveBeenCalledWith([], false, 0, 'Transcript unavailable') - subscription?.unsubscribe() - }) - - it('does not settle after unsubscribe wins a delayed running probe', async () => { - const onInitialSnapshot = vi.fn() - const subscription = await installTranscriptWatcher(UNC_PATH, () => null, { - agent: 'codex', - sessionId: 'wsl-session', - onAppend: () => {}, - onInitialSnapshot - }) - let finishProbe: (() => void) | undefined - mocks.filterRunning.mockImplementationOnce( - () => - new Promise((resolve) => { - finishProbe = () => resolve([]) - }) - ) - - const drain = vi.advanceTimersByTimeAsync(50) - await vi.waitFor(() => expect(finishProbe).toBeDefined()) - subscription?.unsubscribe() - finishProbe?.() - await drain - - expect(onInitialSnapshot).not.toHaveBeenCalled() - }) - - it('suspends observation while stopped and resumes after an explicit start', async () => { - const subscription = await installTranscriptWatcher(UNC_PATH, () => null, { - agent: 'codex', - sessionId: 'wsl-session', - reconciliationIntervalMs: 100, - onAppend: () => {} - }) - await vi.advanceTimersByTimeAsync(50) - const statsBeforeStop = mocks.stat.mock.calls.length - - mocks.observation?.(false) - await vi.advanceTimersByTimeAsync(100) - expect(mocks.stat).toHaveBeenCalledTimes(statsBeforeStop) - - mocks.filterRunning.mockResolvedValue([UNC_PATH]) - mocks.observation?.(true) - await vi.advanceTimersByTimeAsync(0) - - expect(mocks.stat.mock.calls.length).toBeGreaterThan(statsBeforeStop) - expect(mocks.bind).not.toHaveBeenCalled() - subscription?.unsubscribe() - }) - - it('defers a failed observed UNC probe to the next shared observation', async () => { - const subscription = await installTranscriptWatcher(UNC_PATH, () => null, { - agent: 'codex', - sessionId: 'wsl-session', - onAppend: () => {} - }) - await vi.advanceTimersByTimeAsync(50) - mocks.filterRunning.mockClear().mockResolvedValue([]) - mocks.stat.mockClear().mockRejectedValueOnce(new Error('distro stopped')) - - await mocks.observation?.(true) - - expect(mocks.stat).toHaveBeenCalledTimes(1) - expect(mocks.filterRunning).not.toHaveBeenCalled() - await mocks.observation?.(false) - expect(mocks.stat).toHaveBeenCalledTimes(1) - subscription?.unsubscribe() - }) -}) diff --git a/src/main/native-chat/transcript-watch-unflushed-settle.test.ts b/src/main/native-chat/transcript-watch-unflushed-settle.test.ts deleted file mode 100644 index abcd0c47c41..00000000000 --- a/src/main/native-chat/transcript-watch-unflushed-settle.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' - -const mocks = vi.hoisted(() => ({ - install: vi.fn(), - resolve: vi.fn() -})) - -vi.mock('./session-file-resolver', () => ({ - resolveSessionFilePath: mocks.resolve -})) -vi.mock('./transcript-watch-engine', () => ({ - getActiveNativeChatWatcherCount: vi.fn(() => 0), - installTranscriptWatcher: mocks.install -})) - -import { subscribeNativeChatTranscript } from './transcript-watch' -import { WslTranscriptFsError } from './wsl-transcript-fs-gate' - -type Snapshot = [NativeChatMessage[], boolean, number, string | undefined] - -const realPlatform = process.platform - -function subscribeCollecting( - snapshots: Snapshot[], - onTranscriptPending: () => void = () => {} -): Promise<{ unsubscribe: () => void }> { - return subscribeNativeChatTranscript({ - agent: 'claude', - sessionId: 'session-id', - transcriptPath: '/projects/p/session-id.jsonl', - resolvePollIntervalMs: 10, - onAppend: () => {}, - onTranscriptPending, - onInitialSnapshot: (messages, hasMore, beforeOffset, error) => { - snapshots.push([messages, hasMore, beforeOffset, error]) - } - }) -} - -describe('unflushed transcript settles the view', () => { - beforeEach(() => { - vi.useFakeTimers() - mocks.install.mockReset().mockResolvedValue(null) - mocks.resolve.mockReset().mockResolvedValue(null) - Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }) - }) - - afterEach(() => { - Object.defineProperty(process, 'platform', { value: realPlatform, configurable: true }) - vi.useRealTimers() - }) - - it('reports the pending transcript once while it stays unflushed', async () => { - const pending = vi.fn() - const snapshots: Snapshot[] = [] - const subscription = await subscribeCollecting(snapshots, pending) - - await vi.advanceTimersByTimeAsync(1_400) - expect(pending).not.toHaveBeenCalled() - - await vi.advanceTimersByTimeAsync(200) - expect(pending).toHaveBeenCalledTimes(1) - // Pending must not masquerade as an empty snapshot and overwrite retained history. - expect(snapshots).toEqual([]) - - await vi.advanceTimersByTimeAsync(30_000) - expect(pending).toHaveBeenCalledTimes(1) - - subscription.unsubscribe() - }) - - it('leaves the settle to the real snapshot when the file lands inside the grace window', async () => { - const unsubscribe = vi.fn() - mocks.install.mockResolvedValueOnce(null).mockResolvedValue({ unsubscribe, watching: true }) - const pending = vi.fn() - const subscription = await subscribeCollecting([], pending) - - await vi.advanceTimersByTimeAsync(5_000) - // The engine supplies the real snapshot; emitting empty here would flash away history. - expect(pending).not.toHaveBeenCalled() - - subscription.unsubscribe() - }) - - it('does not report a subscription that was torn down first', async () => { - const pending = vi.fn() - const subscription = await subscribeCollecting([], pending) - - await vi.advanceTimersByTimeAsync(100) - subscription.unsubscribe() - await vi.advanceTimersByTimeAsync(5_000) - - expect(pending).not.toHaveBeenCalled() - }) - - it('keeps the WSL gate message instead of overwriting it with the empty window', async () => { - const stalled = new WslTranscriptFsError('timeout', 'WSL transcript files are unavailable.') - mocks.install.mockRejectedValue(stalled) - const pending = vi.fn() - const snapshots: Snapshot[] = [] - const subscription = await subscribeCollecting(snapshots, pending) - - await vi.advanceTimersByTimeAsync(5_000) - - expect(snapshots).toEqual([[[], false, 0, stalled.message]]) - expect(pending).not.toHaveBeenCalled() - - subscription.unsubscribe() - }) -}) diff --git a/src/main/native-chat/transcript-watch.ts b/src/main/native-chat/transcript-watch.ts index 2a20c19dd7b..1128251481b 100644 --- a/src/main/native-chat/transcript-watch.ts +++ b/src/main/native-chat/transcript-watch.ts @@ -1,5 +1,5 @@ -import { UNFLUSHED_SETTLE_MS } from './transcript-watch-contract' -import { subscribeOpenCodeNativeChatTranscript } from './transcript-opencode-subscribe' +import { UNFLUSHED_SETTLE_MS } from './transcript-watching/transcript-watch-contract' +import { subscribeOpenCodeNativeChatTranscript } from './transcript-history/opencode/transcript-opencode-subscribe' import { resolveNativeChatTranscriptAgent } from '../../shared/native-chat/native-chat-agent-support' import { extname } from 'node:path' import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' @@ -7,23 +7,19 @@ import { needsWslHostResolution, toHostReadableTranscriptPath, type WslTranscriptResolutionSnapshot -} from './host-readable-transcript-path' +} from './transcript-discovery/host-readable-transcript-path' import { resolveSessionFilePath } from './session-file-resolver' -import { installTranscriptWatcher } from './transcript-watch-engine' +import { installTranscriptWatcher } from './transcript-watching/transcript-watch-engine' import type { NativeChatTranscriptSubscription, SubscribeNativeChatTranscriptArgs -} from './transcript-watch-contract' +} from './transcript-watching/transcript-watch-contract' import { nativeChatLineDecoderForAgent } from './transcript-tail-reader' -import { WslTranscriptFsError, wslTranscriptFsRefusal } from './wsl-transcript-fs-gate' -import { observeRunningWslDistros } from './wsl-transcript-running-observer' - -export { readNativeChatTranscriptTail } from './transcript-tail-reader' -export { getActiveNativeChatWatcherCount } from './transcript-watcher-count' -export type { - NativeChatTranscriptSubscription, - SubscribeNativeChatTranscriptArgs -} from './transcript-watch-contract' +import { + WslTranscriptFsError, + wslTranscriptFsRefusal +} from './transcript-filesystem/wsl-transcript-fs-error' +import { observeRunningWslDistros } from './transcript-filesystem/wsl-transcript-running-observer' /** Unresolved files retry; native watch failure still permits reconciliation-only mode. */ async function attemptInstall( diff --git a/src/main/native-chat/transcript-file-version.test.ts b/src/main/native-chat/transcript-watching/transcript-file-version.test.ts similarity index 100% rename from src/main/native-chat/transcript-file-version.test.ts rename to src/main/native-chat/transcript-watching/transcript-file-version.test.ts diff --git a/src/main/native-chat/transcript-file-version.ts b/src/main/native-chat/transcript-watching/transcript-file-version.ts similarity index 95% rename from src/main/native-chat/transcript-file-version.ts rename to src/main/native-chat/transcript-watching/transcript-file-version.ts index 67ca14451ab..550a2e76129 100644 --- a/src/main/native-chat/transcript-file-version.ts +++ b/src/main/native-chat/transcript-watching/transcript-file-version.ts @@ -1,4 +1,7 @@ -import { readTranscriptSlice, transcriptFileStat } from './wsl-transcript-fs-access' +import { + readTranscriptSlice, + transcriptFileStat +} from '../transcript-filesystem/wsl-transcript-fs-access' const BOUNDARY_FINGERPRINT_BYTES = 64 diff --git a/src/main/native-chat/transcript-incremental-reader.ts b/src/main/native-chat/transcript-watching/transcript-incremental-reader.ts similarity index 91% rename from src/main/native-chat/transcript-incremental-reader.ts rename to src/main/native-chat/transcript-watching/transcript-incremental-reader.ts index 73946a27169..a294da0b79b 100644 --- a/src/main/native-chat/transcript-incremental-reader.ts +++ b/src/main/native-chat/transcript-watching/transcript-incremental-reader.ts @@ -1,15 +1,18 @@ import type { NativeChatMessage, NativeChatTurnLifecycle -} from '../../shared/native-chat/native-chat-types' -import { transcriptFallbackId } from './transcript-fallback-id' +} from '../../../shared/native-chat/native-chat-types' +import { transcriptFallbackId } from '../transcript-streaming/transcript-fallback-id' import { extendTranscriptBoundary } from './transcript-file-version' -import { inTranscriptFileOrder } from './transcript-file-order' +import { inTranscriptFileOrder } from '../transcript-streaming/transcript-file-order' import { MAX_NATIVE_CHAT_TRANSCRIPT_RECORD_BYTES, type NativeChatLineDecoder -} from './transcript-tail-reader' -import { openTranscriptReadStream, transcriptFileStat } from './wsl-transcript-fs-access' +} from '../transcript-tail-reader' +import { + openTranscriptReadStream, + transcriptFileStat +} from '../transcript-filesystem/wsl-transcript-fs-access' const APPEND_BATCH_MESSAGE_LIMIT = 40 diff --git a/src/main/native-chat/transcript-tail-boundary.ts b/src/main/native-chat/transcript-watching/transcript-tail-boundary.ts similarity index 89% rename from src/main/native-chat/transcript-tail-boundary.ts rename to src/main/native-chat/transcript-watching/transcript-tail-boundary.ts index a626f1ae5d8..4a538ddf6dc 100644 --- a/src/main/native-chat/transcript-tail-boundary.ts +++ b/src/main/native-chat/transcript-watching/transcript-tail-boundary.ts @@ -1,5 +1,5 @@ -import { readTranscriptFile } from './wsl-transcript-fs-access' -import type { TranscriptFileHandle } from './wsl-transcript-fs-access' +import { readTranscriptFile } from '../transcript-filesystem/wsl-transcript-fs-access' +import type { TranscriptFileHandle } from '../transcript-filesystem/wsl-transcript-fs-access' export const TAIL_CHUNK_BYTES = 64 * 1024 diff --git a/src/main/native-chat/transcript-unavailable-snapshot.ts b/src/main/native-chat/transcript-watching/transcript-unavailable-snapshot.ts similarity index 100% rename from src/main/native-chat/transcript-unavailable-snapshot.ts rename to src/main/native-chat/transcript-watching/transcript-unavailable-snapshot.ts diff --git a/src/main/native-chat/transcript-watch-contract.ts b/src/main/native-chat/transcript-watching/transcript-watch-contract.ts similarity index 83% rename from src/main/native-chat/transcript-watch-contract.ts rename to src/main/native-chat/transcript-watching/transcript-watch-contract.ts index 565bd631948..1da7c2f3983 100644 --- a/src/main/native-chat/transcript-watch-contract.ts +++ b/src/main/native-chat/transcript-watching/transcript-watch-contract.ts @@ -1,12 +1,12 @@ export const DESKTOP_READ_WINDOW = 300 export const UNFLUSHED_SETTLE_MS = 1_500 -import type { AgentType } from '../../shared/agent-status/contracts/agent-status-types' +import type { AgentType } from '../../../shared/agent-status/contracts/agent-status-types' import type { NativeChatMessage, NativeChatTurnLifecycle -} from '../../shared/native-chat/native-chat-types' -import type { ResolveSessionFileOptions } from './session-file-resolver' +} from '../../../shared/native-chat/native-chat-types' +import type { ResolveSessionFileOptions } from '../session-file-resolver' export type SubscribeNativeChatTranscriptArgs = ResolveSessionFileOptions & { agent: AgentType diff --git a/src/main/native-chat/transcript-watch-engine.ts b/src/main/native-chat/transcript-watching/transcript-watch-engine.ts similarity index 94% rename from src/main/native-chat/transcript-watch-engine.ts rename to src/main/native-chat/transcript-watching/transcript-watch-engine.ts index 0ec7153a59e..4e534b1847c 100644 --- a/src/main/native-chat/transcript-watch-engine.ts +++ b/src/main/native-chat/transcript-watching/transcript-watch-engine.ts @@ -1,7 +1,7 @@ import type { NativeChatMessage, NativeChatTurnLifecycle -} from '../../shared/native-chat/native-chat-types' +} from '../../../shared/native-chat/native-chat-types' import { boundaryFingerprint, readTranscriptFileVersion, @@ -14,22 +14,22 @@ import { readIncrementalTranscriptMessages, resetIncrementalTranscriptState } from './transcript-incremental-reader' -import { readNativeChatTranscriptTailFile } from './transcript-tail-reader' +import { readNativeChatTranscriptTailFile } from '../transcript-tail-reader' import { emitTranscriptUnavailableSnapshot } from './transcript-unavailable-snapshot' import { transcriptWatcherPathIsInstallable } from './transcript-watcher-install-probe' -import { nativeChatTurnLifecycleDecoderForAgent } from './transcript-turn-lifecycle' +import { nativeChatTurnLifecycleDecoderForAgent } from '../transcript-decoding/transcript-turn-lifecycle' import type { NativeChatTranscriptSubscription, SubscribeNativeChatTranscriptArgs } from './transcript-watch-contract' import { createTranscriptWatchScheduler } from './transcript-watch-scheduler' -import { WslTranscriptFsError } from './wsl-transcript-fs-gate' +import { WslTranscriptFsError } from '../transcript-filesystem/wsl-transcript-fs-error' import { createRunningGuardedTranscriptNativeWatcher, isWslTranscriptWatcherPath, transcriptWatcherPathIsRunning -} from './wsl-transcript-watcher-running-guard' -import { observeWslTranscriptRunningState } from './wsl-transcript-running-observer' +} from '../transcript-filesystem/wsl-transcript-watcher-running-guard' +import { observeWslTranscriptRunningState } from '../transcript-filesystem/wsl-transcript-running-observer' import { trackActiveNativeChatWatcher } from './transcript-watcher-count' export async function installTranscriptWatcher( diff --git a/src/main/native-chat/transcript-watch-scheduler.ts b/src/main/native-chat/transcript-watching/transcript-watch-scheduler.ts similarity index 100% rename from src/main/native-chat/transcript-watch-scheduler.ts rename to src/main/native-chat/transcript-watching/transcript-watch-scheduler.ts diff --git a/src/main/native-chat/transcript-watcher-count.ts b/src/main/native-chat/transcript-watching/transcript-watcher-count.ts similarity index 100% rename from src/main/native-chat/transcript-watcher-count.ts rename to src/main/native-chat/transcript-watching/transcript-watcher-count.ts diff --git a/src/main/native-chat/transcript-watcher-install-probe.ts b/src/main/native-chat/transcript-watching/transcript-watcher-install-probe.ts similarity index 58% rename from src/main/native-chat/transcript-watcher-install-probe.ts rename to src/main/native-chat/transcript-watching/transcript-watcher-install-probe.ts index 8fe3488829d..1561bd30745 100644 --- a/src/main/native-chat/transcript-watcher-install-probe.ts +++ b/src/main/native-chat/transcript-watching/transcript-watcher-install-probe.ts @@ -1,6 +1,6 @@ -import { transcriptFileStat } from './wsl-transcript-fs-access' -import { WslTranscriptFsError } from './wsl-transcript-fs-gate' -import { transcriptWatcherPathIsRunning } from './wsl-transcript-watcher-running-guard' +import { transcriptFileStat } from '../transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../transcript-filesystem/wsl-transcript-fs-error' +import { transcriptWatcherPathIsRunning } from '../transcript-filesystem/wsl-transcript-watcher-running-guard' export async function transcriptWatcherPathIsInstallable( filePath: string, diff --git a/src/main/native-chat/transcript-window-tool-attribution.test.ts b/src/main/native-chat/transcript-window-tool-attribution.test.ts deleted file mode 100644 index bcef9e47702..00000000000 --- a/src/main/native-chat/transcript-window-tool-attribution.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { mkdtemp, rm, writeFile } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, describe, expect, it } from 'vitest' -import { stripNoiseMessages } from '../../shared/native-chat/native-chat-noise' -import { mergeNativeChatMessages } from '../../shared/native-chat/native-chat-merge' -import { foldToolMessages } from '../../shared/native-chat/tools/native-chat-tool-fold' -import { pairToolBlocks } from '../../shared/native-chat/tools/native-chat-tool-pairs' -import { isToolCallBlock, isToolResultBlock } from '../../shared/native-chat/native-chat-types' -import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types' -import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude' -import { readNativeChatTranscriptTailFile } from './transcript-tail-reader' - -let tempRoots: string[] = [] - -afterEach(async () => { - await Promise.all(tempRoots.map((root) => rm(root, { recursive: true, force: true }))) - tempRoots = [] -}) - -async function writeTranscript(records: unknown[]): Promise { - const root = await mkdtemp(join(tmpdir(), 'orca-native-chat-attribution-')) - tempRoots.push(root) - const filePath = join(root, 'transcript.jsonl') - await writeFile(filePath, records.map((record) => JSON.stringify(record)).join('\n')) - return filePath -} - -function toolTurn(n: number): unknown[] { - return [ - { - type: 'assistant', - uuid: `call-${n}`, - timestamp: `2026-08-20T10:00:${String(n).padStart(2, '0')}.000Z`, - message: { - role: 'assistant', - content: [ - { type: 'tool_use', id: `toolu_${n}`, name: 'Bash', input: { command: `echo ${n}` } } - ] - } - }, - { - type: 'user', - uuid: `result-${n}`, - timestamp: `2026-08-20T10:00:${String(n).padStart(2, '0')}.500Z`, - message: { - role: 'user', - content: [{ type: 'tool_result', content: `output ${n}`, tool_use_id: `toolu_${n}` }] - } - } - ] -} - -function unattributedResults(messages: readonly NativeChatMessage[]): string[] { - const stray: string[] = [] - for (const message of messages) { - const tools = message.blocks.filter( - (block) => isToolCallBlock(block) || isToolResultBlock(block) - ) - if (tools.length === 0) { - continue - } - for (const pair of pairToolBlocks(tools)) { - if (pair.call === undefined && pair.result) { - stray.push(pair.result.output) - } - } - } - return stray -} - -async function renderedMessages(filePath: string, limit: number): Promise { - const page = await readNativeChatTranscriptTailFile( - filePath, - limit, - decodeClaudeTranscriptLine, - true - ) - return stripNoiseMessages(foldToolMessages(mergeNativeChatMessages([], page.messages))) -} - -describe('windowed transcript tool attribution', () => { - it('shows a tool result whose call falls outside the read window without guessing its owner', async () => { - const filePath = await writeTranscript([...toolTurn(1), ...toolTurn(2), ...toolTurn(3)]) - - const windowed = await renderedMessages(filePath, 3) - - expect(unattributedResults(windowed)).toEqual(['output 2']) - expect(windowed[0]).toMatchObject({ id: 'result-2', role: 'tool' }) - expect( - windowed.flatMap((message) => - message.blocks.filter(isToolResultBlock).map((block) => block.output) - ) - ).toEqual(['output 2', 'output 3']) - }) - - it('drops the answered result records Claude re-emits at a /compact boundary', async () => { - const [, answeredResult] = toolTurn(1) - const filePath = await writeTranscript([ - ...toolTurn(1), - { - type: 'user', - uuid: 'compact-prompt', - timestamp: '2026-08-20T10:05:00.000Z', - message: { role: 'user', content: '/compact' } - }, - answeredResult - ]) - - const whole = await renderedMessages(filePath, 100) - - expect(unattributedResults(whole)).toEqual([]) - expect( - whole.flatMap((message) => - message.blocks.filter(isToolResultBlock).map((block) => block.output) - ) - ).toEqual(['output 1']) - }) - - it('keeps a hidden interruption from authorizing a later result', async () => { - const [abandonedCall] = toolTurn(1) - const [, laterResult] = toolTurn(2) - const filePath = await writeTranscript([ - abandonedCall, - { - type: 'user', - uuid: 'interrupt', - timestamp: '2026-08-20T10:01:00.000Z', - message: { role: 'user', content: '[Request interrupted by user]' } - }, - laterResult - ]) - - const messages = await renderedMessages(filePath, 100) - - expect(unattributedResults(messages)).toEqual(['output 2']) - expect(messages.map((message) => message.id)).toEqual(['call-1', 'result-2']) - expect(pairToolBlocks(messages[0]!.blocks)[0]?.result).toBeUndefined() - expect(messages[1]?.role).toBe('tool') - }) - - it('uses provider paging cursors unchanged while an older page restores the exact call', async () => { - const filePath = await writeTranscript([...toolTurn(1), ...toolTurn(2), ...toolTurn(3)]) - const page = await readNativeChatTranscriptTailFile( - filePath, - 3, - decodeClaudeTranscriptLine, - true - ) - const cursor = page.beforeOffset - const consumed = page.consumedTo - const windowed = foldToolMessages(page.messages) - const older = await readNativeChatTranscriptTailFile( - filePath, - 100, - decodeClaudeTranscriptLine, - true, - cursor - ) - expect(page.beforeOffset).toBe(cursor) - expect(page.consumedTo).toBe(consumed) - expect(page.hasMore).toBe(true) - expect(windowed[0]).toMatchObject({ id: 'result-2', role: 'tool' }) - expect(older.messages.at(-1)?.id).toBe('call-2') - expect(older.consumedTo).toBe(cursor) - const whole = foldToolMessages(mergeNativeChatMessages(older.messages, page.messages)) - expect(unattributedResults(whole)).toEqual([]) - expect( - whole - .flatMap((message) => pairToolBlocks(message.blocks)) - .map((pair) => [pair.call?.callId, pair.result?.output]) - ).toEqual([ - ['toolu_1', 'output 1'], - ['toolu_2', 'output 2'], - ['toolu_3', 'output 3'] - ]) - }) -}) diff --git a/src/main/native-chat/wsl-transcript-running-observer.test.ts b/src/main/native-chat/wsl-transcript-running-observer.test.ts deleted file mode 100644 index a635a2d0e4f..00000000000 --- a/src/main/native-chat/wsl-transcript-running-observer.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => ({ listRunning: vi.fn() })) - -vi.mock('../wsl/discovery/distro-catalog', () => ({ - listRunningWslDistrosAsync: mocks.listRunning -})) - -import { - observeWslTranscriptRunningState, - resetWslTranscriptRunningObserverForTests, - WSL_TRANSCRIPT_FIRST_OBSERVATION_MS as FIRST, - WSL_TRANSCRIPT_RUNNING_OBSERVATION_MS as INTERVAL -} from './wsl-transcript-running-observer' - -describe('WSL transcript running observer', () => { - beforeEach(() => { - vi.useFakeTimers() - mocks.listRunning.mockReset().mockResolvedValue(['Ubuntu']) - }) - - afterEach(() => { - resetWslTranscriptRunningObserverForTests() - vi.useRealTimers() - }) - - it('shares one running-distro probe across staggered watchers', async () => { - const ubuntu = vi.fn() - const debian = vi.fn() - const stopUbuntu = observeWslTranscriptRunningState( - '\\\\wsl.localhost\\Ubuntu\\home\\ada\\a.jsonl', - () => ubuntu(true), - () => ubuntu(false) - ) - await vi.advanceTimersByTimeAsync(FIRST / 2) - const stopDebian = observeWslTranscriptRunningState( - '\\\\wsl.localhost\\Debian\\home\\ada\\b.jsonl', - () => debian(true), - () => debian(false) - ) - await vi.advanceTimersByTimeAsync(FIRST / 2) - - expect(mocks.listRunning).toHaveBeenCalledTimes(1) - expect(ubuntu).toHaveBeenCalledWith(true) - expect(debian).toHaveBeenCalledWith(false) - - // After the first look, one shared read per cadence. - await vi.advanceTimersByTimeAsync(INTERVAL - 1) - expect(mocks.listRunning).toHaveBeenCalledTimes(1) - await vi.advanceTimersByTimeAsync(1) - expect(mocks.listRunning).toHaveBeenCalledTimes(2) - stopUbuntu() - stopDebian() - }) - - it('coalesces slow subscription callbacks and retains the latest observation', async () => { - let finishFirst: (() => void) | undefined - const callback = vi - .fn() - .mockImplementationOnce( - () => - new Promise((resolve) => { - finishFirst = resolve - }) - ) - .mockResolvedValue(undefined) - const stop = observeWslTranscriptRunningState( - '\\\\wsl.localhost\\Ubuntu\\home\\ada\\a.jsonl', - callback, - callback - ) - - await vi.advanceTimersByTimeAsync(FIRST) - expect(callback).toHaveBeenCalledTimes(1) - - mocks.listRunning.mockResolvedValue([]) - await vi.advanceTimersByTimeAsync(INTERVAL * 3) - expect(mocks.listRunning).toHaveBeenCalledTimes(4) - expect(callback).toHaveBeenCalledTimes(1) - - finishFirst?.() - await vi.waitFor(() => expect(callback).toHaveBeenCalledTimes(2)) - stop() - }) -}) diff --git a/src/main/omp/omp-session-cwd-lookup.ts b/src/main/omp/omp-session-cwd-lookup.ts index 3d18bdc59ff..4cc68fe73ad 100644 --- a/src/main/omp/omp-session-cwd-lookup.ts +++ b/src/main/omp/omp-session-cwd-lookup.ts @@ -2,7 +2,7 @@ import { createHash } from 'node:crypto' import { lstat, opendir, realpath } from 'node:fs/promises' import { basename, extname, isAbsolute, join, relative, resolve } from 'node:path' import { PI_CWD_BUCKET_LAYOUT } from '../ai-vault/scope/session-cwd-bucket-layouts' -import { SESSION_FILE_ID_LAYOUTS } from '../native-chat/session-file-id-layouts' +import { SESSION_FILE_ID_LAYOUTS } from '../native-chat/transcript-discovery/session-file-id-layouts' async function canonicalPath(path: string): Promise { return realpath(path).catch(() => resolve(path)) diff --git a/src/main/omp/omp-session-history.ts b/src/main/omp/omp-session-history.ts index d4dbeb251e7..9d02397a731 100644 --- a/src/main/omp/omp-session-history.ts +++ b/src/main/omp/omp-session-history.ts @@ -2,7 +2,7 @@ import { resumeInvocationFromOptions, sessionFileResumeTargetMatches, type StructuredAgentSessionHistory -} from '../native-chat/structured-agent-cli-conversations' +} from '../native-chat/conversation-adoption/structured-agent-cli-conversations' export const OMP_SESSION_HISTORY: StructuredAgentSessionHistory = { rowAgents: ['omp'], diff --git a/src/main/omp/omp-transcript-import.ts b/src/main/omp/omp-transcript-import.ts index e462093ca76..8611a9a34ac 100644 --- a/src/main/omp/omp-transcript-import.ts +++ b/src/main/omp/omp-transcript-import.ts @@ -1,6 +1,6 @@ import { homedir } from 'node:os' import { resolveOmpSessionsDir } from '../ai-vault/scanners/omp/omp-session-root' -import { directoryTranscriptImport } from '../native-chat/structured-agent-cli-conversations' +import { directoryTranscriptImport } from '../native-chat/conversation-adoption/structured-agent-cli-conversations' import { findOmpSessionByCwd } from './omp-session-cwd-lookup' function sessionsRoot(accountHomePath: string, launchEnv: NodeJS.ProcessEnv) { diff --git a/src/main/opencode-usage/opencode-database-discovery.ts b/src/main/opencode-usage/opencode-database-discovery.ts index 7c46cf0bc49..4c6c714453f 100644 --- a/src/main/opencode-usage/opencode-database-discovery.ts +++ b/src/main/opencode-usage/opencode-database-discovery.ts @@ -1,7 +1,10 @@ import { stat } from 'node:fs/promises' import { basename, join } from 'node:path' -import { wslGatedReaddir, wslGatedStat } from '../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../native-chat/wsl-transcript-fs-gate' +import { + wslGatedReaddir, + wslGatedStat +} from '../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { resolveOpenCodeDataDirectory, resolveOpenCodeDatabasePath diff --git a/src/main/opencode/transcripts/opencode-session-history.ts b/src/main/opencode/transcripts/opencode-session-history.ts index a46a6b4b25a..4f36be36254 100644 --- a/src/main/opencode/transcripts/opencode-session-history.ts +++ b/src/main/opencode/transcripts/opencode-session-history.ts @@ -2,7 +2,7 @@ import { ownOptionsInclude, resumeInvocationFromOptions, type StructuredAgentSessionHistory -} from '../../native-chat/structured-agent-cli-conversations' +} from '../../native-chat/conversation-adoption/structured-agent-cli-conversations' export const OPENCODE_SESSION_HISTORY: StructuredAgentSessionHistory = { rowAgents: ['opencode', 'opencode2'], diff --git a/src/main/opencode/transcripts/opencode-transcript-import.ts b/src/main/opencode/transcripts/opencode-transcript-import.ts index d98d0bf6745..328e3df94ec 100644 --- a/src/main/opencode/transcripts/opencode-transcript-import.ts +++ b/src/main/opencode/transcripts/opencode-transcript-import.ts @@ -1,5 +1,5 @@ import { stat } from 'node:fs/promises' -import { admitTranscriptPath } from '../../native-chat/transcript-path-admission' +import { admitTranscriptPath } from '../../native-chat/transcript-discovery/transcript-path-admission' import { dirname } from 'node:path' import { homedir } from 'node:os' import { isLegacyAgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' @@ -7,7 +7,7 @@ import { getManagedDataAccountService } from '../../managed-data-accounts/servic import { absoluteConversationStore, type StructuredAgentTranscriptImport -} from '../../native-chat/structured-agent-cli-conversations' +} from '../../native-chat/conversation-adoption/structured-agent-cli-conversations' import { resolveOpenCodeDatabasePath } from '../accounts/opencode-data-directory' import { environmentForStructuredOpenCodeAccountHome } from '../accounts/opencode-structured-account-home' diff --git a/src/main/pi/rpc/launch/rpc-launch-resolution.ts b/src/main/pi/rpc/launch/rpc-launch-resolution.ts index f9337e805ef..a6b2b1ce698 100644 --- a/src/main/pi/rpc/launch/rpc-launch-resolution.ts +++ b/src/main/pi/rpc/launch/rpc-launch-resolution.ts @@ -16,7 +16,7 @@ import { type AgentCliVersionCheck, type AgentCliVersionReader } from '../../../agent-launch/preflight/agent-cli-version-probe' -import { AgentCliInstallationError } from '../../../native-chat/agent-cli-installation-error' +import { AgentCliInstallationError } from '../../../native-chat/agent-launch/agent-cli-installation-error' import { resolveAgainAfterRefreshOnMissingProgram, assertStructuredAgentResolutionCurrent, @@ -24,7 +24,7 @@ import { resolveStructuredAgentCommand, type StructuredAgentCommand, type StructuredAgentCommandSettings -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' import type { StructuredAgentLaunchPartResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition' import { isWindowsProcessStartTimeAvailable } from '../../../windows/windows-process-table' import { sessionIdFromFileName } from '../../../ai-vault/parsing/session-scanner-accumulator' diff --git a/src/main/pi/rpc/launch/rpc-launch.ts b/src/main/pi/rpc/launch/rpc-launch.ts index ea8addf2c72..2646ad37416 100644 --- a/src/main/pi/rpc/launch/rpc-launch.ts +++ b/src/main/pi/rpc/launch/rpc-launch.ts @@ -4,7 +4,7 @@ import { STRUCTURED_CHILD_ENV_TO_DELETE } from '../../../runtime/agent-session/e import { LEGACY_NATIVE_CHAT_VISUALS_DIR_ENV, NATIVE_CHAT_SCRATCH_DIR_ENV -} from '../../../native-chat/native-chat-visuals-delivery' +} from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import type { ProviderProcessLaunch } from '../../../provider-process/provider-process-launch' import type { PiBackgroundWorkLaunch } from '../background-work/rpc-background-work-bridge' diff --git a/src/main/pi/rpc/options/rpc-model-catalog-probe.ts b/src/main/pi/rpc/options/rpc-model-catalog-probe.ts index 4377321c924..4c693c62485 100644 --- a/src/main/pi/rpc/options/rpc-model-catalog-probe.ts +++ b/src/main/pi/rpc/options/rpc-model-catalog-probe.ts @@ -1,7 +1,7 @@ import { assertStructuredAgentResolutionCurrent, type StructuredAgentCommandSettings -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { homedir } from 'node:os' import { z } from 'zod' import { requireLegacyAgentSessionAccountHome } from '../../../../shared/agent-session/lifecycle/agent-session-account-home' diff --git a/src/main/pi/rpc/rpc-session-rename.test.ts b/src/main/pi/rpc/rpc-session-rename.test.ts index f7eceff56ee..e722901dded 100644 --- a/src/main/pi/rpc/rpc-session-rename.test.ts +++ b/src/main/pi/rpc/rpc-session-rename.test.ts @@ -1,7 +1,7 @@ import { PassThrough } from 'node:stream' import { afterEach, expect, it, vi } from 'vitest' import { JsonlRpcPeer } from '../../jsonl-rpc/peer' -import { agentSessionRecordFixture } from '../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../native-chat/testing/agent-session-record-test-fixture' import { closePiRpcAdapterFixtures, file, diff --git a/src/main/pi/transcripts/pi-session-history.ts b/src/main/pi/transcripts/pi-session-history.ts index ab4e8fc8acb..c333d9c18e7 100644 --- a/src/main/pi/transcripts/pi-session-history.ts +++ b/src/main/pi/transcripts/pi-session-history.ts @@ -3,7 +3,7 @@ import { resumeInvocationFromOptions, sessionFileResumeTargetMatches, type StructuredAgentSessionHistory -} from '../../native-chat/structured-agent-cli-conversations' +} from '../../native-chat/conversation-adoption/structured-agent-cli-conversations' export const PI_SESSION_HISTORY: StructuredAgentSessionHistory = { rowAgents: ['pi'], diff --git a/src/main/pi/transcripts/pi-transcript-import.ts b/src/main/pi/transcripts/pi-transcript-import.ts index d289094be9e..259b4e69f7b 100644 --- a/src/main/pi/transcripts/pi-transcript-import.ts +++ b/src/main/pi/transcripts/pi-transcript-import.ts @@ -3,7 +3,7 @@ import { join } from 'node:path' import { sessionIdFromFileName } from '../../ai-vault/parsing/session-scanner-accumulator' import { walkSessionFiles } from '../../ai-vault/discovery/session-scanner-discovery' import { piSessionHistorySessionsDir } from '../../ai-vault/discovery/session-scanner-agent-sources' -import { directoryTranscriptImport } from '../../native-chat/structured-agent-cli-conversations' +import { directoryTranscriptImport } from '../../native-chat/conversation-adoption/structured-agent-cli-conversations' import { readPiSessionHeader } from '../rpc/launch/rpc-launch-resolution' export const PI_TRANSCRIPT_IMPORT = directoryTranscriptImport({ diff --git a/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts b/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts index c59f1d36e0c..281f1b67ec1 100644 --- a/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts +++ b/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts @@ -3,8 +3,8 @@ import type { StructuredAgentModelCatalogDeps } from './structured-agent-runtime import { resolveStructuredCodexAccountKind } from '../environment/structured-agent-account-home' import { nativeChatShellEnvironmentPolicy } from '../../../../shared/native-chat/native-chat-shell-environment' import { claudeStructuredAuthPolicyForSettings } from '../../../claude-accounts/runtime-auth/claude-structured-auth-policy' -import { resolveStructuredAgentCommand } from '../../../native-chat/structured-agent-command-resolution' -import { structuredAgentConfiguredArgs } from '../../../native-chat/structured-agent-configured-args' +import { resolveStructuredAgentCommand } from '../../../native-chat/agent-launch/structured-agent-command-resolution' +import { structuredAgentConfiguredArgs } from '../../../native-chat/agent-launch/structured-agent-configured-args' import { getProfileUserDataPath } from '../../../orca-profiles/profile-storage-paths' import { isTuiAgent } from '../../../../shared/agent-launch/catalog/tui-agent-config' import { resolveTuiAgentLaunchEnv } from '../../../../shared/agent-launch/choices/tui-agent-launch-defaults' diff --git a/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts b/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts index a9c1b2a4d2d..a33e65abb18 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts @@ -25,7 +25,7 @@ import { claudeModelCatalogDiscovery, codexModelCatalogDiscovery } from '../model-catalog/structured-agent-model-catalog-discovery' -import { resolveStructuredAgentCommand } from '../../../native-chat/structured-agent-command-resolution' +import { resolveStructuredAgentCommand } from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { getProfileUserDataPath } from '../../../orca-profiles/profile-storage-paths' import type { StructuredAgentAccountHomeRequest, diff --git a/src/main/runtime/agent-session/installation/structured-agent-runtime-registration-types.ts b/src/main/runtime/agent-session/installation/structured-agent-runtime-registration-types.ts index 13e82fd79dd..fd05fb5ef12 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-runtime-registration-types.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-runtime-registration-types.ts @@ -1,12 +1,12 @@ import type { AgentSessionExecutionLocation } from '../../../../shared/agent-session/lifecycle/agent-session-record' import type { AgentSessionAccountHome } from '../../../../shared/agent-session/lifecycle/agent-session-account-home' import type { applyStructuredCodexWorkspaceTrust } from '../../../agent-launch/trust/agent-workspace-trust-spawn' -import type { StructuredAgentCliConversations } from '../../../native-chat/structured-agent-cli-conversations' +import type { StructuredAgentCliConversations } from '../../../native-chat/conversation-adoption/structured-agent-cli-conversations' import type { StructuredAgentSessionAdapter, StructuredAgentSessionLifecycleEvent } from '../../../native-chat/agent-session-wire/providers/structured-agent-session-adapter' -import type { StructuredAgentCommandSettings } from '../../../native-chat/structured-agent-command-resolution' +import type { StructuredAgentCommandSettings } from '../../../native-chat/agent-launch/structured-agent-command-resolution' import type { AcpHostBackendSettings } from '../../../acp/launch/acp-launch-contract' import type { StructuredAgentDefinition } from '../../../native-chat/agent-session-wire/providers/structured-agent-definition' import type { StructuredAgentSessionHost } from '../../../native-chat/agent-session-wire/host/structured-agent-session-host' diff --git a/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts b/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts index d24a8884f9d..c7273f87f2d 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts @@ -1,5 +1,5 @@ import { AcpAtRestCommandCatalog } from '../../../acp/discovery/acp-at-rest-commands' -import { resolveNativeChatVisualsSkillLocation } from '../../../native-chat/native-chat-visuals-skill-location' +import { resolveNativeChatVisualsSkillLocation } from '../../../native-chat/conversation-files/native-chat-visuals-skill-location' import { withSupportedCodexCli } from '../../../codex/structured/launch/codex-cli-installation-error' import { codexStructuredLaunchPart } from '../../../codex/structured/launch/codex-structured-launch-resolution' import { claudeStructuredLaunchPart } from '../../../claude/structured/launch/claude-structured-launch-resolution' diff --git a/src/main/runtime/agent-session/installation/structured-agent-session-host-assembly.ts b/src/main/runtime/agent-session/installation/structured-agent-session-host-assembly.ts index 20dd03b9982..5ca49b58601 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-session-host-assembly.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-session-host-assembly.ts @@ -9,7 +9,7 @@ import { withProviderNaming } from '../../../native-chat/agent-session-wire/providers/structured-agent-session-provider-naming' import { setStructuredAgentSessionHost } from '../../../native-chat/agent-session-wire/host/structured-agent-session-registry' -import { scheduleNativeChatVisualsSweep } from '../../../native-chat/native-chat-visuals-sweep' +import { scheduleNativeChatVisualsSweep } from '../../../native-chat/conversation-files/native-chat-visuals-sweep' import type { InstalledRuntime } from '../structured-agent-session-runtime-teardown' import { AgentSessionRecoveryCapsule } from '../records/agent-session-recovery-capsule' import type { OpenedAgentSessionRecordStore } from '../records/agent-session-record-store-slot' diff --git a/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts b/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts index 2d639b16994..cfbc7432ef0 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts @@ -7,7 +7,7 @@ import type { StructuredAttentionMobileDelivery } from '../structured-agent-sess import type { StructuredAgentCommand, StructuredAgentCommandSettings -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' import type { NativeChatShellEnvironmentPolicy } from '../../../../shared/native-chat/native-chat-shell-environment' import type { ClaudeStructuredAuthPolicy } from '../../../claude-accounts/runtime-auth/claude-structured-auth-policy' import type { RuntimeAgentAccountHomeResolver } from '../model-catalog/structured-agent-model-catalog-wiring' diff --git a/src/main/runtime/agent-session/launch/structured-agent-launch-composition.test-support.ts b/src/main/runtime/agent-session/launch/structured-agent-launch-composition.test-support.ts index cf0b29098f4..4ff9b081463 100644 --- a/src/main/runtime/agent-session/launch/structured-agent-launch-composition.test-support.ts +++ b/src/main/runtime/agent-session/launch/structured-agent-launch-composition.test-support.ts @@ -21,8 +21,8 @@ import { codexStructuredLaunchPart, type CodexStructuredLaunchPartDeps } from '../../../codex/structured/launch/codex-structured-launch-resolution' -import type { PrepareNativeChatVisuals } from '../../../native-chat/native-chat-visuals-delivery' -import type { StructuredAgentCommandSettings } from '../../../native-chat/structured-agent-command-resolution' +import type { PrepareNativeChatVisuals } from '../../../native-chat/conversation-files/native-chat-visuals-delivery' +import type { StructuredAgentCommandSettings } from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { PI_RPC_AGENT } from '../../../pi/rpc-agent-definition' import { piRpcLaunchPart, diff --git a/src/main/runtime/agent-session/launch/structured-agent-launch-composition.test.ts b/src/main/runtime/agent-session/launch/structured-agent-launch-composition.test.ts deleted file mode 100644 index 22d16a2a74a..00000000000 --- a/src/main/runtime/agent-session/launch/structured-agent-launch-composition.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { ORCA_SCRUB_SAFE_PANE_ENV } from '../../../../shared/agent-hook-listener/transport/agent-hook-scrub-safe-env' -import type { AgentSessionJournalIdentity } from '../../../../shared/agent-session/journal/agent-session-journal-identity' -import type { AgentSessionAccountHome } from '../../../../shared/agent-session/lifecycle/agent-session-account-home' -import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' -import { resolveProviderChildEnv } from '../../../provider-process/provider-process-launch' -import { - mintStructuredWorkerHandle, - mintStructuredWorkerPaneKey, - structuredWorkerIdentities -} from '../../orchestration/terminal-authority/structured-sessions/structured-worker-identity' -import { - createCodexStructuredLaunchResolver, - TEST_SPAWN_TOKEN -} from './structured-agent-launch-composition.test-support' - -afterEach(() => { - structuredWorkerIdentities.clear() -}) - -const INHERITED_PANE_ENV: Record = { - PATH: '/usr/bin:/bin', - HOME: '/home/work', - ORCA_PANE_KEY: 'tab_parent:parent-pane', - ORCA_TAB_ID: 'tab_parent', - ORCA_WORKTREE_ID: 'parent-worktree', - ORCA_WORKSPACE_ID: 'parent-workspace', - ORCA_TERMINAL_HANDLE: 'term_parent', - ORCA_AGENT_LAUNCH_TOKEN: 'parent-launch-token', - [ORCA_SCRUB_SAFE_PANE_ENV]: 'parent-scrub-safe-pane', - ORCA_AGENT_HOOK_PORT: '43123' -} - -function makeIdentity( - sessionId: string, - agent: 'claude' | 'codex' | 'grok' | 'pi' -): AgentSessionJournalIdentity { - return { - sessionId, - workspaceId: 'workspace-1', - hostId: LOCAL_EXECUTION_HOST_ID, - agent, - providerHandle: null - } -} - -function makeStore( - sessionId: string, - provider: 'claude' | 'codex' | 'grok' | 'pi', - accountHome: AgentSessionAccountHome -) { - const record = agentSessionRecordFixture({ - sessionId, - provider, - accountHome - }) - return { getRecord: () => record, pinLaunchDirectory: vi.fn() } -} - -function expectNoHostPaneIdentity(childEnv: NodeJS.ProcessEnv): void { - expect(childEnv).not.toHaveProperty('ORCA_PANE_KEY') - expect(childEnv).not.toHaveProperty('ORCA_TAB_ID') - expect(childEnv).not.toHaveProperty('ORCA_WORKTREE_ID') - expect(childEnv).not.toHaveProperty('ORCA_WORKSPACE_ID') - expect(childEnv).not.toHaveProperty('ORCA_AGENT_LAUNCH_TOKEN') - expect(childEnv).not.toHaveProperty(ORCA_SCRUB_SAFE_PANE_ENV) - expect(childEnv).not.toHaveProperty('ORCA_AGENT_HOOK_PORT') -} - -describe('structured agent launch composition pane-identity stripping', () => { - it("preserves a structured worker's own ORCA_TERMINAL_HANDLE while stripping host pane identity", async () => { - const sessionId = '00000000-0000-4000-8000-000000000005' - const workerHandle = mintStructuredWorkerHandle() - structuredWorkerIdentities.register({ - handle: workerHandle, - sessionId, - agent: 'codex', - paneKey: mintStructuredWorkerPaneKey(sessionId), - processIncarnation: `structured:${sessionId}`, - worktreeId: 'workspace-1', - hostScope: { kind: 'local', hostId: 'local' } - }) - - const workerLaunch = await createCodexStructuredLaunchResolver({ - store: makeStore(sessionId, 'codex', { - variable: 'CODEX_HOME', - path: '/home/work/.codex' - }), - resolveEnvironment: async () => ({ ...INHERITED_PANE_ENV }), - resolveCommand: () => ({ command: '/usr/local/bin/codex', prefixArgs: [] }), - resolveLaunchArgs: () => [] - })({ - identity: makeIdentity(sessionId, 'codex'), - spawnToken: TEST_SPAWN_TOKEN - }) - - const childEnv = resolveProviderChildEnv(workerLaunch, INHERITED_PANE_ENV) - expectNoHostPaneIdentity(childEnv) - expect(childEnv.ORCA_TERMINAL_HANDLE).toBe(workerHandle) - }) -}) diff --git a/src/main/runtime/agent-session/launch/structured-agent-launch-composition.ts b/src/main/runtime/agent-session/launch/structured-agent-launch-composition.ts index 42463903dd5..892a19e48a9 100644 --- a/src/main/runtime/agent-session/launch/structured-agent-launch-composition.ts +++ b/src/main/runtime/agent-session/launch/structured-agent-launch-composition.ts @@ -6,12 +6,12 @@ import type { AgentSessionRecord } from '../../../../shared/agent-session/lifecy import { isNativeLocalLocation } from '../../../../shared/execution-host' import type { StructuredAgentDefinition } from '../../../native-chat/agent-session-wire/providers/structured-agent-definition' import { agentDrivesSession } from '../../../native-chat/agent-session-wire/providers/structured-agent-session-provider-support' -import type { StructuredAgentCommandSettings } from '../../../native-chat/structured-agent-command-resolution' +import type { StructuredAgentCommandSettings } from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { createNativeChatVisualsDelivery, type NativeChatVisualsLaunch, type PrepareNativeChatVisuals -} from '../../../native-chat/native-chat-visuals-delivery' +} from '../../../native-chat/conversation-files/native-chat-visuals-delivery' import { resolveAgentSessionLaunchDirectory } from '../agent-session-launch-directory' import type { AgentSessionRecordStore } from '../records/agent-session-record-store' import type { StructuredAgentAdapterContext } from '../installation/structured-agent-runtime-registration-types' diff --git a/src/main/runtime/agent-session/launch/structured-agent-launch-support.ts b/src/main/runtime/agent-session/launch/structured-agent-launch-support.ts index d6c402cb949..743fe9c0667 100644 --- a/src/main/runtime/agent-session/launch/structured-agent-launch-support.ts +++ b/src/main/runtime/agent-session/launch/structured-agent-launch-support.ts @@ -16,13 +16,13 @@ import { resolveStructuredAgentSessionCreateSupport, warnStructuredAgentSessionCreateUnsupported, type StructuredAgentSessionCreateSupport -} from '../../../native-chat/structured-agent-session-create-support' +} from '../../../native-chat/agent-launch/structured-agent-session-create-support' import '../../../native-chat/agent-session-wire/providers/structured-agent-session-adapter' import { isAgentSessionPreSpawnError } from '../../../native-chat/agent-session-wire/providers/structured-agent-session-adapter-errors' import { resolveAgainAfterRefreshOnMissingProgram, type StructuredAgentCommandSettings -} from '../../../native-chat/structured-agent-command-resolution' +} from '../../../native-chat/agent-launch/structured-agent-command-resolution' import { structuredAgentRuntimeMetadata } from '../installation/structured-agent-runtime-metadata' import { refreshHostAgentBaseEnvironmentAfterMiss, diff --git a/src/main/runtime/agent-session/lifecycle/structured-session-recovery-options.ts b/src/main/runtime/agent-session/lifecycle/structured-session-recovery-options.ts index a43701d9a9b..d0e7b232e90 100644 --- a/src/main/runtime/agent-session/lifecycle/structured-session-recovery-options.ts +++ b/src/main/runtime/agent-session/lifecycle/structured-session-recovery-options.ts @@ -5,7 +5,7 @@ import { agentSessionProviderHandleChainHead } from '../../../../shared/agent-se import { getClaudeProfileRouter } from '../../../claude-accounts/profiles/routing/claude-profile-installed-router' import { agentSessionPinnedLaunchDirectory } from '../records/agent-session-record-launch-directory' import { structuredAgentRuntimeMetadata } from '../installation/structured-agent-runtime-metadata' -import type { StructuredAgentTranscriptImport } from '../../../native-chat/structured-agent-cli-conversations' +import type { StructuredAgentTranscriptImport } from '../../../native-chat/conversation-adoption/structured-agent-cli-conversations' export type RecoveryContextCache = { key?: string diff --git a/src/main/runtime/agent-session/naming/structured-agent-session-provider-naming-shutdown.test.ts b/src/main/runtime/agent-session/naming/structured-agent-session-provider-naming-shutdown.test.ts index 99d388152ae..0340376ae9a 100644 --- a/src/main/runtime/agent-session/naming/structured-agent-session-provider-naming-shutdown.test.ts +++ b/src/main/runtime/agent-session/naming/structured-agent-session-provider-naming-shutdown.test.ts @@ -8,7 +8,7 @@ import { expect, it, vi } from 'vitest' import { codexProviderHandle } from '../../../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' import * as processSpawn from '../../../codex/app-server/codex-app-server-process-tree-kill' import { closeTestJournalHostDatabase } from '../../../native-chat/agent-session-journal/store-lifecycle/journal-host-database-test-support' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import { StructuredAgentSessionAdapterRouter } from '../../../native-chat/agent-session-wire/providers/structured-agent-session-adapter-router' import { createStructuredAgentSessionLogger } from '../../../native-chat/agent-session-wire/host/structured-agent-session-logger' import { seedTestAgentSessionRecordStore } from '../records/agent-session-record-store-test-harness' diff --git a/src/main/runtime/agent-session/naming/structured-chat-naming-runtime.ts b/src/main/runtime/agent-session/naming/structured-chat-naming-runtime.ts index 0aaf2bdf654..65c8852b335 100644 --- a/src/main/runtime/agent-session/naming/structured-chat-naming-runtime.ts +++ b/src/main/runtime/agent-session/naming/structured-chat-naming-runtime.ts @@ -16,7 +16,7 @@ import { chatNamingFailure } from '../../../../shared/chat-naming-failure-copy' import { agentSessionCurrentContextRows } from '../../../../shared/agent-session/agent-session-context-clear' import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../../../shared/execution-host' import type { CommitMessageAgentEnvironmentResolvers } from '../../../text-generation/commit-message-agent-environment' -import type { StructuredChatNamingDeps } from '../../../native-chat/structured-chat-naming' +import type { StructuredChatNamingDeps } from '../../../native-chat/conversation-naming/structured-chat-naming-types' import type { StructuredAgentSessionLogger } from '../../../native-chat/agent-session-wire/host/structured-agent-session-logger' import { getStructuredAgentSessionHost } from '../../../native-chat/agent-session-wire/host/structured-agent-session-registry' import { firstStructuredChatNamingPrompt } from '../../../../shared/structured-chat-naming-eligibility' @@ -27,7 +27,7 @@ import { resolveTextGenerationParams } from '../../../text-generation/commit-message-text-generation' import { resolveGenerationTarget } from '../../../agent-hooks/first-work/first-work-generation-target' -import { deferredStructuredChatTitle } from '../../../native-chat/structured-chat-title-handover' +import { deferredStructuredChatTitle } from '../../../native-chat/conversation-naming/structured-chat-title-handover' type StructuredChatNamingRuntime = { resolveWorkspace: ( diff --git a/src/main/runtime/agent-session/records/agent-session-record-store-damaged.test.ts b/src/main/runtime/agent-session/records/agent-session-record-store-damaged.test.ts index 5a29271fa5e..316aaf01188 100644 --- a/src/main/runtime/agent-session/records/agent-session-record-store-damaged.test.ts +++ b/src/main/runtime/agent-session/records/agent-session-record-store-damaged.test.ts @@ -1,5 +1,5 @@ import { listStructuredProviderSessionOwnership } from '../../../native-chat/agent-session-wire/host/structured-provider-session-ownership' -import { findConflictingStructuredAdoption } from '../../../native-chat/structured-agent-session-history-adoption' +import { findConflictingStructuredAdoption } from '../../../native-chat/conversation-adoption/structured-agent-session-history-adoption' import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' diff --git a/src/main/runtime/agent-session/structured-agent-session-create-adoption.ts b/src/main/runtime/agent-session/structured-agent-session-create-adoption.ts index b18a7810aab..198b3010133 100644 --- a/src/main/runtime/agent-session/structured-agent-session-create-adoption.ts +++ b/src/main/runtime/agent-session/structured-agent-session-create-adoption.ts @@ -3,7 +3,7 @@ import { agentSessionRefusalError, isAgentSessionRefusalError } from '../../../shared/agent-session/failures/agent-session-wire-refusals' -import { prepareCodexAdoption } from '../../native-chat/codex-adoption-preparation' +import { prepareCodexAdoption } from '../../native-chat/conversation-adoption/codex-adoption-preparation' import { isAiVaultTitleAgent } from '../../../shared/ai-vault/contracts/ai-vault-session-title' import { generatedSessionTitle } from '../../ai-vault/parsing/session-scanner-accumulator' import { resolveLocalAiVaultSessionTitles } from '../../ai-vault/session-titles/session-title-resolver' @@ -12,7 +12,7 @@ import type { StructuredAgentSessionResumeSource } from '../../../shared/agent-s import type { StructuredAgentCliSourceEvidence, StructuredAgentCliSourceLocator -} from '../../native-chat/structured-agent-cli-source' +} from '../../native-chat/conversation-adoption/structured-agent-cli-source' import type { AgentSessionAccountHome } from '../../../shared/agent-session/lifecycle/agent-session-account-home' import type { AgentSessionProviderHandle, @@ -30,12 +30,12 @@ import { findConflictingStructuredAdoption, resolveStructuredAgentSessionAdoption, structuredAdoptionConflictError -} from '../../native-chat/structured-agent-session-history-adoption' +} from '../../native-chat/conversation-adoption/structured-agent-session-history-adoption' import { structuredAgentRuntimeMetadata } from './installation/structured-agent-runtime-metadata' import type { StructuredAgentTranscriptImport, StructuredAgentTranscriptImportSettings -} from '../../native-chat/structured-agent-cli-conversations' +} from '../../native-chat/conversation-adoption/structured-agent-cli-conversations' export type StructuredAgentSessionCreateIntentInput = { envelope: { sessionId: string; clientOperationId: string } diff --git a/src/main/runtime/agent-session/structured-agent-session-runtime.test.ts b/src/main/runtime/agent-session/structured-agent-session-runtime.test.ts index 26ca9dfed60..76f4e38eddd 100644 --- a/src/main/runtime/agent-session/structured-agent-session-runtime.test.ts +++ b/src/main/runtime/agent-session/structured-agent-session-runtime.test.ts @@ -4,18 +4,15 @@ import { tmpdir } from 'node:os' import { basename, join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' -import { __setWindowsProcessTreeLoaderForTests } from '../../windows/windows-process-table' + import { ensureStructuredAgentSessionHost, stopStructuredAgentSessionRuntime } from './structured-agent-session-runtime' import { seedTestAgentSessionRecordStore } from './records/agent-session-record-store-test-harness' -import { nativeChatScratchFolderFor } from '../../native-chat/native-chat-scratch-folder' +import { nativeChatScratchFolderFor } from '../../native-chat/conversation-files/native-chat-scratch-folder' import { foundAgentSessionRecord } from './records/agent-session-record-founding' -import { - closeTestJournalHostDatabases, - openTestJournalHostDatabase -} from '../../native-chat/agent-session-journal/store-lifecycle/journal-host-database-test-support' +import { closeTestJournalHostDatabases } from '../../native-chat/agent-session-journal/store-lifecycle/journal-host-database-test-support' import { recordingStructuredAgentSessionLogger } from '../../native-chat/agent-session-wire/host/testing/structured-agent-session-logger-test-support' import { onStructuredAgentSessionsHeldChanged } from '../../native-chat/agent-session-wire/host/structured-agent-session-registry' @@ -71,32 +68,6 @@ describe('structured agent-session runtime install', () => { }) } - it.each([false, true])( - 'backfills before the host is readable and keeps startup nonfatal on a failed write (%s)', - async (failWrite) => { - stateDirectory = await mkdtemp(join(tmpdir(), 'orca-structured-runtime-backfill-')) - await seedTestAgentSessionRecordStore(stateDirectory, { records: [oldChat('codex')] }) - if (failWrite) { - openTestJournalHostDatabase(stateDirectory).db.exec( - "CREATE TRIGGER fail_permission_backfill BEFORE UPDATE ON agent_session_records BEGIN SELECT RAISE(ABORT, 'disk full'); END" - ) - } - const logged = recordingStructuredAgentSessionLogger() - const host = await installOver(['codex'], logged.logger) - expect(host.deps.store.getRecord('old-codex')?.permissionMode).toBe( - failWrite ? undefined : 'full-access' - ) - expect((await host.readOptions('old-codex')).current.permissionMode).toBe( - failWrite ? 'ask' : 'full-access' - ) - if (failWrite) { - expect(logged.scopes()).toContain('permission-mode-backfill') - } else { - expect(logged.scopes()).not.toContain('permission-mode-backfill') - } - } - ) - it('moves pre-rename chat visuals under the current root before the host is readable', async () => { stateDirectory = await mkdtemp(join(tmpdir(), 'orca-structured-runtime-visuals-')) await seedTestAgentSessionRecordStore(stateDirectory, { records: [oldChat('codex')] }) diff --git a/src/main/runtime/agent-session/structured-agent-session-runtime.ts b/src/main/runtime/agent-session/structured-agent-session-runtime.ts index a8f3249fc4e..d9d008fc06b 100644 --- a/src/main/runtime/agent-session/structured-agent-session-runtime.ts +++ b/src/main/runtime/agent-session/structured-agent-session-runtime.ts @@ -20,7 +20,7 @@ import { neverThrowingStructuredAgentSessionLogger, type StructuredAgentSessionLogger } from '../../native-chat/agent-session-wire/host/structured-agent-session-logger' -import { migrateLegacyNativeChatVisualsFolders } from '../../native-chat/native-chat-scratch-folder-migration' +import { migrateLegacyNativeChatVisualsFolders } from '../../native-chat/conversation-files/native-chat-scratch-folder-migration' import type { StructuredAgentSessionRuntimeDeps } from './installation/structured-agent-session-runtime-contract' diff --git a/src/main/runtime/construction/runtime-construction.ts b/src/main/runtime/construction/runtime-construction.ts index f00f5e2ed0e..0d4e0c709fb 100644 --- a/src/main/runtime/construction/runtime-construction.ts +++ b/src/main/runtime/construction/runtime-construction.ts @@ -51,7 +51,7 @@ import { RuntimeSessionSearchSettingsController } from '../integrations/ai-vault import { RuntimeAutomationController } from '../integrations/automations/runtime-automation-controller' import { RuntimeOrchestrationFederation } from '../orchestration/federation/runtime-orchestration-federation' import { configureAiVaultSessionSources } from '../../ai-vault/discovery/ai-vault-scan-roots' -import { configureHostReadableTranscriptPathSources } from '../../native-chat/host-readable-transcript-path' +import { configureHostReadableTranscriptPathSources } from '../../native-chat/transcript-discovery/host-readable-transcript-path' import { createEphemeralAgentSessionClaimSigner } from '../agent-session/ownership/agent-session-claim-identity' import { registerConptyDa1OverrideInstaller } from '../terminal/output/terminal-model-query-authority' import { registerTerminalViewAttributesApplier } from '../terminal/view/terminal-view-attribute-store' diff --git a/src/main/runtime/construction/runtime-file-command-state.ts b/src/main/runtime/construction/runtime-file-command-state.ts index da392c5e975..ffae85fe4cc 100644 --- a/src/main/runtime/construction/runtime-file-command-state.ts +++ b/src/main/runtime/construction/runtime-file-command-state.ts @@ -14,7 +14,7 @@ import type { RuntimeRepoSelector } from '../workspaces/catalog/runtime-repo-sel import type { LayoutApplyReply } from '../../../shared/workspace-layout/workspace-layout-command-types' import type { WindowLayoutCommand } from '../terminal/layout/runtime-layout-apply' import { createRuntimeEditorFileOpener } from '../files/editor-open-through-layout-owner' -import { resolveNativeChatMediaPath } from '../../native-chat/native-chat-media-path' +import { resolveNativeChatMediaPath } from '../../native-chat/conversation-files/native-chat-media-path' import { getStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/host/structured-agent-session-registry' import { RuntimeFileSearch } from '../files/search/file-search' import { RuntimeTerminalArtifacts } from '../terminal/artifacts/terminal-artifacts' diff --git a/src/main/runtime/files/chat-scratch-file-root.ts b/src/main/runtime/files/chat-scratch-file-root.ts index cb75684420b..bb33a00ad4a 100644 --- a/src/main/runtime/files/chat-scratch-file-root.ts +++ b/src/main/runtime/files/chat-scratch-file-root.ts @@ -6,7 +6,7 @@ import { type ResolveAuthorizedPathOptions } from '../../ipc/filesystem/access/filesystem-auth' import { isENOENT } from '../../ipc/filesystem/access/filesystem-path-containment' -import { nativeChatScratchRootFor } from '../../native-chat/native-chat-scratch-folder' +import { nativeChatScratchRootFor } from '../../native-chat/conversation-files/native-chat-scratch-folder' import { getProfileUserDataPath } from '../../orca-profiles/profile-storage-paths' import { getFloatingWorkspaceFileRoots } from '../../ipc/floating-workspace-directory' diff --git a/src/main/runtime/files/file-command-host.ts b/src/main/runtime/files/file-command-host.ts index 9fe85b417a3..95f14ddecdb 100644 --- a/src/main/runtime/files/file-command-host.ts +++ b/src/main/runtime/files/file-command-host.ts @@ -6,7 +6,7 @@ import type { RuntimeFileOpenPosition } from '../../../shared/file-access/runtim import type { NativeChatMediaPathRequest, NativeChatMediaPathResolution -} from '../../native-chat/native-chat-media-path' +} from '../../native-chat/conversation-files/native-chat-media-path' export type RuntimeFileCommandHost = { getRuntimeId(): string diff --git a/src/main/runtime/orchestration/crew/crew-mounted-render.test-fixture.ts b/src/main/runtime/orchestration/crew/crew-mounted-render.test-fixture.ts index 121deafa180..e8af83de983 100644 --- a/src/main/runtime/orchestration/crew/crew-mounted-render.test-fixture.ts +++ b/src/main/runtime/orchestration/crew/crew-mounted-render.test-fixture.ts @@ -1,7 +1,7 @@ import { CrewReadModel } from './crew-read-model' import { fixture } from './crew-read-model.test-fixture' import { subscribeCrew } from './crew-stream' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import { agentSessionConversationView } from '../../../../shared/agent-session/lifecycle/agent-session-conversation-view' import { testOrcaSessionId } from '../../../../shared/orca-session-address-test-fixture' import { formatOrcaSessionAddress } from '../../../../shared/orca-session-address' diff --git a/src/main/runtime/orchestration/crew/crew-read-model.test.ts b/src/main/runtime/orchestration/crew/crew-read-model.test.ts index 0e7cf250ec9..9a2bcb0df2a 100644 --- a/src/main/runtime/orchestration/crew/crew-read-model.test.ts +++ b/src/main/runtime/orchestration/crew/crew-read-model.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it, vi } from 'vitest' import { buildCrewSnapshot } from './crew-party-snapshot' import { CrewReadModel } from './crew-read-model' import { subscribeCrew } from './crew-stream' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import { agentSessionConversationView } from '../../../../shared/agent-session/lifecycle/agent-session-conversation-view' import { fixture } from './crew-read-model.test-fixture' diff --git a/src/main/runtime/orchestration/crew/crew-recipient-authority.test.ts b/src/main/runtime/orchestration/crew/crew-recipient-authority.test.ts index 060a9505d6e..7ccc014529d 100644 --- a/src/main/runtime/orchestration/crew/crew-recipient-authority.test.ts +++ b/src/main/runtime/orchestration/crew/crew-recipient-authority.test.ts @@ -1,5 +1,5 @@ import { expect, it } from 'vitest' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import { agentSessionConversationView } from '../../../../shared/agent-session/lifecycle/agent-session-conversation-view' import { isOrcaSessionId, formatOrcaSessionAddress } from '../../../../shared/orca-session-address' import { testOrcaSessionId } from '../../../../shared/orca-session-address-test-fixture' diff --git a/src/main/runtime/orchestration/crew/crew-recipient-delta-cost.test-fixture.ts b/src/main/runtime/orchestration/crew/crew-recipient-delta-cost.test-fixture.ts index 5e2c12e4b19..c732965302b 100644 --- a/src/main/runtime/orchestration/crew/crew-recipient-delta-cost.test-fixture.ts +++ b/src/main/runtime/orchestration/crew/crew-recipient-delta-cost.test-fixture.ts @@ -1,4 +1,4 @@ -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' +import { agentSessionRecordFixture } from '../../../native-chat/testing/agent-session-record-test-fixture' import { agentSessionConversationView } from '../../../../shared/agent-session/lifecycle/agent-session-conversation-view' import { testOrcaSessionId } from '../../../../shared/orca-session-address-test-fixture' import { formatOrcaSessionAddress as address } from '../../../../shared/orca-session-address' diff --git a/src/main/runtime/orchestration/crew/crew-recipient-nested-authority.test.ts b/src/main/runtime/orchestration/crew/crew-recipient-nested-authority.test.ts deleted file mode 100644 index bfc044d5106..00000000000 --- a/src/main/runtime/orchestration/crew/crew-recipient-nested-authority.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { testOrcaSessionId } from '../../../../shared/orca-session-address-test-fixture' -import { fixture } from './crew-read-model.test-fixture' -import { meterCrewSql, sqlTotals } from './crew-recipient-sql.test-fixture' -import { CrewReadModel } from './crew-read-model' -import { OrcaRuntimeService } from '../../orca-runtime' -import { agentSessionConversationView } from '../../../../shared/agent-session/lifecycle/agent-session-conversation-view' -import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' -import { formatOrcaSessionAddress } from '../../../../shared/orca-session-address' -import { - mintStructuredWorkerHandle, - mintStructuredWorkerPaneKey, - structuredWorkerIdentities, - structuredWorkerIdentityFromRow, - structuredWorkerProcessIncarnation -} from '../terminal-authority/structured-sessions/structured-worker-identity' -import { backfillStructuredWorkerOrcaSessionIds } from '../db/schema/structured-worker-orca-session-backfill' -import * as recipientRouting from '../../rpc/methods/orchestration/messaging/recipient-routing' -import { afterEach, expect, it, vi } from 'vitest' - -const hostRef = vi.hoisted((): { current: unknown } => ({ current: null })) -vi.mock('../../../native-chat/agent-session-wire/host/structured-agent-session-registry', () => ({ - getStructuredAgentSessionHost: () => hostRef.current -})) -afterEach(() => { - vi.restoreAllMocks() -}) - -it('keeps retained NULL-session nested structured process reads inside selected batches at N/10N', () => { - const samples = [3, 30].map((population) => { - const f = fixture() - const runtime = new OrcaRuntimeService() - runtime.setOrchestrationDb(f.db) - const records = new Map>() - const identities = Array.from({ length: population }, (_, i) => { - const session = testOrcaSessionId(`chat-nested-${i}`) - const handle = mintStructuredWorkerHandle() - const pane = mintStructuredWorkerPaneKey(session) - const task = () => f.db.createTask({ runId: f.run.id, spec: 'retained nested owner' }).id - const older = f.db.createDispatchContext({ - taskId: task(), - assigneeHandle: `term_older_${i}`, - creator: { kind: 'system' }, - maxDepth: 10 - }) - const oldResource = f.db.createWorkerTerminalResourceStatement({ - dispatchId: older.id, - worktreeId: 'wt_nested', - terminalHandle: handle, - paneKey: null, - processIncarnation: structuredWorkerProcessIncarnation(`chat-older-${i}`), - ownership: 'owned' - }) - f.db.db - .prepare("UPDATE worker_terminal_resources SET updated_at = '2000-01-01' WHERE id = ?") - .run(oldResource.id) - const dispatch = f.db.createDispatchContext({ - taskId: task(), - assigneeHandle: handle, - assigneePaneKey: pane, - processIncarnation: structuredWorkerProcessIncarnation(session), - creator: { kind: 'system' }, - maxDepth: 10 - }) - const resource = f.db.createWorkerTerminalResourceStatement({ - dispatchId: dispatch.id, - worktreeId: 'wt_nested', - terminalHandle: handle, - paneKey: pane, - processIncarnation: structuredWorkerProcessIncarnation(session), - hostScope: JSON.stringify({ kind: 'local', hostId: 'local' }), - ownership: 'owned' - }) - // Retained older writers omitted this column; conflicting evidence must remain NULL after open. - f.db.db - .prepare('UPDATE dispatch_contexts SET assignee_orca_session_id = NULL WHERE id = ?') - .run(dispatch.id) - const identity = structuredWorkerIdentityFromRow(resource) - if (!identity) { - throw new Error('Expected actual validated structured resource identity') - } - structuredWorkerIdentities.register(identity) - records.set( - session, - agentSessionConversationView(agentSessionRecordFixture({ sessionId: session })) - ) - f.handles.set(handle, pane) - f.db.insertMessage({ - from: formatOrcaSessionAddress(session), - to: 'term_worker', - subject: 'Nested peer', - body: session - }) - return { ...identity, dispatch } - }) - backfillStructuredWorkerOrcaSessionIds(f.db.db) - for (const identity of identities) { - expect(f.db.getDispatchContextById(identity.dispatch.id)?.assignee_orca_session_id).toBeNull() - expect( - f.db.getCurrentRunForCoordinator({ - terminalHandle: identity.handle, - paneKey: identity.paneKey, - orcaSessionId: testOrcaSessionId(identity.sessionId) - }) - ).toBeUndefined() - } - f.deps.records = { - listRecords: () => [...records.values()], - getRecord: (id) => records.get(id) ?? null, - getVisibleSessionTabIndex: () => ({ present: true, sessionIds: [...records.keys()] }) - } - hostRef.current = { - deps: { - store: { - getConversation: (id: string) => records.get(id) ?? null, - listConversations: () => [...records.values()] - } - }, - getPersistedVisibleSessionTabIndex: () => ({ present: true, sessionIds: [...records.keys()] }) - } - const context = runtime.createTerminalIdentityReadContext() - f.deps.openIdentityRead = () => ({ - liveHandle: f.deps.liveHandle, - handleForPane: f.deps.handleForPane, - paneForHandle: f.deps.paneForHandle, - name: f.deps.name, - processForHandle: (handle) => runtime.getTerminalProcessIncarnation(handle, context) - }) - f.deps.livePaneForHandle = (handle) => runtime.getLiveTerminalPaneKey(handle, context) - const productionProcess = runtime.getTerminalProcessIncarnation.bind(runtime) - const meter = meterCrewSql(f.db.db) - let nestedProcess = 0 - let selecting = false - const select = recipientRouting.dispatchAssigneeCoordinatorKey - const selection = vi - .spyOn(recipientRouting, 'dispatchAssigneeCoordinatorKey') - .mockImplementation((...args) => { - selecting = true - meter.control.phase = 'selecting' - try { - return select(...args) - } finally { - selecting = false - meter.control.phase = 'trigger' - } - }) - const process = vi - .spyOn(runtime, 'getTerminalProcessIncarnation') - .mockImplementation((...args) => { - if (selecting) { - nestedProcess++ - } - return productionProcess(...args) - }) - const operations = () => ({ - ...sqlTotals(meter.executions), - nestedGet: meter.executions.filter( - (entry) => entry.phase === 'selecting' && entry.method === 'get' - ).length, - nestedProcess - }) - const registration = vi.spyOn(structuredWorkerIdentities, 'register') - const model = new CrewReadModel(() => f.deps, f.db.crewChanges.subscribe) - const stop = model.subscribe(() => {}) - try { - const output = model.read('term_worker') - expect(output.sections.talkingWith).toHaveLength(population) - expect( - output.sections.talkingWith.every((row) => row.sendAddress && row.latest && row.openTarget) - ).toBe(true) - expect(registration).not.toHaveBeenCalled() - const measured = operations() - expect(measured.selected).toBeLessThanOrEqual(5) - // Actual baseline presentation resolves/registers cold identities before the selected callback. - structuredWorkerIdentities.clear() - f.db.crewChanges.notify(new Set(['topology'])) - const beforeCold = operations() - expect(model.read('term_worker')).toEqual(output) - expect(registration).toHaveBeenCalledTimes(population) - expect(operations().nestedGet - beforeCold.nestedGet).toBe(0) - expect(operations().nestedProcess - beforeCold.nestedProcess).toBe(0) - return measured - } finally { - stop() - meter.restore() - process.mockRestore() - selection.mockRestore() - registration.mockRestore() - hostRef.current = null - structuredWorkerIdentities.clear() - } - }) - expect(samples.map((sample) => sample.nestedProcess)).toEqual([0, 0]) - expect(samples.map((sample) => sample.nestedGet)).toEqual([0, 0]) - expect(samples[1].all).toBe(samples[0].all) - expect(samples[1].get).toBeLessThanOrEqual(10 * samples[0].get) -}) diff --git a/src/main/runtime/orchestration/workers/transcripts/worker-transcript-local-read.ts b/src/main/runtime/orchestration/workers/transcripts/worker-transcript-local-read.ts index 27abc46b5be..9e7ec215ba1 100644 --- a/src/main/runtime/orchestration/workers/transcripts/worker-transcript-local-read.ts +++ b/src/main/runtime/orchestration/workers/transcripts/worker-transcript-local-read.ts @@ -5,8 +5,8 @@ import { readNativeChatTranscriptTailFile, type NativeChatLineDecoder } from '../../../../native-chat/transcript-tail-reader' -import { TAIL_CHUNK_BYTES } from '../../../../native-chat/transcript-tail-boundary' -import { transcriptFallbackId } from '../../../../native-chat/transcript-fallback-id' +import { TAIL_CHUNK_BYTES } from '../../../../native-chat/transcript-watching/transcript-tail-boundary' +import { transcriptFallbackId } from '../../../../native-chat/transcript-streaming/transcript-fallback-id' import { localTranscriptOffsetStartsInsideRecord, readLocalTranscriptHandleBoundaryCheckpoint, diff --git a/src/main/runtime/rpc/core.ts b/src/main/runtime/rpc/core.ts index 4d6f7163c21..dd2b33a6c97 100644 --- a/src/main/runtime/rpc/core.ts +++ b/src/main/runtime/rpc/core.ts @@ -3,7 +3,7 @@ import type { ZodError, ZodType } from 'zod' import type { TerminalStreamFrame } from '../../../shared/terminal/stream/terminal-stream-protocol' import type { OrcaRuntimeService } from '../orca-runtime' import type { OrchestrationCompatibilityCallerAuthority } from '../orchestration/terminal-authority/orchestration-compatibility-authority' -import type { StructuredAgentCliSourceEvidence } from '../../native-chat/structured-agent-cli-source' +import type { StructuredAgentCliSourceEvidence } from '../../native-chat/conversation-adoption/structured-agent-cli-source' import type { DeviceCredentialInstalled, PairingGetEndpointsParams, diff --git a/src/main/runtime/rpc/dispatch/dispatcher-stream-options.ts b/src/main/runtime/rpc/dispatch/dispatcher-stream-options.ts index 96fb33d7330..647aac18b7a 100644 --- a/src/main/runtime/rpc/dispatch/dispatcher-stream-options.ts +++ b/src/main/runtime/rpc/dispatch/dispatcher-stream-options.ts @@ -1,5 +1,5 @@ import type { OrcaRuntimeService } from '../../orca-runtime' -import { captureStructuredAgentCliSource } from '../../../native-chat/structured-agent-cli-source' +import { captureStructuredAgentCliSource } from '../../../native-chat/conversation-adoption/structured-agent-cli-source' import type { OrchestrationEntryCaller } from '../../orchestration/addressing/orchestration-caller-identity' import { isRegistrationFencedUnsubscribe, type RpcContext, type RpcRequest } from '../core' import { resolveRpcCallerIdentity } from '../callers/rpc-caller-identity' diff --git a/src/main/runtime/rpc/methods/native-chat/native-chat-subscription-token.test.ts b/src/main/runtime/rpc/methods/native-chat/native-chat-subscription-token.test.ts deleted file mode 100644 index 40388cb1654..00000000000 --- a/src/main/runtime/rpc/methods/native-chat/native-chat-subscription-token.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { RuntimeSubscriptionRegistry } from '../../../client/runtime-subscription-registry' -import type { RpcContext } from '../../core' - -vi.mock('../../../../native-chat/transcript-watch', () => ({ - subscribeNativeChatTranscript: () => Promise.resolve({ unsubscribe: () => {}, watching: true }) -})) -vi.mock('../../../../native-chat/transcript-tail-reader', () => ({ - readNativeChatTranscriptTail: vi.fn() -})) - -import { NATIVE_CHAT_METHODS } from './native-chat' - -type Handler = ( - params: unknown, - ctx: RpcContext, - emit: (value: unknown) => void -) => Promise - -function handler(name: string): Handler { - const method = NATIVE_CHAT_METHODS.find((candidate) => candidate.name === name) - if (!method) { - throw new Error(`${name} not registered`) - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: both methods take (params, ctx[, emit]); the test only calls them with that shape. - return method.handler as Handler -} - -/** One phone connection on a runtime whose subscriptions live in the real registry. */ -function phoneConnection(): { - subscribe: (subscriptionId: string) => Promise - unsubscribe: (subscriptionId: string) => Promise -} { - const registry = new RuntimeSubscriptionRegistry() - const context: RpcContext = { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the handlers under test only touch these registry-backed members and the hook rows. - runtime: { - getAgentProviderSessionRows: () => [], - registerSubscriptionCleanup: registry.register.bind(registry), - cleanupSubscription: registry.cleanup.bind(registry), - cleanupSubscriptionsByPrefix: registry.cleanupByPrefix.bind(registry) - } as unknown as RpcContext['runtime'], - connectionId: 'phone-connection', - clientKind: 'mobile' - } - return { - subscribe: async (subscriptionId) => { - const emitted: unknown[] = [] - await handler('nativeChat.subscribe')( - { agent: 'claude', sessionId: 'session', subscriptionId }, - context, - (value) => emitted.push(value) - ) - return emitted - }, - unsubscribe: async (subscriptionId) => { - await handler('nativeChat.unsubscribe')({ subscriptionId }, context, () => {}) - } - } -} - -describe('nativeChat subscription tokens on one connection', () => { - it('keeps two feeds of the same chat apart when each has its own token', async () => { - const phone = phoneConnection() - const under = await phone.subscribe('claude:session:under') - const top = await phone.subscribe('claude:session:top') - expect(under).toEqual([]) - expect(top).toEqual([]) - - await phone.unsubscribe('claude:session:top') - expect(top).toEqual([{ type: 'end' }]) - expect(under).toEqual([]) - }) - - it('ends the older feed when a second one reuses its token', async () => { - const phone = phoneConnection() - const older = await phone.subscribe('claude:session') - const newer = await phone.subscribe('claude:session') - expect(older).toEqual([{ type: 'end' }]) - expect(newer).toEqual([]) - }) -}) diff --git a/src/main/runtime/rpc/methods/native-chat/native-chat.test.ts b/src/main/runtime/rpc/methods/native-chat/native-chat.test.ts index 045aa5b7742..8d77b225d5a 100644 --- a/src/main/runtime/rpc/methods/native-chat/native-chat.test.ts +++ b/src/main/runtime/rpc/methods/native-chat/native-chat.test.ts @@ -371,20 +371,6 @@ describe('nativeChat.subscribe initial snapshot', () => { expect((emitted[1] as { pending?: boolean }).pending).toBeUndefined() }) - it('does not publish pending semantics to a legacy client', async () => { - watcher.watching = true - watcher.args = null - const emitted: unknown[] = [] - await subscribeHandler()( - { agent: 'claude', sessionId: 's' }, - streamingContext('mobile'), - (value) => emitted.push(value) - ) - - expect(activeWatcherArgs().onTranscriptPending).toBeUndefined() - expect(emitted).toEqual([]) - }) - it('emits one windowed snapshot with pagination state before live appends', async () => { watcher.watching = true watcher.args = null @@ -419,26 +405,6 @@ describe('nativeChat.subscribe initial snapshot', () => { expect((emitted[1] as { messages: NativeChatMessage[] }).messages).toHaveLength(60) }) - it('emits an explicit error snapshot when no transcript can be watched', async () => { - watcher.watching = false - watcher.args = null - const emitted: unknown[] = [] - await subscribeHandler()( - { agent: 'claude', sessionId: 'missing' }, - streamingContext('mobile'), - (value) => emitted.push(value) - ) - - expect(emitted).toEqual([ - { - type: 'snapshot', - messages: [], - hasMore: false, - error: 'Transcript unavailable' - } - ]) - }) - it('cancels pending setup when the stream is cleaned up', async () => { const cleanups = new Map void>() const context = streamingContext('mobile') @@ -511,20 +477,4 @@ describe('nativeChat.subscribe initial snapshot', () => { watcher.setupFactory = null } }) - - it('skips setup for an already-cancelled request', async () => { - const controller = new AbortController() - controller.abort() - const context = { ...streamingContext('mobile'), signal: controller.signal } - watcher.args = null - const emitted: unknown[] = [] - - await subscribeHandler()({ agent: 'claude', sessionId: 'already-closed' }, context, (value) => - emitted.push(value) - ) - - expect(context.runtime.registerSubscriptionCleanup).not.toHaveBeenCalled() - expect(watcher.args).toBeNull() - expect(emitted).toEqual([]) - }) }) diff --git a/src/main/runtime/rpc/methods/native-chat/native-chat.ts b/src/main/runtime/rpc/methods/native-chat/native-chat.ts index 0c0871ef268..54e2374c593 100644 --- a/src/main/runtime/rpc/methods/native-chat/native-chat.ts +++ b/src/main/runtime/rpc/methods/native-chat/native-chat.ts @@ -10,8 +10,8 @@ import { readNativeChatTranscriptTail } from '../../../../native-chat/transcript import type { NativeChatTranscriptSubscription, SubscribeNativeChatTranscriptArgs -} from '../../../../native-chat/transcript-watch-contract' -import { nativeChatTranscriptPathOnExecutionHost } from '../../../../native-chat/ssh-transcript-path' +} from '../../../../native-chat/transcript-watching/transcript-watch-contract' +import { nativeChatTranscriptPathOnExecutionHost } from '../../../../native-chat/transcript-filesystem/ssh-transcript-path' import { defineMethod, defineStreamingMethod, type RpcContext } from '../../core' import { sanitizeNativeChatRpcBlock } from './native-chat-rpc-block-sanitize' import { diff --git a/src/main/runtime/rpc/methods/orchestration/structured-workers/structured-worker-read-cursor.test.ts b/src/main/runtime/rpc/methods/orchestration/structured-workers/structured-worker-read-cursor.test.ts index 2b71d40eed7..b93d5ab7f2d 100644 --- a/src/main/runtime/rpc/methods/orchestration/structured-workers/structured-worker-read-cursor.test.ts +++ b/src/main/runtime/rpc/methods/orchestration/structured-workers/structured-worker-read-cursor.test.ts @@ -7,7 +7,7 @@ import { agentSessionConversationView } from '../../../../../../shared/agent-ses import { beforeEach, describe, expect, it, vi } from 'vitest' import { boundStructuredJournalTail } from '../../../../orchestration/workers/archives/structured-worker-journal-archive' -import { projectSavedChatContextItems } from '../../../../../native-chat/agent-session-saved-prompt-text' +import { projectSavedChatContextItems } from '../../../../../native-chat/conversation-context/agent-session-saved-prompt-text' import { formatWorkerTranscriptMessage } from '../../../../../../shared/worker-transcript-text' import type { AgentJournalRenderItem } from '../../../../../../shared/agent-session/journal/agent-session-journal-types' import type { StructuredWorkerIdentity } from '../../../../orchestration/terminal-authority/structured-sessions/structured-worker-identity' diff --git a/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.test.ts b/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.test.ts index bdc668fa950..af7cee4d511 100644 --- a/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.test.ts +++ b/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.test.ts @@ -9,8 +9,8 @@ import { agentSessionRecordFixture } from '../../../../../../shared/agent-session/lifecycle/agent-session-record.test-fixture' import { setStructuredAgentSessionHost } from '../../../../../native-chat/agent-session-wire/host/structured-agent-session-registry' -import { nativeChatScratchFolderFor } from '../../../../../native-chat/native-chat-scratch-folder' -import { nativeChatVisualRevision } from '../../../../../native-chat/native-chat-visual-file-read' +import { nativeChatScratchFolderFor } from '../../../../../native-chat/conversation-files/native-chat-scratch-folder' +import { nativeChatVisualRevision } from '../../../../../native-chat/conversation-files/native-chat-visual-file-read' import { Store } from '../../../../../persistence/loading-store/store' import { closeTestStores, diff --git a/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.ts b/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.ts index b84641bf277..7776f75878f 100644 --- a/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.ts +++ b/src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.ts @@ -6,9 +6,9 @@ import { ReadVisualParams, type AgentSessionReadVisualResult } from '../../../../../../shared/rpc-contract/agent-session-visual-params' -import { nativeChatScratchFolderFor } from '../../../../../native-chat/native-chat-scratch-folder' +import { nativeChatScratchFolderFor } from '../../../../../native-chat/conversation-files/native-chat-scratch-folder' import { getProfileUserDataPath } from '../../../../../orca-profiles/profile-storage-paths' -import { readNativeChatVisualFile } from '../../../../../native-chat/native-chat-visual-file-read' +import { readNativeChatVisualFile } from '../../../../../native-chat/conversation-files/native-chat-visual-file-read' import { defineMethod } from '../../../core' import { requireInstalledStructuredHost } from '../creation/structured-agent-session-gate' diff --git a/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-create.ts b/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-create.ts index 9390069e22e..8a759b451e6 100644 --- a/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-create.ts +++ b/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-create.ts @@ -33,7 +33,7 @@ import type { OrcaRuntimeService } from '../../../../orca-runtime' import { captureStructuredAgentCliSource, type StructuredAgentCliSourceEvidence -} from '../../../../../native-chat/structured-agent-cli-source' +} from '../../../../../native-chat/conversation-adoption/structured-agent-cli-source' import type { StructuredAgentId } from '../../../../../../shared/agent-session/lifecycle/agent-session-provider-handle' import { resolveUncommittedStructuredCreate } from './structured-agent-session-precommit-refusal' import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../../shared/layout-owner-switch' diff --git a/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-precommit-refusal.ts b/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-precommit-refusal.ts index 46a1484336b..5c0825755c3 100644 --- a/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-precommit-refusal.ts +++ b/src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-precommit-refusal.ts @@ -10,7 +10,7 @@ import { type AgentSessionWireRefusalCode } from '../../../../../../shared/agent-session/failures/agent-session-wire-refusals' import { isAgentSessionCreateAdmissionRefusal } from '../../../../../../shared/agent-session/failures/agent-session-definitive-refusal' -import { AgentConversationCopyError } from '../../../../../native-chat/agent-conversation-copy-error' +import { AgentConversationCopyError } from '../../../../../native-chat/conversation-adoption/agent-conversation-copy-error' import { tuiAgentDisplayName } from '../../../../../../shared/agent-launch/catalog/tui-agent-display-names' export type StructuredCreateRefused = { refusal: AgentSessionWireRefusal } diff --git a/src/main/runtime/rpc/methods/structured-sessions/protocol/structured-agent-session-rest-contract-test-fixture.ts b/src/main/runtime/rpc/methods/structured-sessions/protocol/structured-agent-session-rest-contract-test-fixture.ts index 026bb23718c..955206c0952 100644 --- a/src/main/runtime/rpc/methods/structured-sessions/protocol/structured-agent-session-rest-contract-test-fixture.ts +++ b/src/main/runtime/rpc/methods/structured-sessions/protocol/structured-agent-session-rest-contract-test-fixture.ts @@ -14,7 +14,7 @@ import { RpcDispatcher } from '../../../dispatcher' import type { RpcDispatchStreamingOptions } from '../../../dispatch/dispatcher-stream-options' import { STRUCTURED_AGENT_SESSION_METHODS } from '../structured-agent-session' import type * as fileOperations from 'node:fs/promises' -import * as contextSnapshots from '../../../../../native-chat/agent-session-context-snapshots' +import * as contextSnapshots from '../../../../../native-chat/conversation-context/agent-session-context-snapshots' vi.mock('node:fs/promises', { spy: true }) export const actualFiles = await vi.importActual('node:fs/promises') diff --git a/src/main/runtime/session-tabs/structured-session-cli-writer-host.ts b/src/main/runtime/session-tabs/structured-session-cli-writer-host.ts index 65d37a17105..3c66d0102f8 100644 --- a/src/main/runtime/session-tabs/structured-session-cli-writer-host.ts +++ b/src/main/runtime/session-tabs/structured-session-cli-writer-host.ts @@ -8,8 +8,8 @@ import { assertCliConversationWritersExited } from '../agent-session/lifecycle/s import type { EnrichedAgentHookEventPayload } from '../../agent-hooks/server/server-types' import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' import { CLI_CONVERSATION_ADOPTING_AGENTS } from '../../../shared/structured-agent-cli-conversation-adoption' -import { recordConversationKey } from '../../native-chat/structured-conversation-key' -import type { StructuredAgentCliSourceRoute } from '../../native-chat/structured-agent-cli-source' +import { recordConversationKey } from '../../native-chat/conversation-adoption/structured-conversation-key' +import type { StructuredAgentCliSourceRoute } from '../../native-chat/conversation-adoption/structured-agent-cli-source' import { OrcaRuntimeWithGetWorktreePs } from '../workspaces/process-listing/workspace-process-listing' export abstract class OrcaRuntimeWithStructuredSessionCliWriterHost extends OrcaRuntimeWithGetWorktreePs { diff --git a/src/main/runtime/session-tabs/structured-session-create-support.ts b/src/main/runtime/session-tabs/structured-session-create-support.ts index a784ee9e5c0..804ae6c230f 100644 --- a/src/main/runtime/session-tabs/structured-session-create-support.ts +++ b/src/main/runtime/session-tabs/structured-session-create-support.ts @@ -3,7 +3,7 @@ import { resolveStructuredAgentSessionForkIntent } from '../../native-chat/agent import { captureStructuredAgentCliSource, structuredAgentCliSourceFromEvidence -} from '../../native-chat/structured-agent-cli-source' +} from '../../native-chat/conversation-adoption/structured-agent-cli-source' import type { OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce as RestoreStructuredAgentSessionTabsOnceMethods } from './structured-session-tab-restoration' import { agentSessionRefusalError } from '../../../shared/agent-session/failures/agent-session-wire-refusals' import { StructuredSessionWorkspaceScope } from './structured-session-workspace-scope' diff --git a/src/main/workspaces/removal/retirement/worktree-retirement-discovery-wsl.test.ts b/src/main/workspaces/removal/retirement/worktree-retirement-discovery-wsl.test.ts index 1f335efe069..bd4ddbcb435 100644 --- a/src/main/workspaces/removal/retirement/worktree-retirement-discovery-wsl.test.ts +++ b/src/main/workspaces/removal/retirement/worktree-retirement-discovery-wsl.test.ts @@ -1,24 +1,21 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { mkdir, mkdtemp, readdir, rm, stat, writeFile } from 'node:fs/promises' +import { mkdir, mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { MARINE_CREATURES } from '../../../../shared/marine-creatures' -import { createRetiredNameLookup } from '../../../../shared/worktree/retired-name-registry' -import { encodeClaudeProjectPath } from '../../../ai-vault/scanners/claude/claude-project-dir-encoding' -import { - RETIREMENT_BACKFILL_RETRY_AFTER_FAILURE_MS, - runRetirementBackfillScan -} from './worktree-retirement-backfill-scan' const { runWslTranscriptFsTaskMock, getWslHomeAsyncMock } = vi.hoisted(() => ({ runWslTranscriptFsTaskMock: vi.fn(), getWslHomeAsyncMock: vi.fn() })) -vi.mock('../../../native-chat/wsl-transcript-fs-gate', async (importOriginal) => ({ - ...((await importOriginal()) as Record), - runWslTranscriptFsTask: runWslTranscriptFsTaskMock -})) +vi.mock( + '../../../native-chat/transcript-filesystem/wsl-transcript-fs-gate', + async (importOriginal) => ({ + ...((await importOriginal()) as Record), + runWslTranscriptFsTask: runWslTranscriptFsTaskMock + }) +) // Keep parseWslPath and hasCachedWslHome real for the backfill path under test. vi.mock('../../../wsl/discovery/distro-catalog', async (importOriginal) => ({ @@ -27,11 +24,12 @@ vi.mock('../../../wsl/discovery/distro-catalog', async (importOriginal) => ({ })) const { discoverRetiredWorktreeNames } = await import('./worktree-retirement-discovery') -const { ensureRetiredWorktreeNamesBackfilled } = await import('./worktree-name-retirement') -const { WslTranscriptFsError } = await import('../../../native-chat/wsl-transcript-fs-gate') +await import('./worktree-name-retirement') +const { WslTranscriptFsError } = + await import('../../../native-chat/transcript-filesystem/wsl-transcript-fs-error') const FIRST = MARINE_CREATURES[0].toLowerCase() -const SECOND = MARINE_CREATURES[1].toLowerCase() + const DISTRO_ROOT = '\\\\wsl.localhost\\Ubuntu\\home\\ada\\orca\\workspaces' describe('retirement discovery on WSL', () => { @@ -117,157 +115,7 @@ describe('retirement discovery on WSL', () => { expect(retired.complete).toBe(false) }) - it('keeps a deleted WSL workspace name spent, so the next create cannot reuse its cwd', async () => { - // The distro bucket outlives the deleted workspace and must keep its name retired. - const distroHome = await mkdtemp(join(tmpdir(), 'orca-wsl-distro-home-')) - const workspaceRoot = await mkdtemp(join(tmpdir(), 'orca-wsl-workspaces-')) - try { - await mkdir(join(distroHome, '.claude', 'projects', `-home-ada-orca-workspaces-${FIRST}`), { - recursive: true - }) - - const retired = await discoverRetiredWorktreeNames({ - // The root is listed under its real (empty) path; the UNC spelling supplies the distro. - workspaceRoots: [DISTRO_ROOT], - home: workspaceRoot, - env: {}, - resolveWslHome: async () => distroHome - }) - - expect(retired.names).toEqual(new Set([FIRST])) - expect(createRetiredNameLookup({ exhaustedTiers: 0, names: [...retired.names] })(FIRST)).toBe( - true - ) - } finally { - await rm(distroHome, { force: true, recursive: true }) - await rm(workspaceRoot, { force: true, recursive: true }) - } - }) - // A distro-root workspace folder encodes to the root prefix `-` on the Linux side. - it.each([ - ['a host bucket under the older \\\\wsl$\\ spelling', 'host', `--wsl--ubuntu-${FIRST}`], - ['a distro bucket', 'distro', `-${FIRST}`] - ])('keeps %s below a distro-root workspace folder spent', async (_case, side, bucket) => { - const homes = { - host: await mkdtemp(join(tmpdir(), 'orca-wsl-host-home-')), - distro: await mkdtemp(join(tmpdir(), 'orca-wsl-distro-home-')) - } - try { - await mkdir(join(homes[side], '.claude', 'projects', bucket), { recursive: true }) - - const retired = await discoverRetiredWorktreeNames({ - workspaceRoots: ['\\\\wsl.localhost\\Ubuntu'], - home: homes.host, - env: {}, - resolveWslHome: async () => homes.distro - }) - - expect(retired.names).toEqual(new Set([FIRST])) - } finally { - await rm(homes.host, { force: true, recursive: true }) - await rm(homes.distro, { force: true, recursive: true }) - } - }) // A refused permission stays refused on retry; a slow route may answer next time. - it.each([ - ['a permission refusal', Object.assign(new Error('EACCES'), { code: 'EACCES' }), 1], - ['a slow route', new WslTranscriptFsError('timeout', 'filesystem access is slow'), 2] - ])( - 'after %s on a long-path transcript, a later create rescans only if retrying can help', - async (_case, refusal, expectedProbes) => { - const distro = '\\\\wsl.localhost\\Ubuntu' - const longRoot = `/${'a'.repeat(229)}` - const distroDir = await mkdtemp(join(tmpdir(), 'orca-wsl-distro-')) - const hostHome = await mkdtemp(join(tmpdir(), 'orca-wsl-host-home-')) - try { - const projects = join(distroDir, 'home', 'ada', '.claude', 'projects') - await mkdir(join(distroDir, longRoot), { recursive: true }) - await mkdir(join(distroDir, 'home', 'ada', 'orca', 'workspaces'), { recursive: true }) - await mkdir(join(projects, `-home-ada-orca-workspaces-${FIRST}`), { recursive: true }) - const hashed = join( - projects, - `${encodeClaudeProjectPath(`${longRoot}/${SECOND}`).slice(0, 200)}-1x2y3z` - ) - await mkdir(hashed, { recursive: true }) - await writeFile(join(hashed, 'session.jsonl'), '{}\n') - let probes = 0 - runWslTranscriptFsTaskMock.mockImplementation( - async (options: { operation: string; path: string }) => { - if (options.path.endsWith('session.jsonl')) { - probes += 1 - throw refusal - } - const local = join(distroDir, options.path.slice(distro.length).replaceAll('\\', '/')) - return options.operation === 'readdir' - ? readdir(local, { withFileTypes: true }) - : stat(local) - } - ) - let now = 0 - vi.spyOn(performance, 'now').mockImplementation(() => now) - const store = {} - const create = () => - runRetirementBackfillScan(store, 'ns', () => - discoverRetiredWorktreeNames({ - workspaceRoots: [DISTRO_ROOT, `${distro}${longRoot}`], - home: hostHome, - env: {}, - resolveWslHome: async () => `${distro}\\home\\ada` - }) - ) - - await expect(create()).resolves.toEqual(new Set([FIRST])) - now += RETIREMENT_BACKFILL_RETRY_AFTER_FAILURE_MS + 1 - await expect(create()).resolves.toEqual(new Set([FIRST])) - - expect(probes).toBe(expectedProbes) - } finally { - await rm(distroDir, { force: true, recursive: true }) - await rm(hostHome, { force: true, recursive: true }) - } - } - ) - - it('reaches distro discovery from the real backfill, not only when called directly', async () => { - const distroHome = await mkdtemp(join(tmpdir(), 'orca-wsl-backfill-home-')) - try { - await mkdir(join(distroHome, '.claude', 'projects', `-home-ada-orca-workspaces-${FIRST}`), { - recursive: true - }) - getWslHomeAsyncMock.mockResolvedValue('\\\\wsl.localhost\\Ubuntu\\home\\ada') - runWslTranscriptFsTaskMock.mockImplementation( - (options: { path: string }, task: (signal: AbortSignal) => Promise) => { - const claudeRoot = join('\\\\wsl.localhost\\Ubuntu\\home\\ada', '.claude', 'projects') - return options.path === claudeRoot - ? readdir(join(distroHome, '.claude', 'projects'), { withFileTypes: true }) - : task(new AbortController().signal) - } - ) - const merged: string[] = [] - - await ensureRetiredWorktreeNamesBackfilled( - { - mergeRetiredWorktreeNames: (_repoId: string, names: Iterable) => { - merged.push(...names) - return true - } - } as never, - { - id: 'repo-wsl', - path: '\\\\wsl.localhost\\Ubuntu\\home\\ada\\repos\\a', - displayName: 'a', - badgeColor: '', - addedAt: 0 - } as never, - // Use a UNC root directly; parseWslPath ignores UNC repo routing off Windows. - { workspaceDir: DISTRO_ROOT, nestWorkspaces: false } - ) - - expect(merged).toContain(FIRST) - } finally { - await rm(distroHome, { force: true, recursive: true }) - } - }) }) diff --git a/src/main/workspaces/removal/retirement/worktree-retirement-discovery.ts b/src/main/workspaces/removal/retirement/worktree-retirement-discovery.ts index 4f225990cc4..460ee3c71f7 100644 --- a/src/main/workspaces/removal/retirement/worktree-retirement-discovery.ts +++ b/src/main/workspaces/removal/retirement/worktree-retirement-discovery.ts @@ -12,8 +12,11 @@ import { cachedProjectDirCwd, cwdBucketScopePrefixes } from '../../../ai-vault/scope/session-scanner-scope-discovery' -import { wslGatedReaddir, wslGatedStat } from '../../../native-chat/wsl-transcript-fs-access' -import { WslTranscriptFsError } from '../../../native-chat/wsl-transcript-fs-error' +import { + wslGatedReaddir, + wslGatedStat +} from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-access' +import { WslTranscriptFsError } from '../../../native-chat/transcript-filesystem/wsl-transcript-fs-error' import { mapWithConcurrency } from '../../../../shared/map-with-concurrency' import type { RetirementScanResult } from './worktree-retirement-backfill-scan' import { normalizeRetirableGeneratedName } from './retirable-generated-name' diff --git a/src/main/wsl/discovery/wsl-running-distros.test.ts b/src/main/wsl/discovery/wsl-running-distros.test.ts index 86b34d66051..06f1996abd1 100644 --- a/src/main/wsl/discovery/wsl-running-distros.test.ts +++ b/src/main/wsl/discovery/wsl-running-distros.test.ts @@ -21,7 +21,7 @@ import { } from './distro-catalog' import { filterPathsToRunningWslDistrosAsync } from '../filesystem/wsl-running-path-filter' import '../execution/wsl-interop-spawn-directory' -import { resetWslTranscriptRunningObserverForTests } from '../../native-chat/wsl-transcript-running-observer' +import { resetWslTranscriptRunningObserverForTests } from '../../native-chat/transcript-filesystem/wsl-transcript-running-observer' async function withPlatform(value: NodeJS.Platform, fn: () => Promise): Promise { const original = process.platform @@ -69,32 +69,4 @@ describe('running WSL distro discovery', () => { expect(runProcessMock).toHaveBeenCalledTimes(3) }) }) - - it.each([true, false])( - 'shares the probe while preserving admission and observer failure policies (strict first: %s)', - async (strictFirst) => { - runProcessMock.mockResolvedValueOnce(exited('Ubuntu\n')) - - await withPlatform('win32', async () => { - await listRunningWslDistrosAsync() - let finishProbe: ((result: ProcessResult) => void) | undefined - runProcessMock.mockImplementationOnce( - () => - new Promise((resolve) => { - finishProbe = resolve - }) - ) - const first = listRunningWslDistrosAsync({ requireConfirmed: strictFirst }) - const second = listRunningWslDistrosAsync({ requireConfirmed: !strictFirst }) - const strict = strictFirst ? first : second - const observer = strictFirst ? second : first - const rejected = expect(strict).rejects.toThrow('discovery is unavailable') - expect(runProcessMock).toHaveBeenCalledTimes(2) - finishProbe?.(FAILED) - await rejected - await expect(observer).resolves.toEqual(['Ubuntu']) - expect(runProcessMock).toHaveBeenCalledTimes(2) - }) - } - ) }) diff --git a/src/shared/agent-launch/picker/agent-launch-picker-legacy-permission.test.ts b/src/shared/agent-launch/picker/agent-launch-picker-legacy-permission.test.ts index f06ff7c48bb..c6dd6e927ce 100644 --- a/src/shared/agent-launch/picker/agent-launch-picker-legacy-permission.test.ts +++ b/src/shared/agent-launch/picker/agent-launch-picker-legacy-permission.test.ts @@ -1,14 +1,9 @@ import { expect, it } from 'vitest' -import { - agentPickerDraft, - editAgentPickerArguments, - editAgentPickerDraft, - type AgentPickerEntry -} from './agent-launch-picker' +import { agentPickerDraft, type AgentPickerEntry } from './agent-launch-picker' import { agentPickerChoiceSeeds } from './agent-launch-picker-defaults' import { agentPickerArgumentPermission } from './agent-launch-picker-permission' -import { projectAgentLaunchProfile } from '../choices/agent-launch-profile' -import '../../../main/native-chat/structured-agent-configured-args' + +import '../../../main/native-chat/agent-launch/structured-agent-configured-args' import '../../../main/claude/structured/launch/claude-structured-launch-args' import '../../../main/codex/structured/launch/codex-structured-launch-args' @@ -26,78 +21,6 @@ const entry: AgentPickerEntry = { ...agentPickerChoiceSeeds('claude', settings) } -it('retains inherited raw permission precedence despite a typed choice; removal affects only this launch', () => { - const draft = editAgentPickerDraft(agentPickerDraft(entry), { permissionMode: 'ask' }) - expect(draft.arguments).toBeUndefined() - const permission = agentPickerArgumentPermission(entry, draft) - expect(permission).toEqual({ - flag: '--dangerously-skip-permissions', - remainingArguments: '', - value: 'full-access', - choice: 'full-access', - opaque: false, - ranges: [ - { - key: 'permissionMode', - value: '', - words: ['--dangerously-skip-permissions'], - start: 0, - end: 30, - lift: false, - owned: false - } - ] - }) - expect( - projectAgentLaunchProfile('claude', settings, { target: 'terminal', choices: draft.choices }) - .args - ).toContain('--dangerously-skip-permissions') - const removed = editAgentPickerArguments(draft, permission?.remainingArguments ?? '') - expect(removed.arguments).toBeNull() - expect(agentPickerArgumentPermission(entry, removed)).toBeUndefined() - expect( - projectAgentLaunchProfile('claude', settings, { - target: 'terminal', - choices: removed.choices, - userArgs: removed.arguments ?? '' - }).args - ).not.toContain('--dangerously-skip-permissions') - expect(settings.agentDefaultArgs.claude).toBe('--dangerously-skip-permissions') - expect(agentPickerArgumentPermission(entry, { ...removed, arguments: undefined })?.choice).toBe( - 'full-access' - ) -}) - -it('shows unliftable launch-owned permission bytes without guessing a native choice and preserves unrelated Arguments', () => { - const draft = editAgentPickerArguments( - agentPickerDraft(entry), - '--permission-mode plan --add-dir docs' - ) - expect(draft.choices.permissionMode).toBeUndefined() - const permission = agentPickerArgumentPermission(entry, draft) - expect(permission).toEqual({ - flag: '--permission-mode plan', - remainingArguments: '--add-dir docs', - value: 'plan', - choice: undefined, - opaque: false, - ranges: [ - { - key: 'permissionMode', - value: '', - words: ['--permission-mode', 'plan'], - start: 0, - end: 22, - lift: false, - owned: false - } - ] - }) - const removed = editAgentPickerArguments(draft, permission?.remainingArguments ?? '') - expect(removed.arguments).toBe('--add-dir docs') - expect(agentPickerArgumentPermission(entry, removed)).toBeUndefined() -}) - it('keeps shell-ambiguous Arguments together rather than inventing a removable permission boundary', () => { const draft = { ...agentPickerDraft(entry), diff --git a/src/shared/native-chat/tools/native-chat-tool-viewed-images-shell-read.test.ts b/src/shared/native-chat/tools/native-chat-tool-viewed-images-shell-read.test.ts new file mode 100644 index 00000000000..9674561432c --- /dev/null +++ b/src/shared/native-chat/tools/native-chat-tool-viewed-images-shell-read.test.ts @@ -0,0 +1,15 @@ +import { expect, it } from 'vitest' + +import { nativeChatToolRunViewedImages } from './native-chat-tool-viewed-images' + +it('shows nothing for a shell read, which gave the agent bytes as text', () => { + const codexCat = { + type: 'tool-call', + name: 'read', + callId: 'c1', + input: { command: 'cat /repo/images/shot.png', cwd: '/repo', path: '/repo/images/shot.png' } + } as const + expect( + nativeChatToolRunViewedImages([codexCat, { type: 'tool-result', callId: 'c1', output: '' }]) + ).toEqual([]) +}) diff --git a/tests/e2e/native-chat-live-history.unit.test.ts b/tests/e2e/native-chat-live-history.unit.test.ts deleted file mode 100644 index 42d381646c2..00000000000 --- a/tests/e2e/native-chat-live-history.unit.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -// @vitest-environment happy-dom - -import { act, createElement } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { useAppStore } from '@/store' - -const { transport, resetTransport } = vi.hoisted(() => { - let listener: (frame: unknown) => void = () => {} - const unsubscribe = vi.fn() - const transport = { - unsubscribe, - readSession: vi.fn(), - subscribe: vi.fn((_args, onFrame) => { - listener = onFrame - return unsubscribe - }), - emit: (frame: unknown) => listener(frame) - } - return { - transport, - resetTransport: () => { - listener = () => {} - transport.readSession.mockReset() - } - } -}) - -vi.mock( - '../../src/renderer/src/components/native-chat/session/terminal/read/native-chat-session-transport', - () => ({ - getNativeChatSessionTransport: () => transport - }) -) - -import { useNativeChatLiveSession } from '../../src/renderer/src/components/native-chat/session/terminal/read/use-native-chat-live-session' -import type { - UseNativeChatLiveSessionArgs, - NativeChatLiveSession -} from '../../src/renderer/src/components/native-chat/session/terminal/read/native-chat-live-session-contract' - -describe('useNativeChatLiveSession — file watcher pagination composition', () => { - const AGENT = 'claude' as const - const SESSION = 'sess-1' - const PANE = 'pane-1' - const roots: Root[] = [] - let latest: NativeChatLiveSession | null = null - - function Probe(props: UseNativeChatLiveSessionArgs): null { - latest = useNativeChatLiveSession(props) - return null - } - - async function flush(): Promise { - await act(async () => { - await Promise.resolve() - await Promise.resolve() - }) - } - - async function render(props: UseNativeChatLiveSessionArgs): Promise { - const container = document.createElement('div') - const root = createRoot(container) - roots.push(root) - await act(async () => { - root.render(createElement(Probe, props)) - }) - await flush() - return root - } - - beforeEach(() => { - // Without it React warns on every act() and may not flush updates inside it. - vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) - useAppStore.setState({ agentStatusByPaneKey: {} }) - }) - - afterEach(() => { - for (const root of roots.splice(0)) { - act(() => root.unmount()) - } - latest = null - vi.clearAllMocks() - vi.unstubAllGlobals() - resetTransport() - }) - - it('keeps the original watcher append after a real 300-to-500 loadEarlier', async () => { - // One budget shared by every step, just under the 30 s test timeout, so only the message changes. - const budget = AbortSignal.timeout(27_000) - const { mkdtemp, writeFile, appendFile, rm } = await import('node:fs/promises') - const { tmpdir } = await import('node:os') - const { join } = await import('node:path') - const { readNativeChatTranscriptTail, subscribeNativeChatTranscript } = - await import('../../src/main/native-chat/transcript-watch') - const { createNativeChatMessageListProjection } = - await import('../../src/renderer/src/components/native-chat/conversation/projection/native-chat-message-list-projection') - const directory = await mkdtemp(join(tmpdir(), 'pr31794-live-')) - const filePath = join(directory, 'session.jsonl') - const line = (n: number) => - `${JSON.stringify({ - type: 'assistant', - uuid: String(n).padStart(4, '0'), - timestamp: '2026-06-01T10:00:00Z', - message: { role: 'assistant', content: `reply ${n}` } - })}\n` - await writeFile(filePath, Array.from({ length: 500 }, (_, i) => line(i + 1)).join('')) - transport.readSession.mockImplementation((_agent, _session, limit) => - readNativeChatTranscriptTail({ - agent: AGENT, - sessionId: SESSION, - filePath, - limit, - sourceOrder: true - }) - ) - const session = () => { - if (!latest) { - throw new Error('No live session') - } - return latest - } - const ids = () => - createNativeChatMessageListProjection()(session().messages).conversation.map((row) => row.id) - await render({ - paneKey: PANE, - agent: AGENT, - sessionId: SESSION, - runtimeEnvironmentId: 'env-history' - }) - // A CI run hit the 30 s test timeout with no step named; each wait names its own stall. - const delivered: string[] = [] - const within = (step: string, promise: Promise): Promise => - Promise.race([ - promise, - new Promise((_, reject) => { - const stalled = (): void => { - const rows = latest ? ids().length : null - const limits = transport.readSession.mock.calls.map((call) => call[2]) - reject( - new Error( - `Stalled at ${step}: ${JSON.stringify({ delivered, rows, readSessionLimits: limits })}` - ) - ) - } - if (budget.aborted) { - stalled() - } else { - budget.addEventListener('abort', stalled, { once: true }) - } - }) - ]) - const initialSnapshotCommitted = Promise.withResolvers() - const appendCommitted = Promise.withResolvers() - const subscription = await subscribeNativeChatTranscript({ - agent: AGENT, - sessionId: SESSION, - filePath, - sourceOrder: true, - initialLimit: 300, - debounceMs: 5, - reconciliationIntervalMs: 20, - onInitialSnapshot: (messages, hasMore) => { - delivered.push(`snapshot:${messages.length}`) - void act(async () => { - transport.emit({ type: 'snapshot', messages, hasMore }) - }).then(initialSnapshotCommitted.resolve, initialSnapshotCommitted.reject) - }, - onAppend: (messages) => { - delivered.push(`append:${messages.length}`) - void act(async () => { - transport.emit({ type: 'appended', messages }) - }).then(appendCommitted.resolve, appendCommitted.reject) - }, - onReplace: (messages, hasMore) => { - delivered.push(`replace:${messages.length}`) - return act(() => transport.emit({ type: 'replacement', messages, hasMore })) - } - }) - try { - await within('initial snapshot', initialSnapshotCommitted.promise) - expect(ids()).toEqual(Array.from({ length: 300 }, (_, i) => String(i + 201).padStart(4, '0'))) - await act(async () => { - await expect(within('loadEarlier 300 to 500', session().loadEarlier())).resolves.toBe( - 'applied' - ) - }) - expect(transport.readSession).toHaveBeenLastCalledWith(AGENT, SESSION, 500, undefined, { - wakeWslDistro: true - }) - expect(ids()).toEqual(Array.from({ length: 500 }, (_, i) => String(i + 1).padStart(4, '0'))) - await appendFile(filePath, line(501)) - await within('watcher append of line 501', appendCommitted.promise) - expect(ids()).toEqual(Array.from({ length: 501 }, (_, i) => String(i + 1).padStart(4, '0'))) - expect(ids().at(-1)).toBe('0501') - expect(transport.subscribe).toHaveBeenCalledOnce() - } finally { - subscription.unsubscribe() - await rm(directory, { recursive: true, force: true }) - } - }) -})