From 74df8bf42e594fb84fea49dd41de2629bd910b1d Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Tue, 3 Nov 2026 23:59:28 -0800 Subject: [PATCH] test: remove 997 low-value test declarations (#38058) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- config/dependency-security-exceptions.json | 6 +- config/flaky-test-pattern-baseline.txt | 5 +- config/patches/dependency-security.md | 6 +- config/reliability-gates.jsonc | 658 +++++------------- config/scripts/ci-shard-assignment.test.mjs | 38 +- config/scripts/ci-shard-timings.json | 113 --- .../dependency-security-patches.test.mjs | 74 +- config/scripts/vitest-node-runtime-files.mjs | 1 - .../rich-markdown/editor-content.test.ts | 52 -- .../use-new-workspace-runtime-context.test.ts | 64 -- .../files/mobile-file-media-download.test.ts | 42 +- .../mobile-dictation-desktop-start.test.ts | 195 ------ .../host-workspace-list-action-error.test.tsx | 181 ----- .../host-workspace-list-row-memo.test.tsx | 205 ------ .../host-worktree-child-navigation.test.tsx | 195 ------ .../workspace-options-sheet.test.tsx | 223 ------ mobile/src/layout/side-safe-area.test.tsx | 194 ------ .../bridge-terminal-output-merge.test.ts | 137 +--- .../bridge/bridge-terminal-key.test.ts | 71 +- .../host-area-leave-session.test.tsx | 170 ----- ...-web-shell-offline-grant-staleness.test.ts | 22 - ...obile-web-shell-update-failure-log.test.ts | 62 +- .../platform/use-native-device-verbs.test.tsx | 40 -- mobile/src/session/TerminalPaneView.test.tsx | 194 ------ .../ai-vault-resume-preparation.test.ts | 224 ------ .../mobile-image-source-picker.test.ts | 100 --- .../mobile-markdown-document-lifetime.test.ts | 222 ------ ...ile-structured-session-status-feed.test.ts | 63 -- ...obileNativeChatView.history-paging.test.ts | 224 ------ ...bile-attachment-operation-lifetime.test.ts | 60 -- .../mobile-native-chat-composer-menu.test.ts | 207 ------ ...mobile-native-chat-draft-reconcile.test.ts | 137 +--- .../mobile-native-chat-draft-store.test.ts | 128 +--- ...-mobile-native-chat-composer-tray.test.tsx | 214 ------ ...-native-chat-stopped-before-start.test.tsx | 216 ------ ...bileNativeChatMessage.paced-reveal.test.ts | 203 ------ .../MobileNativeChatMessage.reasoning.test.ts | 186 ----- ...eNativeChatMessage.subagent-group.test.tsx | 208 ------ .../MobileNativeChatNoticeRow.web.test.tsx | 210 ------ .../MobileNativeChatTurnStatus.test.ts | 58 +- .../mobile-native-chat-open-file.test.ts | 213 ------ .../mobile-native-chat-visual-read.test.ts | 210 ------ .../shell-terminal-key-rail.web.test.tsx | 18 - ...-mobile-session-image-attachments.test.tsx | 198 ------ ...mobile-structured-session-opening.test.tsx | 200 ------ .../session/use-mobile-terminal-paste.test.ts | 117 +--- .../mobile-create-pr-action.test.ts | 251 ------- ...bile-source-control-primary-action.test.ts | 221 ------ .../source-control-reply-schema.test.ts | 199 ------ .../tasks/composer-linked-work-item.test.ts | 239 ------- .../task-source-search-reply-schema.test.ts | 199 ------ ...ile-tasks-task-pagination-actions.test.tsx | 192 ----- .../terminal/document/selection-range.test.ts | 55 -- .../terminal-live-pending-flush-state.test.ts | 125 +--- ...inal-text-field-submit-binding.web.test.ts | 57 -- .../use-terminal-live-input-focus.test.ts | 248 ------- .../mobile-endpoint-supervisor-nudge.test.ts | 47 +- .../mobile-relay-background-lifecycle.test.ts | 200 ------ .../relay-host-reachability-verdict.test.ts | 225 ------ .../relay-host-signed-out-verdict.test.ts | 234 ------- .../mobile-web-bundle-compat.test.ts | 213 ------ .../mobile-web-bundle-fetch-slow-link.test.ts | 16 - mobile/src/worktree/agent-row-display.test.ts | 177 ----- .../acp/acp-session-reopen-failure.test.ts | 22 - ...hook-status-session-tabs-republish.test.ts | 139 ---- .../session-search-index-consumer.ts | 1 - ...ion-search-transcript-dependencies.test.ts | 130 ---- ...session-scanner-discovery-wsl-gate.test.ts | 170 ----- ...d-session-ownership-shell-commands.test.ts | 170 ----- .../bitbucket/pull-request-creation.test.ts | 153 ---- .../cdp/input/cdp-keyboard-us-layout.test.ts | 132 ---- ...r-manager-viewport-partial-failure.test.ts | 153 ---- ...-manager-worker-request-user-agent.test.ts | 132 ---- .../browser-external-app-launch.test.ts | 155 ----- .../claude-api-retry-idle-sweep.test.ts | 138 ---- ...claude-folder-trust-file-wsl-guest.test.ts | 131 ---- ...claude-structured-effort-reporting.test.ts | 160 +---- .../claude-structured-spawn-options.test.ts | 165 ----- .../scanner-attribution-memo.test.ts | 161 ----- .../codex-app-server-session-stop.test.ts | 133 ---- .../daemon-pty-adapter-wsl-quit-cwd.test.ts | 140 ---- .../daemon-pty-startup-delivery.test.ts | 145 ---- .../daemon-stream-backpressure-socket.test.ts | 149 ---- src/main/dsh/dsh-home-patch.test.ts | 74 -- src/main/editor/format-editor-text.test.ts | 41 -- .../worktree-add-timeout-override.test.ts | 142 ---- .../creation/worktree-base-divergence.test.ts | 160 ----- ...rktree-posix-path-case-sensitivity.test.ts | 152 ---- .../git/wsl-process-group-termination.test.ts | 113 --- .../github/work-item-search-semantics.test.ts | 129 ---- .../ipc/notifications-macos-removal.test.ts | 174 ----- .../delivery/pty-ipc-gap-escape-state.test.ts | 124 ---- .../spawn-cwd-wsl-stopped-folder.test.ts | 135 ---- ...sh-target-materialization-real-git.test.ts | 15 - ...pty-spawn-wsl-passive-restore-hold.test.ts | 125 ---- ...spawn-host-env-wsl-passive-restore.test.ts | 137 ---- .../repos/base-ref-default-wsl-gate.test.ts | 142 ---- ...orcad-runtime-maintenance-handlers.test.ts | 137 ---- src/main/ipc/shell-server-ssh-editor.test.ts | 144 ---- src/main/ipc/shell-wsl-owner.test.ts | 143 ---- .../worktree-head-identity-reader.test.ts | 21 - ...wsl-move-orchestrator-host-connect.test.ts | 136 ---- ...ecommission-released-peer-recovery.test.ts | 156 ----- .../memory/collector-darwin-footprint.test.ts | 151 ---- .../adopted-directory-probe.test.ts | 59 -- ...ured-agent-session-queued-stop-row.test.ts | 132 ---- ...ctured-agent-session-repeated-stop.test.ts | 128 ---- ...ured-agent-session-resolved-append.test.ts | 17 - ...agent-session-startup-failure-exit.test.ts | 149 ---- ...ured-agent-session-stopped-history.test.ts | 143 ---- ...red-agent-session-stopping-journal.test.ts | 146 ---- ...session-turn-completion-child-work.test.ts | 140 ---- .../structured-rewind-cut.test.ts | 41 +- src/main/orcad/orcad-launch-contract.test.ts | 141 ---- .../orcad-migration-export-holds.test.ts | 77 +- ...-host-partitioned-ssh-pty-bindings.test.ts | 166 ----- ...ence-ssh-lease-tombstone-retention.test.ts | 171 ----- .../plugins/hooks/plugin-hook-sites.test.ts | 149 ---- .../plugin-catalog-install-guard.test.ts | 28 - src/main/plugins/plugin-secrets-store.test.ts | 78 +-- ...-foreground-process-ps-scan-volume.test.ts | 140 ---- .../local-pty-pending-native-spawn.test.ts | 147 ---- .../gemini-usage-fetcher.fallback.test.ts | 154 ---- .../initial-account-rate-limit-target.test.ts | 163 ----- .../agent-session-runtime-attribution.test.ts | 147 ---- .../agent-terminal-launch-host.test.ts | 155 ----- .../browser/browser-tab-listing.test.ts | 179 ----- src/main/runtime/dsb-terminal-runtime.test.ts | 135 ---- .../opencode-run-lifetime-runtime.test.ts | 140 ---- .../opencode-wsl-folder-launch-host.test.ts | 113 --- ...untime-startup-draft-launch-parity.test.ts | 131 ---- ...ntime-terminal-handle-leaf-removal.test.ts | 115 --- ...obile-session-tabs-shell-selection.spec.ts | 137 ---- ...erminal-sleep-concurrent-worktrees.spec.ts | 153 ---- ...ee-terminal-close-reattached-relay.spec.ts | 152 ---- src/main/runtime/orca-runtime.test.ts | 3 - .../orchestration-mailbox-delivery.test.ts | 1 - ...ation-mailbox-transport-settlement.spec.ts | 152 ---- .../crew/crew-pending-questions.test.ts | 106 --- .../db-task-create-readiness.test.ts | 56 -- .../mobile-web-bundle-window-refusals.test.ts | 54 +- .../structured-agent-session-policy.test.ts | 141 ---- .../worktree-create-navigation.test.ts | 151 ---- ...me-legacy-worker-recovery-lifetime.test.ts | 145 ---- .../runtime-managed-worktree-metadata.test.ts | 139 ---- ...ntime-mobile-structured-tab-status.test.ts | 122 ---- ...time-pairing-desktop-control-grant.test.ts | 36 - .../runtime/runtime-reference-catalog.test.ts | 135 ---- ...epository-registration-host-dedupe.test.ts | 154 ---- .../runtime/selected-review-branch.test.ts | 180 ----- .../runtime/terminal-orphan-topology.test.ts | 62 +- src/main/runtime/terminal-tail-buffer.test.ts | 150 ---- .../lifecycle/bind-first-registration.test.ts | 128 ---- .../recovery/restored-pane-start.test.ts | 159 ----- .../windows-firewall-remote-scope.test.ts | 82 --- .../workspace-lifecycle-commands.test.ts | 160 ----- .../computer-use-rename.integration.test.ts | 37 - .../skill-discovery-wsl-plugins.test.ts | 147 ---- .../orcad-migration-snapshot-resume.test.ts | 13 - .../runtime-ssh-access-verification.test.ts | 150 ---- ...ystem-ssh-loopback-route-admission.test.ts | 24 +- .../http1-compatibility-profile-state.test.ts | 145 ---- src/main/startup/serve-options.test.ts | 163 ----- .../client-agent-dashboard-active.test.ts | 127 ---- src/main/telemetry/wsl-move-telemetry.test.ts | 160 ----- .../discovery/wsl-lost-list-output.test.ts | 159 ----- .../wsl/execution/wsl-command-loop.test.ts | 10 - .../wsl-drive-project-new-default.test.ts | 148 ---- .../wsl/filesystem/wsl-unc-delete.test.ts | 154 ---- .../wsl-orcad-slot-process-probe.test.ts | 40 +- .../cmd-j/task-search-activation.test.ts | 80 +-- .../editor/EditorPanelShell.header.test.tsx | 121 ---- .../combined-diff-section-connection.test.ts | 38 - ...rnalContentEvents.cross-workspace.test.tsx | 127 ---- .../commands/RichMarkdownSlashMenu.test.tsx | 118 ---- .../save/flush/editor-pending-flush.test.ts | 104 --- .../host-list-menus.large-config.test.tsx | 103 --- ...ative-chat-launch-default-adoption.test.ts | 104 --- ...native-chat-runtime-owner-selector.test.ts | 122 ---- .../native-chat-sent-prompt-history.test.ts | 60 -- .../native-chat-work-run-pairing.test.ts | 105 --- ...use-native-chat-context-menu.find.test.tsx | 111 --- .../use-native-chat-retained-session.test.ts | 45 -- ...uctured-agent-session-thread-goal.test.tsx | 36 - .../view/plugin-view-editor-capture.test.tsx | 52 +- .../quick-open-history-ranking.test.ts | 86 --- .../AdvancedNetworkSettingsSection.test.ts | 110 --- .../settings/cli/install/CliSection.test.tsx | 121 ---- .../GeneralWorkspaceSettingsSection.test.tsx | 110 --- .../NotificationHostToggles.test.tsx | 108 --- .../settings/search/settings-search.test.ts | 103 --- .../sidebar/OrcaYamlTrustDialog.test.tsx | 13 - ...-workspace-composer-submit-surface.test.ts | 100 --- ...worktree-card-identity-indicators.test.tsx | 97 --- .../tab-bar/TabBar.os-file-drop.test.tsx | 88 --- .../tab-bar/recent-tab-switching.test.ts | 53 +- .../workspace-cleanup-facet-sort.test.ts | 99 +-- ...gent-hook-completion-fresh-working.test.ts | 122 ---- .../multiple-create-reset.test.ts | 125 ---- ...atus-reconnect-replay-notification.test.ts | 112 --- ...rowser-request-ipc-bridge.fallback.test.ts | 139 ---- .../wsl-host-ipc-bridge-focus.test.ts | 103 --- ...poserState-host-context-boundaries.test.ts | 110 +-- .../src/hooks/useGitHubSlugMetadata.test.tsx | 134 ---- ...ectionPaste.terminal-native-paste.test.tsx | 41 -- .../src/i18n/localized-catalog.test.ts | 96 --- .../src/lib/agent-draft-readiness.test.ts | 51 -- .../src/lib/agent-session-launch-plan.test.ts | 99 --- ...gent-startup-creation-continuation.test.ts | 94 --- .../src/lib/ai-vault-omp-cold-resume.test.ts | 108 --- .../src/lib/browser-palette-search.test.ts | 133 ---- ...hub-work-item-target-compatibility.test.ts | 36 - .../host-mirror-handle-gap-teardown.test.ts | 98 --- .../launch-structured-agent-session.test.ts | 96 --- ...workspace-project-checkout-options.test.ts | 115 --- .../lib/pane-manager/pane-drop-no-op.test.ts | 118 ---- .../pane-terminal-output-scheduler.test.ts | 65 -- ...ping-agent-session-retired-binding.test.ts | 115 --- .../lib/use-tab-agent-process-signals.test.ts | 119 ---- ...untime-environment-pairing-refresh.test.ts | 71 +- ...ronment-ssh-state-read-concurrency.test.ts | 117 ---- ...me-repo-base-ref-default-wsl-retry.test.ts | 129 ---- .../server-browser-profile-reuse.test.ts | 109 --- ...eb-session-tabs-sync-agent-handoff.test.ts | 139 ---- ...abs-sync-remote-main-agent-verdict.test.ts | 104 --- .../agent-status-ack-cleanup.test.ts | 29 - ...me-catalog-merge-removed-env-guard.test.ts | 20 - 227 files changed, 226 insertions(+), 27546 deletions(-) delete mode 100644 mobile/src/hooks/mobile-dictation-desktop-start.test.ts delete mode 100644 mobile/src/host-screen/host-workspace-list-action-error.test.tsx delete mode 100644 mobile/src/host-screen/host-workspace-list-row-memo.test.tsx delete mode 100644 mobile/src/host-screen/host-worktree-child-navigation.test.tsx delete mode 100644 mobile/src/host-screen/workspace-options-sheet.test.tsx delete mode 100644 mobile/src/layout/side-safe-area.test.tsx delete mode 100644 mobile/src/mobile-web-shell/host-area-leave-session.test.tsx delete mode 100644 mobile/src/session/TerminalPaneView.test.tsx delete mode 100644 mobile/src/session/ai-vault-resume-preparation.test.ts delete mode 100644 mobile/src/session/mobile-markdown-document-lifetime.test.ts delete mode 100644 mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts delete mode 100644 mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.test.ts delete mode 100644 mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.test.tsx delete mode 100644 mobile/src/session/native-chat/mobile-native-chat-stopped-before-start.test.tsx delete mode 100644 mobile/src/session/native-chat/transcript/MobileNativeChatMessage.paced-reveal.test.ts delete mode 100644 mobile/src/session/native-chat/transcript/MobileNativeChatMessage.reasoning.test.ts delete mode 100644 mobile/src/session/native-chat/transcript/MobileNativeChatMessage.subagent-group.test.tsx delete mode 100644 mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.web.test.tsx delete mode 100644 mobile/src/session/native-chat/visual/mobile-native-chat-open-file.test.ts delete mode 100644 mobile/src/session/native-chat/visual/mobile-native-chat-visual-read.test.ts delete mode 100644 mobile/src/session/use-mobile-session-image-attachments.test.tsx delete mode 100644 mobile/src/session/use-mobile-structured-session-opening.test.tsx delete mode 100644 mobile/src/source-control/mobile-create-pr-action.test.ts delete mode 100644 mobile/src/source-control/mobile-source-control-primary-action.test.ts delete mode 100644 mobile/src/source-control/source-control-reply-schema.test.ts delete mode 100644 mobile/src/tasks/composer-linked-work-item.test.ts delete mode 100644 mobile/src/tasks/task-source-search-reply-schema.test.ts delete mode 100644 mobile/src/tasks/use-mobile-tasks-task-pagination-actions.test.tsx delete mode 100644 mobile/src/terminal/use-terminal-live-input-focus.test.ts delete mode 100644 mobile/src/transport/mobile-relay-background-lifecycle.test.ts delete mode 100644 mobile/src/transport/relay-host-reachability-verdict.test.ts delete mode 100644 mobile/src/transport/relay-host-signed-out-verdict.test.ts delete mode 100644 mobile/src/transport/web-bundle/mobile-web-bundle-compat.test.ts delete mode 100644 mobile/src/worktree/agent-row-display.test.ts delete mode 100644 src/main/agent-hooks/hook-status-session-tabs-republish.test.ts delete mode 100644 src/main/ai-vault-search/session-search-transcript-dependencies.test.ts delete mode 100644 src/main/ai-vault/session-scanner-discovery-wsl-gate.test.ts delete mode 100644 src/main/ai-vault/structured-session-ownership-shell-commands.test.ts delete mode 100644 src/main/bitbucket/pull-request-creation.test.ts delete mode 100644 src/main/browser/cdp/input/cdp-keyboard-us-layout.test.ts delete mode 100644 src/main/browser/guest-lifecycle/manager/browser-manager-viewport-partial-failure.test.ts delete mode 100644 src/main/browser/guest-lifecycle/manager/browser-manager-worker-request-user-agent.test.ts delete mode 100644 src/main/browser/guest-lifecycle/prompts/browser-external-app-launch.test.ts delete mode 100644 src/main/claude/claude-api-retry-idle-sweep.test.ts delete mode 100644 src/main/claude/claude-folder-trust-file-wsl-guest.test.ts delete mode 100644 src/main/claude/structured/launch/claude-structured-spawn-options.test.ts delete mode 100644 src/main/codex-usage/scanner-attribution-memo.test.ts delete mode 100644 src/main/codex/app-server/codex-app-server-session-stop.test.ts delete mode 100644 src/main/daemon/daemon-pty-adapter-wsl-quit-cwd.test.ts delete mode 100644 src/main/daemon/daemon-pty-startup-delivery.test.ts delete mode 100644 src/main/daemon/daemon-stream-backpressure-socket.test.ts delete mode 100644 src/main/git/worktrees/creation/worktree-add-timeout-override.test.ts delete mode 100644 src/main/git/worktrees/creation/worktree-base-divergence.test.ts delete mode 100644 src/main/git/worktrees/removal/worktree-posix-path-case-sensitivity.test.ts delete mode 100644 src/main/git/wsl-process-group-termination.test.ts delete mode 100644 src/main/github/work-item-search-semantics.test.ts delete mode 100644 src/main/ipc/notifications-macos-removal.test.ts delete mode 100644 src/main/ipc/pty/delivery/pty-ipc-gap-escape-state.test.ts delete mode 100644 src/main/ipc/pty/host-env/spawn-cwd-wsl-stopped-folder.test.ts delete mode 100644 src/main/ipc/pty/pty-spawn-wsl-passive-restore-hold.test.ts delete mode 100644 src/main/ipc/pty/spawn-host-env-wsl-passive-restore.test.ts delete mode 100644 src/main/ipc/repos/base-ref-default-wsl-gate.test.ts delete mode 100644 src/main/ipc/runtime-environments/orcad-runtime-maintenance-handlers.test.ts delete mode 100644 src/main/ipc/shell-server-ssh-editor.test.ts delete mode 100644 src/main/ipc/shell-wsl-owner.test.ts delete mode 100644 src/main/ipc/wsl/wsl-move-orchestrator-host-connect.test.ts delete mode 100644 src/main/managed-server/lifecycle/orcad-decommission-released-peer-recovery.test.ts delete mode 100644 src/main/memory/collector-darwin-footprint.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-queued-stop-row.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-repeated-stop.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-startup-failure-exit.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-stopped-history.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-stopping-journal.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-turn-completion-child-work.test.ts delete mode 100644 src/main/orcad/orcad-launch-contract.test.ts delete mode 100644 src/main/persistence/terminal-topology/persistence-host-partitioned-ssh-pty-bindings.test.ts delete mode 100644 src/main/persistence/terminal-topology/persistence-ssh-lease-tombstone-retention.test.ts delete mode 100644 src/main/plugins/hooks/plugin-hook-sites.test.ts delete mode 100644 src/main/providers/agent-foreground-process-ps-scan-volume.test.ts delete mode 100644 src/main/providers/local-pty/local-pty-pending-native-spawn.test.ts delete mode 100644 src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts delete mode 100644 src/main/rate-limits/initial-account-rate-limit-target.test.ts delete mode 100644 src/main/runtime/agent-session/agent-session-runtime-attribution.test.ts delete mode 100644 src/main/runtime/agent-terminal-launch-host.test.ts delete mode 100644 src/main/runtime/browser/browser-tab-listing.test.ts delete mode 100644 src/main/runtime/dsb-terminal-runtime.test.ts delete mode 100644 src/main/runtime/opencode-run-lifetime-runtime.test.ts delete mode 100644 src/main/runtime/opencode-wsl-folder-launch-host.test.ts delete mode 100644 src/main/runtime/orca-runtime-startup-draft-launch-parity.test.ts delete mode 100644 src/main/runtime/orca-runtime-terminal-handle-leaf-removal.test.ts delete mode 100644 src/main/runtime/orca-runtime-tests/mobile-session-tabs-shell-selection.spec.ts delete mode 100644 src/main/runtime/orca-runtime-tests/terminal-sleep-concurrent-worktrees.spec.ts delete mode 100644 src/main/runtime/orca-runtime-tests/worktree-terminal-close-reattached-relay.spec.ts delete mode 100644 src/main/runtime/orchestration-mailbox-transport-settlement.spec.ts delete mode 100644 src/main/runtime/rpc/methods/structured-agent-session-policy.test.ts delete mode 100644 src/main/runtime/rpc/methods/worktree-create-navigation.test.ts delete mode 100644 src/main/runtime/runtime-legacy-worker-recovery-lifetime.test.ts delete mode 100644 src/main/runtime/runtime-managed-worktree-metadata.test.ts delete mode 100644 src/main/runtime/runtime-mobile-structured-tab-status.test.ts delete mode 100644 src/main/runtime/runtime-reference-catalog.test.ts delete mode 100644 src/main/runtime/runtime-repository-registration-host-dedupe.test.ts delete mode 100644 src/main/runtime/selected-review-branch.test.ts delete mode 100644 src/main/runtime/terminal-tail-buffer.test.ts delete mode 100644 src/main/runtime/terminal/lifecycle/bind-first-registration.test.ts delete mode 100644 src/main/runtime/terminal/recovery/restored-pane-start.test.ts delete mode 100644 src/main/runtime/workspaces/workspace-lifecycle-commands.test.ts delete mode 100644 src/main/skills/skill-discovery-wsl-plugins.test.ts delete mode 100644 src/main/ssh/runtime-ssh-access-verification.test.ts delete mode 100644 src/main/startup/http1-compatibility-profile-state.test.ts delete mode 100644 src/main/startup/serve-options.test.ts delete mode 100644 src/main/telemetry/client-agent-dashboard-active.test.ts delete mode 100644 src/main/telemetry/wsl-move-telemetry.test.ts delete mode 100644 src/main/wsl/discovery/wsl-lost-list-output.test.ts delete mode 100644 src/main/wsl/filesystem/wsl-drive-project-new-default.test.ts delete mode 100644 src/main/wsl/filesystem/wsl-unc-delete.test.ts delete mode 100644 src/renderer/src/components/editor/EditorPanelShell.header.test.tsx delete mode 100644 src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.cross-workspace.test.tsx delete mode 100644 src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.test.tsx delete mode 100644 src/renderer/src/components/editor/save/flush/editor-pending-flush.test.ts delete mode 100644 src/renderer/src/components/host-picker/host-list-menus.large-config.test.tsx delete mode 100644 src/renderer/src/components/native-chat/native-chat-launch-default-adoption.test.ts delete mode 100644 src/renderer/src/components/native-chat/native-chat-runtime-owner-selector.test.ts delete mode 100644 src/renderer/src/components/native-chat/native-chat-work-run-pairing.test.ts delete mode 100644 src/renderer/src/components/native-chat/use-native-chat-context-menu.find.test.tsx delete mode 100644 src/renderer/src/components/settings/advanced/network/AdvancedNetworkSettingsSection.test.ts delete mode 100644 src/renderer/src/components/settings/cli/install/CliSection.test.tsx delete mode 100644 src/renderer/src/components/settings/general/GeneralWorkspaceSettingsSection.test.tsx delete mode 100644 src/renderer/src/components/settings/notifications/NotificationHostToggles.test.tsx delete mode 100644 src/renderer/src/components/settings/search/settings-search.test.ts delete mode 100644 src/renderer/src/components/sidebar/workspace-creation/folder/folder-workspace-composer-submit-surface.test.ts delete mode 100644 src/renderer/src/components/sidebar/worktree-card-identity-indicators.test.tsx delete mode 100644 src/renderer/src/components/tab-bar/TabBar.os-file-drop.test.tsx delete mode 100644 src/renderer/src/hooks/agent-hook-completion-fresh-working.test.ts delete mode 100644 src/renderer/src/hooks/composer-state/multiple-create-reset.test.ts delete mode 100644 src/renderer/src/hooks/ipc-events/agent-status-reconnect-replay-notification.test.ts delete mode 100644 src/renderer/src/hooks/ipc-events/browser-request-ipc-bridge.fallback.test.ts delete mode 100644 src/renderer/src/hooks/ipc-events/wsl-host-ipc-bridge-focus.test.ts delete mode 100644 src/renderer/src/hooks/useGitHubSlugMetadata.test.tsx delete mode 100644 src/renderer/src/i18n/localized-catalog.test.ts delete mode 100644 src/renderer/src/lib/agent-session-launch-plan.test.ts delete mode 100644 src/renderer/src/lib/agent-startup-creation-continuation.test.ts delete mode 100644 src/renderer/src/lib/ai-vault-omp-cold-resume.test.ts delete mode 100644 src/renderer/src/lib/browser-palette-search.test.ts delete mode 100644 src/renderer/src/lib/host-mirror-handle-gap-teardown.test.ts delete mode 100644 src/renderer/src/lib/launch-structured-agent-session.test.ts delete mode 100644 src/renderer/src/lib/new-workspace-project-checkout-options.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/pane-drop-no-op.test.ts delete mode 100644 src/renderer/src/lib/resume-sleeping-agent-session-retired-binding.test.ts delete mode 100644 src/renderer/src/lib/use-tab-agent-process-signals.test.ts delete mode 100644 src/renderer/src/runtime/runtime-environment-ssh-state-read-concurrency.test.ts delete mode 100644 src/renderer/src/runtime/runtime-repo-base-ref-default-wsl-retry.test.ts delete mode 100644 src/renderer/src/runtime/server-browser-profile-reuse.test.ts delete mode 100644 src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts delete mode 100644 src/renderer/src/runtime/web-session-tabs-sync-remote-main-agent-verdict.test.ts diff --git a/config/dependency-security-exceptions.json b/config/dependency-security-exceptions.json index 11d948e64d8..8452cbee592 100644 --- a/config/dependency-security-exceptions.json +++ b/config/dependency-security-exceptions.json @@ -5,7 +5,7 @@ "version": "3.0.3", "advisory": "GHSA-vfj7-8cjw-p6xm", "owner": "Orca maintainers", - "reason": "Parser and AST walkers reject excessive nesting via the patch merged in #31681, covered by dependency-security-patches.test.mjs. Remove when a fixed upstream release is available.", + "reason": "Parser and AST walkers reject excessive nesting via the patch merged in #31681. Remove when a fixed upstream release is available.", "expires": "2026-11-20", "patch": { "path": "patches/braces@3.0.3.patch", @@ -18,7 +18,7 @@ "version": "3.0.3", "advisory": "GHSA-vfj7-8cjw-p6xm", "owner": "Orca maintainers", - "reason": "Parser and AST walkers reject excessive nesting via the patch merged in #31681, covered by dependency-security-patches.test.mjs. Remove when a fixed upstream release is available.", + "reason": "Parser and AST walkers reject excessive nesting via the patch merged in #31681. Remove when a fixed upstream release is available.", "expires": "2026-11-20", "patch": { "path": "../../config/patches/braces@3.0.3.patch", @@ -31,7 +31,7 @@ "version": "3.0.3", "advisory": "GHSA-vfj7-8cjw-p6xm", "owner": "Orca maintainers", - "reason": "Parser and AST walkers reject excessive nesting via the patch merged in #31681, covered by dependency-security-patches.test.mjs. Remove when a fixed upstream release is available.", + "reason": "Parser and AST walkers reject excessive nesting via the patch merged in #31681. Remove when a fixed upstream release is available.", "expires": "2026-11-20", "patch": { "path": "../patches/braces@3.0.3.patch", diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index 8ea46fc0308..4c98cdc4f46 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -64,7 +64,6 @@ 1 test-no-polling-loop:src/main/daemon/daemon-idle-shutdown.test.ts 1 test-no-polling-loop:src/main/daemon/daemon-self-retirement-respawn.test.ts 1 test-no-polling-loop:src/main/daemon/daemon-server.test.ts -1 test-no-polling-loop:src/main/daemon/daemon-stream-backpressure-socket.test.ts 1 test-no-polling-loop:src/main/daemon/headless-emulator-snapshot-cost.bench.test.ts 1 test-no-polling-loop:src/main/daemon/node-pty-conout-worker-errors.test.ts 1 test-no-polling-loop:src/main/daemon/repro-12101-mouse-tracking-survives-agent-death.test.ts @@ -182,7 +181,6 @@ 1 test-no-real-time-wait:src/main/claude-usage/scanner-incremental-reader.test.ts 1 test-no-real-time-wait:src/main/claude/claude-agent-sdk-exit-proof.test.ts 1 test-no-real-time-wait:src/main/claude/claude-agent-sdk-user-message-queue.test.ts -1 test-no-real-time-wait:src/main/claude/claude-api-retry-idle-sweep.test.ts 1 test-no-real-time-wait:src/main/claude/claude-stop-ack-race.test.ts 1 test-no-real-time-wait:src/main/claude/claude-subagent-permission-request.test.ts 1 test-no-real-time-wait:src/main/claude/claude-supervised-stop-real-cli.test.ts @@ -203,7 +201,6 @@ 1 test-no-real-time-wait:src/main/daemon/daemon-self-retirement-respawn.test.ts 1 test-no-real-time-wait:src/main/daemon/daemon-server.test.ts 1 test-no-real-time-wait:src/main/daemon/daemon-shutdown-final-checkpoint-caller-deadline.test.ts -1 test-no-real-time-wait:src/main/daemon/daemon-stream-backpressure-socket.test.ts 1 test-no-real-time-wait:src/main/daemon/daemon-tcc-attribution.test.ts 1 test-no-real-time-wait:src/main/daemon/headless-emulator-snapshot-cost.bench.test.ts 1 test-no-real-time-wait:src/main/daemon/node-pty-windows-output-error.win32.test.ts @@ -1226,7 +1223,7 @@ 3 test-no-real-time-wait:src/main/startup/run-electron-vite-dev-web.test.ts 3 test-no-real-time-wait:src/main/startup/run-electron-vite-dev.test.ts 3 test-no-real-time-wait:src/main/windows/windows-host-job.win32.test.ts -3 test-no-real-time-wait:src/main/wsl/execution/wsl-command-loop.test.ts +2 test-no-real-time-wait:src/main/wsl/execution/wsl-command-loop.test.ts 3 test-no-real-time-wait:src/main/wsl/orcad/wsl-orcad-watch.test.ts 3 test-no-real-time-wait:src/renderer/src/components/terminal-pane/pty-connection-hidden-backlog-snapshot.test.ts 3 test-no-real-time-wait:src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-subscribe-ack-input.test.ts diff --git a/config/patches/dependency-security.md b/config/patches/dependency-security.md index e010ff36b0a..2993a3390ad 100644 --- a/config/patches/dependency-security.md +++ b/config/patches/dependency-security.md @@ -24,9 +24,9 @@ The formatting patch applies to desktop and mobile; mobile's older sprintf-js Mobile patch files stay inside its independent workspace for standalone builds. `config/scripts/dependency-security-patches.test.mjs` exercises excessive numeric -precision in both installed desktop formatting entries, each desktop minimatch copy's `**/..` -limit, and glob abort. It uses the packages installed from the workspace lockfile -without running installs or accessing the registry. The mobile dependency-only +precision in both installed desktop formatting entries. The minimatch pattern-count +and glob read-count examples were retired without replacement. The remaining test +uses packages installed from the workspace lockfile without installs or registry access. The mobile dependency-only regression suite was retired; those package examples have no replacement here. Normal patterns and formatting retain their previous behavior. Excessive brace patterns now fail explicitly; out-of-range numeric precision is clamped instead of throwing a RangeError. diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index ad4f7c095cd..69cd65e2bfc 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -650,16 +650,16 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime", "mobile-relay"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["local", "daemon", "ssh", "remote-runtime", "mobile-relay"], - "coverageNotes": "Production mobile controller, parser, presentation, overlay, view, question, permission and composer run with native surfaces mocked on macOS. Desktop selector groups traverse the real observed-write queue, verified helper, IPC chunker, SSH provider and transport callbacks; paired sends traverse the real dispatcher and runtime writer into that provider. Local/daemon contracts and released-version terminal codecs are covered. Provider labels describe contracts, not physical deployments; physical WSL/Linux/Windows remain gaps. Audit885 removes the isolated observed-send mock-call suite; no replacement-equivalence claim is made. Audit1107 removes the small healthy ordinary SSH-send byte/callback sample. Its exact clear/body/Enter sample alarm is lost; retained slow-answer, cancellation and simulated older-host sequence checks are not claimed as equivalent replacement coverage. The SSH delivery fixture uses controlled fake provider settlement, and its paired path uses the real runtime/dispatcher without a physical SSH or paired wire transport. Audit1278 removes the mobile Stop display/disabled/placeholder suite; its local Stop presentation, repeat-stop and dispatch-visibility alarms are intentionally lost. No equivalent replacement or runtime stopping proof is claimed. Audit1292: The blind OpenCode readiness warning and late launch success/failure content-plus-submit write samples are removed. Their mocked readiness/inspection/delivery alarms are lost; no real PTY delivery or equivalent replacement coverage is claimed. Audit1294 removes mobile/src/session/native-chat/input/mobile-native-chat-permission-send.test.ts. Its mock-only request/capability/private-state/copy alarms are intentionally lost. Remaining tests are not equivalent replacement coverage. Narrowed commands require fresh parent-owned validation; no new runtime, platform or physical mobile evidence is claimed. Audit1302: src/main/ipc/pty/ipc/write-input-acceptance.test.ts: Provider registry and write acceptance/refusal/unverifiable receipts are supplied by fixture; local/daemon/WSL/SSH labels use the same fake provider. Chunking/settlement/remount cases inspect delegated calls or canned receipts without actual provider acceptance/acknowledgement. Lose chunk/refusal/pending/remount and raw-pane routing alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts, mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed.", + "coverageNotes": "Production mobile controller, parser, presentation, overlay, view, question, permission and composer run with native surfaces mocked on macOS. Desktop selector groups traverse the real observed-write queue, verified helper, IPC chunker, SSH provider and transport callbacks; paired sends traverse the real dispatcher and runtime writer into that provider. Local/daemon contracts and released-version terminal codecs are covered. Provider labels describe contracts, not physical deployments; physical WSL/Linux/Windows remain gaps. Audit885 removes the isolated observed-send mock-call suite; no replacement-equivalence claim is made. Audit1107 removes the small healthy ordinary SSH-send byte/callback sample. Its exact clear/body/Enter sample alarm is lost; retained slow-answer, cancellation and simulated older-host sequence checks are not claimed as equivalent replacement coverage. The SSH delivery fixture uses controlled fake provider settlement, and its paired path uses the real runtime/dispatcher without a physical SSH or paired wire transport. Audit1278 removes the mobile Stop display/disabled/placeholder suite; its local Stop presentation, repeat-stop and dispatch-visibility alarms are intentionally lost. No equivalent replacement or runtime stopping proof is claimed. Audit1292: The blind OpenCode readiness warning and late launch success/failure content-plus-submit write samples are removed. Their mocked readiness/inspection/delivery alarms are lost; no real PTY delivery or equivalent replacement coverage is claimed. Audit1294 removes mobile/src/session/native-chat/input/mobile-native-chat-permission-send.test.ts. Its mock-only request/capability/private-state/copy alarms are intentionally lost. Remaining tests are not equivalent replacement coverage. Narrowed commands require fresh parent-owned validation; no new runtime, platform or physical mobile evidence is claimed. Audit1302: src/main/ipc/pty/ipc/write-input-acceptance.test.ts: Provider registry and write acceptance/refusal/unverifiable receipts are supplied by fixture; local/daemon/WSL/SSH labels use the same fake provider. Chunking/settlement/remount cases inspect delegated calls or canned receipts without actual provider acceptance/acknowledgement. Lose chunk/refusal/pending/remount and raw-pane routing alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts, mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1310 removes the supplied-geometry/direct-callback chat history-paging suite. Its prepend/scroll-position examples are intentionally lost; no real rendered anchoring, equivalent replacement or physical phone proof is claimed. Narrowed commands need fresh validation.", "motivatingLinks": ["https://github.com/stablyai/orca/pull/25761"], "invariant": "A prompt occurrence leaves the input region only when an accepted reply answers it (a current host's provider acknowledgment, or an older host's whole-write acceptance as before) or the user collapses it to a strip above the composer, which writes nothing and keeps the card mounted with its partial answer; status-backed occurrences are told apart by the host wait's start, so view toggles and remounts preserve them while real clear/replacement supersedes them, and a transcript-only one is dropped when the view stops observing it; obsolete results cannot change replacements; selector groups wait for complete writes before pacing; refusal or unknown handoff never causes replay; a card retires an unsubmitted composer send without writing under it or losing its text, and the hidden composer cannot send or interrupt; Collapse or Escape folds a card without writing, never during an in-flight answer, and a new occurrence shows expanded again; the phone blocks only Send while a card is expanded and shows one card per occurrence; cancellation stops unsent groups and active dictation can stop.", "oracle": "After acknowledged phone replies with stale host status, the exact card disappears and Send is enabled; unknown, refused or unverifiable-settlement replies retain enabled choices, while an older host's accepted reply dismisses like an acknowledged one; a same-occurrence view toggle retains pending acknowledgment and real replacements discard it. A slow 32 KiB SSH answer completes all chunks before Enter; cancellation preserves issued bytes but omits remaining groups. Paired settlement invokes the actual provider and lost callbacks remain unknown; older hosts deliver ordinary messages, launch and selector groups once and their accepted answers dismiss as before. An answered card stays hidden across a view remount; a collapsed card is a strip that frees the composer and expands back; an identical question in a new wait shows again; a composer send caught by a new card keeps its bubble without submitting or clearing the line under it.", "commands": [ "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2", - "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts --maxWorkers=2", + "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts --maxWorkers=2", "ORCA_BACKGROUND_LAUNCH=1 pnpm test tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts --maxWorkers=1", "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2", - "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts --maxWorkers=2" + "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts --maxWorkers=2" ], "testFiles": [ "src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx", @@ -690,7 +690,6 @@ "mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts", "mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts", "mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts", - "mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts", "mobile/src/session/mobile-prompt-controller.test.ts", "mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts", "mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts", @@ -810,7 +809,9 @@ "Audit1302 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-10-05\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-observed-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2\",\n \"result\": \"passed\",\n \"durationSeconds\": 232.36,\n \"summary\": \"257 tests passed across 19 explicit files, covering production cards, observed sends, existing launch/paste callers, IPC validation, SSH acknowledgment and daemon settlement recovery.\"\n }", "Audit1302 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2\",\n \"result\": \"passed\",\n \"durationSeconds\": 39.77,\n \"summary\": \"Test Files 24 passed (24); Tests 326 passed (326). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"\n }", "Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts, mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed.", - "Archived audit1303 retired-suite historical receipt (JSON; original command/currentCommand/results/counts unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1295-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx --maxWorkers=2\",\"result\":\"passed\",\"durationSeconds\":3.85,\"summary\":\"Test Files 13 passed (13); Tests 205 passed (205). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}" + "Archived audit1303 retired-suite historical receipt (JSON; original command/currentCommand/results/counts unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1295-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx --maxWorkers=2\",\"result\":\"passed\",\"durationSeconds\":3.85,\"summary\":\"Test Files 13 passed (13); Tests 205 passed (205). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Audit1310 removes the supplied-geometry/direct-callback chat history-paging suite. Its prepend/scroll-position examples are intentionally lost; no real rendered anchoring, equivalent replacement or physical phone proof is claimed. Narrowed commands need fresh validation.", + "Archived audit1310 historical receipt (original command/currentCommand/result/count/duration unchanged; not evidence for the shortened suite): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1295-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx --maxWorkers=2\",\"result\":\"passed\",\"durationSeconds\":3.85,\"summary\":\"Test Files 13 passed (13); Tests 205 passed (205). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}" ], "promotionCriteria": [ "Record change-reverted red/green evidence and repeatable remote rendered approval/question/dictation journeys.", @@ -1727,16 +1728,16 @@ "date": "2026-11-04", "runner": "local", "platform": "linux", + "result": "passed", "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts --maxWorkers=2", "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts", - "result": "passed", - "durationSeconds": 38.85, - "summary": "Test Files 51 passed (51); Tests 689 passed (689). Deterministic fixtures; no physical phone or deployed mixed-host claim." + "durationSeconds": 43.39, + "summary": "Test Files 48 passed (48); Tests 673 passed (673). Focused unit selector; native adapters remain mocked where present. Single local execution, not a p95 or cross-platform measurement. Source head 574edb34b8e5448541e7a69aac098958d91ffb06." } ], "runtimeBudget": { "p95Seconds": 30, - "scope": "Unit fixtures; p95 not established." + "scope": "Unit fixtures; p95 not established. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution." }, "flakeHistory": { "status": "not-started", @@ -1756,7 +1757,8 @@ "Test-value audit1293 removes src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts (1 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required.", "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-09-11\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts\",\"result\":\"passed\",\"durationSeconds\":7.71,\"summary\":\"245 tests passed across 22 files. Retained-heap oracle fails on baseline at 68.6 MB and passes under 8 MiB with streaming; gap/schema and cursor-cleanup assertions passed.\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts\"}", "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-10-02\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts\",\"result\":\"passed\",\"durationSeconds\":5.56,\"summary\":\"450 tests passed across 42 files, run as the vitest invocation the test script makes. Damage fails the load with every row kept; the retained-heap, newer-row-past-a-gap and cursor-cleanup assertions passed.\"}", - "Audit1302: src/main/native-chat/agent-session-journal/journal-streaming-replay.test.ts: Keep actual SQLite replay classification of future versions, first sequence damage and missing epoch against independent literal state/damage expectations. Remove GC/heap thresholds and private reducer-call counters plus same-connection PASSIVE checkpoint busy checks, which do not demonstrate an independently blocked checkpoint. Lose assembled replay memory/snapshot scheduling alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim." + "Audit1302: src/main/native-chat/agent-session-journal/journal-streaming-replay.test.ts: Keep actual SQLite replay classification of future versions, first sequence damage and missing epoch against independent literal state/damage expectations. Remove GC/heap thresholds and private reducer-call counters plus same-connection PASSIVE checkpoint busy checks, which do not demonstrate an independently blocked checkpoint. Lose assembled replay memory/snapshot scheduling alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", + "Retired during audit1312 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts\",\"result\":\"passed\",\"durationSeconds\":38.85,\"summary\":\"Test Files 51 passed (51); Tests 689 passed (689). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}" ], "promotionCriteria": ["Retain red/green heap and value assertions and complete CI soak."], "demotionRule": "Keep experimental until cross-platform and soak evidence; investigate failures without weakening content or memory assertions." @@ -1926,12 +1928,12 @@ "providers": ["local", "ssh", "remote-runtime", "mobile"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["local", "mobile"], - "coverageNotes": "Deterministic host and direct/Relay transport tests cover per-operation duplicate protection, caller changes, missing host journal rows, acknowledgement loss, and expiry. Mobile tests cover new-action identity, remount, storage refusal, current attachment paths, and capability changes; renderer tests cover one request per send and the bounded hand-back. Live SSH/remote runtimes and physical iOS/Android lifecycle are not exercised. The test-value audit removes the mock send-bridge suite: skills/options/attachment forwarding and accepted/unknown/queued/rejected callback policy no longer have that isolated alarm. Remaining send/draft tests are not claimed as equivalent coverage. Historical run commands and counts are preserved as historical records; a narrowed selector is not a new passed run. Audit1294 removes mobile/src/session/use-mobile-structured-agent-session-newer-approval-send.test.tsx. Its mock-only request/capability/private-state/copy alarms are intentionally lost. Remaining tests are not equivalent replacement coverage. Narrowed commands require fresh parent-owned validation; no new runtime, platform or physical mobile evidence is claimed. Audit1302: src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts: Direct performSend composition seeds real journal outcomes but adapter dispatch is never reachable from performSend at all (owner lines90–159: delivery runs separately). Thus both never-redispatch claims are unexercised; caller ledgers are not constructed and supplied acceptance callbacks never execute. Remaining assertions copy seeded unknown state or count one queued row. Lose isolated submission replay/dedup characterization; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/mobile-structured-send-delivery.test.ts, mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed.", + "coverageNotes": "Audit1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic host and direct/Relay transport tests cover per-operation duplicate protection, caller changes, missing host journal rows, acknowledgement loss, and expiry. Mobile tests cover new-action identity, remount, storage refusal, current attachment paths, and capability changes; renderer tests cover one request per send and the bounded hand-back. Live SSH/remote runtimes and physical iOS/Android lifecycle are not exercised. The test-value audit removes the mock send-bridge suite: skills/options/attachment forwarding and accepted/unknown/queued/rejected callback policy no longer have that isolated alarm. Remaining send/draft tests are not claimed as equivalent coverage. Historical run commands and counts are preserved as historical records; a narrowed selector is not a new passed run. Audit1294 removes mobile/src/session/use-mobile-structured-agent-session-newer-approval-send.test.tsx. Its mock-only request/capability/private-state/copy alarms are intentionally lost. Remaining tests are not equivalent replacement coverage. Narrowed commands require fresh parent-owned validation; no new runtime, platform or physical mobile evidence is claimed. Audit1302: src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts: Direct performSend composition seeds real journal outcomes but adapter dispatch is never reachable from performSend at all (owner lines90–159: delivery runs separately). Thus both never-redispatch claims are unexercised; caller ledgers are not constructed and supplied acceptance callbacks never execute. Remaining assertions copy seeded unknown state or count one queued row. Lose isolated submission replay/dedup characterization; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/mobile-structured-send-delivery.test.ts, mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1308: src/renderer/src/lib/launch-structured-agent-session.test.ts removed. Unresolvable probe retries, exhausted selector refusal and unknown-outcome create-envelope mock alarms disappear. No equivalent replacement coverage is claimed.", "motivatingLinks": ["https://github.com/stablyai/orca/pull/20133"], "invariant": "One structured-send operation id causes at most one provider dispatch. Each user Send on phone or desktop is a new action with a fresh id and one request; neither automatically resends an ambiguous action. The host retains the original action's outcome, and a later phone Send remains possible with equal text, after remount, or when phone storage fails. Repeated transmission of the same operation remains protected by host deduplication.", - "oracle": "Inject adapter/RPC acknowledgement loss, caller replacement, logical-client close after response, auth recovery with a written request, missing host submissions, legacy pending rows, stale fences, expiry, mobile remount, and phone storage refusal. Assert at most one dispatch for repeated transmission of one operation. For a later phone Send, assert a fresh id and second delivery while the original host entry remains unknown; equal images use current uploaded paths and delivery fields follow current host capabilities.", + "oracle": "The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Inject adapter/RPC acknowledgement loss, caller replacement, logical-client close after response, auth recovery with a written request, missing host submissions, legacy pending rows, stale fences, expiry, mobile remount, and phone storage refusal. Assert at most one dispatch for repeated transmission of one operation. For a later phone Send, assert a fresh id and second delivery while the original host entry remains unknown; equal images use current uploaded paths and delivery fields follow current host capabilities.", "commands": [ - "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts", + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx", "ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-structured-send-new-action.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx ../mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/mobile-web-shell/page-storage-keys.test.ts ../mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts ../mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx ../mobile/src/mobile-web-shell/bridge-host-init.test.ts ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2" ], "testFiles": [ @@ -1943,7 +1945,6 @@ "src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts", "src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts", "src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx", - "src/renderer/src/lib/launch-structured-agent-session.test.ts", "mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts", "mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts", "mobile/src/session/mobile-structured-send-new-action.test.ts", @@ -2024,27 +2025,27 @@ "runner": "local", "platform": "linux", "result": "passed", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts --maxWorkers=2", - "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts", - "durationSeconds": 11.86, - "summary": "Test Files 9 passed (9); Tests 145 passed (145). Focused unit selector; native adapters remain mocked where present. Single local execution, not a p95 or cross-platform measurement. Source head 36536504876bf7fa79c79256377cde97f1f3a133." + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx --maxWorkers=2", + "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx", + "durationSeconds": 11.53, + "summary": "Test Files 8 passed (8); Tests 142 passed (142). Focused unit selector; native adapters remain mocked where present. Single local execution, not a p95 or cross-platform measurement. Source head 574edb34b8e5448541e7a69aac098958d91ffb06." } ], "runtimeBudget": { "p95Seconds": 45, - "scope": "Two deterministic unit commands; initial target pending CI soak. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution." + "scope": "Historical pre-audit selector budget; changed selectors have no new runtime measurement. Unchanged receipts remain scoped to their own commands. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution." }, "flakeHistory": { "status": "not-started", - "evidence": "Focused local runs pass; repeated CI history is not established." + "evidence": "Changed selectors have no new soak result after Audit1308; any retained unchanged-source receipts apply only to their own commands. Historical account: Focused local runs pass; repeated CI history is not established." }, "redGreenEvidence": { - "status": "partial", - "evidence": "The exact phone acknowledgement-loss/later-equal-Send test fails twice with the original sender and classifier restored byte-for-byte: the second Send returns unknown under the earlier id. It passes with per-action identity. Existing host tests also pin cross-caller recovery, missing submissions, and legacy pending rows against repeat dispatch; those host paths were not changed." + "status": "missing", + "evidence": "Audit1308 changed referenced test sources; no new red/green validation of the reduced set. Historical account: The exact phone acknowledgement-loss/later-equal-Send test fails twice with the original sender and classifier restored byte-for-byte: the second Send returns unknown under the earlier id. It passes with per-action identity. Existing host tests also pin cross-caller recovery, missing submissions, and legacy pending rows against repeat dispatch; those host paths were not changed." }, "performanceBudget": { "required": true, - "evidence": "No polling, timers, subprocesses, or provider fanout were added. The phone send path no longer reads or writes AsyncStorage or hashes image contents. The existing host admission, pre-effect unknown record, and settlement transactions are unchanged. No capacity-scale host fsync benchmark is recorded." + "evidence": "Historical account for the pre-audit test set; removed work-count alarms and changed selectors have no fresh measurement. No polling, timers, subprocesses, or provider fanout were added. The phone send path no longer reads or writes AsyncStorage or hashes image contents. The existing host admission, pre-effect unknown record, and settlement transactions are unchanged. No capacity-scale host fsync benchmark is recorded." }, "promotionCriteria": [ "Soak both commands in CI with zero unexplained flakes.", @@ -2064,7 +2065,11 @@ "Audit1302 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-10-13\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 6.47,\n \"summary\": \"Eleven focused host, shared, renderer and orchestration files passed 196 tests, including unknown send outcomes, reused message IDs and host replay without duplicate dispatch. The send-idempotency suite pins only that an unknown outcome is never resent; the removed refusal-retry matrix has no replacement here.\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts\"\n }", "Audit1302 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts --maxWorkers=2\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 15.7,\n \"summary\": \"Test Files 10 passed (10); Tests 149 passed (149). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"\n }", "Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/mobile-structured-send-delivery.test.ts, mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed.", - "Archived audit1303 retired-suite historical receipt (JSON; original command/currentCommand/results/counts unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1295-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config mobile/vitest.config.ts mobile/src/session/mobile-structured-send-new-action.test.ts mobile/src/session/use-mobile-structured-agent-session-send.test.tsx mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx mobile/src/session/mobile-structured-send-delivery.test.ts mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx mobile/src/session/use-mobile-structured-agent-session.test.tsx mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts mobile/src/mobile-web-shell/page-storage-keys.test.ts mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx mobile/src/mobile-web-shell/bridge-host-init.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/rpc-client-delivery-ambiguity.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-structured-send-new-action.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx ../mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/mobile-web-shell/page-storage-keys.test.ts ../mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts ../mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx ../mobile/src/mobile-web-shell/bridge-host-init.test.ts ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\"result\":\"passed\",\"durationSeconds\":5.5,\"summary\":\"Test Files 17 passed (17); Tests 260 passed (260). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}" + "Archived audit1303 retired-suite historical receipt (JSON; original command/currentCommand/results/counts unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1295-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config mobile/vitest.config.ts mobile/src/session/mobile-structured-send-new-action.test.ts mobile/src/session/use-mobile-structured-agent-session-send.test.tsx mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx mobile/src/session/mobile-structured-send-delivery.test.ts mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx mobile/src/session/use-mobile-structured-agent-session.test.tsx mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts mobile/src/mobile-web-shell/page-storage-keys.test.ts mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx mobile/src/mobile-web-shell/bridge-host-init.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/rpc-client-delivery-ambiguity.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-structured-send-new-action.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx ../mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/mobile-web-shell/page-storage-keys.test.ts ../mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts ../mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx ../mobile/src/mobile-web-shell/bridge-host-init.test.ts ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\"result\":\"passed\",\"durationSeconds\":5.5,\"summary\":\"Test Files 17 passed (17); Tests 260 passed (260). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Audit1308: src/renderer/src/lib/launch-structured-agent-session.test.ts removed. Unresolvable probe retries, exhausted selector refusal and unknown-outcome create-envelope mock alarms disappear. No equivalent replacement coverage is claimed.", + "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-10-13\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 6.47,\n \"summary\": \"Eleven focused host, shared, renderer and orchestration files passed 196 tests, including unknown send outcomes, reused message IDs and host replay without duplicate dispatch. The send-idempotency suite pins only that an unknown outcome is never resent; the removed refusal-retry matrix has no replacement here.\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts\"\n }", + "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts --maxWorkers=2\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 15.7,\n \"summary\": \"Test Files 10 passed (10); Tests 149 passed (149). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"\n }", + "Retired during audit1312 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"result\":\"passed\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session/lifecycle/agent-session-operation-ledger.test.ts src/shared/agent-session/requests/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts\",\"durationSeconds\":11.86,\"summary\":\"Test Files 9 passed (9); Tests 145 passed (145). Focused unit selector; native adapters remain mocked where present. Single local execution, not a p95 or cross-platform measurement. Source head 36536504876bf7fa79c79256377cde97f1f3a133.\"}" ], "demotionRule": "Keep experimental or demote if one operation dispatches twice, uncertainty is reported as definite rejection, a later phone Send reuses the earlier action or is blocked by phone storage, or the focused commands flake without a diagnosed harness defect." }, @@ -2304,16 +2309,16 @@ "date": "2026-11-04", "runner": "local", "platform": "linux", + "result": "passed", "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-journal --maxWorkers=2", "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-journal", - "result": "passed", - "durationSeconds": 35.19, - "summary": "Test Files 51 passed (51); Tests 689 passed (689). Deterministic fixtures; no physical phone or deployed mixed-host claim." + "durationSeconds": 35.55, + "summary": "Test Files 48 passed (48); Tests 673 passed (673). Focused unit selector; native adapters remain mocked where present. Single local execution, not a p95 or cross-platform measurement. Source head 574edb34b8e5448541e7a69aac098958d91ffb06." } ], "runtimeBudget": { "p95Seconds": 30, - "scope": "Real SQLite journal unit and production subscriber tests; no launched app." + "scope": "Real SQLite journal unit and production subscriber tests; no launched app. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution." }, "flakeHistory": { "status": "not-started", @@ -2331,7 +2336,8 @@ "Linux and Windows execution and live SSH transport have not been exercised.", "The existing full reduced-state snapshot and batch projection cost are outside this SQL read budget.", "Test-value audit1293 removes src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts (1 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required.", - "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-09-07\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-journal\",\"result\":\"passed\",\"durationSeconds\":9.94,\"summary\":\"214 tests passed across 19 files, including actual SQLite row and JSON parse counts through production subscriber catch-up.\"}" + "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-09-07\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-journal\",\"result\":\"passed\",\"durationSeconds\":9.94,\"summary\":\"214 tests passed across 19 files, including actual SQLite row and JSON parse counts through production subscriber catch-up.\"}", + "Retired during audit1312 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-journal --maxWorkers=2\",\"currentCommand\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-journal\",\"result\":\"passed\",\"durationSeconds\":35.19,\"summary\":\"Test Files 51 passed (51); Tests 689 passed (689). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}" ], "promotionCriteria": [ "Complete CI soak requirements while preserving the deterministic row budget and pagination oracles." @@ -2732,14 +2738,14 @@ "providers": ["local", "daemon", "ssh", "remote-runtime"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["local", "daemon", "ssh", "remote-runtime"], - "coverageNotes": "Shared primitive and status/title parser tests cover provider-independent string behavior on macOS. The status pending-storage suite now retains only its raw UTF-16 parser fixture; its site ownership spy, large-chunk replay and segmented pending-cap fixture were removed. The removed terminal normalizer suite has no replacement-equivalence claim. Native execution, process ownership, wire formats and mobile UI were not validated by this audit. Audit1287 removes the mixed-terminator/malformed-frame example from agent-status-osc.test.ts. Its earliest mixed BEL/ST, malformed-frame removal and individual payload-offset alarms are intentionally lost. The every-split and pending-cap registrations remain; no equivalent replacement is claimed.", + "coverageNotes": "Shared primitive and status/title parser tests cover provider-independent string behavior on macOS. The status pending-storage suite now retains only its raw UTF-16 parser fixture; its site ownership spy, large-chunk replay and segmented pending-cap fixture were removed. The removed terminal normalizer suite has no replacement-equivalence claim. Native execution, process ownership, wire formats and mobile UI were not validated by this audit. Audit1287 removes the mixed-terminator/malformed-frame example from agent-status-osc.test.ts. Its earliest mixed BEL/ST, malformed-frame removal and individual payload-offset alarms are intentionally lost. The every-split and pending-cap registrations remain; no equivalent replacement is claimed. Audit1313: src/main/runtime/terminal-tail-buffer.test.ts: Random warm/cold comparisons use the same production function as oracle; remaining case counts String.prototype charCodeAt against a private work threshold. Lose cache-total equivalence and prefix-work alarm without replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", "motivatingLinks": [ "https://github.com/stablyai/orca/blob/main/src/shared/agent-status/transport/agent-status-osc.test.ts" ], "invariant": "Small incomplete status, ANSI and title controls must not retain the larger consumed output strings they were sliced from. Every retained control fragment is routed through ownRetainedString, which preserves exact UTF-16, current caps and trimming, payload order and clean-output offsets on both the Buffer and the code-unit fallback copier.", "oracle": "The primitive forced-GC fixture measures retained backing storage. The remaining title-site spy and raw UTF-16 status/title parser fidelity checks do not establish status-site or terminal-normalizer backing-storage retention. The status ownership spy, large-chunk replay and segmented pending-cap fixture, along with the earlier normalizer per-site spy and fidelity suite, were removed as low-value during the test audit. The retained status UTF-16 fixture checks exact prompt bytes and offsets across BEL/ST completion; it does not measure retained memory.", "commands": [ - "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts" + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-whitespace.test.ts" ], "testFiles": [ "src/shared/own-retained-string.test.ts", @@ -2748,7 +2754,6 @@ "src/shared/agent-status/contracts/agent-status-types.test.ts", "src/shared/osc-title-scan-tail-retention.test.ts", "src/shared/osc-title-scan-tail.test.ts", - "src/main/runtime/terminal-tail-buffer.test.ts", "src/main/runtime/terminal-tail-whitespace.test.ts" ], "assertionRefs": [ @@ -2780,16 +2785,16 @@ "date": "2026-11-04", "runner": "local", "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts --maxWorkers=2", - "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts", "result": "passed", - "durationSeconds": 4.64, - "summary": "Test Files 8 passed (8); Tests 48 passed (48). Fixture correctness only; no timing, work-count or retained-heap conclusion." + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-whitespace.test.ts --maxWorkers=2", + "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-whitespace.test.ts", + "durationSeconds": 3.39, + "summary": "Test Files 7 passed (7); Tests 39 passed (39). Focused unit selector; native adapters remain mocked where present. Single local execution, not a p95 or cross-platform measurement. Source head 574edb34b8e5448541e7a69aac098958d91ffb06." } ], "runtimeBudget": { "p95Seconds": 60, - "scope": "Focused unit/provider-contract suite; local runtime budget, not an established p95." + "scope": "Focused unit/provider-contract suite; local runtime budget, not an established p95. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution." }, "flakeHistory": { "status": "not-started", @@ -2811,7 +2816,9 @@ "Tail-buffer row retention is a larger instance of the same pattern and is not covered by this gate.", "The status-site ownRetainedString spy, large-chunk byte-exact replay and segmented at-cap/over-cap fixture were removed during the test audit. The old assertion reference drops an owned frame that grows past the pending cap was already stale and is removed too. Status-site retention and these dedicated large-replay/cap alarms are no longer established by this suite; raw UTF-16 fidelity and primitive memory checks are not equivalent replacements. No obligation closure or waiver of historical budgets is claimed.", "Audit1287 removes the mixed-terminator/malformed-frame example from agent-status-osc.test.ts. Its earliest mixed BEL/ST, malformed-frame removal and individual payload-offset alarms are intentionally lost. The every-split and pending-cap registrations remain; no equivalent replacement is claimed.", - "Audit1287 retires this complete historical receipt after test removals; its old counts/results do not validate the reduced suite. Original receipt verbatim:\n{\n \"date\": \"2026-09-07\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status-osc.test.ts src/shared/agent-status-osc-pending-retention.test.ts src/shared/agent-status-types.test.ts src/main/runtime/terminal-ansi-pending-retention.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 11.4,\n \"summary\": \"114 tests passed across nine primitive, parser, payload, normalization, title and terminal-buffer files.\"\n }" + "Audit1287 retires this complete historical receipt after test removals; its old counts/results do not validate the reduced suite. Original receipt verbatim:\n{\n \"date\": \"2026-09-07\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status-osc.test.ts src/shared/agent-status-osc-pending-retention.test.ts src/shared/agent-status-types.test.ts src/main/runtime/terminal-ansi-pending-retention.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 11.4,\n \"summary\": \"114 tests passed across nine primitive, parser, payload, normalization, title and terminal-buffer files.\"\n }", + "Audit1313: src/main/runtime/terminal-tail-buffer.test.ts: Random warm/cold comparisons use the same production function as oracle; remaining case counts String.prototype charCodeAt against a private work threshold. Lose cache-total equivalence and prefix-work alarm without replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", + "Audit1313 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts --maxWorkers=2\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/own-retained-string.test.ts src/shared/agent-status/transport/agent-status-osc.test.ts src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts src/shared/agent-status/contracts/agent-status-types.test.ts src/shared/osc-title-scan-tail-retention.test.ts src/shared/osc-title-scan-tail.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/terminal-tail-whitespace.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 4.64,\n \"summary\": \"Test Files 8 passed (8); Tests 48 passed (48). Fixture correctness only; no timing, work-count or retained-heap conclusion.\"\n }" ], "promotionCriteria": [ "Complete CI soak with zero unexplained flakes and preserve the observable oracle." @@ -2898,18 +2905,17 @@ "surfaces": ["terminal transcript preview", "fullscreen TUI scrolling"], "platforms": ["macos", "linux", "windows"], "providers": ["local", "daemon", "ssh", "remote-runtime"], - "coveredPlatforms": ["macos"], + "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["local", "daemon"], - "coverageNotes": "The trim operation is platform-independent and preserves the same spaces/tabs policy for all providers. Real Pi 0.84.2 was exercised in a hidden macOS Electron renderer through CDP using a folder workspace.", + "coverageNotes": "The trim operation is platform-independent and preserves the same spaces/tabs policy for all providers. Real Pi 0.84.2 was exercised in a hidden macOS Electron renderer through CDP using a folder workspace. Audit1313: src/main/runtime/terminal-tail-buffer.test.ts: Random warm/cold comparisons use the same production function as oracle; remaining case counts String.prototype charCodeAt against a private work threshold. Lose cache-total equivalence and prefix-work alarm without replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", "motivatingLinks": ["https://github.com/stablyai/orca/issues/14770"], "invariant": "Transcript preview trimming preserves internal whitespace and terminal read contents without quadratic main-process work on padded fullscreen redraws.", "oracle": "Preserve 32,000 spaces before a marker while trimming trailing spaces/tabs in both retained-row and carried-prefix redraw paths; four redraws must finish within 500 ms. Existing tail equivalence tests preserve cursor, retention, and pagination behavior.", "commands": [ - "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/terminal-tail-whitespace.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/retained-tail-redraw-window.equivalence.test.ts" + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/terminal-tail-whitespace.test.ts src/main/runtime/retained-tail-redraw-window.equivalence.test.ts" ], "testFiles": [ "src/main/runtime/terminal-tail-whitespace.test.ts", - "src/main/runtime/terminal-tail-buffer.test.ts", "src/main/runtime/retained-tail-redraw-window.equivalence.test.ts" ], "assertionRefs": [ @@ -2923,18 +2929,19 @@ ], "evidenceRuns": [ { - "date": "2026-09-06", + "date": "2026-11-04", "runner": "local", - "platform": "macos", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/terminal-tail-whitespace.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/retained-tail-redraw-window.equivalence.test.ts", + "platform": "linux", "result": "passed", - "durationSeconds": 3.96, - "summary": "17 tests passed. Before the fix both padding budget cases failed, taking approximately 1.7 seconds each." + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/runtime/terminal-tail-whitespace.test.ts src/main/runtime/retained-tail-redraw-window.equivalence.test.ts --maxWorkers=2", + "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/terminal-tail-whitespace.test.ts src/main/runtime/retained-tail-redraw-window.equivalence.test.ts", + "durationSeconds": 2.82, + "summary": "Test Files 2 passed (2); Tests 4 passed (4). Focused unit selector; native adapters remain mocked where present. Single local execution, not a p95 or cross-platform measurement. Source head 574edb34b8e5448541e7a69aac098958d91ffb06." } ], "runtimeBudget": { "p95Seconds": 30, - "scope": "Three unit test files; padding cases allow 500 ms for four redraws." + "scope": "Three unit test files; padding cases allow 500 ms for four redraws. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution." }, "flakeHistory": { "status": "not-started", @@ -2950,7 +2957,9 @@ }, "knownGaps": [ "The user manually compared the fixed dev app with production and confirmed improved responsiveness. A live Terminal.app comparison was not exercised; timing measurements used CDP wheel events.", - "Linux, Windows, and live SSH rendering were not exercised; the shared trimming behavior is covered by unit tests." + "Linux, Windows, and live SSH rendering were not exercised; the shared trimming behavior is covered by unit tests.", + "Audit1313: src/main/runtime/terminal-tail-buffer.test.ts: Random warm/cold comparisons use the same production function as oracle; remaining case counts String.prototype charCodeAt against a private work threshold. Lose cache-total equivalence and prefix-work alarm without replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", + "Audit1313 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-09-06\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/terminal-tail-whitespace.test.ts src/main/runtime/terminal-tail-buffer.test.ts src/main/runtime/retained-tail-redraw-window.equivalence.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 3.96,\n \"summary\": \"17 tests passed. Before the fix both padding budget cases failed, taking approximately 1.7 seconds each.\"\n }" ], "promotionCriteria": [ "Complete CI soak requirements and retain the padding budget and tail equivalence oracles." @@ -3163,13 +3172,13 @@ "providers": ["local", "ssh", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "ssh", "remote-runtime"], - "coverageNotes": "Deterministic candidate-builder contracts inject local and paired-runtime worktrees with the same bare id into the real aggregated tab buckets, including folder workspaces and a paired runtime transporting an SSH-owned worktree. A mirror contract preserves distinct same-id runtime tabs. A paired Electron journey verifies both visible browser rows and both visible simulator rows, then requires each selection to render its owning active tab and update backing host-qualified state. The test-value audit narrowed simulator-palette-search.test.ts to the recency tie, same-id host isolation, ambiguous legacy host rejection and oversized-input getter tripwire. Supplied timestamp, empty-query context ordering, host projection, alias/range, metadata matching, active-tab score, over-token/whitespace and malformed display-name fallback examples were removed. Their separate alarms are lost; retained candidate and activation contracts do not establish equivalent coverage. Historical run commands and results remain historical. The audit also removed the standalone workspace-tab-palette-search suite: supplied row, title, metadata, alias, range, ranking and ownership examples no longer provide their separate alarms. Other retained contracts are not replacement coverage. The mirror-identity fixture now retains same-id sibling-runtime isolation and the repeated-frame subscriber tripwire only. Supplied field, owner, alias and mapping examples are removed; their separate alarms are lost. The retained sibling-runtime case is not equivalent coverage for them. The test-value audit removed tabs-open-close-lifecycle host-stamping examples and ordinary tab/group operations from tabs-pane-layout-operations. Local-null, explicit-runtime and SSH-without-active-host stamp assertions are lost; retained mobile/MRU and subscriber/atomic split cases do not replace them. Candidate and live Cmd-J evidence retains its own scope. Historical commands, outcomes and narratives remain historical.", + "coverageNotes": "Audit1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic candidate-builder contracts inject local and paired-runtime worktrees with the same bare id into the real aggregated tab buckets, including folder workspaces and a paired runtime transporting an SSH-owned worktree. A mirror contract preserves distinct same-id runtime tabs. A paired Electron journey verifies both visible browser rows and both visible simulator rows, then requires each selection to render its owning active tab and update backing host-qualified state. The test-value audit narrowed simulator-palette-search.test.ts to the recency tie, same-id host isolation, ambiguous legacy host rejection and oversized-input getter tripwire. Supplied timestamp, empty-query context ordering, host projection, alias/range, metadata matching, active-tab score, over-token/whitespace and malformed display-name fallback examples were removed. Their separate alarms are lost; retained candidate and activation contracts do not establish equivalent coverage. Historical run commands and results remain historical. The audit also removed the standalone workspace-tab-palette-search suite: supplied row, title, metadata, alias, range, ranking and ownership examples no longer provide their separate alarms. Other retained contracts are not replacement coverage. The mirror-identity fixture now retains same-id sibling-runtime isolation and the repeated-frame subscriber tripwire only. Supplied field, owner, alias and mapping examples are removed; their separate alarms are lost. The retained sibling-runtime case is not equivalent coverage for them. The test-value audit removed tabs-open-close-lifecycle host-stamping examples and ordinary tab/group operations from tabs-pane-layout-operations. Local-null, explicit-runtime and SSH-without-active-host stamp assertions are lost; retained mobile/MRU and subscriber/atomic split cases do not replace them. Candidate and live Cmd-J evidence retains its own scope. Historical commands, outcomes and narratives remain historical. Audit1308: src/renderer/src/lib/browser-palette-search.test.ts removed. Browser-palette token and pasted-query size-guard work alarms disappear. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-4965", "https://github.com/stablyai/orca/pull/15371" ], "invariant": "Candidate identity is the tuple of execution-host identity and worktree identity: same-id sibling hosts retain separate correct browser, simulator, and workspace tabs, each tab appears exactly once, and selection activates its owning host.", - "oracle": "Aggregate two hosts' tabs under one intentionally colliding worktree id, stamp each unified tab with its execution host, and require exactly one candidate per owner. Repeat with an SSH physical owner transported by a paired-runtime alias. Independently require each browser and simulator Cmd-J selection to update activeWorkspaceExecutionHostId plus its owning workspace/group and render the selected tab active. Ambiguous legacy tabs without ownership fail closed.", + "oracle": "The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Aggregate two hosts' tabs under one intentionally colliding worktree id, stamp each unified tab with its execution host, and require exactly one candidate per owner. Repeat with an SSH physical owner transported by a paired-runtime alias. Independently require each browser and simulator Cmd-J selection to update activeWorkspaceExecutionHostId plus its owning workspace/group and render the selected tab active. Ambiguous legacy tabs without ownership fail closed.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx", @@ -3177,9 +3186,9 @@ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/lib/open-mobile-emulator-tab.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/lib/open-mobile-emulator-tab.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", "pnpm exec playwright test tests/e2e/paired-cmd-j-host-qualified-tabs.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", "SKIP_BUILD=1 pnpm exec playwright test tests/e2e/paired-cmd-j-host-qualified-tabs.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1" ], @@ -3189,15 +3198,14 @@ "src/renderer/src/components/tab-bar/use-open-tab-search.test.ts", "src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts", "src/renderer/src/lib/browser-palette-page-entries.test.ts", - "src/renderer/src/lib/browser-palette-search.test.ts", "src/renderer/src/lib/simulator-palette-search.test.ts", "src/renderer/src/lib/browser-page-palette-activation.test.ts", "src/renderer/src/lib/simulator-tab-palette-activation.test.ts", "src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts", "src/renderer/src/lib/open-mobile-emulator-tab.test.ts", "src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts", - "src/shared/workspace-session/workspace-session-schema.test.ts", - "tests/e2e/paired-cmd-j-host-qualified-tabs.spec.ts" + "tests/e2e/paired-cmd-j-host-qualified-tabs.spec.ts", + "src/shared/workspace-session/workspace-session-schema.test.ts" ], "assertionRefs": [ { @@ -3273,16 +3281,6 @@ "summary": "The byte-identical oracle on affected baseline 012e9f410c failed 4 tests and passed 24: each same-id browser and simulator tab was routed through both hosts, and ambiguous unstamped rows were duplicated instead of failing closed.", "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts" }, - { - "date": "2026-08-20", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts", - "result": "passed", - "durationSeconds": 2.3, - "summary": "6 test files passed, 104 tests passed on the STA-4965 candidate. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/shared/workspace-session/workspace-session-schema.test.ts" - }, { "date": "2026-08-20", "runner": "local", @@ -3313,16 +3311,6 @@ "summary": "The same oracle on fe45b73c20 with only browser-palette-page-entries.ts and simulator-palette-search.ts restored from origin/main failed the same 4 tests and passed 24; restored candidate files passed all 28.", "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts" }, - { - "date": "2026-08-20", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts", - "result": "passed", - "durationSeconds": 10.5, - "summary": "The review-hardened candidate passed 189/189 focused contracts, including direct browser-result host fallback, SSH plus paired-runtime current-row aliases, explicit-owner fail-closed behavior, generic workspace rows, simulator reuse, local creation stamping, activation, and same-id distinct-runtime mirror preservation. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts" - }, { "date": "2026-08-20", "runner": "local", @@ -3333,16 +3321,6 @@ "summary": "The final-review oracle failed 4/16 before the follow-up: same-id folder simulator reuse selected no owning tab, inactive local simulator creation was unstamped, the paired SSH/runtime alias resolved local metadata, and an explicit unknown owner fell back locally. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage.", "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts" }, - { - "date": "2026-08-21", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts", - "result": "passed", - "durationSeconds": 24.3, - "summary": "The OpenCode-review-hardened candidate passed 225/225 focused contracts, including unfiltered ownership ambiguity under host filters and scoped open-tab search, review-cache ownership, rendered palette repo metadata, folder workspaces, inactive local simulator creation, paired SSH/runtime display metadata, and explicit-owner fail-closed resolution. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage." - }, { "date": "2026-08-20", "runner": "local", @@ -3412,23 +3390,33 @@ "durationSeconds": 20.6, "summary": "After a fresh build exposed an unrelated mirrored terminal entering the broad backing-state diagnostic, the exact built candidate passed with that oracle scoped to the four seeded browser/simulator tabs; rendered active-tab and backing execution-host assertions remained green.", "currentCommand": "SKIP_BUILD=1 pnpm exec playwright test tests/e2e/paired-cmd-j-host-qualified-tabs.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1" + }, + { + "date": "2026-08-20", + "runner": "local", + "platform": "macos", + "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts", + "result": "passed", + "durationSeconds": 2.3, + "summary": "6 test files passed, 104 tests passed on the STA-4965 candidate. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.", + "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/shared/workspace-session/workspace-session-schema.test.ts" } ], "runtimeBudget": { "p95Seconds": 180, - "scope": "focused deterministic contracts plus one paired Electron journey" + "scope": "Historical pre-audit selector budget; changed selectors have no new runtime measurement. Unchanged receipts remain scoped to their own commands." }, "flakeHistory": { "status": "not-started", - "evidence": "The candidate contract has no timers. Initial Electron attempts exposed seeded-worktree discovery and programmatic keyboard-selection harness races before the ownership assertions; soak history is not yet available." + "evidence": "Changed selectors have no new soak result after Audit1308; any retained unchanged-source receipts apply only to their own commands. Historical account: The candidate contract has no timers. Initial Electron attempts exposed seeded-worktree discovery and programmatic keyboard-selection harness races before the ownership assertions; soak history is not yet available." }, "redGreenEvidence": { - "status": "complete", - "evidence": "Byte-identical browser and simulator candidate tests failed 4/28 on affected baseline 012e9f410c, passed 28/28 on candidate fe45b73c20, failed the same 4/28 when only the two candidate filters were disabled, and returned to 28/28 after restoration. Review-round topology contracts failed 4/40 before the alias/generic/local-stamping follow-up; final-review contracts then failed 4/16 before folder/background/worktree-metadata hardening, 1/6 before paired repository-metadata hardening, 1/7 before unavailable-runtime fail-closed hardening, 1/12 with the component's physical-host bypass restored, 1/6 before review-cache ownership used the same resolver, 1/6 before the downstream document rejected a remote owner's unqualified legacy cache entry, and 1/6 when a host filter or scoped open-tab search hid the same-id sibling from legacy ambiguity detection. Manual local-provider ownership failed 1/12 before explicit host routing, and pre-hydration dedupe failed 1/13 before exact stamped-owner reuse; the combined contracts then passed 25/25 and the six-file review suite passed 73/73. The prior completed focused suite passed 225/225. Historical results include examples subsequently removed in the test-value audit; they do not establish current coverage for those removed assertions." + "status": "missing", + "evidence": "Audit1308 changed referenced test sources; no new red/green validation of the reduced set. Historical account: Byte-identical browser and simulator candidate tests failed 4/28 on affected baseline 012e9f410c, passed 28/28 on candidate fe45b73c20, failed the same 4/28 when only the two candidate filters were disabled, and returned to 28/28 after restoration. Review-round topology contracts failed 4/40 before the alias/generic/local-stamping follow-up; final-review contracts then failed 4/16 before folder/background/worktree-metadata hardening, 1/6 before paired repository-metadata hardening, 1/7 before unavailable-runtime fail-closed hardening, 1/12 with the component's physical-host bypass restored, 1/6 before review-cache ownership used the same resolver, 1/6 before the downstream document rejected a remote owner's unqualified legacy cache entry, and 1/6 when a host filter or scoped open-tab search hid the same-id sibling from legacy ambiguity detection. Manual local-provider ownership failed 1/12 before explicit host routing, and pre-hydration dedupe failed 1/13 before exact stamped-owner reuse; the combined contracts then passed 25/25 and the six-file review suite passed 73/73. The prior completed focused suite passed 225/225. Historical results include examples subsequently removed in the test-value audit; they do not establish current coverage for those removed assertions." }, "performanceBudget": { "required": true, - "evidence": "Candidate construction adds bounded in-memory ownership filtering over the already-read worktree tab bucket and introduces no polling or network work." + "evidence": "Historical account for the pre-audit test set; removed work-count alarms and changed selectors have no fresh measurement. Candidate construction adds bounded in-memory ownership filtering over the already-read worktree tab bucket and introduces no polling or network work." }, "promotionCriteria": [ "Collect paired Electron CI soak history without unexplained flakes.", @@ -3442,7 +3430,12 @@ "The test-value audit removed the palette repository resolver examples and manual emulator local-provider routing assertion. Their historical alarms are no longer provided by these suites; the retained emulator cases cover narrower coordination boundaries. No equivalent coverage is claimed.", "The test-value audit narrowed simulator-palette-search.test.ts to the recency tie, same-id host isolation, ambiguous legacy host rejection and oversized-input getter tripwire. Supplied timestamp, empty-query context ordering, host projection, alias/range, metadata matching, active-tab score, over-token/whitespace and malformed display-name fallback examples were removed. Their separate alarms are lost; retained candidate and activation contracts do not establish equivalent coverage. Historical run commands and results remain historical.", "The mirror-identity fixture now retains same-id sibling-runtime isolation and the repeated-frame subscriber tripwire only. Supplied field, owner, alias and mapping examples are removed; their separate alarms are lost. The retained sibling-runtime case is not equivalent coverage for them.", - "The test-value audit removed tabs-open-close-lifecycle host-stamping examples and ordinary tab/group operations from tabs-pane-layout-operations. Local-null, explicit-runtime and SSH-without-active-host stamp assertions are lost; retained mobile/MRU and subscriber/atomic split cases do not replace them. Candidate and live Cmd-J evidence retains its own scope. Historical commands, outcomes and narratives remain historical." + "The test-value audit removed tabs-open-close-lifecycle host-stamping examples and ordinary tab/group operations from tabs-pane-layout-operations. Local-null, explicit-runtime and SSH-without-active-host stamp assertions are lost; retained mobile/MRU and subscriber/atomic split cases do not replace them. Candidate and live Cmd-J evidence retains its own scope. Historical commands, outcomes and narratives remain historical.", + "Audit1308: src/renderer/src/lib/browser-palette-search.test.ts removed. Browser-palette token and pasted-query size-guard work alarms disappear. No equivalent replacement coverage is claimed.", + "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-20\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 10.5,\n \"summary\": \"The review-hardened candidate passed 189/189 focused contracts, including direct browser-result host fallback, SSH plus paired-runtime current-row aliases, explicit-owner fail-closed behavior, generic workspace rows, simulator reuse, local creation stamping, activation, and same-id distinct-runtime mirror preservation. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session-schema.test.ts\"\n }", + "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-21\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session-schema.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 24.3,\n \"summary\": \"The OpenCode-review-hardened candidate passed 225/225 focused contracts, including unfiltered ownership ambiguity under host filters and scoped open-tab search, review-cache ownership, rendered palette repo metadata, folder workspaces, inactive local simulator creation, paired SSH/runtime display metadata, and explicit-owner fail-closed resolution. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\"\n }", + "Retired during audit1312 current-main refresh; original complete execution receipt (unchanged): {\"date\":\"2026-08-20\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\"result\":\"passed\",\"durationSeconds\":10.5,\"summary\":\"The review-hardened candidate passed 189/189 focused contracts, including direct browser-result host fallback, SSH plus paired-runtime current-row aliases, explicit-owner fail-closed behavior, generic workspace rows, simulator reuse, local creation stamping, activation, and same-id distinct-runtime mirror preservation. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts\"}", + "Retired during audit1312 current-main refresh; original complete execution receipt (unchanged): {\"date\":\"2026-08-21\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts\",\"result\":\"passed\",\"durationSeconds\":24.3,\"summary\":\"The OpenCode-review-hardened candidate passed 225/225 focused contracts, including unfiltered ownership ambiguity under host filters and scoped open-tab search, review-cache ownership, rendered palette repo metadata, folder workspaces, inactive local simulator creation, paired SSH/runtime display metadata, and explicit-owner fail-closed resolution. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\"}" ], "demotionRule": "Cannot promote if a same-id sibling tab duplicates, disappears, activates another host, or if ambiguous unstamped ownership is guessed." }, @@ -4243,7 +4236,7 @@ "providers": ["lan", "tailscale", "cloud-relay"], "coveredPlatforms": ["macos"], "coveredProviders": ["lan", "tailscale", "cloud-relay"], - "coverageNotes": "Deterministic TypeScript tests cover shared close-code policy, foreground retry timers, continuous-outage escalation, persistent pairing-rejection escalation, a bounded background Relay grace, direct-winner and background cancellation, credential-gated Relay presentation, and concurrent LAN/Tailscale authentication. Physical iOS/Android radios, GFE, and production relay recovery remain live-test gaps. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/transport/connection-health.test.ts. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed.", + "coverageNotes": "Deterministic TypeScript tests cover shared close-code policy, foreground retry timers, continuous-outage escalation, persistent pairing-rejection escalation, a bounded background Relay grace, direct-winner and background cancellation, credential-gated Relay presentation, and concurrent LAN/Tailscale authentication. Physical iOS/Android radios, GFE, and production relay recovery remain live-test gaps. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/transport/connection-health.test.ts. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1310 removes the fake-logical-session background lifecycle suite and two supervisor-nudge examples whose fake migration or negative spy supplied the oracle. Its quick-background/grace-expiry/recovery/lease-rotation examples are intentionally lost. The retained nudge callbacks still check healthy-session retention and losing physical-candidate closure; they do not replace the retired lifecycle alarms. Narrowed commands need fresh validation; no physical radio, production Relay or deployed mixed-version claim.", "motivatingLinks": [ "https://github.com/stablyai/orca-cloud/pull/96", "https://linear.app/stably/issue/STA-4587", @@ -4252,14 +4245,13 @@ "invariant": "A foregrounded paired phone must recover from relay HOST_OFFLINE without a foreground or network-change signal, while direct recovery must select the first authenticated configured LAN or Tailscale endpoint without serial timeout delays. Relay recovery presentation starts only for a failed active Relay or a dial backed by an eligible credential, remains pending through a genuine failed-dial cooldown, and exposes the supervisor's consecutive failed-dial count so a continuous outage escalates to unreachable. A relay credential the desktop keeps refusing escalates to re-pair once its transient-rejection budget is spent, and only an authenticated session clears that verdict. A healthy Relay survives backgrounding for at most 30 seconds and is reused on a quick foreground without redial; expiry, transport failure, direct success, or stop cancels the grace safely, late confirmation bookkeeping preserves lease rotation for a retained live Relay, and losing probes or replacements close without affecting the winner.", "oracle": "Inject deterministic relay close codes, missing and expired credentials, delayed credential reads, random bytes, fake timers, suspended timers, stale callbacks, and independently controlled direct clients. Require HOST_OFFLINE to replace any faster transport timer with one 5-15 second retry, require no retry before the selected delay, keep Relay presentation absent when no socket can open, clear it across lifecycle races, retain it through real dial cooldowns, retain one healthy Relay through 29,999 milliseconds of background time without a new dial, close it exactly once at 30 seconds or on a later foreground when the timer was suspended, reject a stale expiry after foreground, recover a retained Relay transport failure only after foreground, resume a lease rotation that came due during the grace, arm a lease whose confirmation persistence finishes during the grace, and leave healthy direct sessions unchanged. Establish Relay through the supervisor and require its active close plus 11 failed replacements to publish reconnect attempt 12 and a Relay-unreachable verdict, race all unique non-relay endpoints, select the first authenticated path, close every loser exactly once, and retain no retry after direct connectivity wins. Drive a supervisor-established Relay through four simulated hours of E2EE rejections and require the re-pair verdict, require it to survive a background/foreground cycle, require two rejections followed by acceptance to never latch it, and require a plain transport outage to still read Relay-unreachable.", "commands": [ - "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-background-lifecycle.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts", + "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts", "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/shared/remote-runtime/relay/mobile-relay-close-codes.test.ts --reporter=dot" ], "testFiles": [ "mobile/src/transport/mobile-direct-endpoint-probe.test.ts", "mobile/src/transport/mobile-relay-background-grace.test.ts", - "mobile/src/transport/mobile-relay-background-lifecycle.test.ts", "mobile/src/transport/mobile-relay-reconnect-controller.test.ts", "mobile/src/transport/mobile-endpoint-supervisor.test.ts", "mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts", @@ -4285,18 +4277,6 @@ "a stale expiry callback cannot close a foreground connection" ] }, - { - "file": "mobile/src/transport/mobile-relay-background-lifecycle.test.ts", - "assertions": [ - "a quick background round trip reuses Relay without a new dial", - "Relay closes exactly once at grace expiry and recovers only after foreground", - "foreground enforces an overdue grace when the OS suspended its timer", - "a retained Relay transport failure suspends without a background redial and recovers on foreground", - "a healthy direct connection is unchanged by the Relay-only grace", - "a lease rotation that becomes due during the grace starts promptly on foreground", - "lease rotation remains armed when confirmation persistence finishes during the grace" - ] - }, { "file": "mobile/src/transport/mobile-relay-reconnect-controller.test.ts", "assertions": [ @@ -4394,7 +4374,9 @@ "Background-to-foreground recovery remains covered by supervisor tests but lacks a physical sleep/wake run.", "Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/transport/connection-health.test.ts. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed.", "Archived audit1303 retired-suite historical receipt (JSON; original command/currentCommand/results/counts unchanged): {\"date\":\"2026-08-26\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-background-lifecycle.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts mobile/src/transport/connection-health.test.ts\",\"result\":\"passed\",\"durationSeconds\":1.15,\"summary\":\"Eleven focused mobile transport files passed with 135 assertions, including 30-second Relay retention, supervisor-level timer-suspension recovery, mid-grace transport failure, late-persistence lease rotation, manual retry, and direct-path non-regression.\"}", - "Archived audit1303 retired-suite historical receipt (JSON; original command/currentCommand/results/counts unchanged): {\"date\":\"2026-08-18\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts mobile/src/transport/connection-health.test.ts\",\"result\":\"passed\",\"durationSeconds\":1.25,\"summary\":\"Eight focused mobile transport files passed with 109 assertions, including production-lifecycle outage escalation, persistent pairing-rejection escalation, and logical count publication.\"}" + "Archived audit1303 retired-suite historical receipt (JSON; original command/currentCommand/results/counts unchanged): {\"date\":\"2026-08-18\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts mobile/src/transport/connection-health.test.ts\",\"result\":\"passed\",\"durationSeconds\":1.25,\"summary\":\"Eight focused mobile transport files passed with 109 assertions, including production-lifecycle outage escalation, persistent pairing-rejection escalation, and logical count publication.\"}", + "Audit1310 removes the fake-logical-session background lifecycle suite and two supervisor-nudge examples whose fake migration or negative spy supplied the oracle. Its quick-background/grace-expiry/recovery/lease-rotation examples are intentionally lost. The retained nudge callbacks still check healthy-session retention and losing physical-candidate closure; they do not replace the retired lifecycle alarms. Narrowed commands need fresh validation; no physical radio, production Relay or deployed mixed-version claim.", + "Archived audit1310 historical receipt (original command/currentCommand/result/count/duration unchanged; not evidence for the shortened suite): {\"date\":\"2026-08-26\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-background-lifecycle.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts mobile/src/transport/connection-health.test.ts\",\"result\":\"passed\",\"durationSeconds\":1.15,\"summary\":\"Eleven focused mobile transport files passed with 135 assertions, including 30-second Relay retention, supervisor-level timer-suspension recovery, mid-grace transport failure, late-persistence lease rotation, manual retry, and direct-path non-regression.\"}" ], "demotionRule": "Keep experimental or demote if focused tests flake, HOST_OFFLINE can park indefinitely, direct probes serialize configured endpoints, loser cleanup leaks clients, or physical-device radio cost exceeds the measured budget." }, @@ -4719,7 +4701,7 @@ "providers": ["local", "ssh"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "ssh"], - "coverageNotes": "Local deterministic evidence covers git-common classification, desktop watcher debounce counts, non-overlapping poller semantics, macOS native-watch fallback, preload cleanup, Source Control active-visible repo filtering, paired-runtime publication to two independent clients with reconnect and stale-cache checks, and process generation-scope rollover across sibling targets. A headed macOS Electron host and separate paired Electron client both rendered a worktree created through the real Git binary and watcher entry path without reconnect. Linux/Windows desktop clients, multi-target live fanout, and WSL runs remain gaps.", + "coverageNotes": "Local deterministic evidence covers git-common classification, desktop watcher debounce counts, non-overlapping poller semantics, macOS native-watch fallback, preload cleanup, Source Control active-visible repo filtering, paired-runtime publication to two independent clients with reconnect and stale-cache checks, and process generation-scope rollover across sibling targets. A headed macOS Electron host and separate paired Electron client both rendered a worktree created through the real Git binary and watcher entry path without reconnect. Linux/Windows desktop clients, multi-target live fanout, and WSL runs remain gaps. Audit1309: src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts: Keep real filesystem HEAD/loose/packed/relative metadata reads and traversal refusal. Remove invalid-oid/nonstandard-layout characterization. Lose malformed object-id and bare-dir omission alarms; no Git-produced layout claim. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/7086", "https://github.com/stablyai/orca/issues/16477" @@ -4837,16 +4819,6 @@ } ], "evidenceRuns": [ - { - "date": "2026-07-12", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/worktree-base-directory-event-filter.test.ts src/main/ipc/worktree-base-directory-watcher.test.ts src/main/ipc/worktree-base-directory-poller.test.ts src/main/ipc/worktree-head-identity-reader.test.ts src/renderer/src/hooks/worktree-head-identity-apply.test.ts src/renderer/src/components/right-sidebar/git-status-push-signal-refresh.test.ts src/renderer/src/hooks/useIpcEvents-browser-tab-create.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/worktrees/creation/worktree-base-directory-event-filter.test.ts src/main/ipc/worktrees/creation/worktree-base-directory-watcher.test.ts src/main/ipc/worktrees/creation/worktree-base-directory-poller.test.ts src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts src/renderer/src/components/right-sidebar/git-status-push-signal-refresh.test.ts", - "result": "passed", - "durationSeconds": 3.12, - "summary": "7 files and 130 tests passed locally, adding head-identity emit-on-change without structural fanout, reflog status triggers, config.worktree structural classification, the in-place index backstop, and the spawn-free head reader with symref traversal rejection and hex-object-id output validation." - }, { "date": "2026-07-27", "runner": "local", @@ -4913,7 +4885,9 @@ "SSH watches classify head-move triggers but skip the metadata-read identity diff; remote background-worktree heads still wait on a structural event or activation.", "The Docker/Linux journey covers one direct SSH target; a live multi-target fanout and large-catalog benchmark remains missing.", "Paired web clients intentionally do not run the desktop direct SSH coordinator, and paired-close non-interference lacks a new live run.", - "WSL direct SSH fanout remains an explicit live-test gap rather than inferred coverage." + "WSL direct SSH fanout remains an explicit live-test gap rather than inferred coverage.", + "Audit1309: src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts: Keep real filesystem HEAD/loose/packed/relative metadata reads and traversal refusal. Remove invalid-oid/nonstandard-layout characterization. Lose malformed object-id and bare-dir omission alarms; no Git-produced layout claim. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", + "Audit1309 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-07-12\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/worktree-base-directory-event-filter.test.ts src/main/ipc/worktree-base-directory-watcher.test.ts src/main/ipc/worktree-base-directory-poller.test.ts src/main/ipc/worktree-head-identity-reader.test.ts src/renderer/src/hooks/worktree-head-identity-apply.test.ts src/renderer/src/components/right-sidebar/git-status-push-signal-refresh.test.ts src/renderer/src/hooks/useIpcEvents-browser-tab-create.test.ts\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/worktrees/creation/worktree-base-directory-event-filter.test.ts src/main/ipc/worktrees/creation/worktree-base-directory-watcher.test.ts src/main/ipc/worktrees/creation/worktree-base-directory-poller.test.ts src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts src/renderer/src/components/right-sidebar/git-status-push-signal-refresh.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 3.12,\n \"summary\": \"7 files and 130 tests passed locally, adding head-identity emit-on-change without structural fanout, reflog status triggers, config.worktree structural classification, the in-place index backstop, and the spawn-free head reader with symref traversal rejection and hex-object-id output validation.\"\n }" ], "demotionRule": "Keep experimental or demote if the focused gate flakes without a product or harness bug, if index-only churn can emit worktrees:changed, or if structural add/remove/HEAD/lock changes fail to converge." }, @@ -4936,17 +4910,16 @@ "providers": ["local", "orca-server"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "orca-server"], - "coverageNotes": "A real two-client integration harness pairs two authenticated devices to one OrcaRuntimeService over WebSockets with E2EE and drives worktree.create through the real RPC dispatcher, in both headed-notifier and headless orca-serve shapes. The headless case is a modeled `orca serve` runtime shape — no notifier, no attached window — not a CLI bootstrap, and the in-process reveal non-regression calls createManagedWorktree directly. A headed Electron journey runs the desktop app as the remote server with two independently paired web clients in separate partitions and asserts each client's rendered active worktree. Windows and Linux are covered by the platform-neutral contract only; the reported field topology was a Windows server with mixed-OS clients.", + "coverageNotes": "A real two-client integration harness pairs two authenticated devices to one OrcaRuntimeService over WebSockets with E2EE and drives worktree.create through the real RPC dispatcher, in both headed-notifier and headless orca-serve shapes. The headless case is a modeled `orca serve` runtime shape — no notifier, no attached window — not a CLI bootstrap, and the in-process reveal non-regression calls createManagedWorktree directly. A headed Electron journey runs the desktop app as the remote server with two independently paired web clients in separate partitions and asserts each client's rendered active worktree. Windows and Linux are covered by the platform-neutral contract only; the reported field topology was a Windows server with mixed-OS clients. Audit1313: src/main/runtime/rpc/methods/worktree-create-navigation.test.ts: Fake runtime creation/show/dedupe under real RPC decoder only inspects forwarded navigation option matrices, with duplicate desktop case. Lose runtime/mobile/legacy CLI/follow/all navigation authority wiring; no rendered reveal. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", "motivatingLinks": ["https://github.com/stablyai/orca/issues/11027"], "invariant": "View intent belongs to the connection that requested it. With no explicit navigation target, a workspace create arriving over a paired desktop/web client connection activates only that caller; it must not emit an activateWorktree client event to sibling connections or drive the host renderer. An explicit navigation of host/clients/all remains an allowed, tested follow intent. Shared model state — repo/worktree catalogs, terminals, agent output — must keep propagating to every client unchanged. In-process and CLI creates keep notifying the host and all clients so a headless server's only viewer still gets its reveal.", "oracle": "notifyActivateWorktree takes its navigation target as a required parameter, so a create call site that forgets to forward it is a compile error rather than a silent fallback to broadcasting — mutation testing confirmed all six call sites (folder, git, and remote/SSH branches, startup and no-startup each) are killed by typecheck. Behaviorally: two paired clients subscribe to runtime.clientEvents. Client A creates a workspace with activate:true; a reposChanged fence emitted after the create must be the next frame each client sees, proving no activation frame was interleaved, while worktreesChanged still arrives on both streams. The host notifier's activateWorktree is asserted not called and the host's mirrored selection is unchanged. Repeated ordinary worktree.activate calls by A are the negative control. The same file asserts the in-process create still reaches host and clients, headed and headless. The Electron journey asserts the observer's data-rendered-active-worktree-id is unchanged while the created workspace still appears in its sidebar.", "commands": [ - "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/multi-client-navigation-isolation.integration.test.ts src/main/runtime/rpc/methods/worktree-create-navigation.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/multi-client-navigation-isolation.integration.test.ts", "pnpm run test:e2e:multi-client-navigation -- tests/e2e/multi-client-navigation-isolation.spec.ts" ], "testFiles": [ "src/main/runtime/multi-client-navigation-isolation.integration.test.ts", - "src/main/runtime/rpc/methods/worktree-create-navigation.test.ts", "tests/e2e/multi-client-navigation-isolation.spec.ts" ], "assertionRefs": [ @@ -4962,15 +4935,6 @@ "routes explicit host and paired-client follow intent without changing the default" ] }, - { - "file": "src/main/runtime/rpc/methods/worktree-create-navigation.test.ts", - "assertions": [ - "resolves create activation from the paired runtime client kind", - "resolves create activation from the paired mobile client kind", - "honors an explicit follow navigation on create", - "keeps an explicit all-surface reveal from a paired caller" - ] - }, { "file": "tests/e2e/multi-client-navigation-isolation.spec.ts", "assertions": [ @@ -4980,15 +4944,6 @@ } ], "evidenceRuns": [ - { - "date": "2026-08-18", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/multi-client-navigation-isolation.integration.test.ts src/main/runtime/rpc/methods/worktree-create-navigation.test.ts", - "result": "passed", - "durationSeconds": 6, - "summary": "Eleven paired-runtime contracts pass, including the headless orca-serve shape, the in-process reveal non-regression, and the CLI-shaped explicit all-surface reveal. With the caller scoping removed the observer's stream carried activateWorktree instead of the fence." - }, { "date": "2026-08-18", "runner": "local", @@ -5030,7 +4985,9 @@ "Host-first rollout: the fix is enforced by the host, so a NEW client paired to an OLD always-on server still sees the original cross-client jump. Shipping clients alone fixes nothing for anyone; the runtime/server must be updated. Nothing breaks under that skew — the old host simply keeps its old behavior — but release notes and support triage must check the host version, not the client version. No skew combination is worse than pre-fix main: an old CLI is recognized by its cliProvenanceRequest marker so its --activate reveal survives an updated host.", "A future contributor could still hardcode a wrong navigation target at a call site: the required object key makes omission a compile error, but not substitution. Only the folder/no-startup branch has a behavioral test that would catch it.", "Out of scope, host-only and unreachable from the product UI: a paired caller that explicitly requests terminal.create with presentation 'focused' still navigates the host renderer (the Orca client always sends 'background'); agent-session already downgrades that presentation for paired callers.", - "Out of scope: browser.tabCreate/tabSwitch carry no paired-client navigation policy, so they can change the host's active browser tab when the host is already on that worktree. They never cross worktrees, and the paired web client sends activate:true deliberately." + "Out of scope: browser.tabCreate/tabSwitch carry no paired-client navigation policy, so they can change the host's active browser tab when the host is already on that worktree. They never cross worktrees, and the paired web client sends activate:true deliberately.", + "Audit1313: src/main/runtime/rpc/methods/worktree-create-navigation.test.ts: Fake runtime creation/show/dedupe under real RPC decoder only inspects forwarded navigation option matrices, with duplicate desktop case. Lose runtime/mobile/legacy CLI/follow/all navigation authority wiring; no rendered reveal. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", + "Audit1313 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-18\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/multi-client-navigation-isolation.integration.test.ts src/main/runtime/rpc/methods/worktree-create-navigation.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 6,\n \"summary\": \"Eleven paired-runtime contracts pass, including the headless orca-serve shape, the in-process reveal non-regression, and the CLI-shaped explicit all-surface reveal. With the caller scoping removed the observer's stream carried activateWorktree instead of the fence.\"\n }" ], "demotionRule": "Demote if a create, activate, or reveal path can move a viewer that did not request it, or if scoping regresses the in-process/CLI reveal that a headless server's only remote viewer depends on." }, @@ -5225,7 +5182,7 @@ "providers": ["local", "daemon", "ssh", "paired-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "daemon", "paired-runtime"], - "coverageNotes": "Deterministic lifecycle, IPC, window-notification, and runtime ownership tests cover generation replacement, stale callbacks, same-frame old-document publication, blocked external navigation, cancelled renderer navigation, concurrent provisional starts, process loss, timeout cleanup, headless fallback, and competing windows. Isolated macOS Electron validation covers cancelled navigation, forced renderer recovery, and headless serve promotion with live daemon-backed PTY identity and I/O. Linux, Windows, WSL, SSH promotion, and mixed-version desktop/server live runs remain uncollected; the renderer generation stays local to Electron IPC and does not change the remote wire. The renderer scheduling fixture now retains in-flight coalescing only, and payload partitioning retains publication-failure and main-dropped-worktree retry. Immediate publication, stale SSH cleanup, reconnect resync and isolated partition membership alarms were removed; other lifecycle evidence does not establish equivalent coverage.", + "coverageNotes": "Deterministic lifecycle, IPC, window-notification, and runtime ownership tests cover generation replacement, stale callbacks, same-frame old-document publication, blocked external navigation, cancelled renderer navigation, concurrent provisional starts, process loss, timeout cleanup, headless fallback, and competing windows. Isolated macOS Electron validation covers cancelled navigation, forced renderer recovery, and headless serve promotion with live daemon-backed PTY identity and I/O. Linux, Windows, WSL, SSH promotion, and mixed-version desktop/server live runs remain uncollected; the renderer generation stays local to Electron IPC and does not change the remote wire. The renderer scheduling fixture now retains in-flight coalescing only, and payload partitioning retains publication-failure and main-dropped-worktree retry. Immediate publication, stale SSH cleanup, reconnect resync and isolated partition membership alarms were removed; other lifecycle evidence does not establish equivalent coverage. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/14066", "https://linear.app/stably/issue/STA-4016", @@ -5319,16 +5276,6 @@ } ], "evidenceRuns": [ - { - "date": "2026-08-14", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/runtime-graph-reload-lifecycle.test.ts src/main/window/runtime-renderer-notification-sender.test.ts src/main/window/renderer-document-navigation.test.ts src/main/ipc/runtime.test.ts src/main/window/attach-main-window-services.test.ts src/main/runtime/orca-runtime.test.ts src/renderer/src/runtime/sync-runtime-graph-scheduling.test.ts src/renderer/src/runtime/sync-runtime-graph-payload-partition.test.ts", - "result": "passed", - "durationSeconds": 12, - "summary": "Eight focused files passed with 1,175 tests and one pre-existing skip after the merge from latest main. The suite covers exact reload settlement, timeout replacement, cancelled-navigation recovery, concurrent provisional starts, document and window authority, notification containment, preload IPC, and renderer publication scheduling.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/runtime-graph-reload-lifecycle.test.ts src/main/window/runtime-renderer-notification-sender.test.ts src/main/window/renderer-document-navigation.test.ts src/main/ipc/runtime.test.ts src/main/window/attach-main-window-services.test.ts src/main/runtime/orca-runtime.test.ts src/renderer/src/runtime/sync-runtime-graph-scheduling.test.ts src/renderer/src/runtime/sync-runtime-graph-payload-partition.test.ts" - }, { "date": "2026-08-14", "runner": "local", @@ -5385,7 +5332,8 @@ "The live journeys cover macOS and daemon-backed local terminals, not Linux desktop, Windows, WSL, or SSH promotion.", "The same-frame stale-document race is deterministic fault injection because Playwright cannot schedule an invoke precisely between did-start-loading and frame replacement.", "No mixed-version live run is required because rendererGeneration is confined to same-version preload/main Electron IPC and the remote runtime wire is unchanged.", - "The renderer scheduling fixture now retains in-flight coalescing only, and payload partitioning retains publication-failure and main-dropped-worktree retry. Immediate publication, stale SSH cleanup, reconnect resync and isolated partition membership alarms were removed; other lifecycle evidence does not establish equivalent coverage." + "The renderer scheduling fixture now retains in-flight coalescing only, and payload partitioning retains publication-failure and main-dropped-worktree retry. Immediate publication, stale SSH cleanup, reconnect resync and isolated partition membership alarms were removed; other lifecycle evidence does not establish equivalent coverage.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-14\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/runtime-graph-reload-lifecycle.test.ts src/main/window/runtime-renderer-notification-sender.test.ts src/main/window/renderer-document-navigation.test.ts src/main/ipc/runtime.test.ts src/main/window/attach-main-window-services.test.ts src/main/runtime/orca-runtime.test.ts src/renderer/src/runtime/sync-runtime-graph-scheduling.test.ts src/renderer/src/runtime/sync-runtime-graph-payload-partition.test.ts\",\"result\":\"passed\",\"durationSeconds\":12,\"summary\":\"Eight focused files passed with 1,175 tests and one pre-existing skip after the merge from latest main. The suite covers exact reload settlement, timeout replacement, cancelled-navigation recovery, concurrent provisional starts, document and window authority, notification containment, preload IPC, and renderer publication scheduling.\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/runtime-graph-reload-lifecycle.test.ts src/main/window/runtime-renderer-notification-sender.test.ts src/main/window/renderer-document-navigation.test.ts src/main/ipc/runtime.test.ts src/main/window/attach-main-window-services.test.ts src/main/runtime/orca-runtime.test.ts src/renderer/src/runtime/sync-runtime-graph-scheduling.test.ts src/renderer/src/runtime/sync-runtime-graph-payload-partition.test.ts\"}" ], "demotionRule": "Demote if a reload revision settles more than once, a stale document or competing window can publish, a timer survives settlement, desktop failure stays indefinitely reloading, headless fallback returns after successful promotion, or either isolated Electron journey loses runtime or PTY identity." }, @@ -5756,7 +5704,7 @@ "providers": ["remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["remote-runtime"], - "coverageNotes": "A macOS desktop host and a separate paired Electron client exercise the Explorer preview action end to end: the document renders inside the client-local orca-preview guest, the client gains exactly one browser workspace and it is the document one, the host's own page registry and tab snapshot gain nothing, the split does not take focus from the source editor, an explicit click activates the preview, and closing it with its X leaves the editor and terminal intact. Both host oracles are read in a run where an ordinary URL browser tab from the same client does arrive in them, because a host that cannot project browser pages at all would otherwise answer the absence question the same way. A second journey quits that client with a document tab open and relaunches it on the same profile, which is the restore the user's original report turned on: the same workspace row comes back blank and named by the document, renders again, and does so over a grant URL that differs from the one that was quit. The same journey pins the guest URL to the orca-preview scheme, so a regression back to file:// or to a host browser page is visible. The rewritten journey caught a live gap the old one could not see: since previews became browser pages they had no chrome to hand focus to their guest, so main refused every reported link click as coming from an unfocused guest and the one route out of a preview was dead. Main's own log answered it — registered true, grant live, focused false — after the embedder reported the anchor really taking six real presses. The pane now hands focus over when it is the reader's surface, and the journey routes. The preview's own components moved out of the editor folder into browser-pane/workspace-doc/ with this change, since a preview is a browser pane and no longer an editor one; their i18n keys keep the historical auto.components.editor.HtmlDocPreview prefix rather than dropping thirty translated strings across four locales for a tidier key path. Seven gate commands named those files by their old paths and were re-issued at the new ones, and the nine dated runs behind them were retired rather than left naming files that are no longer there, on the same principle this gate already applied twice: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. Each retired record's finding is carried into the replacement run recorded below it, including the red half of the entry/subresource pair, which was re-measured by re-applying its mutant at the new path rather than being dropped. The fence over the page-management channels was decided channel by channel before it was built, and that decision record is: of the 23 channels that can name a page, 6 are allowed for a workspace-document page — the tool channels the reader drives, which reach it through the one authority that reads both halves of the registry — 9 refuse (download accept and cancel, devtools, certificate proceed, client page metadata publish, viewport override, WebAuthn account response, registration probe, registration repair), 3 cannot name a page at all because they take a profile or a worktree instead, and the rest are the registration and activation doors, refused on membership. What shipped is not the per-channel guard that classification implies: eleven call sites read the browsing map directly, so a guard per channel is exactly the caller-flag seam that has to be remembered at every future call site. The registry was split in two instead, which makes the classification structural — a document guest is absent from the browsing map, so every refusing channel refuses by absence rather than by a guard, and a channel added later inherits the refusal without being classified at all. Unit contracts cover grant scoping and path containment, per-owner read routing, refusal of a truncated or over-cap read, the old-server binary degradation, scheme admission on will-attach-webview, the guest navigation policy, the plan layer's local/SSH/paired/out-of-worktree routing, and the distinct sentence the reader sees for each failure reason. The paired browser-creation surfaces this gate still owns — capability rejection cleanup, canonical page identity, reconciliation rollback, close convergence — keep their existing headed and headless journeys. Linux and Windows live journeys remain gaps. A refused subresource is a notice on a rendered document rather than a failure panel, since only the entry document's failure leaves the reader with nothing; the entry document is served as text by every owner and cannot be refused for its format at all. Grants die with the renderer document that minted them, at window creation and on every reload, because the renderer holds the only record of which preview owns which grant. The mint and revoke channels answer only the trusted renderer, and a preview tab is never republished to mobile clients, which have no grant to read it with. External URLs leave the preview only via a trusted user click on an anchor while the preview is focused; script-driven navigation and popups reach nothing. The guest's navigation sinks are deny-only and route no URL at all, and the guest carries a preview-only preload, pinned by will-attach-webview and denied to every other attach path, whose capture-phase click handler reports an anchor's URL to main; main answers only a report from a preview guest with a live bound grant that is the focused contents, and only for http(s). Two earlier reader-facing records from 2026-08-26, which measured a shape where a subresource failure was filtered out and the panel still carried an unsupported-asset sentence, were retired with that shape rather than left contradicting the 10/10 runs of the command they share. Two more were retired on the same principle when the external-link design changed: a record showing that removing a recent-input gate let the unattended document open two browser tabs, and a record showing that dropping the webview's allowpopups attribute left a target=_blank press with nothing to route. Both measured machinery that no longer exists — the input gate could not tell a script's navigation from the click it followed, which is the case now recorded below, and the preload intercepts the press before Chromium considers a popup at all, so allowpopups was removed. WebRTC is fenced by the guest's IP-handling policy alone: this Chromium answers a webrtc content-security-policy directive as unrecognized and gathers candidates anyway, so the directive is deliberately absent from the served policy and a unit assertion keeps it out. A standalone Electron probe on the same version gathered 7 candidates including a STUN reflexive one without the policy and 0 with it. Two command paths this gate used to carry no longer exist, and their structured evidence was retired with them rather than left naming files that are no longer there: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. The first is src/renderer/src/lib/file-preview-capability-cleanup.test.ts, deleted in STA-5557 when HTML previews stopped creating paired browser tabs: on 2026-08-12 its five-assertion STA-4063 oracle ran red 5/5 on scan e4c278eb52 and on main bc2e30000b, partially green 2/5 on pre-fix PR #14100, and green 5/5 on candidate 14b5877a75; the surviving half of that oracle is web-runtime-browser-capability-cleanup.test.ts, still commanded above. The second is tests/e2e/paired-remote-html-browser-focus.spec.ts, renamed in STA-5557 to paired-remote-html-preview-local-render.spec.ts because the journey no longer proves anything about browser focus: its 2026-08-12 runs established the owning-runtime routing and no-focus-steal behavior of the then-current browser-tab preview (red on main 346e59c879 and a90a18d43d, green on candidates d05217c40d and b7de9c0961, red again with only the side-preview focus or routing seam disabled), and its 2026-08-26 runs are recorded below under the current path. An adversarial pass after the journey landed found four coverage gaps rather than shipping defects, and all four are pinned here. The page url had a third door: the navigation commit wrote whatever url was reported, unfenced, and while nothing reaches it for a document page today it sat one report away from giving a document page a URL that persistence, the publish boundary and the address bar all read. The group projection was the load-bearing half of the mobile publish boundary that no test had: removing it alone left the whole suite green, because no test in it had tab groups, while the phone payload gained a group whose order and active tab named a document tab it was never sent. The focus handoff was covered only in the narrowing direction, so widening it — dropping the page-activity check, or either half of the reader-surface check — survived both suites. And the handoff itself was offered once and never again, so another app taking the front left the preview's one route out shut until the pane remounted, which is a desktop gap rather than a test-only one. The standalone capability-cleanup and browser-placement ledger suites were removed, including their mixed-version rejection, cleanup transfer, capacity and partition alarms. web-runtime-session.test.ts no longer supplies browser creation, rollback or recovery assertions. The grouped workspace-document exclusion remains; the separate no-group publication example was removed. Existing live/browser-store evidence does not establish equivalent replacement alarms. Earlier descriptions of the removed fixtures and their run results are historical. The client-owned-placement and layout-groups suites are removed. The broad mirror fixture no longer supplies preview placement/reservation/move assertions. Browser rows retain pre-response replay and real local-workspace reuse; focus intent retains response ordering and exact-leaf sibling ordering. Supplied browser-layout and placement/focus projection alarms are lost; retained evidence is narrower and not equivalent. The test-value audit retires renderer grant-release mock ledgers, direct creation/title/session-field copies, notification/onboarding calls and several placement/per-page projections. Canonical-ID rejection, document reopen/restore and preview URL fences remain; they do not prove close-to-registry revocation or replace every lost alarm. Historical execution evidence is unchanged. The test-value audit removed the direct legacy editor-preview hydration example from tabs-hydration.test.ts. Dropping legacy preview chrome while preserving the same-document ordinary editor no longer has that alarm. The surviving corrupt-id deduplication case does not cover that migration, and retained preview journeys are not equivalent replacement coverage. Historical run counts and migration mutation findings describe the earlier suite.", + "coverageNotes": "Audit1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): A macOS desktop host and a separate paired Electron client exercise the Explorer preview action end to end: the document renders inside the client-local orca-preview guest, the client gains exactly one browser workspace and it is the document one, the host's own page registry and tab snapshot gain nothing, the split does not take focus from the source editor, an explicit click activates the preview, and closing it with its X leaves the editor and terminal intact. Both host oracles are read in a run where an ordinary URL browser tab from the same client does arrive in them, because a host that cannot project browser pages at all would otherwise answer the absence question the same way. A second journey quits that client with a document tab open and relaunches it on the same profile, which is the restore the user's original report turned on: the same workspace row comes back blank and named by the document, renders again, and does so over a grant URL that differs from the one that was quit. The same journey pins the guest URL to the orca-preview scheme, so a regression back to file:// or to a host browser page is visible. The rewritten journey caught a live gap the old one could not see: since previews became browser pages they had no chrome to hand focus to their guest, so main refused every reported link click as coming from an unfocused guest and the one route out of a preview was dead. Main's own log answered it — registered true, grant live, focused false — after the embedder reported the anchor really taking six real presses. The pane now hands focus over when it is the reader's surface, and the journey routes. The preview's own components moved out of the editor folder into browser-pane/workspace-doc/ with this change, since a preview is a browser pane and no longer an editor one; their i18n keys keep the historical auto.components.editor.HtmlDocPreview prefix rather than dropping thirty translated strings across four locales for a tidier key path. Seven gate commands named those files by their old paths and were re-issued at the new ones, and the nine dated runs behind them were retired rather than left naming files that are no longer there, on the same principle this gate already applied twice: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. Each retired record's finding is carried into the replacement run recorded below it, including the red half of the entry/subresource pair, which was re-measured by re-applying its mutant at the new path rather than being dropped. The fence over the page-management channels was decided channel by channel before it was built, and that decision record is: of the 23 channels that can name a page, 6 are allowed for a workspace-document page — the tool channels the reader drives, which reach it through the one authority that reads both halves of the registry — 9 refuse (download accept and cancel, devtools, certificate proceed, client page metadata publish, viewport override, WebAuthn account response, registration probe, registration repair), 3 cannot name a page at all because they take a profile or a worktree instead, and the rest are the registration and activation doors, refused on membership. What shipped is not the per-channel guard that classification implies: eleven call sites read the browsing map directly, so a guard per channel is exactly the caller-flag seam that has to be remembered at every future call site. The registry was split in two instead, which makes the classification structural — a document guest is absent from the browsing map, so every refusing channel refuses by absence rather than by a guard, and a channel added later inherits the refusal without being classified at all. Unit contracts cover grant scoping and path containment, per-owner read routing, refusal of a truncated or over-cap read, the old-server binary degradation, scheme admission on will-attach-webview, the guest navigation policy, the plan layer's local/SSH/paired/out-of-worktree routing, and the distinct sentence the reader sees for each failure reason. The paired browser-creation surfaces this gate still owns — capability rejection cleanup, canonical page identity, reconciliation rollback, close convergence — keep their existing headed and headless journeys. Linux and Windows live journeys remain gaps. A refused subresource is a notice on a rendered document rather than a failure panel, since only the entry document's failure leaves the reader with nothing; the entry document is served as text by every owner and cannot be refused for its format at all. Grants die with the renderer document that minted them, at window creation and on every reload, because the renderer holds the only record of which preview owns which grant. The mint and revoke channels answer only the trusted renderer, and a preview tab is never republished to mobile clients, which have no grant to read it with. External URLs leave the preview only via a trusted user click on an anchor while the preview is focused; script-driven navigation and popups reach nothing. The guest's navigation sinks are deny-only and route no URL at all, and the guest carries a preview-only preload, pinned by will-attach-webview and denied to every other attach path, whose capture-phase click handler reports an anchor's URL to main; main answers only a report from a preview guest with a live bound grant that is the focused contents, and only for http(s). Two earlier reader-facing records from 2026-08-26, which measured a shape where a subresource failure was filtered out and the panel still carried an unsupported-asset sentence, were retired with that shape rather than left contradicting the 10/10 runs of the command they share. Two more were retired on the same principle when the external-link design changed: a record showing that removing a recent-input gate let the unattended document open two browser tabs, and a record showing that dropping the webview's allowpopups attribute left a target=_blank press with nothing to route. Both measured machinery that no longer exists — the input gate could not tell a script's navigation from the click it followed, which is the case now recorded below, and the preload intercepts the press before Chromium considers a popup at all, so allowpopups was removed. WebRTC is fenced by the guest's IP-handling policy alone: this Chromium answers a webrtc content-security-policy directive as unrecognized and gathers candidates anyway, so the directive is deliberately absent from the served policy and a unit assertion keeps it out. A standalone Electron probe on the same version gathered 7 candidates including a STUN reflexive one without the policy and 0 with it. Two command paths this gate used to carry no longer exist, and their structured evidence was retired with them rather than left naming files that are no longer there: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. The first is src/renderer/src/lib/file-preview-capability-cleanup.test.ts, deleted in STA-5557 when HTML previews stopped creating paired browser tabs: on 2026-08-12 its five-assertion STA-4063 oracle ran red 5/5 on scan e4c278eb52 and on main bc2e30000b, partially green 2/5 on pre-fix PR #14100, and green 5/5 on candidate 14b5877a75; the surviving half of that oracle is web-runtime-browser-capability-cleanup.test.ts, still commanded above. The second is tests/e2e/paired-remote-html-browser-focus.spec.ts, renamed in STA-5557 to paired-remote-html-preview-local-render.spec.ts because the journey no longer proves anything about browser focus: its 2026-08-12 runs established the owning-runtime routing and no-focus-steal behavior of the then-current browser-tab preview (red on main 346e59c879 and a90a18d43d, green on candidates d05217c40d and b7de9c0961, red again with only the side-preview focus or routing seam disabled), and its 2026-08-26 runs are recorded below under the current path. An adversarial pass after the journey landed found four coverage gaps rather than shipping defects, and all four are pinned here. The page url had a third door: the navigation commit wrote whatever url was reported, unfenced, and while nothing reaches it for a document page today it sat one report away from giving a document page a URL that persistence, the publish boundary and the address bar all read. The group projection was the load-bearing half of the mobile publish boundary that no test had: removing it alone left the whole suite green, because no test in it had tab groups, while the phone payload gained a group whose order and active tab named a document tab it was never sent. The focus handoff was covered only in the narrowing direction, so widening it — dropping the page-activity check, or either half of the reader-surface check — survived both suites. And the handoff itself was offered once and never again, so another app taking the front left the preview's one route out shut until the pane remounted, which is a desktop gap rather than a test-only one. The standalone capability-cleanup and browser-placement ledger suites were removed, including their mixed-version rejection, cleanup transfer, capacity and partition alarms. web-runtime-session.test.ts no longer supplies browser creation, rollback or recovery assertions. The grouped workspace-document exclusion remains; the separate no-group publication example was removed. Existing live/browser-store evidence does not establish equivalent replacement alarms. Earlier descriptions of the removed fixtures and their run results are historical. The client-owned-placement and layout-groups suites are removed. The broad mirror fixture no longer supplies preview placement/reservation/move assertions. Browser rows retain pre-response replay and real local-workspace reuse; focus intent retains response ordering and exact-leaf sibling ordering. Supplied browser-layout and placement/focus projection alarms are lost; retained evidence is narrower and not equivalent. The test-value audit retires renderer grant-release mock ledgers, direct creation/title/session-field copies, notification/onboarding calls and several placement/per-page projections. Canonical-ID rejection, document reopen/restore and preview URL fences remain; they do not prove close-to-registry revocation or replace every lost alarm. Historical execution evidence is unchanged. The test-value audit removed the direct legacy editor-preview hydration example from tabs-hydration.test.ts. Dropping legacy preview chrome while preserving the same-document ordinary editor no longer has that alarm. The surviving corrupt-id deduplication case does not cover that migration, and retained preview journeys are not equivalent replacement coverage. Historical run counts and migration mutation findings describe the earlier suite. Audit1308: src/renderer/src/components/editor/EditorPanelShell.header.test.tsx removed. Header visibility and mocked editor-surface inventory alarms disappear. No equivalent replacement coverage is claimed. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-5557/remote-html-preview-render-workspace-files-locally-over-the-file", "https://linear.app/stably/issue/STA-3872/unable-to-open-html-files-in-remote-server", @@ -5767,7 +5715,7 @@ "https://github.com/stablyai/orca/pull/13876" ], "invariant": "Opening one HTML preview from a paired client renders the workspace document in exactly one client-local browser tab, located by that document and served over the orca-preview scheme. The client gains exactly that one browser workspace and it is the document one — blank where a URL page carries a URL, named by the document, with the chip naming the file — while the host gains no browser page at all, neither in its own page registry nor in the tab snapshot its clients publish into. The preview occupies its own split without taking focus from the source editor; an explicit click activates it, and closing it removes only the preview. Following a document from a file link is the other half of that switch and does move the reader to it, tab group included, whether the preview is new or already open, because opening a file is a request to look at it. A document tab quit with the client comes back as the same row on a grant the relaunched client mints afresh. A preview is named by the browser page it is open in, not by a namespace of its own, and the page registry has two halves: a workspace-document guest is registered in its own map and is absent from the browsing one entirely. That absence is the fence. Page, session and profile management, agent tab enumeration and command targeting, download routing and certificate attribution all read the browsing map directly, in more places than a per-channel guard could be remembered in, so none of them can name a document page and none of them carries a guard. Browser tools the reader drives (element grab, hover describe, selection capture, the annotation viewport bridge) are the one operation that legitimately spans the halves, and they go through the single authority that reads both, keyed by the page and its hosting renderer. The halves are disjoint in both directions: browsing registration refuses a page the document half already holds, and minting a grant refuses a page the browsing half already holds, so one id can never name a surface in both. The headless backend acts on that refusal by destroying the window it had already opened rather than leaving a policy-less page behind an id nothing can drive, keeping nothing under that id for its own shutdown to hand back. Registration refuses on the same terms when the guest it was asked about is already gone. The exit door is guarded in both its halves: a preview withdraws by revoking its grant and never through the unregister channel, so a page the document half holds arriving there is refused before either the registration teardown or the grab-state disposal beside it, which would otherwise drop the intent an in-flight preview grab compares by identity and leave that grab answering ok without ever arming its guest. A bridge request whose guest does not resolve is refused without tearing down the page it named, so a misaddressed request cannot cancel a healthy page's in-flight downloads and grabs. The annotation viewport bridge resolves its guest when its serialized op actually runs rather than when the request arrived, so a cross-process navigation while it waited cannot leave the bridge installed in a retired guest while the reader looks at a new one. State main keys by a preview's page is disposed when that page's grant is revoked, which is the only signal a preview's surface is gone. A tool asking for a page whose guest has not attached yet waits for that registration and arms when it arrives, rather than answering not-ready at the reader; that wait resolves only the request already naming this page, never the worktree-wide or any-tab waits the CLI and agents use to ask for a browser tab to drive. Handing the previewed document to the reader's own machine routes on the owners its grant was minted against — the file's own connection owner and the worktree's own runtime owner, neither read from the tab's stored fields. Only a document proven to live on this machine reaches the client OS; one with a resolved remote owner is downloaded first; and one whose owner cannot be resolved at all, workspace root included, is refused with a message naming that, because the download route would otherwise read the same absolute path on the client and hand back a same-named local file under the remote document's name. A runtime-owned path that falls outside its worktree root is refused by that route itself and surfaces as a failure toast rather than a download. Nothing the document does writes a file to this machine either: the preview partition denies downloads outright instead of routing them through the browser download flow, which has no page to attribute a preview's bytes to and would otherwise reserve a name in this desktop's Downloads folder and write them there unprompted. That refusal is visible to the reader and invisible to the document: the preview's shell carries a fixed sentence saying downloads are off, published at most once per preview per interval so a document asking in a loop cannot fill Orca's chrome, while the page itself gets back exactly what it got before, which is nothing. The sentence names no file, because the document chooses the name it offers; and a refusal never takes the document away the way an entry document's own failure does, whatever it names. A preview is a browser tab, not an editor tab in a preview mode: it is named the way a browser tab is named — by the document it shows when that document declares a title, and by the file it shows when it does not — while the chip goes on naming the file and the host whatever the document calls itself. A title is refused on the same terms the url is: a document that declares none has Chromium report the grant URL as its title, and that title is stored, mirrored onto the tab and written to disk, so anything carrying the scheme falls back to the file instead. It is created by the preview action as a page located by its document, it carries the workspace-relative path copy the editor's path header owned, and closing it revokes the grant that made the document readable while a URL tab closing beside it revokes nothing. Chrome persisted by builds that made previews editor tabs is dropped on restore rather than coming back naming a surface no restore can produce, and the ordinary editor tab for the same document is left alone. A document tab is held back at the mobile publish boundary — no client holds its grant, and the wire has no tab kind for it — while an ordinary browser tab beside it still publishes. It is held back from the group projection that publishes tab order, recency and group activity as well as from the tab list itself, so no published group names a tab the phone is never sent. A browser page can be located by a workspace document instead of a URL, and the document is the whole of its stored identity. The grant and the orca-preview URL that document is served over are minted when the page mounts and replaced by a hard reload, so neither is ever written to the page's url, mirrored onto its tab, persisted or published: such a page's url is the blank URL from creation through restore, including when a session written elsewhere carries a grant URL in, and what the session carries is the worktree and path a restored page mints afresh against today's owners. Every door onto a page's url holds that line — creation, the title update, and the navigation commit alike — so a report about a document page cannot give it a URL it never had, and the title fallback and the loading affordance follow the url each door actually wrote. The mirror carries the document too, so a tab entry cannot go on naming a document its active page has left. Every guest in the app is policy-attached through one door: a workspace document takes a restricted profile there rather than a separate installer beside it, so the attachment bookkeeping that door owns — what registration refuses, and what teardown frees — covers a preview on the same terms as a browsing page, and a preview takes none of the browsing machinery that door installs. That authority answers from the moment the embedder hands the guest over rather than only after a later navigation: the guest binds to the grant it is already showing, so the tools reach the document the reader opened and not just one they navigated to. A read the host reports as truncated or over-cap is refused rather than served partially, and a document outside the paired worktree is refused with a message naming that boundary instead of a bare read failure. The rendered document reaches nothing off-machine on its own: every served response carries a self-only content security policy, the preview session cancels any request that is not in-document, subframes cannot navigate outside the grant, a guest no document has yet bound to a grant may not navigate at all, the guest gathers no ICE candidates, and an SSH path that canonicalizes outside the grant root is refused before it is read. The one route out is a link the reader presses: a trusted click on an anchor, reported by the preview's own preload from a guest still bound to a live grant, leaves through the configured browser destination and captured source route — and only after the reader confirms the exact destination URL, so a document cannot spend a single stray press exfiltrating what it can read into a link it authored. The preview hands its guest that focus itself whenever it is the surface the reader is in — a browsing page gets it from the chrome around it, and a preview has no chrome to get it from — and it does so only then, so a preview mounted behind a terminal or an editor never takes the keyboard from what the reader is actually in. It offers again when the window itself takes focus back and nothing in the embedder has claimed that focus, because another app coming to the front lands focus on the embedder rather than the guest and the route out would otherwise stay shut until something remounted the pane — while the same window focus also arrives when the reader presses a tab, that being the guest's own blur returning, and taking focus back from there would fight the reader for their own click. Nothing else does. A navigation or popup the document starts by itself is swallowed whatever else is happening, including immediately after a genuine press elsewhere in the document, so a page that can read its grant cannot hand it to a browser tab; a middle click opens nothing; and a fragment link is answered inside the document. A preview attach carries the preview preload and no renderer-supplied one, and no other attach path can acquire it. A subresource the workspace will not send degrades the document to a notice naming that file, never to a failure panel over a page that rendered. A grant outlives neither the tab that owns it nor the renderer document that minted it, and only the trusted renderer can mint or revoke one. For the browser creations this gate still owns, owner-pinned creation returns the canonical host page identity before navigation readiness; delayed navigation cannot turn a created page into an unidentifiable failure or a duplicate retry. Capability rejection before host mutation must preserve the original error, issue no RPC, surface a failure toast, and remove only a caller-declared newly-created empty split. Post-create reconciliation failure requires exact rollback; ambiguous rollback rejects without local fallback.", - "oracle": "In paired Electron, write an HTML fixture that declares its own title on the host, invoke the Explorer preview action on the client, and require the document text to be readable out of the orca-preview guest before judging any absence. With that presence established, require the client to hold exactly one browser workspace more than its baseline and that workspace to be the document one: page and tab url blank, the document path mirrored onto both, the tab named by the document's title, the chip naming the file, no editor row of the retired preview species anywhere, and the guest URL carrying the orca-preview scheme. Ask the host through its own page registry as well as through the tab snapshot, and in the same run open an ordinary URL browser tab from the same client and require that one to arrive in both — the presence precondition without which “the host gained nothing” is satisfied just as well by an oracle that cannot see browser pages at all. Require the preview to sit in a group other than the source editor's while the active group and tab remain the source editor's. Then click away to the terminal, click the preview tab, require it to reactivate and still render, close it with its own X, and require the document tab to be gone while the host still holds only the URL tab and the source group, source editor and terminal survive. Quit the client with a document tab open and relaunch it on the same profile: require the same workspace row to come back, blank and named by the document, rendering the document again over a grant URL that differs from the one that was quit, with no preview-scheme or document-named page anywhere in what the host holds. Prove the halves are live by flipping one product property at a time and requiring the run to fail: publish document workspaces to the host like ordinary ones, and stop mirroring the document onto the workspace row. Have the fixture document attempt its own egress on every load — an unattended window.open and location.href to an off-machine URL, plus an inline ICE gathering probe — and require the same baseline counts and a candidate count of zero, so the document's own attempts are measured rather than assumed. Then, as a separate phase after the close oracle has already run, bring the client window to the front, press the document's heading with a real mouse event, and require the document to report that the same press drove it to attempt a second window.open and location.href while both browser counts stay at that phase's baseline and nothing routes — the case a recent-input gate cannot distinguish from the press's own effect. Only then press the target=_blank link with a real mouse event and require both a recorded routing call that returned success and a browser count above that baseline, with the preview tab still open. Drive the preload's click policy as a unit oracle over a real document: a dispatched click, a trusted press on an external anchor, an anchor reached through what it wraps, an SVG animated href, a sibling preview link, fragment and percent-encoded fragment targets, a bare hash, and a middle click. Create a browser page located by a workspace document, handing creation a live grant URL, and require its stored url, its mirrored tab url and the written session payload all to be blank with no orca-preview string anywhere in what was written, while an ordinary page created the same way keeps the URL it was given and asks for the address bar the document page never does. Parse the written page and tab through the session schema and require the document to survive both halves. Hydrate them back and require the document page to return blank and still named — including when its page row was salvaged away and only the tab's own copy remains, and when a foreign session carried a grant URL into both rows. Drive the mirror across a page switch out of the document and back, and across a repair in which the document is the only mirrored field that differs. Name a document page from its document and require the tab to take that name, name it with an empty title and require the file, name it with a live grant URL and require the file again with no preview scheme anywhere in the written session, and require an ordinary blank browser tab beside it to still be called New Tab. Dispatch a title update out of a rendered preview's own guest and require it to reach the page state while the identity chip still reads the document's workspace-relative path. Attach a browsing guest and a workspace-document guest through the same method in one run and require the browsing one to take clicked-link routing, popup handling and auth-identity detach tracking while the document guest takes none of them, stays inside the grant it is showing, denies every window it asks for, and is dropped from the page-keyed document registry by the same teardown that frees its id for a later attach. Register a browsing guest and attach a workspace-document guest in one run against the real manager, require the one door to answer each page with the guest of its own half, and require the document page to be absent from the browsing map and from its enumeration. Drive both browsing registration entry points with a page the document half already holds and require them to register nothing, and drive the mint channel with a page the browsing half already holds and require it to refuse; and drive the offscreen one with a guest that is missing and with one already destroyed, requiring the same refusal. Arm a grab on a live preview target, drive the unregister channel at that same target in the window before the queued operation runs, and require the grab to reach the guest anyway — then drive the same sequence for an ordinary browser page and require its grab state to be disposed after all. Hold one viewport-bridge op open, queue a second behind it, swap the page's guest while that second op waits, and require the injection to land in the guest the page has then. Revoke a grant after a tool has run against its page and require that page's grab state to be cancelled and disposed. Ask a tool for a document page whose guest has not attached, require the request to park in the registration wait, attach the guest, and require the same request to arm on it; require a page nothing ever renders to answer not-ready once that wait elapses. With a document open, ask a tool for a browsing page id and require it to be answered by the browsing half or not at all, with the same channel reaching the document guest under the page it really renders. Drive open-externally for a document whose per-file owner is remote while the workspace-scoped owner is unresolved, for a runtime-owned worktree whose preview tab carries no runtime id of its own, and for a worktree that resolves no runtime owner while the tab still carries one, requiring the download route in each; and for an owner that cannot be resolved at all, and for an unknown workspace root while nothing names another host, requiring a refusal that neither opens nor downloads. Drive the headless backend with a page the document half already holds and require it to reject, destroy the window, and unregister nothing — then shut the backend down and require it still to have unregistered nothing. Navigate a bound preview guest at a second grant through both latch events and require it to stay on the grant it bound to. Mount a preview while a renderer drag is already in flight and require its guest to be click-through at the moment it is appended, not a turn later. Render the editor panel shell in each remaining tab mode and require the path header exactly where the surface does not already name itself. Drive the preview action and require a browser tab located by the document rather than an editor tab, require a second open of the same document to activate the tab it is already in, and require closing that tab to revoke its grant while a URL tab closed beside it revokes none. Publish a worktree holding a document tab and a URL tab and require only the URL tab to reach the mobile snapshot. Install the shared partition policies for a preview partition and for an ordinary browsing partition in the same run, fire each one's own will-download listener, and require the preview's to cancel while the browsing one still reaches the download router — then require the preview protocol installer to be what asks for that deny. In the same run, require the cancelled download to raise a reader-facing notice and the routed one to raise none. require the guest registry to name the bound grant for a live preview guest and nothing for a contents that is not one, has committed no document, or is gone. Drive the shell with a refusal and require one fixed sentence, still one row after three more refusals, standing beside an asset failure rather than being counted with it, gone behind the failure panel, and ignored when it names another preview's grant. Drive the main-side report gate directly for a sender that is no preview guest, a guest with no bound or a revoked grant, a genuine press Electron's webview focus flag misreports as unfocused, and non-web URLs; drive the reader-facing confirmation for accept, cancel, and a confirmed tab the browser refuses; and drive will-attach-webview in both preload directions. Run the per-owner reader, grant-containment, scheme-admission, guest-policy, and plan-routing contracts as unit oracles, including a host-reported truncation, an over-cap binary, and an out-of-worktree paired path. Drive the reader-facing component with the payloads the reader can actually produce — the entry document fails only as truncated or unreadable, a subresource additionally as a refused format — and require the asset case to leave the guest mounted. Drive the closed-tab cleanup hook, the window installer, and the grant IPC handlers directly, requiring the grant to be released when the preview tab closes, cleared at window creation and on a cross-document main-frame navigation, and refused to any sender that is not the trusted renderer. For the browser creations this gate still owns, run the unchanged contract oracle for direct create and side-preview callers with absent status, unknown capabilities, and a mixed-version host, requiring the original unsupported error or visible toast, zero RPCs, and no retained new split; hold a real navigation response beyond the 15-second client deadline after host creation and require the first RPC to return the exact host inventory page ID, one host page, and no retry; repeat reconciliation faults against headless serve and retain the separate exact-page reconciliation rollback oracle.", + "oracle": "The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): In paired Electron, write an HTML fixture that declares its own title on the host, invoke the Explorer preview action on the client, and require the document text to be readable out of the orca-preview guest before judging any absence. With that presence established, require the client to hold exactly one browser workspace more than its baseline and that workspace to be the document one: page and tab url blank, the document path mirrored onto both, the tab named by the document's title, the chip naming the file, no editor row of the retired preview species anywhere, and the guest URL carrying the orca-preview scheme. Ask the host through its own page registry as well as through the tab snapshot, and in the same run open an ordinary URL browser tab from the same client and require that one to arrive in both — the presence precondition without which “the host gained nothing” is satisfied just as well by an oracle that cannot see browser pages at all. Require the preview to sit in a group other than the source editor's while the active group and tab remain the source editor's. Then click away to the terminal, click the preview tab, require it to reactivate and still render, close it with its own X, and require the document tab to be gone while the host still holds only the URL tab and the source group, source editor and terminal survive. Quit the client with a document tab open and relaunch it on the same profile: require the same workspace row to come back, blank and named by the document, rendering the document again over a grant URL that differs from the one that was quit, with no preview-scheme or document-named page anywhere in what the host holds. Prove the halves are live by flipping one product property at a time and requiring the run to fail: publish document workspaces to the host like ordinary ones, and stop mirroring the document onto the workspace row. Have the fixture document attempt its own egress on every load — an unattended window.open and location.href to an off-machine URL, plus an inline ICE gathering probe — and require the same baseline counts and a candidate count of zero, so the document's own attempts are measured rather than assumed. Then, as a separate phase after the close oracle has already run, bring the client window to the front, press the document's heading with a real mouse event, and require the document to report that the same press drove it to attempt a second window.open and location.href while both browser counts stay at that phase's baseline and nothing routes — the case a recent-input gate cannot distinguish from the press's own effect. Only then press the target=_blank link with a real mouse event and require both a recorded routing call that returned success and a browser count above that baseline, with the preview tab still open. Drive the preload's click policy as a unit oracle over a real document: a dispatched click, a trusted press on an external anchor, an anchor reached through what it wraps, an SVG animated href, a sibling preview link, fragment and percent-encoded fragment targets, a bare hash, and a middle click. Create a browser page located by a workspace document, handing creation a live grant URL, and require its stored url, its mirrored tab url and the written session payload all to be blank with no orca-preview string anywhere in what was written, while an ordinary page created the same way keeps the URL it was given and asks for the address bar the document page never does. Parse the written page and tab through the session schema and require the document to survive both halves. Hydrate them back and require the document page to return blank and still named — including when its page row was salvaged away and only the tab's own copy remains, and when a foreign session carried a grant URL into both rows. Drive the mirror across a page switch out of the document and back, and across a repair in which the document is the only mirrored field that differs. Name a document page from its document and require the tab to take that name, name it with an empty title and require the file, name it with a live grant URL and require the file again with no preview scheme anywhere in the written session, and require an ordinary blank browser tab beside it to still be called New Tab. Dispatch a title update out of a rendered preview's own guest and require it to reach the page state while the identity chip still reads the document's workspace-relative path. Attach a browsing guest and a workspace-document guest through the same method in one run and require the browsing one to take clicked-link routing, popup handling and auth-identity detach tracking while the document guest takes none of them, stays inside the grant it is showing, denies every window it asks for, and is dropped from the page-keyed document registry by the same teardown that frees its id for a later attach. Register a browsing guest and attach a workspace-document guest in one run against the real manager, require the one door to answer each page with the guest of its own half, and require the document page to be absent from the browsing map and from its enumeration. Drive both browsing registration entry points with a page the document half already holds and require them to register nothing, and drive the mint channel with a page the browsing half already holds and require it to refuse; and drive the offscreen one with a guest that is missing and with one already destroyed, requiring the same refusal. Arm a grab on a live preview target, drive the unregister channel at that same target in the window before the queued operation runs, and require the grab to reach the guest anyway — then drive the same sequence for an ordinary browser page and require its grab state to be disposed after all. Hold one viewport-bridge op open, queue a second behind it, swap the page's guest while that second op waits, and require the injection to land in the guest the page has then. Revoke a grant after a tool has run against its page and require that page's grab state to be cancelled and disposed. Ask a tool for a document page whose guest has not attached, require the request to park in the registration wait, attach the guest, and require the same request to arm on it; require a page nothing ever renders to answer not-ready once that wait elapses. With a document open, ask a tool for a browsing page id and require it to be answered by the browsing half or not at all, with the same channel reaching the document guest under the page it really renders. Drive open-externally for a document whose per-file owner is remote while the workspace-scoped owner is unresolved, for a runtime-owned worktree whose preview tab carries no runtime id of its own, and for a worktree that resolves no runtime owner while the tab still carries one, requiring the download route in each; and for an owner that cannot be resolved at all, and for an unknown workspace root while nothing names another host, requiring a refusal that neither opens nor downloads. Drive the headless backend with a page the document half already holds and require it to reject, destroy the window, and unregister nothing — then shut the backend down and require it still to have unregistered nothing. Navigate a bound preview guest at a second grant through both latch events and require it to stay on the grant it bound to. Mount a preview while a renderer drag is already in flight and require its guest to be click-through at the moment it is appended, not a turn later. Render the editor panel shell in each remaining tab mode and require the path header exactly where the surface does not already name itself. Drive the preview action and require a browser tab located by the document rather than an editor tab, require a second open of the same document to activate the tab it is already in, and require closing that tab to revoke its grant while a URL tab closed beside it revokes none. Publish a worktree holding a document tab and a URL tab and require only the URL tab to reach the mobile snapshot. Install the shared partition policies for a preview partition and for an ordinary browsing partition in the same run, fire each one's own will-download listener, and require the preview's to cancel while the browsing one still reaches the download router — then require the preview protocol installer to be what asks for that deny. In the same run, require the cancelled download to raise a reader-facing notice and the routed one to raise none. require the guest registry to name the bound grant for a live preview guest and nothing for a contents that is not one, has committed no document, or is gone. Drive the shell with a refusal and require one fixed sentence, still one row after three more refusals, standing beside an asset failure rather than being counted with it, gone behind the failure panel, and ignored when it names another preview's grant. Drive the main-side report gate directly for a sender that is no preview guest, a guest with no bound or a revoked grant, a genuine press Electron's webview focus flag misreports as unfocused, and non-web URLs; drive the reader-facing confirmation for accept, cancel, and a confirmed tab the browser refuses; and drive will-attach-webview in both preload directions. Run the per-owner reader, grant-containment, scheme-admission, guest-policy, and plan-routing contracts as unit oracles, including a host-reported truncation, an over-cap binary, and an out-of-worktree paired path. Drive the reader-facing component with the payloads the reader can actually produce — the entry document fails only as truncated or unreadable, a subresource additionally as a refused format — and require the asset case to leave the guest mounted. Drive the closed-tab cleanup hook, the window installer, and the grant IPC handlers directly, requiring the grant to be released when the preview tab closes, cleared at window creation and on a cross-document main-frame navigation, and refused to any sender that is not the trusted renderer. For the browser creations this gate still owns, run the unchanged contract oracle for direct create and side-preview callers with absent status, unknown capabilities, and a mixed-version host, requiring the original unsupported error or visible toast, zero RPCs, and no retained new split; hold a real navigation response beyond the 15-second client deadline after host creation and require the first RPC to return the exact host inventory page ID, one host page, and no retry; repeat reconciliation faults against headless serve and retain the separate exact-page reconciliation rollback oracle.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime-browser.test.ts src/main/runtime/rpc/methods/browser.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts src/renderer/src/runtime/web-session-tabs-sync-browser-tabs.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/document-preview/doc-preview-grant-registry.test.ts src/main/browser/document-preview/doc-preview-file-reader.test.ts src/main/browser/document-preview/doc-preview-protocol.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/main/browser/document-preview/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts", @@ -5780,8 +5728,8 @@ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx", "pnpm exec vitest run --config config/vitest.config.ts src/preload/doc-preview-link-interception.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/main/window/main-window-webview-security.test.ts src/main/ipc/doc-preview-grant-ipc.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/main/ipc/browser/browser.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", - "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", + "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", + "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/doc-preview-grant-ipc.test.ts src/renderer/src/components/link-actions/LinkActionPopover.test.tsx src/renderer/src/runtime/sync-runtime-graph-editor-diff-tabs.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/ipc/browser/browser-tab-registration-wait.test.ts src/main/ipc/doc-preview-grant-ipc.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-policy-profile.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-page-conversion.test.ts src/renderer/src/runtime/sync-runtime-graph-conversion-publish.test.ts src/renderer/src/lib/workspace-doc-address-input.test.ts src/shared/workspace-session/workspace-doc-history.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx src/renderer/src/components/browser-pane/assemble-chrome/browser-address-bar-suggestions.test.ts", @@ -5818,7 +5766,6 @@ "src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts", "src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts", "src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts", - "src/renderer/src/components/editor/EditorPanelShell.header.test.tsx", "src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", "src/shared/doc-preview-scheme.test.ts", "src/renderer/src/lib/doc-preview-grants.test.ts", @@ -5831,14 +5778,14 @@ "src/renderer/src/store/slices/browser-page-conversion.test.ts", "src/renderer/src/runtime/sync-runtime-graph-conversion-publish.test.ts", "src/renderer/src/lib/workspace-doc-address-input.test.ts", - "src/shared/workspace-session/workspace-doc-history.test.ts", "src/renderer/src/components/browser-pane/assemble-chrome/browser-address-bar-suggestions.test.ts", "tests/e2e/paired-remote-html-preview-local-render.spec.ts", "tests/e2e/paired-preview-address-bar-convergence.spec.ts", "tests/e2e/paired-browser-create-navigation-deadline.spec.ts", "tests/e2e/paired-browser-creation-reconciliation-failure.spec.ts", "src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts", - "src/renderer/src/runtime/web-session-tabs-sync-browser-tabs.test.ts" + "src/renderer/src/runtime/web-session-tabs-sync-browser-tabs.test.ts", + "src/shared/workspace-session/workspace-doc-history.test.ts" ], "assertionRefs": [ { @@ -6165,16 +6112,6 @@ "summary": "Candidate with the sole delta that every failure sets the blocking reason, asset or not, failed 4/14 on the asset-notice assertions — the same mutant the retired 2026-08-26 record measured at 3/10, re-run at the relocated path. A refused subresource must name itself over a document that stays mounted; only the entry document may take the page away.", "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx" }, - { - "date": "2026-08-27", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser-preview-tool-authorization.test.ts src/main/browser/browser-manager-annotation-bridge.test.ts src/main/browser/browser-manager-guest-lifecycle.test.ts src/main/browser/offscreen-browser-backend-lifecycle.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", - "result": "passed", - "durationSeconds": 1.52, - "summary": "174/174 at the relocated paths, replacing the 2026-08-27 records of the same command at 162/162 and 158/158. Those established, as sole deltas: removing the unregister-channel refusal goes red because the grab disposal beside it drops the intent an in-flight preview grab compares by identity, while an ordinary browser page driven through the identical sequence still has its grab state disposed; removing registration’s already-gone-guest refusal goes red for both a missing and a destroyed guest; removing the map deletion on the refusal path goes red at shutdown; the bridge’s own unregisterGuest branch, restored, tears down a live page over a misaddressed request; and the download fall-through for an unresolved owner hands back a same-named local file under a remote document’s name.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx" - }, { "date": "2026-08-27", "runner": "local", @@ -6395,16 +6332,6 @@ "summary": "Seven focused host and renderer contract files passed 183/183 tests, covering owning-runtime routing, focused and unfocused creation, placement, reconciliation, failure cleanup, and teardown. Historical command preserved after removal of the mocked preview test suite in the test-value audit; currentCommand links only the remaining suites. The recorded result, count, duration and mutation narrative describe the historical command. The current command has not been rerun here and does not claim equivalent coverage.", "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime-browser.test.ts src/main/runtime/rpc/methods/browser.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts src/renderer/src/runtime/web-session-tabs-sync-browser-tabs.test.ts" }, - { - "date": "2026-08-13", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-browser.test.ts src/renderer/src/hooks/useIpcEvents-browser-tab-create.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-session-tabs.test.ts src/renderer/src/store/slices/browser.test.ts src/renderer/src/runtime/web-runtime-session.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-browser.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-session-tabs.test.ts src/renderer/src/store/slices/browser.test.ts", - "result": "passed", - "durationSeconds": 10.83, - "summary": "Five post-review host, IPC, store, and client lifecycle files passed 1,356 tests with one pre-existing skip, including both shards that failed before the canonical page/workspace identity and typed missing-page cleanup corrections." - }, { "date": "2026-08-13", "runner": "local", @@ -6418,19 +6345,19 @@ ], "runtimeBudget": { "p95Seconds": 90, - "scope": "focused host/renderer contracts plus normal and faulted headed/headless paired-runtime journeys" + "scope": "Historical pre-audit selector budget; changed selectors have no new runtime measurement. Unchanged receipts remain scoped to their own commands." }, "flakeHistory": { "status": "unknown", - "evidence": "The candidate passed isolated local macOS Electron runs before and after the final rebase; longer CI and soak history is not yet available." + "evidence": "Changed selectors have no new soak result after Audit1308; any retained unchanged-source receipts apply only to their own commands. Historical account: The candidate passed isolated local macOS Electron runs before and after the final rebase; longer CI and soak history is not yet available." }, "redGreenEvidence": { - "status": "complete", - "evidence": "The unchanged STA-4063 five-assertion oracle is red 5/5 on scan e4c278eb52 and latest main bc2e30000b, partially green 2/5 on pre-fix PR #14100, green 5/5 on candidate 14b5877a75, and red 3/5 when only the new transaction cleanup boundary is disabled on final candidate a9e6fb7716. The earlier STA-4025 transaction oracle remains red on the post-#13909 base and green on this candidate. The byte-identical STA-4231 headed oracle is red on latest-main b19d99bd00: both 15-second creates return runtime_timeout without a page ID while the host retains two distinct pages after retry. The relevant behavior is unchanged through 0a0ae974d4. The candidate is green with one returned canonical ID, one host/client page, and no retry; disabling only the navigation-independent acknowledgement is red with the same two orphaned pages. The byte-identical 2ec1dcf1 Electron oracle remains red on latest main 346e59c879, green on candidate d05217c40d, and red with only side-preview focus disabled; historical 5806b23f evidence separately proves owning-runtime routing. For the STA-5557 preview behavior the rewritten journey is green 1/1 on the candidate and red when the sole delta makes the same preview action also create a browser tab, failing on clientBrowserWorkspaceCount 1 versus baseline 0. That absence assertion is gated on first reading the rendered document out of the guest, so it cannot pass by the preview never happening. Against pre-STA-5557 builds the journey is red by construction rather than by measurement: those builds have no html-preview tab mode, so the run would fail earlier, at materialization, and it has not been executed against an old build. The reader-facing half is red-green at the unit layer. The 6/6 green and 2/6 red pair recorded earlier on 2026-08-26 measured the superseded shape, where a subresource failure was filtered out entirely; the shipping shape is green 10/10 and red 3/10 when the sole delta makes every failure, asset or entry, set the blocking reason. The containment lines the second review found uncovered are red-green too: installing the scheme handler on the default session instead of the preview session, and widening the scheme privileges with allowServiceWorkers and bypassCSP, each fail the protocol suite as the sole delta, and deleting the html-preview case from the closed-tab cleanup fails the hook suite. The external-link phase added on 2026-08-26 is red-green by measurement: green 3/3 on the trusted-click design, in which the guest's own preload reports a trusted press on an anchor and main answers only a focused preview guest holding a live grant. Its earlier shape depended on the webview carrying allowpopups and went red with zero recorded routing calls when only that attribute was dropped; the preload now intercepts the press before Chromium considers a popup, so the attribute is gone and that measurement retired with it. One further run failed in the paired-client harness before reaching any preview step, when the client window never exposed its store within the launch timeout; that is a harness flake, not a preview signal. The two egress fences added on 2026-08-26 are red-green by measurement in the same journey, whose fixture attempts its own escape on every load and again from inside the handler for the reader's press on the heading: with deny-only navigation sinks the document opens no browser tab and the counts hold at baseline, and with the sole delta that those sinks route again behind a two-second recent-input gate the post-press location.href opens a real tab, clientBrowserWorkspaceCountAllWorktrees 1 against a baseline of 0. An earlier measurement of that same gate, taken when it was the shipping design, showed the unattended on-load egress opening two tabs once the gate was removed entirely; it was retired with the gate, because a gate that cannot tell a script's navigation from the click it followed was never the fence the claim needed. With the guest's IP-handling policy the document reports candidates=0, and with the sole delta that the policy call is removed it reports candidates=6. Both mutants were applied to the shipping module, rebuilt, and run against the live paired journey rather than reasoned about." + "status": "missing", + "evidence": "Audit1308 changed referenced test sources; no new red/green validation of the reduced set. Historical account: The unchanged STA-4063 five-assertion oracle is red 5/5 on scan e4c278eb52 and latest main bc2e30000b, partially green 2/5 on pre-fix PR #14100, green 5/5 on candidate 14b5877a75, and red 3/5 when only the new transaction cleanup boundary is disabled on final candidate a9e6fb7716. The earlier STA-4025 transaction oracle remains red on the post-#13909 base and green on this candidate. The byte-identical STA-4231 headed oracle is red on latest-main b19d99bd00: both 15-second creates return runtime_timeout without a page ID while the host retains two distinct pages after retry. The relevant behavior is unchanged through 0a0ae974d4. The candidate is green with one returned canonical ID, one host/client page, and no retry; disabling only the navigation-independent acknowledgement is red with the same two orphaned pages. The byte-identical 2ec1dcf1 Electron oracle remains red on latest main 346e59c879, green on candidate d05217c40d, and red with only side-preview focus disabled; historical 5806b23f evidence separately proves owning-runtime routing. For the STA-5557 preview behavior the rewritten journey is green 1/1 on the candidate and red when the sole delta makes the same preview action also create a browser tab, failing on clientBrowserWorkspaceCount 1 versus baseline 0. That absence assertion is gated on first reading the rendered document out of the guest, so it cannot pass by the preview never happening. Against pre-STA-5557 builds the journey is red by construction rather than by measurement: those builds have no html-preview tab mode, so the run would fail earlier, at materialization, and it has not been executed against an old build. The reader-facing half is red-green at the unit layer. The 6/6 green and 2/6 red pair recorded earlier on 2026-08-26 measured the superseded shape, where a subresource failure was filtered out entirely; the shipping shape is green 10/10 and red 3/10 when the sole delta makes every failure, asset or entry, set the blocking reason. The containment lines the second review found uncovered are red-green too: installing the scheme handler on the default session instead of the preview session, and widening the scheme privileges with allowServiceWorkers and bypassCSP, each fail the protocol suite as the sole delta, and deleting the html-preview case from the closed-tab cleanup fails the hook suite. The external-link phase added on 2026-08-26 is red-green by measurement: green 3/3 on the trusted-click design, in which the guest's own preload reports a trusted press on an anchor and main answers only a focused preview guest holding a live grant. Its earlier shape depended on the webview carrying allowpopups and went red with zero recorded routing calls when only that attribute was dropped; the preload now intercepts the press before Chromium considers a popup, so the attribute is gone and that measurement retired with it. One further run failed in the paired-client harness before reaching any preview step, when the client window never exposed its store within the launch timeout; that is a harness flake, not a preview signal. The two egress fences added on 2026-08-26 are red-green by measurement in the same journey, whose fixture attempts its own escape on every load and again from inside the handler for the reader's press on the heading: with deny-only navigation sinks the document opens no browser tab and the counts hold at baseline, and with the sole delta that those sinks route again behind a two-second recent-input gate the post-press location.href opens a real tab, clientBrowserWorkspaceCountAllWorktrees 1 against a baseline of 0. An earlier measurement of that same gate, taken when it was the shipping design, showed the unattended on-load egress opening two tabs once the gate was removed entirely; it was retired with the gate, because a gate that cannot tell a script's navigation from the click it followed was never the fence the claim needed. With the guest's IP-handling policy the document reports candidates=0, and with the sole delta that the policy call is removed it reports candidates=6. Both mutants were applied to the shipping module, rebuilt, and run against the live paired journey rather than reasoned about." }, "performanceBudget": { "required": true, - "evidence": "The transaction adds no timer, polling, retry, subprocess, RPC method, stream opcode, or global host publication. Owner-pinned success performs one create RPC followed by one asynchronous goto, retains one page, and performs zero retry or cleanup calls. A lost create acknowledgement performs one exact close by the preallocated canonical ID and at most one confirming snapshot. Headless goto publishes one worktree-scoped session snapshot after navigation. Capability rejection performs zero RPCs and one bounded empty-group close. The E2E-only one-shot faults are build-gated, retain one promise/flag and a bounded page-id set, and reset after each run. Pending client placements remain capped at 128 and clear on adoption, failure, or teardown." + "evidence": "Historical account for the pre-audit test set; removed work-count alarms and changed selectors have no fresh measurement. The transaction adds no timer, polling, retry, subprocess, RPC method, stream opcode, or global host publication. Owner-pinned success performs one create RPC followed by one asynchronous goto, retains one page, and performs zero retry or cleanup calls. A lost create acknowledgement performs one exact close by the preallocated canonical ID and at most one confirming snapshot. Headless goto publishes one worktree-scoped session snapshot after navigation. Capability rejection performs zero RPCs and one bounded empty-group close. The E2E-only one-shot faults are build-gated, retain one promise/flag and a bounded page-id set, and reset after each run. Pending client placements remain capped at 128 and clear on adoption, failure, or teardown." }, "promotionCriteria": [ "Collect Linux and physical Windows paired-server evidence.", @@ -6450,7 +6377,10 @@ "The standalone capability-cleanup and browser-placement ledger suites were removed, including their mixed-version rejection, cleanup transfer, capacity and partition alarms. web-runtime-session.test.ts no longer supplies browser creation, rollback or recovery assertions. The grouped workspace-document exclusion remains; the separate no-group publication example was removed. Existing live/browser-store evidence does not establish equivalent replacement alarms. Earlier descriptions of the removed fixtures and their run results are historical.", "The client-owned-placement and layout-groups suites are removed. The broad mirror fixture no longer supplies preview placement/reservation/move assertions. Browser rows retain pre-response replay and real local-workspace reuse; focus intent retains response ordering and exact-leaf sibling ordering. Supplied browser-layout and placement/focus projection alarms are lost; retained evidence is narrower and not equivalent.", "The test-value audit retires renderer grant-release mock ledgers, direct creation/title/session-field copies, notification/onboarding calls and several placement/per-page projections. Canonical-ID rejection, document reopen/restore and preview URL fences remain; they do not prove close-to-registry revocation or replace every lost alarm. Historical execution evidence is unchanged.", - "The test-value audit removed the direct legacy editor-preview hydration example from tabs-hydration.test.ts. Dropping legacy preview chrome while preserving the same-document ordinary editor no longer has that alarm. The surviving corrupt-id deduplication case does not cover that migration, and retained preview journeys are not equivalent replacement coverage. Historical run counts and migration mutation findings describe the earlier suite." + "The test-value audit removed the direct legacy editor-preview hydration example from tabs-hydration.test.ts. Dropping legacy preview chrome while preserving the same-document ordinary editor no longer has that alarm. The surviving corrupt-id deduplication case does not cover that migration, and retained preview journeys are not equivalent replacement coverage. Historical run counts and migration mutation findings describe the earlier suite.", + "Audit1308: src/renderer/src/components/editor/EditorPanelShell.header.test.tsx removed. Header visibility and mocked editor-surface inventory alarms disappear. No equivalent replacement coverage is claimed.", + "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-27\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser-preview-tool-authorization.test.ts src/main/browser/browser-manager-annotation-bridge.test.ts src/main/browser/browser-manager-guest-lifecycle.test.ts src/main/browser/offscreen-browser-backend-lifecycle.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.52,\n \"summary\": \"174/174 at the relocated paths, replacing the 2026-08-27 records of the same command at 162/162 and 158/158. Those established, as sole deltas: removing the unregister-channel refusal goes red because the grab disposal beside it drops the intent an in-flight preview grab compares by identity, while an ordinary browser page driven through the identical sequence still has its grab state disposed; removing registration’s already-gone-guest refusal goes red for both a missing and a destroyed guest; removing the map deletion on the refusal path goes red at shutdown; the bridge’s own unregisterGuest branch, restored, tears down a live page over a misaddressed request; and the download fall-through for an unresolved owner hands back a same-named local file under a remote document’s name.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx\"\n }", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-browser.test.ts src/renderer/src/hooks/useIpcEvents-browser-tab-create.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-session-tabs.test.ts src/renderer/src/store/slices/browser.test.ts src/renderer/src/runtime/web-runtime-session.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-browser.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-session-tabs.test.ts src/renderer/src/store/slices/browser.test.ts\",\"result\":\"passed\",\"durationSeconds\":10.83,\"summary\":\"Five post-review host, IPC, store, and client lifecycle files passed 1,356 tests with one pre-existing skip, including both shards that failed before the canonical page/workspace identity and typed missing-page cleanup corrections.\"}" ], "demotionRule": "Demote if a paired HTML preview creates a browser workspace on either side, renders from anything other than the orca-preview scheme, serves a read the host reported as truncated or over-cap, serves a document without the self-only content security policy, lets the preview session carry an off-machine request, reads an SSH path that canonicalizes outside its grant root, steals focus from the source editor when it opens, removes unrelated editor or terminal state on close, hides a document that rendered because one subresource was refused, leaves a grant readable after the tab or renderer that owns it is gone, mints one for a sender that is not the trusted renderer, opens a browser tab for a navigation the document started with no one at the keyboard, or lets the guest gather an ICE candidate; or if a paired browser creation this gate still owns produces duplicate client or host identities or loses its requested split." }, @@ -10055,7 +9985,7 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "daemon", "ssh", "wsl", "remote-runtime"], - "coverageNotes": "Audit1301 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic macOS tests cover controller claims, daemon and SSH/relay operation replay, mixed-version selection, runtime ownership, exact provisional handoff, durable terminal retirement, two independent viewer mirrors, guarded adoption of legacy live PTYs, and completion classification when either the outer remote transport or authoritative host/provider process inspection becomes unreachable. The adoption harness models v1.4.150 agent/setup/shell tabs, current-generation restart and reconnect, exact handle/incarnation/worktree/host checks, topology CAS, competing clients, split-pane/group restoration, WSL ownership, and SSH owner rejection. The secondary parity repro runs independent clients against one headless remote Orca runtime over encrypted pairing and a real daemon-backed PTY, with tokened fixture-process identity separated from unrelated Codex app-server startup probes. The automated primary topology runs an isolated headed macOS Orca desktop server plus a separate paired web client and proves viewer-local fresh/resume focus, exact legacy placement, writable PTYs, unrelated-terminal survival, and host/client cleanup. SSH coverage is provider/relay contract and fault-injection coverage only; it does not substitute for paired-server coverage. Live Windows, Linux, WSL, SSH, and physical paired-Linux hosts remain gaps. The isolated intent-owner ledger suite was removed. web-runtime-session.test.ts retains exact post-create handoff confirmation only; its previous fresh/resume presentation and publication-replay assertions are no longer current references. Those direct owner-partition, cap and RPC/focus alarms are lost; other agent-session evidence does not establish equivalence. Structured handoff and adopted-leaf focus references now point to their dedicated surviving fixtures. Mixed-version recovery retains actual old-host recovery followed by a new CLI tab; its unsupported/missing-evidence tables are removed. Orphan recovery retains in-flight adoption-conflict serialization only; previous topology/pruning and absence/adoption examples are removed. These removed projection, capability and adoption-shape alarms are lost; retained cases do not replace them. Audit1301: src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts reduced. Direct dedupe eviction/count and equal receipt-position sample loses authored alarm. No equivalent replacement coverage is claimed.", + "coverageNotes": "Audits1301 and1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic macOS tests cover controller claims, daemon and SSH/relay operation replay, mixed-version selection, runtime ownership, exact provisional handoff, durable terminal retirement, two independent viewer mirrors, guarded adoption of legacy live PTYs, and completion classification when either the outer remote transport or authoritative host/provider process inspection becomes unreachable. The adoption harness models v1.4.150 agent/setup/shell tabs, current-generation restart and reconnect, exact handle/incarnation/worktree/host checks, topology CAS, competing clients, split-pane/group restoration, WSL ownership, and SSH owner rejection. The secondary parity repro runs independent clients against one headless remote Orca runtime over encrypted pairing and a real daemon-backed PTY, with tokened fixture-process identity separated from unrelated Codex app-server startup probes. The automated primary topology runs an isolated headed macOS Orca desktop server plus a separate paired web client and proves viewer-local fresh/resume focus, exact legacy placement, writable PTYs, unrelated-terminal survival, and host/client cleanup. SSH coverage is provider/relay contract and fault-injection coverage only; it does not substitute for paired-server coverage. Live Windows, Linux, WSL, SSH, and physical paired-Linux hosts remain gaps. The isolated intent-owner ledger suite was removed. web-runtime-session.test.ts retains exact post-create handoff confirmation only; its previous fresh/resume presentation and publication-replay assertions are no longer current references. Those direct owner-partition, cap and RPC/focus alarms are lost; other agent-session evidence does not establish equivalence. Structured handoff and adopted-leaf focus references now point to their dedicated surviving fixtures. Mixed-version recovery retains actual old-host recovery followed by a new CLI tab; its unsupported/missing-evidence tables are removed. Orphan recovery retains in-flight adoption-conflict serialization only; previous topology/pruning and absence/adoption examples are removed. These removed projection, capability and adoption-shape alarms are lost; retained cases do not replace them. Audit1301: src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts reduced. Direct dedupe eviction/count and equal receipt-position sample loses authored alarm. No equivalent replacement coverage is claimed. Audit1308: src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts removed. Exact provisional replacement and post-create exit confirmation handoff pipeline alarms disappear. No equivalent replacement coverage is claimed. Audit1313: src/main/runtime/terminal-orphan-topology.test.ts: Retain actual topology merge algorithms preserving disjoint subtrees and grafted/ambiguous shared anchors with independent expected trees. Remove duplicate leaf validator and empty/no-change passthrough examples. Lose basic validation and no-group/noop characterization; no renderer layout proof. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/8878", "https://github.com/stablyai/orca/issues/9151", @@ -10078,8 +10008,7 @@ "Manual headed paired-server journey: isolated Orca desktop host + separate paired web client + real Codex process + 20-second WebSocket fault + reconnect + explicit stop", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal-orphan-owner.test.ts src/main/runtime/terminal-orphan-topology.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts --maxWorkers=1", "pnpm exec vitest run --config config/vitest.config.ts src/main/providers/pty-process-inspection.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/runtime/orca-runtime.test.ts tests/e2e/remote-agent-completion-authority.unit.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts tests/e2e/remote-agent-completion-authority.unit.test.ts src/main/providers/pty-process-inspection.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts src/renderer/src/components/terminal-pane/pty-connection-completion-and-notification.test.ts src/renderer/src/lib/codex-session-restart.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts" + "pnpm exec vitest run --config config/vitest.config.ts tests/e2e/remote-agent-completion-authority.unit.test.ts src/main/providers/pty-process-inspection.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts src/renderer/src/components/terminal-pane/pty-connection-completion-and-notification.test.ts src/renderer/src/lib/codex-session-restart.test.ts" ], "testFiles": [ "src/main/providers/pty-process-inspection.test.ts", @@ -10112,14 +10041,17 @@ "src/main/runtime/terminal-orphan-topology.test.ts", "src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts", "src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts", - "src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts", - "src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts" + "src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts" ], "assertionRefs": [ { "file": "src/main/runtime/orca-runtime.test.ts", "assertions": [ - "completion-sensitive process inspection preserves authoritative host/provider failures" + "v1.4.150-shaped agent, setup, and shell PTYs adopt as one CAS transaction while stale incarnation and competing clients fail safely", + "current-generation restart and disconnect/reconnect preserve output, input, resize, title, tab, leaf, handle, and incarnation identity", + "split-pane and multi-group legacy topology merges beside a newer host-owned terminal without replacing it", + "equivalent Windows and separator-normalized persisted worktree keys canonicalize without duplicate terminal topology", + "connection mismatch, reused handles, SSH ownership mismatch, and stale topology revisions cannot claim a live PTY while WSL ownership succeeds" ] }, { @@ -10241,13 +10173,6 @@ "file": "src/renderer/src/runtime/web-runtime-session.test.ts", "assertions": ["confirms only the exact handoff after its post-create list completes"] }, - { - "file": "src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts", - "assertions": [ - "replaces only the provisional tab with an exact structured-create handoff", - "retires an exact provisional handoff only after a post-create snapshot confirms exit" - ] - }, { "file": "src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts", "assertions": [ @@ -10364,11 +10289,11 @@ }, "flakeHistory": { "status": "unknown", - "evidence": "Changed selectors have no new soak result after Audit1301; any retained unchanged-source receipts apply only to their own commands. Historical account: New experimental gate with deterministic local coverage and no soak history yet." + "evidence": "Changed selectors have no new soak result after Audits1301 and1308; any retained unchanged-source receipts apply only to their own commands. Historical account: New experimental gate with deterministic local coverage and no soak history yet." }, "redGreenEvidence": { "status": "missing", - "evidence": "Audit1301 changed referenced test sources; no new red/green validation of the reduced set. Historical account: Issue #10192 has byte-identical renderer-oracle evidence: origin/main@ee87bb38d (and earlier ef985ed80 and 94d3db4a2) fails activated fresh and resume rows by requesting focused host presentation, the PR client change passes all four rows, and disabling it turns the activated rows red again. The original PR still fails an old-client focused request against a new headless host; host-boundary normalization turns that mixed-version control green while trusted local callers remain focused. Issue #9151 has local red/green evidence for completion authority. The exact retirement oracle is red on pre-#9687 commit 2a32c5c9a and PR #9053 head d3a1d3047, and green on main@4fce2de49. Saved CI artifacts are still needed." + "evidence": "Audits1301 and1308 changed referenced test sources; no new red/green validation of the reduced set. Historical account: Issue #10192 has byte-identical renderer-oracle evidence: origin/main@ee87bb38d (and earlier ef985ed80 and 94d3db4a2) fails activated fresh and resume rows by requesting focused host presentation, the PR client change passes all four rows, and disabling it turns the activated rows red again. The original PR still fails an old-client focused request against a new headless host; host-boundary normalization turns that mixed-version control green while trusted local callers remain focused. Issue #9151 has local red/green evidence for completion authority. The exact retirement oracle is red on pre-#9687 commit 2a32c5c9a and PR #9053 head d3a1d3047, and green on main@4fce2de49. Saved CI artifacts are still needed." }, "performanceBudget": { "required": true, @@ -10391,7 +10316,10 @@ "Structured handoff and adopted-leaf focus references now point to their dedicated surviving fixtures. Mixed-version recovery retains actual old-host recovery followed by a new CLI tab; its unsupported/missing-evidence tables are removed. Orphan recovery retains in-flight adoption-conflict serialization only; previous topology/pruning and absence/adoption examples are removed. These removed projection, capability and adoption-shape alarms are lost; retained cases do not replace them.", "Audit1301: src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts reduced. Direct dedupe eviction/count and equal receipt-position sample loses authored alarm. No equivalent replacement coverage is claimed.", "Audit1301 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-07-21\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/runtime/orca-runtime-agent-session-operation.test.ts src/main/runtime/remote-agent-session-host-authority.integration.test.ts src/main/runtime/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 7.67,\n \"summary\": \"Ten focused files and 325 tests passed after the final review fixes, covering claim scope, mixed-version Pi/SSH fallback ordering, operation replay, terminal retirement, causal inventory fencing, exact concurrent handoff confirmation, daemon-generation integration, transport behavior, and remote host integration.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/runtime/orca-runtime-agent-session-operation.test.ts src/main/runtime/remote-agent-session-host-authority.integration.test.ts src/main/runtime/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/runtime/web-runtime-session.test.ts\"\n }", - "Audit1301 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts\",\n \"assertions\": [\n \"a causally post-operation inventory waits out an older request and concurrent confirmations share the fresh request\"\n ]\n }\n]" + "Audit1301 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts\",\n \"assertions\": [\n \"a causally post-operation inventory waits out an older request and concurrent confirmations share the fresh request\"\n ]\n }\n]", + "Audit1308: src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts removed. Exact provisional replacement and post-create exit confirmation handoff pipeline alarms disappear. No equivalent replacement coverage is claimed.", + "Audit1308 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts\",\n \"assertions\": [\n \"replaces only the provisional tab with an exact structured-create handoff\",\n \"retires an exact provisional handoff only after a post-create snapshot confirms exit\"\n ]\n }\n]", + "Audit1313: src/main/runtime/terminal-orphan-topology.test.ts: Retain actual topology merge algorithms preserving disjoint subtrees and grafted/ambiguous shared anchors with independent expected trees. Remove duplicate leaf validator and empty/no-change passthrough examples. Lose basic validation and no-group/noop characterization; no renderer layout proof. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim." ], "demotionRule": "Keep experimental or demote if the focused gate flakes without a product or harness bug, if a retry can physically spawn twice, if a stale exit/publication can replace or resurrect a terminal, or if mixed-version fallback occurs after an authority side effect." }, @@ -10667,9 +10595,9 @@ ], "platforms": ["macos", "linux", "windows"], "providers": ["local", "daemon", "ssh", "remote-runtime"], - "coveredPlatforms": ["macos"], + "coveredPlatforms": ["macos", "linux"], "coveredProviders": [], - "coverageNotes": "Local macOS evidence on main@1282f5c2d, including #7192's runtime-mirror geometry authority slice. Deterministic provider-contract coverage now includes settled window-wake reassertion and SSH relay applied-size readback. Live shell-visible SSH/remote geometry and Windows ConPTY readback remain non-blocking gaps.", + "coverageNotes": "Local macOS evidence on main@1282f5c2d, including #7192's runtime-mirror geometry authority slice. Deterministic provider-contract coverage now includes settled window-wake reassertion and SSH relay applied-size readback. Live shell-visible SSH/remote geometry and Windows ConPTY readback remain non-blocking gaps. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/6644", "https://github.com/stablyai/orca/pull/6649", @@ -10741,14 +10669,14 @@ ], "evidenceRuns": [ { - "date": "2026-07-21", + "date": "2026-11-04", "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-size-reconcile.test.ts src/renderer/src/components/terminal-pane/pty-size-reassertion.test.ts src/renderer/src/components/terminal-pane/split-right-white-screen.test.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/main/ipc/pty-applied-size-reporting.test.ts src/main/runtime/orca-runtime.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-size-reconcile.test.ts src/renderer/src/components/terminal-pane/pty-size-reassertion.test.ts src/renderer/src/components/terminal-pane/split-right-white-screen.test.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/main/ipc/pty/delivery/pty-applied-size-reporting.test.ts src/main/runtime/orca-runtime.test.ts", + "platform": "linux", "result": "passed", - "durationSeconds": 18.54, - "summary": "8 test files passed, 1,272 tests passed on the PR branch." + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/renderer/src/components/terminal-pane/pty-size-reconcile.test.ts src/renderer/src/components/terminal-pane/pty-size-reassertion.test.ts src/renderer/src/components/terminal-pane/split-right-white-screen.test.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/main/ipc/pty/delivery/pty-applied-size-reporting.test.ts src/main/runtime/orca-runtime.test.ts --maxWorkers=2", + "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-size-reconcile.test.ts src/renderer/src/components/terminal-pane/pty-size-reassertion.test.ts src/renderer/src/components/terminal-pane/split-right-white-screen.test.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/main/ipc/pty/delivery/pty-applied-size-reporting.test.ts src/main/runtime/orca-runtime.test.ts", + "durationSeconds": 26.94, + "summary": "Test Files 6 passed (6); Tests 1444 passed | 3 skipped (1447). Focused current selector, single Linux execution; native adapters remain mocked where present. No physical-device, p95, or deployed mixed-host claim. Source head 361cc80b18cbc73bdcda3cc2d67f47aab4b9d2e7." } ], "runtimeBudget": { @@ -10775,7 +10703,8 @@ "Local and Docker-backed SSH Electron wake specs exist but remain manual/non-blocking pending runtime and flake history.", "The deterministic gate proves the SSH provider/relay readback contract, not live shell-visible convergence across every remote path.", "Does not yet prove Windows ConPTY geometry/readback.", - "Current command uses deterministic fake providers for the main/renderer contracts, not a real remote PTY." + "Current command uses deterministic fake providers for the main/renderer contracts, not a real remote PTY.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-07-21\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-size-reconcile.test.ts src/renderer/src/components/terminal-pane/pty-size-reassertion.test.ts src/renderer/src/components/terminal-pane/split-right-white-screen.test.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/main/ipc/pty-applied-size-reporting.test.ts src/main/runtime/orca-runtime.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-size-reconcile.test.ts src/renderer/src/components/terminal-pane/pty-size-reassertion.test.ts src/renderer/src/components/terminal-pane/split-right-white-screen.test.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/main/ipc/pty/delivery/pty-applied-size-reporting.test.ts src/main/runtime/orca-runtime.test.ts\",\"result\":\"passed\",\"durationSeconds\":18.54,\"summary\":\"8 test files passed, 1,272 tests passed on the PR branch.\"}" ], "demotionRule": "Cannot promote without deterministic oracle and runtime history." }, @@ -11578,7 +11507,7 @@ "providers": ["paired-runtime", "ssh"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["paired-runtime"], - "coverageNotes": "This audit removes the browser response-after-manual-disconnect example. Its distinct in-flight response fencing alarm is lost; retained queued-call disconnect tests are not an equivalent replacement. Historical commands and results remain historical. Deterministic headed macOS runs launch an isolated Orca desktop server and a separate paired web client. A byte-identical headless run uses an isolated `orca serve` host. Both create six real paired host PTYs with bounded high-output scrollback, prove sustained output while all six client xterms are warm-mounted but hidden causes zero renderer scheduler work, then drive 180 timed real-PTY spinner frames through a second production `session.tabs.subscribeAll` transport and the real Zustand store. Each worktree must publish and apply only its exact initial-working and final-idle titles. Both topologies ordinary-park five xterms without enabling the lossy retention budget, assert bounded cells/heap/timer lag, then restore the bounded authoritative tail exactly once on the original PTY including output produced while parked and continued input/output. The headed oracle additionally keeps every remote target workspace and terminal unmounted in the host renderer before and after client recovery. Production-parity React contracts reproduce and remove the independent cold-park watcher/title feedback loop, including memo-cache discard, StrictMode cleanup replay, detached split-PTY replacement, and the zero-park fast path. A deterministic cross-boundary contract drives 24 real PTY trackers in distinct worktrees through 2,880 decorative spinner and bare-native redraw chunks and counts host publications, serialized bytes, renderer apply calls, and store mutations while preserving exact per-PTY raw chunks and per-worktree semantic transitions. Fake-clock continuation beyond the 30-minute stale boundary requires one deduplicated refresh per worktree every 15 minutes, globally spaced by at least 50 ms, with every mirrored working status still fresh. Exact-identical Pi plus rotating normalized Pi/Grok frames renew coarse working/permission state; rich prompt/tool/question fields retain their original 30-minute lease, and `stateStartedAt`/acknowledgement identity remain stable across title/hook source swaps. A separate headed desktop-client journey relaunches the host main process while retaining the daemon and a daemon-backgrounded PTY, then requires first-observation foreground sync and continued input/output on the same PTY. Unit coverage keeps layout versions out of snapshot output high-waters when a restored snapshot has no output sequence and retries an existing subscriber only after authoritative provider inventory proves the retained PTY exists. Separate headed/headless ACK-starvation tests recover one stalled stream without replacing its PTY. A headed output-drop oracle proves a successful snapshot probe cannot certify a stale live stream when the authoritative PTY sequence advanced beyond the client high-water. Unit coverage requires an unsequenced client to establish a first-probe baseline, remain attached at the same sequence, and recover only after a later probe advances. A capability-disabled run proves legacy hosts retain the prior lossy limit/TTL fallback. Unit tests cover exact-owner capability routing, raw-stream release, singleton side-effect facts with timed handoff cleanup, 64 simultaneous synthetic-title sources with zero decorative client events after convergence, per-client late-subscribe recovery, semantic bell/title transitions, local animation-frame preservation, legacy no-output-pause delivery gating with exact snapshot restore, provider-authoritative snapshots, 128 active streams plus retry after a 129th-stream capacity rejection, capacity-pressure backoff, full split-leaf remint reconciliation, truncation, and manual-disconnect queue fencing. Linux, Windows, live SSH, and production-scale paired hosts remain gaps. The audit removed the E2E parking override/debug-handle suite. Override exposure guards, delay/retention independence, TTL filtering and debug-handle forwarding alarms are lost. Retained real parking journeys do not establish equivalent test-harness configuration coverage. The test-value audit removes separate responsive-stream, intervening-control-frame and capacity-error classification examples. Retained multiplexer checks use fake clocks and captured transport callbacks; their per-stream isolation, deadline and probe-baseline assertions do not prove an actual host restart or equivalent replacement coverage. Historical run commands and results remain historical. The test-value audit removes the standalone retained/live metadata projection and additive-preference negative example from runtime-client-events.test.ts. Its remaining case checks partial-authority event admission through a captured callback; it does not establish full-pair projection, actual peer delivery or equivalent replacement coverage. Historical commands and results remain historical. The isolated accepted-handle-events observer suite is removed. Its runtime/worktree/pane membership, once-only delivery and listener-release ledger alarms are lost. Live hidden-retention evidence does not establish equivalent observer coverage. Audit1292: The entire host-session mirror empty-inventory settle suite is removed. Live-unpublished versus genuinely-empty hydration, same-ID revision response/in-flight fencing and cleared-tracking pending-probe invalidation alarms are lost. The singleton store and inventories were mocked; no real host census or resume delivery or equivalent replacement coverage is claimed. Audit1302: src/main/runtime/session-tabs-inventory-publication.test.ts: Keep actual runtime public inventory authority across renderer epochs/resync, fallback, aborts and invalid graph publishers. Remove copied nonempty publication, private scan counters/collector and known-host replacement, fake refresh that performs the restoration itself, environment capability flag, and private resume-store characterization whose fixture supplies reservation/rollback ordering. Lose isolated refresh/churn/receiver/capability and copied-payload wiring alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", + "coverageNotes": "This audit removes the browser response-after-manual-disconnect example. Its distinct in-flight response fencing alarm is lost; retained queued-call disconnect tests are not an equivalent replacement. Historical commands and results remain historical. Deterministic headed macOS runs launch an isolated Orca desktop server and a separate paired web client. A byte-identical headless run uses an isolated `orca serve` host. Both create six real paired host PTYs with bounded high-output scrollback, prove sustained output while all six client xterms are warm-mounted but hidden causes zero renderer scheduler work, then drive 180 timed real-PTY spinner frames through a second production `session.tabs.subscribeAll` transport and the real Zustand store. Each worktree must publish and apply only its exact initial-working and final-idle titles. Both topologies ordinary-park five xterms without enabling the lossy retention budget, assert bounded cells/heap/timer lag, then restore the bounded authoritative tail exactly once on the original PTY including output produced while parked and continued input/output. The headed oracle additionally keeps every remote target workspace and terminal unmounted in the host renderer before and after client recovery. Production-parity React contracts reproduce and remove the independent cold-park watcher/title feedback loop, including memo-cache discard, StrictMode cleanup replay, detached split-PTY replacement, and the zero-park fast path. A deterministic cross-boundary contract drives 24 real PTY trackers in distinct worktrees through 2,880 decorative spinner and bare-native redraw chunks and counts host publications, serialized bytes, renderer apply calls, and store mutations while preserving exact per-PTY raw chunks and per-worktree semantic transitions. Fake-clock continuation beyond the 30-minute stale boundary requires one deduplicated refresh per worktree every 15 minutes, globally spaced by at least 50 ms, with every mirrored working status still fresh. Exact-identical Pi plus rotating normalized Pi/Grok frames renew coarse working/permission state; rich prompt/tool/question fields retain their original 30-minute lease, and `stateStartedAt`/acknowledgement identity remain stable across title/hook source swaps. A separate headed desktop-client journey relaunches the host main process while retaining the daemon and a daemon-backgrounded PTY, then requires first-observation foreground sync and continued input/output on the same PTY. Unit coverage keeps layout versions out of snapshot output high-waters when a restored snapshot has no output sequence and retries an existing subscriber only after authoritative provider inventory proves the retained PTY exists. Separate headed/headless ACK-starvation tests recover one stalled stream without replacing its PTY. A headed output-drop oracle proves a successful snapshot probe cannot certify a stale live stream when the authoritative PTY sequence advanced beyond the client high-water. Unit coverage requires an unsequenced client to establish a first-probe baseline, remain attached at the same sequence, and recover only after a later probe advances. A capability-disabled run proves legacy hosts retain the prior lossy limit/TTL fallback. Unit tests cover exact-owner capability routing, raw-stream release, singleton side-effect facts with timed handoff cleanup, 64 simultaneous synthetic-title sources with zero decorative client events after convergence, per-client late-subscribe recovery, semantic bell/title transitions, local animation-frame preservation, legacy no-output-pause delivery gating with exact snapshot restore, provider-authoritative snapshots, 128 active streams plus retry after a 129th-stream capacity rejection, capacity-pressure backoff, full split-leaf remint reconciliation, truncation, and manual-disconnect queue fencing. Linux, Windows, live SSH, and production-scale paired hosts remain gaps. The audit removed the E2E parking override/debug-handle suite. Override exposure guards, delay/retention independence, TTL filtering and debug-handle forwarding alarms are lost. Retained real parking journeys do not establish equivalent test-harness configuration coverage. The test-value audit removes separate responsive-stream, intervening-control-frame and capacity-error classification examples. Retained multiplexer checks use fake clocks and captured transport callbacks; their per-stream isolation, deadline and probe-baseline assertions do not prove an actual host restart or equivalent replacement coverage. Historical run commands and results remain historical. The test-value audit removes the standalone retained/live metadata projection and additive-preference negative example from runtime-client-events.test.ts. Its remaining case checks partial-authority event admission through a captured callback; it does not establish full-pair projection, actual peer delivery or equivalent replacement coverage. Historical commands and results remain historical. The isolated accepted-handle-events observer suite is removed. Its runtime/worktree/pane membership, once-only delivery and listener-release ledger alarms are lost. Live hidden-retention evidence does not establish equivalent observer coverage. Audit1292: The entire host-session mirror empty-inventory settle suite is removed. Live-unpublished versus genuinely-empty hydration, same-ID revision response/in-flight fencing and cleared-tracking pending-probe invalidation alarms are lost. The singleton store and inventories were mocked; no real host census or resume delivery or equivalent replacement coverage is claimed. Audit1302: src/main/runtime/session-tabs-inventory-publication.test.ts: Keep actual runtime public inventory authority across renderer epochs/resync, fallback, aborts and invalid graph publishers. Remove copied nonempty publication, private scan counters/collector and known-host replacement, fake refresh that performs the restoration itself, environment capability flag, and private resume-store characterization whose fixture supplies reservation/rollback ordering. Lose isolated refresh/churn/receiver/capability and copied-payload wiring alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/8652", "https://github.com/stablyai/orca/pull/10625" @@ -12094,16 +12023,6 @@ "durationSeconds": 17.5, "summary": "The headed six-PTY oracle kept all warm-mounted hidden streams at zero renderer scheduler enqueues/drains under sustained output and under 500 ms timer lag, then restored the authoritative flood tail and parked/live markers exactly once.", "currentCommand": "ORCA_E2E_WEB_CLIENT=1 SKIP_BUILD=1 pnpm exec playwright test tests/e2e/paired-remote-terminal-retention-memory.spec.ts --config tests/playwright.config.ts --project electron-headful --workers=1" - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-multiplex-initial-snapshot-buffering.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection-visibility-and-exit.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-activation-inventory-fallback.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-partial-reconciliation.test.ts src/renderer/src/runtime/remote-runtime-terminal-stall-recovery.test.ts src/renderer/src/runtime/runtime-client-events.test.ts src/renderer/src/web/web-preload-api-runtime-environment.test.ts src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-multiplex-initial-snapshot-buffering.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection-visibility-and-exit.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-activation-inventory-fallback.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-partial-reconciliation.test.ts src/renderer/src/runtime/remote-runtime-terminal-stall-recovery.test.ts src/renderer/src/runtime/runtime-client-events.test.ts src/renderer/src/web/web-preload-api-runtime-environment.test.ts src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts", - "result": "passed", - "durationSeconds": 39.74, - "summary": "Test Files 19 passed (19); Tests 1858 passed | 3 skipped (1861). Fixture correctness only; no timing, work-count or retained-heap conclusion." } ], "runtimeBudget": { @@ -12140,7 +12059,8 @@ "The isolated accepted-handle-events observer suite is removed. Its runtime/worktree/pane membership, once-only delivery and listener-release ledger alarms are lost. Live hidden-retention evidence does not establish equivalent observer coverage.", "Audit1292: The entire host-session mirror empty-inventory settle suite is removed. Live-unpublished versus genuinely-empty hydration, same-ID revision response/in-flight fencing and cleared-tracking pending-probe invalidation alarms are lost. The singleton store and inventories were mocked; no real host census or resume delivery or equivalent replacement coverage is claimed.", "Audit1302: src/main/runtime/session-tabs-inventory-publication.test.ts: Keep actual runtime public inventory authority across renderer epochs/resync, fallback, aborts and invalid graph publishers. Remove copied nonempty publication, private scan counters/collector and known-host replacement, fake refresh that performs the restoration itself, environment capability flag, and private resume-store characterization whose fixture supplies reservation/rollback ordering. Lose isolated refresh/churn/receiver/capability and copied-payload wiring alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim.", - "Audit1302 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/runtime/session-tabs-inventory-publication.test.ts src/main/runtime/rpc/methods/session-tabs-inventory-census-race.test.ts src/main/runtime/rpc/methods/session-tabs.test.ts src/main/daemon/terminal-host-agent-session.test.ts tests/e2e/session-tabs-empty-inventory-daemon-oracle.unit.test.ts --maxWorkers=2\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/session-tabs-inventory-publication.test.ts src/main/runtime/rpc/methods/session-tabs-inventory-census-race.test.ts src/main/runtime/rpc/methods/session-tabs.test.ts src/main/daemon/terminal-host-agent-session.test.ts tests/e2e/session-tabs-empty-inventory-daemon-oracle.unit.test.ts --maxWorkers=1\",\n \"result\": \"passed\",\n \"durationSeconds\": 10.35,\n \"summary\": \"Test Files 5 passed (5); Tests 58 passed (58). Fixture correctness only; no timing, work-count or retained-heap conclusion.\"\n }" + "Audit1302 retired the complete dated receipt after source coverage changed. Its result does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/runtime/session-tabs-inventory-publication.test.ts src/main/runtime/rpc/methods/session-tabs-inventory-census-race.test.ts src/main/runtime/rpc/methods/session-tabs.test.ts src/main/daemon/terminal-host-agent-session.test.ts tests/e2e/session-tabs-empty-inventory-daemon-oracle.unit.test.ts --maxWorkers=2\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/session-tabs-inventory-publication.test.ts src/main/runtime/rpc/methods/session-tabs-inventory-census-race.test.ts src/main/runtime/rpc/methods/session-tabs.test.ts src/main/daemon/terminal-host-agent-session.test.ts tests/e2e/session-tabs-empty-inventory-daemon-oracle.unit.test.ts --maxWorkers=1\",\n \"result\": \"passed\",\n \"durationSeconds\": 10.35,\n \"summary\": \"Test Files 5 passed (5); Tests 58 passed (58). Fixture correctness only; no timing, work-count or retained-heap conclusion.\"\n }", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-multiplex-initial-snapshot-buffering.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection-visibility-and-exit.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-activation-inventory-fallback.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-partial-reconciliation.test.ts src/renderer/src/runtime/remote-runtime-terminal-stall-recovery.test.ts src/renderer/src/runtime/runtime-client-events.test.ts src/renderer/src/web/web-preload-api-runtime-environment.test.ts src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-multiplex-initial-snapshot-buffering.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection-visibility-and-exit.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-activation-inventory-fallback.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts src/renderer/src/components/terminal-pane/terminal-parked-watcher-partial-reconciliation.test.ts src/renderer/src/runtime/remote-runtime-terminal-stall-recovery.test.ts src/renderer/src/runtime/runtime-client-events.test.ts src/renderer/src/web/web-preload-api-runtime-environment.test.ts src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts\",\"result\":\"passed\",\"durationSeconds\":39.74,\"summary\":\"Test Files 19 passed (19); Tests 1858 passed | 3 skipped (1861). Fixture correctness only; no timing, work-count or retained-heap conclusion.\"}" ], "demotionRule": "Keep experimental or demote to protection none if decorative title frequency changes paired client-event or session-tabs publication volume after convergence, a semantic fact is dropped, paired first paint is blank, passive work reconnects after manual disconnect, a capable hidden paired manager survives ordinary parking, a legacy hidden pane schedules raw xterm output or bypasses its fallback, reveal loses requested bounded history or live PTY identity, retained buffer cells do not fall, or either headed/headless scenario flakes." }, @@ -12371,7 +12291,7 @@ "providers": ["local", "daemon", "ssh", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["ssh"], - "coverageNotes": "Local macOS evidence covers main-process pending-output and projection-admission caps, saturated-tail wait detection, direct-SSH source retention/accounting, always-on V1 negotiation with legacy peer fallback, exact provider-generation pause ownership, renderer exit/data ordering, decoder input bounds, bounded reconnect scheduling, and WSL stdio transport settlement through deterministic unit contracts. A joined main/runtime oracle exercises renderer-sourced headed semantics and headless-model snapshot semantics without launching a live paired server. Separately, a macOS-hosted Docker OpenSSH run exercises only the deployed Linux relay and direct SSH provider with an exact 256 KiB source-credit plateau, concurrent PTY typing, fixed-size filesystem frames, Git churn, and owner-lease reconnect. Neither deterministic topology labels nor Docker SSH constitute live headed desktop, headless orca serve, or physical WSL evidence; local/daemon, prior-version daemons, Windows named pipes/ConPTY, folder workspaces, and mixed-version clients also remain uncovered.", + "coverageNotes": "Local macOS evidence covers main-process pending-output and projection-admission caps, saturated-tail wait detection, direct-SSH source retention/accounting, always-on V1 negotiation with legacy peer fallback, exact provider-generation pause ownership, renderer exit/data ordering, decoder input bounds, bounded reconnect scheduling, and WSL stdio transport settlement through deterministic unit contracts. A joined main/runtime oracle exercises renderer-sourced headed semantics and headless-model snapshot semantics without launching a live paired server. Separately, a macOS-hosted Docker OpenSSH run exercises only the deployed Linux relay and direct SSH provider with an exact 256 KiB source-credit plateau, concurrent PTY typing, fixed-size filesystem frames, Git churn, and owner-lease reconnect. Neither deterministic topology labels nor Docker SSH constitute live headed desktop, headless orca serve, or physical WSL evidence; local/daemon, prior-version daemons, Windows named pipes/ConPTY, folder workspaces, and mixed-version clients also remain uncovered. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/6836", "https://github.com/stablyai/orca/pull/6858", @@ -12512,15 +12432,6 @@ "summary": "1 test file(s) passed, 209 tests passed on main@1282f5c2d in a clean checkout.", "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty/delivery/pty-renderer-inflight-credit.test.ts src/main/ipc/pty/delivery/pty-global-renderer-credit.test.ts src/main/ipc/pty/delivery/pty-output-batching-drain.test.ts src/main/ipc/pty/delivery/pty-pending-output-cap.test.ts" }, - { - "date": "2026-07-10", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-tail-wait-memo.test.ts", - "result": "passed", - "durationSeconds": 21.33, - "summary": "2 test files passed, 609 tests passed on pushed commit 4fb14eac3897." - }, { "date": "2026-07-29", "runner": "local", @@ -12569,7 +12480,8 @@ "Runtime provenance coverage is deterministic and does not include a live high-throughput provider artifact.", "Terminal-wait scale evidence is deterministic and does not yet include a live saturated-tail event-loop artifact.", "Current command does not prove renderer parse pressure, scheduler queue depth, event-loop delay, or active key latency.", - "Live hidden-output pressure, active input latency, and full Electron perf artifacts remain unproved." + "Live hidden-output pressure, active input latency, and full Electron perf artifacts remain unproved.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-07-10\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-tail-wait-memo.test.ts\",\"result\":\"passed\",\"durationSeconds\":21.33,\"summary\":\"2 test files passed, 609 tests passed on pushed commit 4fb14eac3897.\"}" ], "demotionRule": "Cannot promote without metric artifacts and bounded-byte assertions." }, @@ -14372,7 +14284,7 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "daemon"], - "coverageNotes": "Runtime and provider-contract tests on macOS cover exact-PTY authorization, verified managed-Claude launch identity, local/daemon fresh confirmation, exact ConPTY membership, and unsupported-provider fail-closed behavior. Physical Windows, live Linux, SSH, WSL, and remote-runtime validation remain explicit gaps; providers without confirmation preserve conservative refusal when no verified launch identity exists. Protocol-version constant inventories are excluded from handshake and foreground-confirmation evidence.", + "coverageNotes": "Runtime and provider-contract tests on macOS cover exact-PTY authorization, verified managed-Claude launch identity, local/daemon fresh confirmation, exact ConPTY membership, and unsupported-provider fail-closed behavior. Physical Windows, live Linux, SSH, WSL, and remote-runtime validation remain explicit gaps; providers without confirmation preserve conservative refusal when no verified launch identity exists. Protocol-version constant inventories are excluded from handshake and foreground-confirmation evidence. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": ["https://github.com/stablyai/orca/issues/8303"], "invariant": "A guarded note send writes only to the exact PTY binding checked by the guard and only while permission/wait evidence allows input; a generic busy title requires independent agent identity from the title, a verified managed launch bound to the current PTY incarnation, or the foreground process; fresh hook state conflicting with an ordinary shell foreground requires fresh provider confirmation of a recognized agent in that PTY.", "oracle": "A quarter-circle Claude task title authorizes without a provider lookup only on the exact PTY incarnation carrying verified managed-Claude launch identity; the same title in a bare pane, with an unverified launch hint, or after PTY incarnation replacement is refused while current Claude activity remains a working signal. Fresh explicit state plus ordinary PowerShell plus confirmed recognized agent is sendable on the same PTY. Confirmed shell/non-agent, unavailable confirmation, PTY exit, handle rebind, or a callback PTY mismatch returns a refusal or not-writable result and writes zero bytes.", @@ -14463,16 +14375,6 @@ "result": "passed", "durationSeconds": 20.9, "summary": "The focused file passed 9 tests, including current-incarnation managed-Claude authorization with zero foreground calls and fail-closed bare, unverified, and replacement-incarnation quarter-circle titles." - }, - { - "date": "2026-08-25", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/providers/windows-pty-job-membership.test.ts src/main/daemon/daemon-foreground-confirmation-protocol.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts", - "result": "passed", - "durationSeconds": 13.74, - "summary": "Twelve focused test files passed (1351 tests, 1 skipped), covering runtime confirmation and PTY revalidation, guarded RPC zero-write behavior, PTY controller routing, local/daemon fresh scans, exact ConPTY membership, and unchanged renderer note routing." } ], "runtimeBudget": { @@ -14500,7 +14402,8 @@ "Physical Windows validation is unavailable on the current macOS host; exact ConPTY membership is covered deterministically.", "Electron annotation golden-path, repeat-send, adjacent-menu evidence, and screenshots are left to coordinator validation.", "SSH, WSL, legacy daemon, and remote-runtime providers without confirmation remain intentionally fail-closed on an ordinary-shell conflict; no live artifacts cover those degraded paths.", - "No live Linux PTY, paired-web, mobile/relay, restore/replay, or multi-window artifact is attached; those surfaces receive no renderer, persistence, or protocol change." + "No live Linux PTY, paired-web, mobile/relay, restore/replay, or multi-window artifact is attached; those surfaces receive no renderer, persistence, or protocol change.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-25\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/providers/windows-pty-job-membership.test.ts src/main/daemon/daemon-foreground-confirmation-protocol.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts\",\"result\":\"passed\",\"durationSeconds\":13.74,\"summary\":\"Twelve focused test files passed (1351 tests, 1 skipped), covering runtime confirmation and PTY revalidation, guarded RPC zero-write behavior, PTY controller routing, local/daemon fresh scans, exact ConPTY membership, and unchanged renderer note routing.\"}" ], "demotionRule": "Keep non-blocking or demote to protection none if a generic busy title authorizes without exact title, verified-launch, or foreground identity; provider confirmation becomes unconditional; exact-PTY mismatch can write bytes; unsupported providers fail open; or the focused gate flakes without an actionable product or harness defect." }, @@ -14522,7 +14425,7 @@ "providers": ["local", "daemon", "ssh", "remote-runtime"], "coveredPlatforms": ["macos", "windows"], "coveredProviders": ["local"], - "coverageNotes": "Source-built macOS and physical Windows Electron journeys cover the public CLI through RPC, runtime identity, real PTY/ConPTY writes, and a delayed fake Codex composer. Focused units cover CLI intent, fresh Claude/Codex foreground identity, framing bytes, stale-agent shell fallback, and exact legacy behavior for every other configured agent. SSH, daemon, and remote-runtime remain explicit gaps.", + "coverageNotes": "Source-built macOS and physical Windows Electron journeys cover the public CLI through RPC, runtime identity, real PTY/ConPTY writes, and a delayed fake Codex composer. Focused units cover CLI intent, fresh Claude/Codex foreground identity, framing bytes, stale-agent shell fallback, and exact legacy behavior for every other configured agent. SSH, daemon, and remote-runtime remain explicit gaps. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-4328", "https://github.com/stablyai/orca/issues/14525" @@ -14604,16 +14507,6 @@ "result": "passed", "durationSeconds": 47.6, "summary": "Two independent physical Windows source-built Electron/CLI/ConPTY journeys passed at cf814f2f46 with the prompt marker received, zero premature Enter, and no rescue send (47.6s and 47.1s)." - }, - { - "date": "2026-08-15", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/cli/handlers/terminal.test.ts src/main/runtime/rpc/terminal-agent-prompt-send.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/orca-runtime.test.ts src/shared/agent-prompt-injection.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/cli/handlers/terminal.test.ts src/main/runtime/rpc/terminal-agent-prompt-send.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/orca-runtime.test.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts", - "result": "passed", - "durationSeconds": 23.2, - "summary": "After rebasing onto current main, the final narrowed runtime/CLI/RPC suite passed 1,201 tests with one skip, including fresh Claude/Codex identity, stale-agent shell fallback, and legacy timing for every other configured agent." } ], "runtimeBudget": { @@ -14640,7 +14533,8 @@ "Live SSH/daemon/remote-runtime evidence is not yet attached.", "Only Claude and Codex opt into settlement; every other agent retains the legacy behavior until independently validated.", "A new CLI talking to an old host safely loses the optional field and therefore keeps the old timing behavior until the host updates.", - "The E2E fake agent models the submission race without requiring external agent authentication; live account-backed agents are covered separately." + "The E2E fake agent models the submission race without requiring external agent authentication; live account-backed agents are covered separately.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-15\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/cli/handlers/terminal.test.ts src/main/runtime/rpc/terminal-agent-prompt-send.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/orca-runtime.test.ts src/shared/agent-prompt-injection.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/cli/handlers/terminal.test.ts src/main/runtime/rpc/terminal-agent-prompt-send.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/orca-runtime.test.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts\",\"result\":\"passed\",\"durationSeconds\":23.2,\"summary\":\"After rebasing onto current main, the final narrowed runtime/CLI/RPC suite passed 1,201 tests with one skip, including fresh Claude/Codex identity, stale-agent shell fallback, and legacy timing for every other configured agent.\"}" ], "demotionRule": "Keep experimental or demote to protection none if direct shell/mobile/query input is reframed, an unrecognized target receives bracketed paste, a prompt can emit duplicate Enter, mixed-version decoding rejects the optional field, or the deterministic fake-composer candidate requires a rescue send." }, @@ -14662,7 +14556,7 @@ "providers": ["local", "daemon", "ssh", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local"], - "coverageNotes": "Local macOS evidence covers the runtime PTY write contract, live Codex orchestration, a dev-runtime CLI repro, and Claude and Codex composer traces. A deterministic worker-start RPC/SQLite contract proves delayed Codex composer acceptance, exactly one submitted turn, durable Dispatch success, and explicit stalled failure when Enter is swallowed without a rescue send. The render gate stays on the PTY-owning runtime, so paired and SSH callers use the same provider write/output path without a wire change. SSH, daemon, remote-runtime, Linux, and Windows live journeys remain gaps. The test-value audit intentionally removes these direct unit alarms: Only OMP joins submit to paste frame. Exact bracketed multiline prompt frame. Claude/team/unknown lead-line positives and Codex/OpenCode negatives. DSB generic lead/submit/delay fallback on three platform strings. 2KB/8KB Windows and 8KB Darwin delay below700ms. 320KB ingest estimate greater than1.4x3342ms. 145KB delay greater than1500ms. Darwin8KB <=2ms, zero/NaN Linux and negative Windows return zero. 400KB greater than40KB across platforms and Windows slower than Darwin. Antigravity100-line settle addition and Aider one-line delta. Retained unit cases still assert safe typed-lead normalization, pinned measured delay floors and chunk byte-budget bounds, ESC sanitization, exact framed UTF-8 chunk reassembly, and held-draft discrimination. The later test-value audit also removes the 16MiB arithmetic extrapolation: Windows delay greater than250000ms and Linux delay greater than4000ms, including its no-cap scaling alarm; that test never delivered ceiling input. They are not equivalent replacements for the removed unit alarms. The OMP-versus-Claude/Codex/unidentified-agent submit-join predicate obligation remains deferred. Runtime PTY-writer, worker-start and live evidence above are unchanged; no runtime-writer coverage loss, new execution, owner-gap closure or credit is claimed.", + "coverageNotes": "Local macOS evidence covers the runtime PTY write contract, live Codex orchestration, a dev-runtime CLI repro, and Claude and Codex composer traces. A deterministic worker-start RPC/SQLite contract proves delayed Codex composer acceptance, exactly one submitted turn, durable Dispatch success, and explicit stalled failure when Enter is swallowed without a rescue send. The render gate stays on the PTY-owning runtime, so paired and SSH callers use the same provider write/output path without a wire change. SSH, daemon, remote-runtime, Linux, and Windows live journeys remain gaps. The test-value audit intentionally removes these direct unit alarms: Only OMP joins submit to paste frame. Exact bracketed multiline prompt frame. Claude/team/unknown lead-line positives and Codex/OpenCode negatives. DSB generic lead/submit/delay fallback on three platform strings. 2KB/8KB Windows and 8KB Darwin delay below700ms. 320KB ingest estimate greater than1.4x3342ms. 145KB delay greater than1500ms. Darwin8KB <=2ms, zero/NaN Linux and negative Windows return zero. 400KB greater than40KB across platforms and Windows slower than Darwin. Antigravity100-line settle addition and Aider one-line delta. Retained unit cases still assert safe typed-lead normalization, pinned measured delay floors and chunk byte-budget bounds, ESC sanitization, exact framed UTF-8 chunk reassembly, and held-draft discrimination. The later test-value audit also removes the 16MiB arithmetic extrapolation: Windows delay greater than250000ms and Linux delay greater than4000ms, including its no-cap scaling alarm; that test never delivered ceiling input. They are not equivalent replacements for the removed unit alarms. The OMP-versus-Claude/Codex/unidentified-agent submit-join predicate obligation remains deferred. Runtime PTY-writer, worker-start and live evidence above are unchanged; no runtime-writer coverage loss, new execution, owner-gap closure or credit is claimed. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-3800", "https://linear.app/stably/issue/STA-3904", @@ -14751,46 +14645,6 @@ "durationSeconds": 21.84, "summary": "Two deterministic worker-start RPC contracts passed with fake clocks and reopened SQLite receipts for one accepted turn and one swallowed-Enter stalled outcome." }, - { - "date": "2026-08-14", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "result": "passed", - "durationSeconds": 11.32, - "summary": "4 files and 1,303 tests passed with one skipped. Claude and Codex both wait for post-marker quiescence, and a Codex marker arriving at 7.9 seconds receives a fresh window through its final slow frame. Exact-build live Codex workers accepted injected prompts without manual Enter, replied, called worker_done, and settled successfully in the rendered Electron UI." - }, - { - "date": "2026-08-13", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "result": "passed", - "durationSeconds": 13.3, - "summary": "4 files and 1,283 tests passed. The hardened multi-frame oracle failed on the first-marker candidate because it submitted at 751 ms during an intermediate Claude frame; the quiescence candidate waited through the final 1,000 ms frame and submitted once at 2,500 ms. Continuous render output remained bounded to one fallback submit at 8 seconds. An isolated Claude Code 2.1.231 Haiku probe saw the first marker at 400 ms, continued output through 1,500 ms, sent one Enter at 3,000 ms after 1.5 seconds quiet, and created the expected marker; no Fable or Opus probe was used." - }, - { - "date": "2026-08-13", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "result": "passed", - "durationSeconds": 16.9, - "summary": "4 files and 1,282 tests passed. Unmodified main wrote Enter at 500 ms before the deterministic Claude composer rendered at 750 ms; the candidate waited for the split show-cursor marker and wrote one Enter. A live Claude Code 2.1.231 Haiku trace rendered the pasted marker and show-cursor in one 523-byte frame without submitting a model request." - }, - { - "date": "2026-07-07", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts", - "result": "passed", - "durationSeconds": 7.4, - "summary": "4 test files passed, 697 tests passed; covers framing, runtime PTY writes, orchestration RPC dispatch, and coordinator dispatch behavior." - }, { "date": "2026-07-07", "runner": "local", @@ -14825,7 +14679,11 @@ "knownGaps": [ "Live harness command currently expects an already-running dev runtime and generated out/bin/orca-dev wrapper.", "No Windows ConPTY, Linux PTY, SSH, daemon, or remote-runtime live evidence yet.", - "Push-on-idle orchestration message banners remain outside this dispatch-prompt gate." + "Push-on-idle orchestration message banners remain outside this dispatch-prompt gate.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-14\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"result\":\"passed\",\"durationSeconds\":11.32,\"summary\":\"4 files and 1,303 tests passed with one skipped. Claude and Codex both wait for post-marker quiescence, and a Codex marker arriving at 7.9 seconds receives a fresh window through its final slow frame. Exact-build live Codex workers accepted injected prompts without manual Enter, replied, called worker_done, and settled successfully in the rendered Electron UI.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"result\":\"passed\",\"durationSeconds\":13.3,\"summary\":\"4 files and 1,283 tests passed. The hardened multi-frame oracle failed on the first-marker candidate because it submitted at 751 ms during an intermediate Claude frame; the quiescence candidate waited through the final 1,000 ms frame and submitted once at 2,500 ms. Continuous render output remained bounded to one fallback submit at 8 seconds. An isolated Claude Code 2.1.231 Haiku probe saw the first marker at 400 ms, continued output through 1,500 ms, sent one Enter at 3,000 ms after 1.5 seconds quiet, and created the expected marker; no Fable or Opus probe was used.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"result\":\"passed\",\"durationSeconds\":16.9,\"summary\":\"4 files and 1,282 tests passed. Unmodified main wrote Enter at 500 ms before the deterministic Claude composer rendered at 750 ms; the candidate waited for the split show-cursor marker and wrote one Enter. A live Claude Code 2.1.231 Haiku trace rendered the pasted marker and show-cursor in one 523-byte frame without submitting a model request.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-07-07\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-launch/startup/agent-prompt-injection.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts src/main/runtime/orchestration/coordinator.test.ts\",\"result\":\"passed\",\"durationSeconds\":7.4,\"summary\":\"4 test files passed, 697 tests passed; covers framing, runtime PTY writes, orchestration RPC dispatch, and coordinator dispatch behavior.\"}" ], "demotionRule": "Demote or quarantine if the live harness flakes without a product bug or harness bug filed to the terminal-input owner." }, @@ -14849,7 +14707,7 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "daemon", "ssh"], - "coverageNotes": "A deterministic isolated runtime/SQLite/RPC harness covers Run and Dispatch ownership, exact handle and remint-stable pane recipients, fixed Delivery replay and acknowledgment, restart, concurrent pollers, filtered waiters, and built-CLI parity with direct DB state. Schema v28 durably records every coordinator handle through SQLite triggers, backfills current handles on open, recreates routing triggers after old table rebuilds, and atomically canonicalizes coordinator-addressed inserts to run:. A 125-row oracle and 151-row reconciliation suite cover paged old-handle backlogs, commit-without-notify recovery, duplicate Dispatch owners, cancellation, and lifecycle fencing. Federation tests use real databases, dropped acknowledgments, restart/reconnect, and protocol v1/v2 peers; duplicate read rows do not wake again. Daemon and SSH pointer settlement is deterministic, with SSH bounded at 60 seconds beyond the 20-second mux health window. No RPC parameter, stream opcode, or published payload changed. Live headed/headless paired-runtime, physical SSH, WSL, Linux, and Windows acceptance remain gaps.", + "coverageNotes": "A deterministic isolated runtime/SQLite/RPC harness covers Run and Dispatch ownership, exact handle and remint-stable pane recipients, fixed Delivery replay and acknowledgment, restart, concurrent pollers, filtered waiters, and built-CLI parity with direct DB state. Schema v28 durably records every coordinator handle through SQLite triggers, backfills current handles on open, recreates routing triggers after old table rebuilds, and atomically canonicalizes coordinator-addressed inserts to run:. A 125-row oracle and 151-row reconciliation suite cover paged old-handle backlogs, commit-without-notify recovery, duplicate Dispatch owners, cancellation, and lifecycle fencing. Federation tests use real databases, dropped acknowledgments, restart/reconnect, and protocol v1/v2 peers; duplicate read rows do not wake again. Daemon and SSH pointer settlement is deterministic, with SSH bounded at 60 seconds beyond the 20-second mux health window. No RPC parameter, stream opcode, or published payload changed. Live headed/headless paired-runtime, physical SSH, WSL, Linux, and Windows acceptance remain gaps. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-4325", "https://github.com/stablyai/orca/issues/14522", @@ -15010,35 +14868,6 @@ } ], "evidenceRuns": [ - { - "date": "2026-10-14", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-titles.test.ts src/main/runtime/orchestration-mailbox-delivery.test.ts src/main/runtime/orchestration-mailbox-routing.test.ts", - "result": "passed", - "durationSeconds": 10.63, - "summary": "The three consolidated mailbox roots passed all 64 cases (22 title, 12 delivery, 30 routing) in a focused local run with the production Bun and Node project routing." - }, - { - "date": "2026-09-05", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts", - "result": "passed", - "durationSeconds": 4.73, - "summary": "267 tests passed after the pointer-write path moved to the three-valued WriteSettlement union. New coverage: a dropped in-flight SSH settlement reaches the stager as unverifiable with bytes handed to the transport, a settled write that throws mid-pointer preserves the write-attempted reservation, an Enter whose settlement is lost stays at enter-attempted with no second Enter after restart, a refusal releases the reservation and drains a delivery parked behind its watermark, the production controller refuses before any byte when the routed provider cannot settle, and a census pins the five production IPtyProvider classes and rejects a settlement synthesized from the fire-and-forget write. Each new assertion was verified red against the pre-fix shape.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-delivery.test.ts src/main/ipc/pty/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts" - }, - { - "date": "2026-08-13", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts", - "result": "passed", - "durationSeconds": 8.22, - "summary": "245 tests passed across mailbox identity, durable coordinator-handle migration, insertion-time canonicalization, duplicate-free 51-row ownership branch caps, unrestricted reservation merging, direct and Dispatch pointer suppression, persisted reconciliation, 50-row paging and filtered waits, cross-PTY serialization, lifecycle fencing, bounded daemon and SSH transport settlement, outstanding Deliveries, reminted Dispatch ownership, acknowledgment, cancellation, and bounded pane lookup.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-delivery.test.ts src/main/ipc/pty/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts" - }, { "date": "2026-08-14", "runner": "local", @@ -15056,15 +14885,6 @@ "result": "passed", "durationSeconds": 8.42, "summary": "The byte-identical five-case STA-4325 oracle (SHA-256 76a2d672d27e04e5b1db2f52df3b303468bdf006be7cfd003bf443618a856c35) failed 5/5 on reported v1.4.180, latest main, and production-reverted candidate, then passed 5/5 on the final candidate. It aligns pointer counts with one fixed Delivery across concurrent pollers and filters, replays that Delivery through restart, routes a 125-row old-handle backlog before rebind, repairs committed mail without relying on a notification callback, and matches built-CLI output to direct isolated SQLite state." - }, - { - "date": "2026-08-14", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/main/runtime/rpc/methods/orchestration/runs/runs.test.ts src/main/runtime/rpc/methods/orchestration/messaging/send.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts", - "result": "passed", - "durationSeconds": 14.15, - "summary": "1,293 tests passed and 1 was skipped across Run-bound pointer delivery, PTY retirement and respawn, stale-leaf liveness, direct-mail routing, filtered waiter ownership, canonical stored-recipient notification, and orchestration RPC behavior." } ], "runtimeBudget": { @@ -15092,7 +14912,11 @@ "No daemon, paired remote-runtime, live SSH, WSL, Linux, or Windows journey has been collected.", "Daemon and SSH PTY writes are transport-settled but remain owner-unacknowledged; capability-negotiated owner acknowledgments are separate protocol work.", "Direct and Dispatch mail stay durable and explicitly checkable but do not synthesize agent turns until a backward-compatible pinned action or passive UI exists.", - "Passive renderer-native unread UI and opt-in coordinator wake policy remain separate product work." + "Passive renderer-native unread UI and opt-in coordinator wake policy remain separate product work.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-10-14\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-titles.test.ts src/main/runtime/orchestration-mailbox-delivery.test.ts src/main/runtime/orchestration-mailbox-routing.test.ts\",\"result\":\"passed\",\"durationSeconds\":10.63,\"summary\":\"The three consolidated mailbox roots passed all 64 cases (22 title, 12 delivery, 30 routing) in a focused local run with the production Bun and Node project routing.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-09-05\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts\",\"result\":\"passed\",\"durationSeconds\":4.73,\"summary\":\"267 tests passed after the pointer-write path moved to the three-valued WriteSettlement union. New coverage: a dropped in-flight SSH settlement reaches the stager as unverifiable with bytes handed to the transport, a settled write that throws mid-pointer preserves the write-attempted reservation, an Enter whose settlement is lost stays at enter-attempted with no second Enter after restart, a refusal releases the reservation and drains a delivery parked behind its watermark, the production controller refuses before any byte when the routed provider cannot settle, and a census pins the five production IPtyProvider classes and rejects a settlement synthesized from the fire-and-forget write. Each new assertion was verified red against the pre-fix shape.\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-delivery.test.ts src/main/ipc/pty/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts\",\"result\":\"passed\",\"durationSeconds\":8.22,\"summary\":\"245 tests passed across mailbox identity, durable coordinator-handle migration, insertion-time canonicalization, duplicate-free 51-row ownership branch caps, unrestricted reservation merging, direct and Dispatch pointer suppression, persisted reconciliation, 50-row paging and filtered waits, cross-PTY serialization, lifecycle fencing, bounded daemon and SSH transport settlement, outstanding Deliveries, reminted Dispatch ownership, acknowledgment, cancellation, and bounded pane lookup.\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-delivery.test.ts src/main/ipc/pty/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-14\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/main/runtime/rpc/methods/orchestration/runs/runs.test.ts src/main/runtime/rpc/methods/orchestration/messaging/send.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts\",\"result\":\"passed\",\"durationSeconds\":14.15,\"summary\":\"1,293 tests passed and 1 was skipped across Run-bound pointer delivery, PTY retirement and respawn, stale-leaf liveness, direct-mail routing, filtered waiter ownership, canonical stored-recipient notification, and orchestration RPC behavior.\"}" ], "demotionRule": "Demote or quarantine if the deterministic oracle flakes without a product or harness bug owned by orchestration." }, @@ -15116,7 +14940,7 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["local", "daemon", "ssh"], - "coverageNotes": "A deterministic service-state-machine oracle now models a current-contract worker and coordinator whose renderer graph identities disappear across an app/runtime update. It exercises the production verifier with restored PTY and hydrated hook commitments, proves authenticated completion replay across a fresh runtime, explicit takeover, ordinary mail routing, remote-attachment process fencing, retained Task/Dispatch/terminal identity, and unchanged fixture marker bytes, and rejects foreign pane evidence. Other deterministic units cover authority-aware legacy formatting, local worker presentation, retained-output reads after adoption, reveal-failure warnings, stable-pane Run/Dispatch routing, creator pane/process/Run-generation fencing, indexed retained-Run lookup scaling, the SSH in-process CLI fallback, exact legacy Task/Dispatch/report-receipt verification, JSON rejection envelopes, and capability-negotiated Run-home settlement. The federation oracle drops the first terminal ACK, restarts Run-home twice, verifies replay through the existing polling path and the persisted acknowledgment watermark, races identical reports, accepts preexisting same-outcome ACK batches, rejects conflicting batches, and covers protocol v1/v2 completion across independently updated peers. A migration unit upgrades a v26 database with a zeroed watermark without replaying older migrations. Three isolated macOS Electron journeys launch fake Codex workers through the real RPC path. Distinct installed A/B artifacts plus live SSH, WSL, folder, Linux, and Windows cutover journeys remain explicit gaps. Test-value audit1293 removes src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts (4 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required.", + "coverageNotes": "A deterministic service-state-machine oracle now models a current-contract worker and coordinator whose renderer graph identities disappear across an app/runtime update. It exercises the production verifier with restored PTY and hydrated hook commitments, proves authenticated completion replay across a fresh runtime, explicit takeover, ordinary mail routing, remote-attachment process fencing, retained Task/Dispatch/terminal identity, and unchanged fixture marker bytes, and rejects foreign pane evidence. Other deterministic units cover authority-aware legacy formatting, local worker presentation, retained-output reads after adoption, reveal-failure warnings, stable-pane Run/Dispatch routing, creator pane/process/Run-generation fencing, indexed retained-Run lookup scaling, the SSH in-process CLI fallback, exact legacy Task/Dispatch/report-receipt verification, JSON rejection envelopes, and capability-negotiated Run-home settlement. The federation oracle drops the first terminal ACK, restarts Run-home twice, verifies replay through the existing polling path and the persisted acknowledgment watermark, races identical reports, accepts preexisting same-outcome ACK batches, rejects conflicting batches, and covers protocol v1/v2 completion across independently updated peers. A migration unit upgrades a v26 database with a zeroed watermark without replaying older migrations. Three isolated macOS Electron journeys launch fake Codex workers through the real RPC path. Distinct installed A/B artifacts plus live SSH, WSL, folder, Linux, and Windows cutover journeys remain explicit gaps. Test-value audit1293 removes src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts (4 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/11107#discussion_r3663321387", "https://github.com/stablyai/orca/issues/13858", @@ -15472,16 +15296,6 @@ "result": "passed", "durationSeconds": 12.6, "summary": "The restart journey retained one daemon PTY and PID, adopted its exact original background pane, preserved readable ACK output and Task/Dispatch authority, cleared all resume state, and recorded no second spawn, resume argv, input, signal, interruption, or duplicate after workspace re-entry." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/runtime/orca-runtime.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts", - "result": "passed", - "durationSeconds": 24.4, - "summary": "Test Files 1 passed (1); Tests 1445 passed | 3 skipped (1448). Deterministic fixtures; no physical phone or deployed mixed-host claim." } ], "runtimeBudget": { @@ -15513,7 +15327,8 @@ "The Electron journey uses a deterministic fake Codex CLI rather than a real account.", "The local presentation journey tolerates its existing terminal-handle remint and proves continuity by PTY/incarnation/tab/leaf; byte-stable handle proof across a real A/B cutover awaits the runtime-authority/RPC implementation.", "The local Electron restart proof covers retained visible output from the same-build daemon checkpoint, not transcript recovery after a transport cut or distinct A/B runtime replacement.", - "Test-value audit1293 removes src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts (4 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required." + "Test-value audit1293 removes src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts (4 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required.", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/runtime/orca-runtime.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts\",\"result\":\"passed\",\"durationSeconds\":24.4,\"summary\":\"Test Files 1 passed (1); Tests 1445 passed | 3 skipped (1448). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}" ], "demotionRule": "Keep experimental or demote if either Electron journey flakes without a product or harness defect, if local worker-start can return before tab materialization without an explicit reveal warning, if focus moves to the worker, if restart or workspace re-entry spawns/resumes/duplicates/interferes with the worker, if retained output becomes unreadable, or if pane-stable delivery reads the wrong mailbox." }, @@ -17708,7 +17523,7 @@ "providers": ["local", "remote-runtime", "ssh"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["local", "remote-runtime", "ssh"], - "coverageNotes": "This audit removes the paired-web stopped-during-outage and late-ready direct-branch examples. The retained pre-ready cancellation, failed-unwatch retry and replay-continuity cases are not equivalent replacements for those ordering alarms. Current web-runtime-client.test.ts assertions below name only its retained pre-ready callback case; replay cases remain in their separate suite. Historical commands and results remain historical. Deterministic tests cover one shared healthy child per runtime process plus at most four bounded fault-quarantine children, native desktop, paired-runtime, and WSL snapshot processes sharing the same eight-physical-child reservation with typed serialized event-driven capacity recovery, including recursive re-wait when crash recovery reclaims an announced slot, one quarantine attempt per watch lifetime, generation-scoped cancellation, bounded termination with removable deadline waiters, replacement-crawl errors that cannot be blessed by a late readiness ack, pre-ready paired-web cancellation registered before an unbounded capacity wait, already-resolved and late physical-exit retry of a rejected paired-web teardown, physical desktop-install and local/SSH PTY teardown, stale-generation rejection, per-child stat bounds, final-overflow RPC delivery, and renderer eviction before terminal callbacks can retry. Destructive local and SSH removal fences both ID-derived and resolved-cwd terminal roots, including sibling-root/cwd combinations with reverse admission rollback, closes descendant relay watches before parent deletion, enumerates authoritative provider/cwd ownership, falls back to daemon spawn cwd before OSC 7, and keeps daemon/relay immediate shutdown pending until native exit or a bounded fail-closed error; a dead relay PID discovered during attach also settles concurrent shutdown before its stale entry is reaped, while Windows runtime watcher deadlines retain one removable physical-close waiter, treat Node's error-close path as positive physical-exit proof, and clear root ownership on late close. The relay policy test maps a standard repository's base, Git common directory, and worktree roots to one healthy supervisor, then proves a shared-child failure recovers those roots in separate quarantine supervisors with overflow and resumed events. Abortable capacity, quarantine, runtime-root, relay, SSH, and relay pre-install setup waits attach one reaction to each shared promise and explicitly remove 10,000 cancelled caller closures while one anchor remains. Built-entry macOS harnesses kill the desktop/runtime and SSH relay watcher children, then require automatic resubscription and later events while the host process and relay PTY survive. SSH bundles the child boundary beside relay.js, requires both artifacts for install completeness, and preserves registration-owned same-root cancellation. The test-value audit corrects the active renderer watch command/file reference to the existing runtime-file-client-watch.test.ts, whose three cases are retained unchanged. They observe synthetic subscription retry/fanout and unsubscribe/unwatch calls, not actual preload/main bookkeeping or physical watcher-process cleanup. The separate read/preview client suite loses forwarding, supplied-error and negative-only examples, retaining binary detection and Unicode SVG decoding. Historical evidence commands/results are unchanged. Test-value audit1293 removes src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts (2 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required.", + "coverageNotes": "This audit removes the paired-web stopped-during-outage and late-ready direct-branch examples. The retained pre-ready cancellation, failed-unwatch retry and replay-continuity cases are not equivalent replacements for those ordering alarms. Current web-runtime-client.test.ts assertions below name only its retained pre-ready callback case; replay cases remain in their separate suite. Historical commands and results remain historical. Deterministic tests cover one shared healthy child per runtime process plus at most four bounded fault-quarantine children, native desktop, paired-runtime, and WSL snapshot processes sharing the same eight-physical-child reservation with typed serialized event-driven capacity recovery, including recursive re-wait when crash recovery reclaims an announced slot, one quarantine attempt per watch lifetime, generation-scoped cancellation, bounded termination with removable deadline waiters, replacement-crawl errors that cannot be blessed by a late readiness ack, pre-ready paired-web cancellation registered before an unbounded capacity wait, already-resolved and late physical-exit retry of a rejected paired-web teardown, physical desktop-install and local/SSH PTY teardown, stale-generation rejection, per-child stat bounds, final-overflow RPC delivery, and renderer eviction before terminal callbacks can retry. Destructive local and SSH removal fences both ID-derived and resolved-cwd terminal roots, including sibling-root/cwd combinations with reverse admission rollback, closes descendant relay watches before parent deletion, enumerates authoritative provider/cwd ownership, falls back to daemon spawn cwd before OSC 7, and keeps daemon/relay immediate shutdown pending until native exit or a bounded fail-closed error; a dead relay PID discovered during attach also settles concurrent shutdown before its stale entry is reaped, while Windows runtime watcher deadlines retain one removable physical-close waiter, treat Node's error-close path as positive physical-exit proof, and clear root ownership on late close. The relay policy test maps a standard repository's base, Git common directory, and worktree roots to one healthy supervisor, then proves a shared-child failure recovers those roots in separate quarantine supervisors with overflow and resumed events. Abortable capacity, quarantine, runtime-root, relay, SSH, and relay pre-install setup waits attach one reaction to each shared promise and explicitly remove 10,000 cancelled caller closures while one anchor remains. Built-entry macOS harnesses kill the desktop/runtime and SSH relay watcher children, then require automatic resubscription and later events while the host process and relay PTY survive. SSH bundles the child boundary beside relay.js, requires both artifacts for install completeness, and preserves registration-owned same-root cancellation. The test-value audit corrects the active renderer watch command/file reference to the existing runtime-file-client-watch.test.ts, whose three cases are retained unchanged. They observe synthetic subscription retry/fanout and unsubscribe/unwatch calls, not actual preload/main bookkeeping or physical watcher-process cleanup. The separate read/preview client suite loses forwarding, supplied-error and negative-only examples, retaining binary detection and Unicode SVG decoding. Historical evidence commands/results are unchanged. Test-value audit1293 removes src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts (2 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/5308", "https://github.com/stablyai/orca/issues/7547", @@ -18217,16 +18032,6 @@ "durationSeconds": 0.46, "summary": "The Electron-as-Node host survived watcher-child SIGSEGV, replaced and resubscribed the child, and delivered a post-crash event." }, - { - "date": "2026-07-13", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts", - "result": "passed", - "durationSeconds": 9.03, - "summary": "Twenty-three watcher, desktop/serve removal, terminal-spawn gate, daemon/SSH physical PTY exit, provider ownership, RPC, renderer, and cross-platform path files passed with 1,630 tests. The suite includes same-turn watcher replacement, authoritative SSH teardown before Git, bounded kill-without-exit failures that retain ownership, and local/SSH deletion fail-closed behavior." - }, { "date": "2026-07-13", "runner": "local", @@ -18245,16 +18050,6 @@ "durationSeconds": 9.79, "summary": "Six relay registry, filesystem/Git handler, bundled subprocess, install, and package contract files passed with 225 tests, including terminal removal notification and recursive deletion fencing." }, - { - "date": "2026-07-13", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts", - "result": "passed", - "durationSeconds": 8.85, - "summary": "18 focused supervisor, disconnect termination, pool, child-entry, delivery, desktop/WSL cleanup and IPC removal, removal-gate, runtime-host/commands and physical PTY teardown, RPC, SSH ownership, renderer/paired-web clients, and cross-platform path files passed with 1,193 tests." - }, { "date": "2026-07-12", "runner": "local", @@ -18281,76 +18076,6 @@ "result": "passed", "durationSeconds": 2.2, "summary": "Ten consecutive invocations survived, automatically replaced and resubscribed the child, and delivered a post-crash event. The watcher-canary temp-directory count remained 8 before and after the loop." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity-wait.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-pending-assignment.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/shared/promise-settlement-waiters.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity-wait.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-pending-assignment.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/shared/promise-settlement-waiters.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot", - "result": "passed", - "durationSeconds": 30.19, - "summary": "Test Files 38 passed (38); Tests 2018 passed | 4 skipped (2022). Deterministic fixtures; no physical phone or deployed mixed-host claim." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot", - "result": "passed", - "durationSeconds": 30.2, - "summary": "Test Files 35 passed (35); Tests 2009 passed | 4 skipped (2013). Deterministic fixtures; no physical phone or deployed mixed-host claim." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot", - "result": "passed", - "durationSeconds": 27.78, - "summary": "Test Files 28 passed (28); Tests 1944 passed | 4 skipped (1948). Deterministic fixtures; no physical phone or deployed mixed-host claim." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts", - "result": "passed", - "durationSeconds": 29, - "summary": "Test Files 27 passed (27); Tests 1941 passed | 4 skipped (1945). Deterministic fixtures; no physical phone or deployed mixed-host claim." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts", - "result": "passed", - "durationSeconds": 29.2, - "summary": "Test Files 26 passed (26); Tests 1924 passed | 3 skipped (1927). Deterministic fixtures; no physical phone or deployed mixed-host claim." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts", - "result": "passed", - "durationSeconds": 27.48, - "summary": "Test Files 24 passed (24); Tests 1839 passed | 3 skipped (1842). Deterministic fixtures; no physical phone or deployed mixed-host claim." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts --maxWorkers=2", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts", - "result": "passed", - "durationSeconds": 26.75, - "summary": "Test Files 20 passed (20); Tests 1764 passed | 3 skipped (1767). Deterministic fixtures; no physical phone or deployed mixed-host claim." } ], "runtimeBudget": { @@ -18383,7 +18108,16 @@ "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem-watcher.test.ts src/main/ipc/filesystem-watcher-native-capacity.test.ts src/main/ipc/parcel-watcher-child-registry.test.ts src/main/ipc/parcel-watcher-child-launch.test.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-supervisor-capacity-wait.test.ts src/main/ipc/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-pending-assignment.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/shared/promise-settlement-waiters.test.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty-provider-shutdown.test.ts src/main/providers/local-pty-provider-session-inventory.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity-wait.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-pending-assignment.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/shared/promise-settlement-waiters.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"result\":\"passed\",\"durationSeconds\":11.98,\"summary\":\"Thirty-nine watcher, cancellation, desktop/serve, paired-web, WSL, SSH/relay, removal, and PTY physical-exit files passed with 2,008 tests. Capacity, quarantine, runtime-root, relay, and SSH setup layers each prove that 10,000 aborted callers detach while one anchor remains and zero waiters remain after settlement.\"}", "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem-watcher.test.ts src/main/ipc/filesystem-watcher-native-capacity.test.ts src/main/ipc/parcel-watcher-child-registry.test.ts src/main/ipc/parcel-watcher-child-launch.test.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty-provider-shutdown.test.ts src/main/providers/local-pty-provider-session-inventory.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"result\":\"passed\",\"durationSeconds\":11.59,\"summary\":\"Thirty-four watcher, cancellation, desktop/serve, paired-web, WSL, removal, PTY physical-exit, provider, renderer, and path files passed with 1,999 tests; paired-web cancellation now owns a server token before ready and aborts pending setup without a late native install.\"}", "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem-watcher.test.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty-provider-shutdown.test.ts src/main/providers/local-pty-provider-session-inventory.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"result\":\"passed\",\"durationSeconds\":10.01,\"summary\":\"Twenty-seven watcher, WSL, desktop/serve removal, local/daemon/SSH PTY physical-exit, provider ownership, RPC, renderer, relay PTY admission, and cross-platform path files passed with 1,886 tests. The suite includes nested removal fencing, renderer/runtime cwd-only admission, legacy provider rows, rejected kill retries, transient WSL capacity, and synchronous-startup reservation cleanup.\"}", - "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty-provider-shutdown.test.ts src/main/providers/local-pty-provider-session-inventory.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"result\":\"passed\",\"durationSeconds\":9.12,\"summary\":\"Twenty-five watcher, WSL, desktop/serve removal, local/daemon/SSH PTY physical-exit, provider ownership, RPC, renderer, relay PTY admission, and cross-platform path files passed with 1,762 tests. The suite covers operations joining in-flight termination, kill failures retaining ownership, bounded timeouts, atomic relay spawn/revive caps, and PTY-removal admission.\"}" + "Historical receipt before test removal; complete original commands/results/counts preserved without assigning them to remaining tests: {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty-provider-shutdown.test.ts src/main/providers/local-pty-provider-session-inventory.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"result\":\"passed\",\"durationSeconds\":9.12,\"summary\":\"Twenty-five watcher, WSL, desktop/serve removal, local/daemon/SSH PTY physical-exit, provider ownership, RPC, renderer, relay PTY admission, and cross-platform path files passed with 1,762 tests. The suite covers operations joining in-flight termination, kill failures retaining ownership, bounded timeouts, atomic relay spawn/revive caps, and PTY-removal admission.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"result\":\"passed\",\"durationSeconds\":9.03,\"summary\":\"Twenty-three watcher, desktop/serve removal, terminal-spawn gate, daemon/SSH physical PTY exit, provider ownership, RPC, renderer, and cross-platform path files passed with 1,630 tests. The suite includes same-turn watcher replacement, authoritative SSH teardown before Git, bounded kill-without-exit failures that retain ownership, and local/SSH deletion fail-closed behavior.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/parcel-watcher-process.test.ts src/main/ipc/parcel-watcher-disconnect-termination.test.ts src/main/ipc/runtime-watcher-process-pool.test.ts src/main/ipc/parcel-watcher-process-entry.test.ts src/main/ipc/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/ipc/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts\",\"result\":\"passed\",\"durationSeconds\":8.85,\"summary\":\"18 focused supervisor, disconnect termination, pool, child-entry, delivery, desktop/WSL cleanup and IPC removal, removal-gate, runtime-host/commands and physical PTY teardown, RPC, SSH ownership, renderer/paired-web clients, and cross-platform path files passed with 1,193 tests.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity-wait.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-pending-assignment.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/shared/promise-settlement-waiters.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity-wait.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-pending-assignment.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/shared/promise-settlement-waiters.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"result\":\"passed\",\"durationSeconds\":30.19,\"summary\":\"Test Files 38 passed (38); Tests 2018 passed | 4 skipped (2022). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/main/runtime/rpc/methods/files-watch-cancellation.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"result\":\"passed\",\"durationSeconds\":30.2,\"summary\":\"Test Files 35 passed (35); Tests 2009 passed | 4 skipped (2013). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --reporter=dot\",\"result\":\"passed\",\"durationSeconds\":27.78,\"summary\":\"Test Files 28 passed (28); Tests 1944 passed | 4 skipped (1948). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/pty-subprocess.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"result\":\"passed\",\"durationSeconds\":29,\"summary\":\"Test Files 27 passed (27); Tests 1941 passed | 4 skipped (1945). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/session.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"result\":\"passed\",\"durationSeconds\":29.2,\"summary\":\"Test Files 26 passed (26); Tests 1924 passed | 3 skipped (1927). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty/pty-listener-teardown-and-orphans.test.ts src/main/daemon/terminal-host.test.ts src/main/daemon/terminal-host-session-reaping-leak.test.ts\",\"result\":\"passed\",\"durationSeconds\":27.48,\"summary\":\"Test Files 24 passed (24); Tests 1839 passed | 3 skipped (1842). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}", + "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-process-pool.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/ipc/worktrees-remove-archive-hooks.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/file-watcher-host.test.ts src/main/runtime/orca-runtime-files-watch.test.ts src/main/runtime/worktree-teardown.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/methods/files.test.ts src/renderer/src/runtime/runtime-file-client-watch.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts\",\"result\":\"passed\",\"durationSeconds\":26.75,\"summary\":\"Test Files 20 passed (20); Tests 1764 passed | 3 skipped (1767). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"}" ], "demotionRule": "Demote or quarantine if the fault harness flakes without a product or harness bug, if healthy operation exceeds one runtime watcher child, if total physical operation exceeds eight children including retiring generations, if quarantine children outlive their roots or repeat after fusing, if event delivery becomes unbounded, or if metadata/stat work returns to the serve process." }, @@ -20458,33 +20192,6 @@ } ], "evidenceRuns": [ - { - "date": "2026-10-05", - "runner": "local", - "platform": "macos", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/orchestration src/main/runtime/runtime-legacy-worker-terminal-recovery-persistence.test.ts src/main/runtime/runtime-legacy-worker-terminal-recovery-retry.test.ts src/main/runtime/runtime-legacy-worker-missing-workspace-recovery.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/cli/handlers/orchestration-lifecycle-rejection.test.ts src/cli/handlers/orchestration-migration.test.ts tests/e2e/cross-version-wire/orchestration-delivery-downgrade.unit.test.ts", - "result": "passed", - "durationSeconds": 27.41, - "summary": "2,587 tests passed across 151 files; seven tests and one file skipped. Includes all 45 state combinations, rollback, historical upgrade migrations, SQL downgrade compatibility, active-worker index query plans, old-writer reintroduction, and read-only repair with a competing writer. The 50 focused tests passed separately. Reverting the index and read-only preflight produces two failures." - }, - { - "date": "2026-10-05", - "runner": "local", - "platform": "macos", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/orchestration src/main/runtime/runtime-legacy-worker-terminal-recovery-persistence.test.ts src/main/runtime/runtime-legacy-worker-terminal-recovery-retry.test.ts src/main/runtime/runtime-legacy-worker-missing-workspace-recovery.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/cli/handlers/orchestration-lifecycle-rejection.test.ts src/cli/handlers/orchestration-migration.test.ts tests/e2e/cross-version-wire/orchestration-delivery-downgrade.unit.test.ts", - "result": "passed", - "durationSeconds": 21.07, - "summary": "48 focused tests passed after worker-first startup lookups and per-pass workspace deduplication. Reverting both optimizations produces four failures. The broader lifecycle command also passed 2,585 tests across 151 files in 21.07s, with seven tests and one file skipped." - }, - { - "date": "2026-10-05", - "runner": "local", - "platform": "macos", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/orchestration src/main/runtime/runtime-legacy-worker-terminal-recovery-persistence.test.ts src/main/runtime/runtime-legacy-worker-terminal-recovery-retry.test.ts src/main/runtime/runtime-legacy-worker-missing-workspace-recovery.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/cli/handlers/orchestration-lifecycle-rejection.test.ts src/cli/handlers/orchestration-migration.test.ts tests/e2e/cross-version-wire/orchestration-delivery-downgrade.unit.test.ts", - "result": "passed", - "durationSeconds": 56.25, - "summary": "2,581 tests passed across 150 files; seven tests and one file skipped. Includes SQLite lifecycle, runtime recovery, federation, CLI and SQL downgrade contracts." - }, { "date": "2026-10-05", "runner": "local", @@ -20531,7 +20238,10 @@ }, "knownGaps": [ "Native Linux/Windows desktop and WSL worker journeys, active-worker SSH absence/deleted-folder recovery, and stranded old-relay process discovery remain untested locally.", - "No latency claim against the original customer profile; deterministic counts and one synthetic scale measurement cover the identified work." + "No latency claim against the original customer profile; deterministic counts and one synthetic scale measurement cover the identified work.", + "Retired during audit1312 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-10-05\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/orchestration src/main/runtime/runtime-legacy-worker-terminal-recovery-persistence.test.ts src/main/runtime/runtime-legacy-worker-terminal-recovery-retry.test.ts src/main/runtime/runtime-legacy-worker-missing-workspace-recovery.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/cli/handlers/orchestration-lifecycle-rejection.test.ts src/cli/handlers/orchestration-migration.test.ts tests/e2e/cross-version-wire/orchestration-delivery-downgrade.unit.test.ts\",\"result\":\"passed\",\"durationSeconds\":27.41,\"summary\":\"2,587 tests passed across 151 files; seven tests and one file skipped. Includes all 45 state combinations, rollback, historical upgrade migrations, SQL downgrade compatibility, active-worker index query plans, old-writer reintroduction, and read-only repair with a competing writer. The 50 focused tests passed separately. Reverting the index and read-only preflight produces two failures.\"}", + "Retired during audit1312 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-10-05\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/orchestration src/main/runtime/runtime-legacy-worker-terminal-recovery-persistence.test.ts src/main/runtime/runtime-legacy-worker-terminal-recovery-retry.test.ts src/main/runtime/runtime-legacy-worker-missing-workspace-recovery.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/cli/handlers/orchestration-lifecycle-rejection.test.ts src/cli/handlers/orchestration-migration.test.ts tests/e2e/cross-version-wire/orchestration-delivery-downgrade.unit.test.ts\",\"result\":\"passed\",\"durationSeconds\":21.07,\"summary\":\"48 focused tests passed after worker-first startup lookups and per-pass workspace deduplication. Reverting both optimizations produces four failures. The broader lifecycle command also passed 2,585 tests across 151 files in 21.07s, with seven tests and one file skipped.\"}", + "Retired during audit1312 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-10-05\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/runtime/orchestration src/main/runtime/runtime-legacy-worker-terminal-recovery-persistence.test.ts src/main/runtime/runtime-legacy-worker-terminal-recovery-retry.test.ts src/main/runtime/runtime-legacy-worker-missing-workspace-recovery.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/cli/handlers/orchestration-lifecycle-rejection.test.ts src/cli/handlers/orchestration-migration.test.ts tests/e2e/cross-version-wire/orchestration-delivery-downgrade.unit.test.ts\",\"result\":\"passed\",\"durationSeconds\":56.25,\"summary\":\"2,581 tests passed across 150 files; seven tests and one file skipped. Includes SQLite lifecycle, runtime recovery, federation, CLI and SQL downgrade contracts.\"}" ], "promotionCriteria": [ "Collect cross-platform and provider soak evidence with no weakened identity, transaction or bounded-work assertions." diff --git a/config/scripts/ci-shard-assignment.test.mjs b/config/scripts/ci-shard-assignment.test.mjs index e06b928fcea..cdc4ed247d1 100644 --- a/config/scripts/ci-shard-assignment.test.mjs +++ b/config/scripts/ci-shard-assignment.test.mjs @@ -1,20 +1,7 @@ -import { mkdtempSync, readFileSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join, resolve } from 'node:path' -import { afterEach, describe, expect, it, vi } from 'vitest' -import RuntimeSequencer from './vitest-runtime-sequencer.mjs' +import { describe, expect, it } from 'vitest' import { balanceFiles } from './ci-shard-assignment.mjs' import { discoverE2eFiles, planE2e, verifyE2eSelection } from './ci-e2e-shard-plan.mjs' import { parseTimingLog } from './ci-shard-timing-import.mjs' -import TimingSequencer from './ci-unit-sequencer.mjs' - -const directories = [] -afterEach(() => { - vi.unstubAllEnvs() - for (const directory of directories.splice(0)) { - rmSync(directory, { recursive: true, force: true }) - } -}) describe('timing-weighted shard selection', () => { it('distributes long files, includes unknowns exactly once, and ignores discovery order', () => { @@ -37,29 +24,6 @@ describe('timing-weighted shard selection', () => { expect(() => balanceFiles(['a'], 0, {})).toThrow('count') }) - it('uses the post-filter Vitest discovery unchanged across eight shards', async () => { - const directory = mkdtempSync(join(tmpdir(), 'orca-unit-shards-')) - directories.push(directory) - vi.stubEnv('ORCA_SHARD_MANIFEST', join(directory, 'assignment.json')) - const specs = Array.from({ length: 37 }, (_, i) => ({ - moduleId: resolve(`src/fixture-${i}.test.ts`) - })) - const selected = [] - for (let index = 1; index <= 8; index++) { - const sequencer = new TimingSequencer({ - config: { root: process.cwd(), shard: { index, count: 8 } } - }) - expect(sequencer.sort).toBe(RuntimeSequencer.prototype.sort) - selected.push(...(await sequencer.shard(specs))) - const manifest = JSON.parse(readFileSync(join(directory, 'assignment.json'), 'utf8')) - expect(manifest.selectedShard).toBe(index) - expect(manifest.baselineSha256).toMatch(/^[a-f0-9]{64}$/) - } - expect(new Set(selected).size).toBe(specs.length) - expect(selected).toHaveLength(specs.length) - expect(new Set(selected)).toEqual(new Set(specs)) - }) - it('keeps nested/serial E2E files atomic and fails closed on discovery errors', () => { const spec = (id, file) => ({ id, file, tests: [{ projectName: 'electron-headless' }] }) const report = { diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index b8d77c67b37..18aa3c60c83 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -793,7 +793,6 @@ "src/main/agent-hooks/grok-replay-guard.test.ts": 533, "src/main/agent-hooks/hook-process-capture.test.ts": 118, "src/main/agent-hooks/hook-script-outside-orca.test.ts": 171, - "src/main/agent-hooks/hook-status-session-tabs-republish.test.ts": 645, "src/main/agent-hooks/installer-utils-remote.test.ts": 75, "src/main/agent-hooks/installer-utils.test.ts": 264, "src/main/agent-hooks/local-agent-cli-presence.test.ts": 153, @@ -1005,7 +1004,6 @@ "src/main/ai-vault-search/session-search-store-is-memory.test.ts": 805, "src/main/ai-vault-search/session-search-store-scope-catalog.test.ts": 268, "src/main/ai-vault-search/session-search-synthetic-corpus.test.ts": 372, - "src/main/ai-vault-search/session-search-transcript-dependencies.test.ts": 505, "src/main/ai-vault-search/session-search-typo-policy.test.ts": 158, "src/main/ai-vault-search/session-search-typo-scope.test.ts": 195, "src/main/ai-vault-search/session-search-write-buffer.test.ts": 197, @@ -1078,7 +1076,6 @@ "src/main/ai-vault/scanners/devin/session-scanner-devin-parser.test.ts": 283, "src/main/ai-vault/session-scanner-directory-reader.test.ts": 190, "src/main/ai-vault/session-scanner-discovery-pipeline.test.ts": 244, - "src/main/ai-vault/session-scanner-discovery-wsl-gate.test.ts": 180, "src/main/ai-vault/scanners/droid/session-scanner-droid-parser.test.ts": 166, "src/main/ai-vault/session-scanner-first-user-prompt.test.ts": 60, "src/main/ai-vault/scanners/session-scanner-graph-parsers.test.ts": 176, @@ -1157,7 +1154,6 @@ "src/main/ai-vault/structured-chat-conversation-copy.test.ts": 917, "src/main/ai-vault/structured-session-ownership-acp-and-pi.test.ts": 2020, "src/main/ai-vault/structured-session-ownership-client-audience.test.ts": 1092, - "src/main/ai-vault/structured-session-ownership-shell-commands.test.ts": 930, "src/main/ai-vault/structured-session-ownership.test.ts": 1191, "src/main/amp/hook-service.test.ts": 68, "src/main/antigravity/antigravity-acp-runtime-probe.test.ts": 470, @@ -1235,7 +1231,6 @@ "src/main/bitbucket/client.test.ts": 496, "src/main/bitbucket/credential-connection.test.ts": 156, "src/main/bitbucket/credential-store.test.ts": 245, - "src/main/bitbucket/pull-request-creation.test.ts": 417, "src/main/bitbucket/pull-request-mappers.test.ts": 44, "src/main/bitbucket/repository-ref.test.ts": 426, "src/main/bitbucket/status-no-decrypt.test.ts": 256, @@ -1290,7 +1285,6 @@ "src/main/browser/client-hosts/file-transfers/browser-client-upload-staging.test.ts": 140, "src/main/browser/client-hosts/file-transfers/browser-client-upload-transfer.test.ts": 93, "src/main/browser/cdp/input/browser-element-functions.test.ts": 228, - "src/main/browser/guest-lifecycle/prompts/browser-external-app-launch.test.ts": 96, "src/main/browser/guest-lifecycle/find/browser-find-frame-traversal.electron.test.ts": 1807, "src/main/browser/cookies/browser-google-cookie-clear-target.test.ts": 6235, "src/main/browser/element-grab/browser-grab-payload.test.ts": 55, @@ -1325,8 +1319,6 @@ "src/main/browser/guest-lifecycle/manager/browser-manager-tab-identity.test.ts": 619, "src/main/browser/guest-lifecycle/manager/browser-manager-viewport-override.test.ts": 597, "src/main/browser/guest-lifecycle/manager/browser-manager-viewport-ownership.test.ts": 550, - "src/main/browser/guest-lifecycle/manager/browser-manager-viewport-partial-failure.test.ts": 547, - "src/main/browser/guest-lifecycle/manager/browser-manager-worker-request-user-agent.test.ts": 498, "src/main/browser/network/execution-routes/browser-network-deferred-socket.test.ts": 101, "src/main/browser/network/execution-routes/browser-network-execution-route.test.ts": 272, "src/main/browser/network/tunnels/browser-network-tunnel-client.test.ts": 127, @@ -1382,7 +1374,6 @@ "src/main/browser/webauthn/browser-webauthn-profile-delete.test.ts": 358, "src/main/browser/cdp/commands/cdp-bridge-integration.test.ts": 28361, "src/main/browser/cdp/transport/cdp-client-response-writer.test.ts": 279, - "src/main/browser/cdp/input/cdp-keyboard-us-layout.test.ts": 84, "src/main/browser/cdp/commands/cdp-print-to-pdf.test.ts": 68, "src/main/browser/cdp/commands/cdp-screenshot.test.ts": 415, "src/main/browser/cdp/transport/cdp-ws-proxy-first-paint.test.ts": 366, @@ -1583,7 +1574,6 @@ "src/main/claude/claude-agent-sdk-process-spawn.test.ts": 182, "src/main/claude/claude-agent-sdk-root-kill-fallback.test.ts": 154, "src/main/claude/claude-agent-sdk-user-message-queue.test.ts": 143, - "src/main/claude/claude-api-retry-idle-sweep.test.ts": 2411, "src/main/claude/claude-api-retry-row.test.ts": 901, "src/main/claude/claude-background-run-isolation-real-cli.test.ts": 147, "src/main/claude/claude-background-stop-records.test.ts": 1249, @@ -1613,7 +1603,6 @@ "src/main/claude/claude-descendant-escalation-boundary.test.ts": 593, "src/main/claude/claude-diff-panel-auto-open.test.ts": 117, "src/main/claude/claude-fold-steer-receipt.test.ts": 1019, - "src/main/claude/claude-folder-trust-file-wsl-guest.test.ts": 72, "src/main/claude/claude-folder-trust-file.test.ts": 762, "src/main/claude/claude-hook-decision-script.test.ts": 336, "src/main/claude/claude-hook-event-install.test.ts": 198, @@ -1711,7 +1700,6 @@ "src/main/claude/structured/launch/claude-structured-options.test.ts": 420, "src/main/claude/structured/launch/claude-structured-provider-fallback.test.ts": 670, "src/main/claude/structured/launch/claude-structured-real-cli-options.test.ts": 902, - "src/main/claude/structured/launch/claude-structured-spawn-options.test.ts": 344, "src/main/claude/windows-hook-files.test.ts": 356, "src/main/claude/windows-native-hook-registration.test.ts": 246, "src/main/cli/appimage-copy-pre-extraction.test.ts": 328, @@ -1823,7 +1811,6 @@ "src/main/codex-usage/codex-rollout-file-lifetime.test.ts": 173, "src/main/codex-usage/codex-rollout-resume-state.test.ts": 67, "src/main/codex-usage/codex-usage-long-context-billing.test.ts": 615, - "src/main/codex-usage/scanner-attribution-memo.test.ts": 396, "src/main/codex-usage/scanner-checkpoint-races.test.ts": 523, "src/main/codex-usage/scanner-file-generation.test.ts": 480, "src/main/codex-usage/scanner-file-identity.test.ts": 389, @@ -1841,7 +1828,6 @@ "src/main/codex/app-server/codex-app-server-connection-exit-order.test.ts": 264, "src/main/codex/app-server/codex-app-server-connection-tree-unproven.test.ts": 634, "src/main/codex/app-server/codex-app-server-direct-open.test.ts": 280, - "src/main/codex/app-server/codex-app-server-session-stop.test.ts": 174, "src/main/codex/app-server/codex-app-server-session-supervision.test.ts": 14357, "src/main/codex/app-server/codex-app-server-session.test.ts": 343, "src/main/codex/app-server/codex-app-server-teardown.integration.test.ts": 16506, @@ -2209,7 +2195,6 @@ "src/main/daemon/daemon-pty-adapter-wsl-distro-loss-relaunch.test.ts": 1128, "src/main/daemon/daemon-pty-adapter-wsl-distro-loss.test.ts": 1098, "src/main/daemon/daemon-pty-adapter-wsl-passive-restore-hold.test.ts": 892, - "src/main/daemon/daemon-pty-adapter-wsl-quit-cwd.test.ts": 955, "src/main/daemon/daemon-pty-adapter-wsl-sleep-cwd-relaunch.test.ts": 1248, "src/main/daemon/daemon-pty-adapter-wsl-sleep-cwd.test.ts": 1166, "src/main/daemon/daemon-pty-adapter.test.ts": 2102, @@ -2223,7 +2208,6 @@ "src/main/daemon/daemon-pty-router-unanswering-legacy.test.ts": 5191, "src/main/daemon/daemon-pty-router.test.ts": 914, "src/main/daemon/daemon-pty-spawn-preparations.test.ts": 48, - "src/main/daemon/daemon-pty-startup-delivery.test.ts": 1156, "src/main/daemon/daemon-pty-upgrade-adoption.test.ts": 939, "src/main/daemon/daemon-pty-write-settlement-recovery.test.ts": 552, "src/main/daemon/daemon-ready-identity.test.ts": 53, @@ -2248,7 +2232,6 @@ "src/main/daemon/daemon-shutdown-final-checkpoint-caller-deadline.test.ts": 2543, "src/main/daemon/daemon-socket-endpoint-path.test.ts": 529, "src/main/daemon/daemon-spawner.test.ts": 708, - "src/main/daemon/daemon-stream-backpressure-socket.test.ts": 293, "src/main/daemon/daemon-stream-backpressure.test.ts": 63, "src/main/daemon/daemon-stream-binary-framing.test.ts": 87, "src/main/daemon/daemon-stream-binary-negotiation.test.ts": 847, @@ -2599,9 +2582,7 @@ "src/main/git/worktrees/creation/worktree-add-local-base-refresh.test.ts": 362, "src/main/git/worktrees/creation/worktree-add-pending-removal.test.ts": 346, "src/main/git/worktrees/creation/worktree-add-remote-branch-upstream-real-git.test.ts": 786, - "src/main/git/worktrees/creation/worktree-add-timeout-override.test.ts": 334, "src/main/git/worktrees/creation/worktree-base-divergence-real-git.test.ts": 707, - "src/main/git/worktrees/creation/worktree-base-divergence.test.ts": 102, "src/main/git/worktrees/creation/worktree-base-refresh-contention.test.ts": 269, "src/main/git/worktrees/creation/worktree-base-refresh-lock-real-git.test.ts": 1588, "src/main/git/worktrees/creation/worktree-checkout-progress-real-git.test.ts": 769, @@ -2626,7 +2607,6 @@ "src/main/git/worktrees/movement/worktree-mutation-route-invalidation.test.ts": 287, "src/main/git/worktrees/catalog/worktree-path-deduplication.test.ts": 220, "src/main/git/worktrees/catalog/worktree-porcelain-parsing.test.ts": 357, - "src/main/git/worktrees/removal/worktree-posix-path-case-sensitivity.test.ts": 279, "src/main/git/worktrees/creation/worktree-preparation-add.test.ts": 233, "src/main/git/worktrees/creation/worktree-preparation-base-oid.test.ts": 322, "src/main/git/worktrees/creation/worktree-preparation-cancel-latency.bench.test.ts": 361, @@ -2650,7 +2630,6 @@ "src/main/git/wsl-direct-git-read-commands.test.ts": 74, "src/main/git/command-runner/wsl-linked-worktree-git-route-invalidation.test.ts": 48, "src/main/git/command-runner/wsl-linked-worktree-git-routing.test.ts": 93, - "src/main/git/wsl-process-group-termination.test.ts": 109, "src/main/git/wsl-windows-checkout-git-config.test.ts": 64, "src/main/git/wsl-windows-checkout-git-probe.test.ts": 273, "src/main/git/wsl-windows-checkout-sample-script.test.ts": 165, @@ -2763,7 +2742,6 @@ "src/main/github/work-item-search-freshness.test.ts": 1425, "src/main/github/work-item-search-isolation.test.ts": 701, "src/main/github/work-item-search-pagination.test.ts": 618, - "src/main/github/work-item-search-semantics.test.ts": 645, "src/main/gitlab/client-mr-auth-rate-limit.test.ts": 475, "src/main/gitlab/client-mr-branch-lookup.test.ts": 563, "src/main/gitlab/client-mr-listing.test.ts": 558, @@ -3003,7 +2981,6 @@ "src/main/ipc/notification-options-localization.test.ts": 313, "src/main/ipc/notifications-custom-sound.test.ts": 289, "src/main/ipc/notifications-delivery-gating.test.ts": 325, - "src/main/ipc/notifications-macos-removal.test.ts": 378, "src/main/ipc/notifications-message-formatting.test.ts": 229, "src/main/ipc/notifications-mobile-fanout.test.ts": 361, "src/main/ipc/notifications-permission-onboarding.test.ts": 354, @@ -3044,7 +3021,6 @@ "src/main/ipc/pty/delivery/pty-delivery-health-heal.test.ts": 4433, "src/main/ipc/pty/delivery/pty-global-renderer-credit.test.ts": 4242, "src/main/ipc/pty/delivery/pty-hidden-delivery-gate.test.ts": 59, - "src/main/ipc/pty/delivery/pty-ipc-gap-escape-state.test.ts": 3446, "src/main/ipc/pty/delivery/pty-ipc-hidden-delivery-gate.test.ts": 4081, "src/main/ipc/pty/delivery/pty-output-batching-drain.test.ts": 5239, "src/main/ipc/pty/delivery/pty-pending-data-drain-queue-differential.test.ts": 1566, @@ -3058,7 +3034,6 @@ "src/main/ipc/pty/host-env/opencode-hook-installation.test.ts": 668, "src/main/ipc/pty/host-env/spawn-cwd-hung-folder.test.ts": 650, "src/main/ipc/pty/host-env/spawn-cwd-wsl-move.test.ts": 817, - "src/main/ipc/pty/host-env/spawn-cwd-wsl-stopped-folder.test.ts": 676, "src/main/ipc/pty/ipc/claude-old-terminal.test.ts": 47, "src/main/ipc/pty/ipc/closed-pane-spawn-refusal.test.ts": 242, "src/main/ipc/pty/ipc/codex-shared-server.test.ts": 89, @@ -3123,7 +3098,6 @@ "src/main/ipc/pty/pty-spawn-env-terminal-basics.test.ts": 4943, "src/main/ipc/pty/pty-spawn-runtime-handle-binding.test.ts": 5282, "src/main/ipc/pty/pty-spawn-timing.test.ts": 45, - "src/main/ipc/pty/pty-spawn-wsl-passive-restore-hold.test.ts": 3817, "src/main/ipc/pty/pty-ssh-stop-verdict.test.ts": 3615, "src/main/ipc/pty/pty-startup-barrier-and-listing.test.ts": 4011, "src/main/ipc/pty/pty-startup-swap-window-presence.test.ts": 5899, @@ -3139,7 +3113,6 @@ "src/main/ipc/pty/runtime/spawn-preflight-requested-shell.test.ts": 1924, "src/main/ipc/pty/runtime/spawn-preflight-saved-resume.test.ts": 1949, "src/main/ipc/pty/runtime/spawn-preflight-wsl-background-hold.test.ts": 1568, - "src/main/ipc/pty/spawn-host-env-wsl-passive-restore.test.ts": 1263, "src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts": 1131, "src/main/ipc/qoder-desktop-execution-host.test.ts": 4373, "src/main/ipc/qoder-independent-renderer-spawn-review.test.ts": 3873, @@ -3165,12 +3138,10 @@ "src/main/ipc/repos-remote-base-ref-queries.test.ts": 1448, "src/main/ipc/repos-remote-client-events.test.ts": 1914, "src/main/ipc/repos-sparse-presets.test.ts": 1310, - "src/main/ipc/repos/base-ref-default-wsl-gate.test.ts": 107, "src/main/ipc/repos/folder-workspace-attachment-handlers.test.ts": 1932, "src/main/ipc/repos/local-repo-registration.test.ts": 469, "src/main/ipc/repos/project-host-setup-wsl-move-fence.test.ts": 2529, "src/main/ipc/repos/repo-ipc-wsl-move-fence.test.ts": 2655, - "src/main/ipc/runtime-environments/orcad-runtime-maintenance-handlers.test.ts": 802, "src/main/ipc/runtime-environments/runtime-environment-browser-client-host-handler.test.ts": 827, "src/main/ipc/runtime-environments/runtime-environment-capability-evidence.test.ts": 46, "src/main/ipc/runtime-environments/runtime-environment-cli-removal-routing.test.ts": 891, @@ -3221,8 +3192,6 @@ "src/main/ipc/settings.test.ts": 1544, "src/main/ipc/filesystem/watcher-process/shallow-watch-delivery-probe-lifetime.test.ts": 86, "src/main/ipc/shell-paths-exist-wsl-stopped-distro.test.ts": 244, - "src/main/ipc/shell-server-ssh-editor.test.ts": 342, - "src/main/ipc/shell-wsl-owner.test.ts": 330, "src/main/ipc/shell.test.ts": 357, "src/main/ipc/skill-cloud-install-ipc-schemas.test.ts": 116, "src/main/ipc/skill-cloud-ipc-handlers.test.ts": 255, @@ -3351,7 +3320,6 @@ "src/main/ipc/wsl/wsl-move-handlers-setting-off.test.ts": 724, "src/main/ipc/wsl/wsl-move-handlers.test.ts": 1263, "src/main/ipc/wsl/wsl-move-host-connect.test.ts": 984, - "src/main/ipc/wsl/wsl-move-orchestrator-host-connect.test.ts": 1141, "src/main/ipc/wsl/wsl-move-orchestrator-wiring.test.ts": 1283, "src/main/ipc/wsl/wsl-move-partial-undo.test.ts": 939, "src/main/ipc/wsl/wsl-move-release-here-host-asleep.test.ts": 344, @@ -3513,7 +3481,6 @@ "src/main/managed-server/install/remote-node-runtime-store-lock-host-release.test.ts": 2283, "src/main/managed-server/install/remote-node-runtime-store-windows.test.ts": 5867, "src/main/managed-server/install/remote-versioned-install.test.ts": 272, - "src/main/managed-server/lifecycle/orcad-decommission-released-peer-recovery.test.ts": 156, "src/main/managed-server/lifecycle/orcad-decommission-transaction.test.ts": 157, "src/main/managed-server/lifecycle/orcad-managed-auto-update.test.ts": 332, "src/main/managed-server/lifecycle/orcad-managed-bound-port.test.ts": 313, @@ -3547,7 +3514,6 @@ "src/main/media/preview-grant-range-read.test.ts": 691, "src/main/media/preview-raster-header-check.test.ts": 716, "src/main/media/remote-file-preview-reader.test.ts": 47, - "src/main/memory/collector-darwin-footprint.test.ts": 405, "src/main/memory/collector-windows-sweep.test.ts": 871, "src/main/memory/collector.test.ts": 925, "src/main/memory/host-malloc-tunables.test.ts": 41, @@ -3780,7 +3746,6 @@ "src/main/native-chat/agent-session-wire/structured-agent-session-queued-quit.test.ts": 2500, "src/main/native-chat/agent-session-wire/structured-agent-session-queued-refused-card.test.ts": 2478, "src/main/native-chat/agent-session-wire/structured-agent-session-queued-repeat-press.test.ts": 2765, - "src/main/native-chat/agent-session-wire/structured-agent-session-queued-stop-row.test.ts": 1871, "src/main/native-chat/agent-session-wire/structured-agent-session-queued-withdrawn-draft.test.ts": 1916, "src/main/native-chat/agent-session-wire/structured-agent-session-ready-operation-order.test.ts": 2646, "src/main/native-chat/agent-session-wire/structured-agent-session-reasoning-sweep.test.ts": 747, @@ -3794,7 +3759,6 @@ "src/main/native-chat/agent-session-wire/structured-agent-session-recovery-resolution.test.ts": 4345, "src/main/native-chat/agent-session-wire/structured-agent-session-refusal-details.test.ts": 380, "src/main/native-chat/agent-session-wire/structured-agent-session-registry.test.ts": 48, - "src/main/native-chat/agent-session-wire/structured-agent-session-repeated-stop.test.ts": 2059, "src/main/native-chat/agent-session-wire/structured-agent-session-replaced-server-turn.test.ts": 2308, "src/main/native-chat/agent-session-wire/structured-agent-session-resend-answer.test.ts": 3851, "src/main/native-chat/agent-session-wire/structured-agent-session-resend-ledger-refusal.test.ts": 2302, @@ -3845,7 +3809,6 @@ "src/main/native-chat/agent-session-wire/structured-agent-session-startup-attempt-host.test.ts": 10511, "src/main/native-chat/agent-session-wire/structured-agent-session-startup-attempt-registrations.test.ts": 2980, "src/main/native-chat/agent-session-wire/structured-agent-session-startup-attempt-reports.test.ts": 1857, - "src/main/native-chat/agent-session-wire/structured-agent-session-startup-failure-exit.test.ts": 393, "src/main/native-chat/agent-session-wire/structured-agent-session-startup-provider-errors.test.ts": 8128, "src/main/native-chat/agent-session-wire/structured-agent-session-startup-reconcile-failure.test.ts": 3220, "src/main/native-chat/agent-session-wire/structured-agent-session-status-feed-children.test.ts": 2318, @@ -3862,8 +3825,6 @@ "src/main/native-chat/agent-session-wire/structured-agent-session-stop-note-opened-turn.test.ts": 1446, "src/main/native-chat/agent-session-wire/structured-agent-session-stop-note-withdrawn-send.test.ts": 1566, "src/main/native-chat/agent-session-wire/structured-agent-session-stop-receipts.test.ts": 2862, - "src/main/native-chat/agent-session-wire/structured-agent-session-stopped-history.test.ts": 2263, - "src/main/native-chat/agent-session-wire/structured-agent-session-stopping-journal.test.ts": 772, "src/main/native-chat/agent-session-wire/structured-agent-session-subagent-recency.test.ts": 1511, "src/main/native-chat/agent-session-wire/structured-agent-session-subagent-rows.test.ts": 918, "src/main/native-chat/agent-session-wire/structured-agent-session-subscriber-background-tasks.test.ts": 829, @@ -3877,7 +3838,6 @@ "src/main/native-chat/agent-session-wire/structured-agent-session-thread-goal.test.ts": 3029, "src/main/native-chat/agent-session-wire/structured-agent-session-transition-rollback.test.ts": 919, "src/main/native-chat/agent-session-wire/structured-agent-session-transition.test.ts": 1068, - "src/main/native-chat/agent-session-wire/structured-agent-session-turn-completion-child-work.test.ts": 240, "src/main/native-chat/agent-session-wire/structured-agent-session-turn-completion-feed.test.ts": 276, "src/main/native-chat/agent-session-wire/structured-agent-session-unexpected-exit.test.ts": 430, "src/main/native-chat/agent-session-wire/structured-agent-session-unnamed-stop-races.test.ts": 2959, @@ -4122,7 +4082,6 @@ "src/main/orcad/orcad-idle-exit-monitor.test.ts": 106, "src/main/orcad/orcad-idle-stop-record.test.ts": 199, "src/main/orcad/orcad-instance-lock.test.ts": 315, - "src/main/orcad/orcad-launch-contract.test.ts": 564, "src/main/orcad/orcad-launcher-isolation.integration.test.ts": 617, "src/main/orcad/orcad-lifecycle-host.test.ts": 407, "src/main/orcad/orcad-local-serve-selection.test.ts": 533, @@ -4432,7 +4391,6 @@ "src/main/persistence/terminal-topology/persistence-cross-host-pane-identity.test.ts": 1744, "src/main/persistence/terminal-topology/persistence-empty-root-layout-load.test.ts": 1841, "src/main/persistence/terminal-topology/persistence-floating-terminal-trust.test.ts": 2121, - "src/main/persistence/terminal-topology/persistence-host-partitioned-ssh-pty-bindings.test.ts": 1813, "src/main/persistence/terminal-topology/persistence-layout-binding-recovery.test.ts": 1835, "src/main/persistence/terminal-topology/persistence-pane-alias-selective-saves.test.ts": 1961, "src/main/persistence/terminal-topology/persistence-pane-identity-migration.test.ts": 5772, @@ -4440,7 +4398,6 @@ "src/main/persistence/terminal-topology/persistence-pty-binding-reconciliation.test.ts": 1898, "src/main/persistence/terminal-topology/persistence-runtime-admitted-terminal-membership.test.ts": 2143, "src/main/persistence/terminal-topology/persistence-split-pane-incarnation.test.ts": 1790, - "src/main/persistence/terminal-topology/persistence-ssh-lease-tombstone-retention.test.ts": 1927, "src/main/persistence/terminal-topology/persistence-ssh-pending-pty-kill.test.ts": 1776, "src/main/persistence/terminal-topology/persistence-ssh-readoption-automation-migration.test.ts": 2463, "src/main/persistence/terminal-topology/persistence-ssh-remote-cli-control-migration.test.ts": 1738, @@ -4563,7 +4520,6 @@ "src/main/plugins/contributions/plugin-theme-registries.test.ts": 250, "src/main/plugins/hooks/plugin-agent-hook-sites.test.ts": 279, "src/main/plugins/hooks/plugin-hook-participants.test.ts": 341, - "src/main/plugins/hooks/plugin-hook-sites.test.ts": 195, "src/main/plugins/hooks/plugin-hook-warming.test.ts": 472, "src/main/plugins/installation/plugin-atomic-file-write.test.ts": 2051, "src/main/plugins/installation/plugin-catalog-install-guard.test.ts": 475, @@ -4707,7 +4663,6 @@ "src/main/providers/agent-foreground-process-batch.test.ts": 141, "src/main/providers/agent-foreground-process-git-bash-launcher.test.ts": 144, "src/main/providers/agent-foreground-process-git-bash.win32.test.ts": 361, - "src/main/providers/agent-foreground-process-ps-scan-volume.test.ts": 244, "src/main/providers/agent-foreground-process-real-rows.test.ts": 152, "src/main/providers/agent-foreground-process-remote-evidence.test.ts": 154, "src/main/providers/agent-foreground-process-shell-path.test.ts": 143, @@ -4721,7 +4676,6 @@ "src/main/providers/local-pty/local-pty-default-shell.test.ts": 272, "src/main/providers/local-pty/local-pty-executable-spawn.test.ts": 96, "src/main/providers/local-pty/local-pty-foreground-inspection-cheap-tier.test.ts": 266, - "src/main/providers/local-pty/local-pty-pending-native-spawn.test.ts": 376, "src/main/providers/local-pty/local-pty-provider-foreground-process.test.ts": 520, "src/main/providers/local-pty/local-pty-provider-io-events.test.ts": 493, "src/main/providers/local-pty/local-pty-provider-shell-readiness.test.ts": 551, @@ -4819,12 +4773,10 @@ "src/main/rate-limits/cursor-session-token.test.ts": 96, "src/main/rate-limits/cursor-usage-mapping.test.ts": 141, "src/main/rate-limits/gemini-oauth-sources.test.ts": 97, - "src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts": 107, "src/main/rate-limits/gemini-usage-fetcher.test.ts": 104, "src/main/rate-limits/grok-auth.test.ts": 105, "src/main/rate-limits/grok-fetcher.test.ts": 121, "src/main/rate-limits/grok-weekly-zero.test.ts": 163, - "src/main/rate-limits/initial-account-rate-limit-target.test.ts": 278, "src/main/rate-limits/kimi-fetcher-wsl-home.test.ts": 187, "src/main/rate-limits/kimi-fetcher.test.ts": 197, "src/main/rate-limits/minimax/minimax-fetcher.test.ts": 138, @@ -4892,7 +4844,6 @@ "src/main/runtime/agent-session/agent-session-conversation-clear-commit.test.ts": 957, "src/main/runtime/agent-session/agent-session-death-evidence-persistence.test.ts": 1113, "src/main/runtime/agent-session/agent-session-launch-directory.test.ts": 926, - "src/main/runtime/agent-session/agent-session-runtime-attribution.test.ts": 1015, "src/main/runtime/agent-session/agent-session-tab-layout.test.ts": 275, "src/main/runtime/agent-session/agent-session-unsaved-creation-supersession.test.ts": 726, "src/main/runtime/agent-session/ownership/agent-session-claim-identity.test.ts": 55, @@ -4960,7 +4911,6 @@ "src/main/runtime/agent-state-rules/agent-state-rules-live-update.test.ts": 379, "src/main/runtime/agent-state-rules/agent-state-text-anchors.test.ts": 171, "src/main/runtime/agent-status-store-attach-titles.test.ts": 4361, - "src/main/runtime/agent-terminal-launch-host.test.ts": 3981, "src/main/runtime/agent-terminal-startup-prompt.test.ts": 4264, "src/main/runtime/antigravity-readiness-transcripts.test.ts": 5396, "src/main/runtime/antigravity-terminal-readiness.test.ts": 167, @@ -5000,7 +4950,6 @@ "src/main/runtime/browser-tab-create-publication.test.ts": 588, "src/main/runtime/browser/browser-profiles-client-placement.test.ts": 278, "src/main/runtime/browser/browser-screencasts.test.ts": 632, - "src/main/runtime/browser/browser-tab-listing.test.ts": 50, "src/main/runtime/claude-agent-teams-service.test.ts": 115, "src/main/runtime/claude-agent-teams-shim-env.test.ts": 84, "src/main/runtime/claude-dialog-interactive-wait.test.ts": 3975, @@ -5048,7 +4997,6 @@ "src/main/runtime/device-registry-own-quarantine.test.ts": 123, "src/main/runtime/device-registry-startup-offer.test.ts": 102, "src/main/runtime/draft-paste-ready-scanner-chunking.test.ts": 1475, - "src/main/runtime/dsb-terminal-runtime.test.ts": 4103, "src/main/runtime/dsh-launch-prompt-resubmit.test.ts": 6527, "src/main/runtime/dsh-readiness-transcript.test.ts": 71, "src/main/runtime/e2ee-keypair.test.ts": 77, @@ -5173,9 +5121,7 @@ "src/main/runtime/omp-user-bubble-launch-authority.test.ts": 4274, "src/main/runtime/opencode-agent-row-structure-transcript.test.ts": 277, "src/main/runtime/opencode-finished-session-authority.test.ts": 4172, - "src/main/runtime/opencode-run-lifetime-runtime.test.ts": 4824, "src/main/runtime/opencode-run-lifetime-status.test.ts": 141, - "src/main/runtime/opencode-wsl-folder-launch-host.test.ts": 4495, "src/main/runtime/orca-runtime-agent-session-account-env.test.ts": 4192, "src/main/runtime/orca-runtime-agent-session-operation.test.ts": 4039, "src/main/runtime/orca-runtime-agent-skill-share.test.ts": 4096, @@ -5228,7 +5174,6 @@ "src/main/runtime/orca-runtime-serve-browser-restore.test.ts": 4469, "src/main/runtime/orca-runtime-session-tabs-path-alias.test.ts": 4827, "src/main/runtime/orca-runtime-skill-recovery.test.ts": 4007, - "src/main/runtime/orca-runtime-startup-draft-launch-parity.test.ts": 4247, "src/main/runtime/orca-runtime-structured-agent-registration-seam.test.ts": 4596, "src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts": 4338, "src/main/runtime/orca-runtime-structured-session-restore.test.ts": 4836, @@ -5242,7 +5187,6 @@ "src/main/runtime/orca-runtime-terminal-create-replay-after-close.test.ts": 4654, "src/main/runtime/orca-runtime-terminal-cwd.test.ts": 4038, "src/main/runtime/orca-runtime-terminal-handle-incarnation.test.ts": 4449, - "src/main/runtime/orca-runtime-terminal-handle-leaf-removal.test.ts": 3545, "src/main/runtime/orca-runtime-terminal-rename-retention.test.ts": 5757, "src/main/runtime/orca-runtime-terminal-retirement.test.ts": 3714, "src/main/runtime/orca-runtime-terminal-saved-name-respawn.test.ts": 4349, @@ -5678,7 +5622,6 @@ "src/main/runtime/rpc/methods/structured-agent-session-legacy-command-wait.test.ts": 2712, "src/main/runtime/rpc/methods/structured-agent-session-options-read.test.ts": 1676, "src/main/runtime/rpc/methods/structured-agent-session-options-workspace-scope.test.ts": 6537, - "src/main/runtime/rpc/methods/structured-agent-session-policy.test.ts": 1011, "src/main/runtime/rpc/methods/structured-agent-session-precommit-refusal.test.ts": 1595, "src/main/runtime/rpc/methods/structured-agent-session-rename.test.ts": 4670, "src/main/runtime/rpc/methods/structured-agent-session-restart-dismiss-fence.test.ts": 2207, @@ -5711,7 +5654,6 @@ "src/main/runtime/rpc/methods/workspace-layout.test.ts": 3391, "src/main/runtime/rpc/methods/worktree-catalog-snapshot-method.test.ts": 1590, "src/main/runtime/rpc/methods/worktree-create-foreign-automation-provenance.test.ts": 1528, - "src/main/runtime/rpc/methods/worktree-create-navigation.test.ts": 1652, "src/main/runtime/rpc/methods/worktree-runtime-routing.test.ts": 1735, "src/main/runtime/rpc/methods/worktree-schemas.test.ts": 260, "src/main/runtime/rpc/methods/worktree.test.ts": 1645, @@ -5864,7 +5806,6 @@ "src/main/runtime/runtime-interrupted-local-worktree-removal.test.ts": 7328, "src/main/runtime/runtime-layout-apply.test.ts": 169, "src/main/runtime/runtime-legacy-worker-missing-workspace-recovery.test.ts": 91, - "src/main/runtime/runtime-legacy-worker-recovery-lifetime.test.ts": 178, "src/main/runtime/runtime-legacy-worker-terminal-recovery-persistence.test.ts": 479, "src/main/runtime/runtime-legacy-worker-terminal-recovery-retry.test.ts": 102, "src/main/runtime/runtime-linear-comment-edit.test.ts": 458, @@ -5878,7 +5819,6 @@ "src/main/runtime/runtime-machine-name.test.ts": 65, "src/main/runtime/runtime-managed-worktree-metadata-sweep.test.ts": 1812, "src/main/runtime/runtime-managed-worktree-metadata-wsl-move.test.ts": 1812, - "src/main/runtime/runtime-managed-worktree-metadata.test.ts": 494, "src/main/runtime/runtime-managed-worktree-queries.test.ts": 1074, "src/main/runtime/runtime-metadata-ownership-watch.test.ts": 100, "src/main/runtime/runtime-metadata.test.ts": 224, @@ -5887,7 +5827,6 @@ "src/main/runtime/runtime-mobile-dictation-controller.test.ts": 243, "src/main/runtime/runtime-mobile-last-assistant-message.test.ts": 253, "src/main/runtime/runtime-mobile-session-incarnation-projection.test.ts": 254, - "src/main/runtime/runtime-mobile-structured-tab-status.test.ts": 805, "src/main/runtime/runtime-native-chat-follow-up-events.test.ts": 4069, "src/main/runtime/runtime-nested-repo-import-host-twin.test.ts": 281, "src/main/runtime/runtime-orchestration-crew-names.test.ts": 256, @@ -5900,7 +5839,6 @@ "src/main/runtime/runtime-project-host-setup-git-remote-link.test.ts": 839, "src/main/runtime/runtime-provisioned-root-adoption.test.ts": 537, "src/main/runtime/runtime-reference-agents.test.ts": 207, - "src/main/runtime/runtime-reference-catalog.test.ts": 291, "src/main/runtime/runtime-reference-find.test.ts": 245, "src/main/runtime/runtime-reference-integration.test.ts": 4136, "src/main/runtime/runtime-remote-fetch-ref-maintenance.test.ts": 419, @@ -5909,7 +5847,6 @@ "src/main/runtime/runtime-repository-hooks-commands.test.ts": 402, "src/main/runtime/runtime-repository-issue-command.test.ts": 557, "src/main/runtime/runtime-repository-registration-controller.test.ts": 742, - "src/main/runtime/runtime-repository-registration-host-dedupe.test.ts": 693, "src/main/runtime/runtime-resolved-worktree-cache.test.ts": 46, "src/main/runtime/runtime-rpc-browser-host-admission.test.ts": 4402, "src/main/runtime/runtime-rpc-client-activity.test.ts": 4799, @@ -5976,7 +5913,6 @@ "src/main/runtime/runtime-worktree-terminal-provisioning.test.ts": 84, "src/main/runtime/runtime-wsl-background-read-gate.test.ts": 1247, "src/main/runtime/saved-structured-agent-session-restoration.test.ts": 40, - "src/main/runtime/selected-review-branch.test.ts": 56, "src/main/runtime/serve-browser-tab-layout.test.ts": 384, "src/main/runtime/serve-browser-tabs-layout-owner.test.ts": 321, "src/main/runtime/session-lifecycle/close/close-by-split-handle-layout-owner.test.ts": 5010, @@ -6121,7 +6057,6 @@ "src/main/runtime/terminal-subscribe-exit-waiter-leak.test.ts": 4118, "src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts": 5399, "src/main/runtime/terminal-surface-close.test.ts": 120, - "src/main/runtime/terminal-tail-buffer.test.ts": 2483, "src/main/runtime/terminal-tail-crlf.test.ts": 4235, "src/main/runtime/terminal-tail-redraw-row.equivalence.test.ts": 1478, "src/main/runtime/terminal-tail-redraw-scaling.test.ts": 352, @@ -6151,12 +6086,10 @@ "src/main/runtime/terminal/launch/window-tab-start-switch-off.test.ts": 4475, "src/main/runtime/terminal/layout/agent-generated-tab-titles.test.ts": 5572, "src/main/runtime/terminal/layout/pane-grid-recording.test.ts": 237, - "src/main/runtime/terminal/lifecycle/bind-first-registration.test.ts": 4390, "src/main/runtime/terminal/lifecycle/main-pane-liveness.test.ts": 4871, "src/main/runtime/terminal/lifecycle/ssh-exit-code-pane-outcome.test.ts": 4334, "src/main/runtime/terminal/query/terminal-identity-read-context.test.ts": 5310, "src/main/runtime/terminal/recovery/replaced-pane-handle-recovery.test.ts": 6080, - "src/main/runtime/terminal/recovery/restored-pane-start.test.ts": 62, "src/main/runtime/terminal/stop/exact-terminal-stop-liveness.test.ts": 4321, "src/main/runtime/terminal/stop/terminal-update-admission.test.ts": 48, "src/main/runtime/terminal/surface/closed-pane-exit-cleanup-main.test.ts": 4281, @@ -6199,7 +6132,6 @@ "src/main/runtime/workspaces/persisted-surfaces/sweep-terminal-placement.test.ts": 12352, "src/main/runtime/workspaces/remote-workspace-terminal-sleep.test.ts": 46, "src/main/runtime/workspaces/workspace-host-notifications.test.ts": 4332, - "src/main/runtime/workspaces/workspace-lifecycle-commands.test.ts": 89, "src/main/runtime/workspaces/workspace-lifecycle-queue.test.ts": 116, "src/main/runtime/workspaces/workspace-query-prototype.test.ts": 4444, "src/main/runtime/workspaces/workspace-sleep-execution.test.ts": 110, @@ -6273,7 +6205,6 @@ "src/main/skills/skill-discovery-target.test.ts": 124, "src/main/skills/skill-discovery-wsl-alias-sharing.test.ts": 165, "src/main/skills/skill-discovery-wsl-bash-filter.test.ts": 2105, - "src/main/skills/skill-discovery-wsl-plugins.test.ts": 99, "src/main/skills/skill-discovery-wsl-script-roundtrip.test.ts": 136, "src/main/skills/skill-discovery-wsl.test.ts": 104, "src/main/skills/skill-install-destinations.test.ts": 58, @@ -6481,7 +6412,6 @@ "src/main/ssh/remote-exec/remote-exec-input.test.ts": 71, "src/main/ssh/remote-exec/remote-frame-protocol.test.ts": 126, "src/main/ssh/remote-exec/remote-host-failure-producers.test.ts": 356, - "src/main/ssh/runtime-ssh-access-verification.test.ts": 222, "src/main/ssh/runtime-ssh-access.test.ts": 739, "src/main/ssh/sftp-namespace-resolution.test.ts": 84, "src/main/ssh/sftp-stream-late-error.test.ts": 209, @@ -6577,7 +6507,6 @@ "src/main/startup/gpu-fallback-marker.test.ts": 61, "src/main/startup/gpu-lifecycle-install-dir-acl-guard.test.ts": 641, "src/main/startup/headless-serve-x11-reexec.test.ts": 76, - "src/main/startup/http1-compatibility-profile-state.test.ts": 81, "src/main/startup/hydrate-shell-path.test.ts": 462, "src/main/startup/hydrate-shell-path.windows.test.ts": 426, "src/main/startup/legacy-worker-renderer-recovery.test.ts": 47, @@ -6607,7 +6536,6 @@ "src/main/startup/serve-desktop-activation.test.ts": 42, "src/main/startup/serve-mode-argv.test.ts": 85, "src/main/startup/serve-native-quit-guard.test.ts": 61, - "src/main/startup/serve-options.test.ts": 72, "src/main/startup/serve-signal-handlers.test.ts": 48, "src/main/startup/serve-user-data-path-guard.test.ts": 198, "src/main/startup/single-instance-lock-exit.electron.test.ts": 551, @@ -6636,7 +6564,6 @@ "src/main/telemetry/burst-cap.test.ts": 177, "src/main/telemetry/chat-ui-launch-tip-cohort.test.ts": 235, "src/main/telemetry/classify-error.test.ts": 43, - "src/main/telemetry/client-agent-dashboard-active.test.ts": 240, "src/main/telemetry/client-agent-dashboard-snapshot.test.ts": 201, "src/main/telemetry/client-deferred-start.test.ts": 8659, "src/main/telemetry/client-lifecycle.test.ts": 206, @@ -6647,7 +6574,6 @@ "src/main/telemetry/posthog-transport-main-http-client.test.ts": 123, "src/main/telemetry/validator-warn-cache.test.ts": 187, "src/main/telemetry/validator.test.ts": 170, - "src/main/telemetry/wsl-move-telemetry.test.ts": 184, "src/main/telemetry/wsl-usage-reporter.test.ts": 407, "src/main/telemetry/wsl-usage-snapshot.test.ts": 208, "src/main/pty/history/terminal-history-async-delete.test.ts": 374, @@ -6864,7 +6790,6 @@ "src/main/wsl/history/wsl-fish-history-cleanup.test.ts": 158, "src/main/wsl/execution/wsl-interop-spawn-directory.test.ts": 52, "src/main/wsl/discovery/wsl-last-seen-running-age.test.ts": 84, - "src/main/wsl/discovery/wsl-lost-list-output.test.ts": 135, "src/main/wsl/execution/wsl-managed-cli-path-restore.test.ts": 68, "src/main/persistence/moving-wsl-projects/wsl-move-incidental-writes.test.ts": 1170, "src/main/persistence/moving-wsl-projects/wsl-move-scan-pruning.test.ts": 1824, @@ -6872,7 +6797,6 @@ "src/main/wsl/discovery/wsl-running-distros.test.ts": 201, "src/main/wsl/filesystem/wsl-running-path-filter.test.ts": 57, "src/main/wsl/filesystem/wsl-shell-cwd-report.test.ts": 111, - "src/main/wsl/filesystem/wsl-unc-delete.test.ts": 102, "src/main/wsl/filesystem/wsl-unc-delete.wsl.test.ts": 93, "src/main/wsl/discovery/distro-catalog.test.ts": 148, "src/main/wsl/filesystem/wsl-automount-root.test.ts": 108, @@ -6883,7 +6807,6 @@ "src/main/wsl/discovery/wsl-distro-holder-script.test.ts": 28870, "src/main/wsl/discovery/wsl-distro-holder.test.ts": 328, "src/main/wsl/discovery/wsl-distro-registry-id.test.ts": 77, - "src/main/wsl/filesystem/wsl-drive-project-new-default.test.ts": 212, "src/main/wsl/filesystem/wsl-drive-project-windows-git.test.ts": 249, "src/main/wsl/filesystem/wsl-drive-unc-conversion-renderer.test.ts": 123, "src/main/wsl/filesystem/wsl-drive-unc-conversion-run.test.ts": 621, @@ -7438,7 +7361,6 @@ "src/renderer/src/components/editor/EditorPanel.diff-rich-owner.test.tsx": 2823, "src/renderer/src/components/editor/EditorPanelHeader.test.tsx": 2230, "src/renderer/src/components/editor/EditorPanelHeaderPath.test.tsx": 2664, - "src/renderer/src/components/editor/EditorPanelShell.header.test.tsx": 301, "src/renderer/src/components/editor/external-changes/ExternalFileChangeBanner.test.tsx": 2142, "src/renderer/src/components/editor/external-changes/ExternalFileChangeCompareDialog.test.tsx": 775, "src/renderer/src/components/editor/ImageViewer.test.tsx": 1872, @@ -7471,7 +7393,6 @@ "src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.lazy-chunk.test.tsx": 367, "src/renderer/src/components/editor/rich-markdown/links/RichMarkdownLinkBubble.render.test.tsx": 439, "src/renderer/src/components/editor/rich-markdown/search/RichMarkdownSearchBar.ime-enter.test.tsx": 358, - "src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.test.tsx": 676, "src/renderer/src/components/editor/rich-markdown/tables/RichMarkdownTableControls.test.tsx": 2354, "src/renderer/src/components/editor/ai-vault-session-log-content.test.tsx": 1899, "src/renderer/src/components/editor/check-details/actions/check-annotation-path.test.ts": 53, @@ -7564,7 +7485,6 @@ "src/renderer/src/components/editor/editor-panel-render-model.test.ts": 2795, "src/renderer/src/components/editor/editor-path-display.test.ts": 45, "src/renderer/src/components/editor/editor-path-move-inflight.test.ts": 50, - "src/renderer/src/components/editor/save/flush/editor-pending-flush.test.ts": 62, "src/renderer/src/components/editor/external-changes/editor-restored-tab-conflict-scan.recheck.test.ts": 1796, "src/renderer/src/components/editor/external-changes/editor-restored-tab-conflict-scan.test.ts": 1809, "src/renderer/src/components/editor/save/queue/editor-save-disk-precondition.races.test.ts": 1290, @@ -7773,7 +7693,6 @@ "src/renderer/src/components/editor/save/commands/useEditorCmdSaveRequest.test.tsx": 732, "src/renderer/src/components/editor/useEditorPanelActiveTabContentLoad.overview.test.tsx": 2064, "src/renderer/src/components/editor/useEditorPanelContentState.test.tsx": 3214, - "src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.cross-workspace.test.tsx": 1095, "src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.test.tsx": 2018, "src/renderer/src/components/editor/useEditorPanelFileLoadRetry.host-connection.test.tsx": 2075, "src/renderer/src/components/editor/useEditorPanelFileLoadRetry.test.tsx": 1953, @@ -7870,7 +7789,6 @@ "src/renderer/src/components/github/use-image-input.test.ts": 2619, "src/renderer/src/components/gitlab-item-dialog/use-gitlab-primary-actions.test.ts": 2141, "src/renderer/src/components/host-picker/bounded-host-list.test.tsx": 667, - "src/renderer/src/components/host-picker/host-list-menus.large-config.test.tsx": 2394, "src/renderer/src/components/host-picker/host-list-surfaces.large-config.test.tsx": 2963, "src/renderer/src/components/jira-attachment-image.test.tsx": 2117, "src/renderer/src/components/jira-connect-scoped-token.test.tsx": 1047, @@ -8076,7 +7994,6 @@ "src/renderer/src/components/native-chat/native-chat-image-references.test.tsx": 2485, "src/renderer/src/components/native-chat/native-chat-incremental-assembler.test.ts": 234, "src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts": 273, - "src/renderer/src/components/native-chat/native-chat-launch-default-adoption.test.ts": 234, "src/renderer/src/components/native-chat/native-chat-leaf-routing.test.ts": 49, "src/renderer/src/components/native-chat/native-chat-live-message-preparation.test.ts": 252, "src/renderer/src/components/native-chat/native-chat-message-grouping.test.ts": 199, @@ -8103,7 +8020,6 @@ "src/renderer/src/components/native-chat/native-chat-reply-reveals.test.tsx": 294, "src/renderer/src/components/native-chat/native-chat-resolved-path-ownership.test.ts": 1749, "src/renderer/src/components/native-chat/native-chat-row-height-estimate.test.ts": 293, - "src/renderer/src/components/native-chat/native-chat-runtime-owner-selector.test.ts": 341, "src/renderer/src/components/native-chat/native-chat-runtime-owner.test.ts": 338, "src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts": 1358, "src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts": 284, @@ -8134,7 +8050,6 @@ "src/renderer/src/components/native-chat/native-chat-visual-markdown-syntax.test.tsx": 2351, "src/renderer/src/components/native-chat/native-chat-visual-read-client.test.ts": 163, "src/renderer/src/components/native-chat/native-chat-web-link-actions.test.ts": 401, - "src/renderer/src/components/native-chat/native-chat-work-run-pairing.test.ts": 368, "src/renderer/src/components/native-chat/native-chat-working-status-shared-clock.test.tsx": 412, "src/renderer/src/components/native-chat/omp-model-switch-capability.test.ts": 211, "src/renderer/src/components/native-chat/serialized-screen-background-rows.test.ts": 131, @@ -8173,7 +8088,6 @@ "src/renderer/src/components/native-chat/use-native-chat-composer-recall.test.tsx": 405, "src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx": 763, "src/renderer/src/components/native-chat/use-native-chat-context-clear-floor.test.tsx": 271, - "src/renderer/src/components/native-chat/use-native-chat-context-menu.find.test.tsx": 2619, "src/renderer/src/components/native-chat/use-native-chat-context-menu.targets.test.tsx": 3071, "src/renderer/src/components/native-chat/use-native-chat-context-menu.test.tsx": 3668, "src/renderer/src/components/native-chat/use-native-chat-draft.test.tsx": 590, @@ -8588,7 +8502,6 @@ "src/renderer/src/components/settings/accounts/AccountsPane.section-lifetime.test.tsx": 2893, "src/renderer/src/components/settings/accounts/AccountsPane.test.tsx": 2701, "src/renderer/src/components/settings/advanced/network/AdvancedNetworkSettingsSection.interaction.test.tsx": 2206, - "src/renderer/src/components/settings/advanced/network/AdvancedNetworkSettingsSection.test.ts": 1969, "src/renderer/src/components/settings/agents/surfaces/AgentDefaultSurfaceSection.test.tsx": 3070, "src/renderer/src/components/agent/launch-settings/AgentLaunchChoiceEditor.test.tsx": 1565, "src/renderer/src/components/agent/launch-settings/AgentLaunchDefaultsEditor.test.tsx": 1320, @@ -8614,13 +8527,11 @@ "src/renderer/src/components/settings/chat/ChatSettingsSection.navigation.test.tsx": 2540, "src/renderer/src/components/settings/chat/ChatSettingsSection.test.tsx": 2718, "src/renderer/src/components/settings/cli/install/CliSection.install-failure.test.tsx": 2250, - "src/renderer/src/components/settings/cli/install/CliSection.test.tsx": 2135, "src/renderer/src/lib/skill-runtime-command.test.ts": 1670, "src/renderer/src/components/settings/source-control/CommitMessageAiPane.test.tsx": 3099, "src/renderer/src/components/settings/virtual-machines/runtimes/EphemeralVmRuntimesSection.test.tsx": 2270, "src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx": 1025, "src/renderer/src/components/settings/general/GeneralPane.section-lifetime.test.tsx": 1546, - "src/renderer/src/components/settings/general/GeneralWorkspaceSettingsSection.test.tsx": 1989, "src/renderer/src/components/settings/accounts/providers/GrokAccountsSection.test.tsx": 623, "src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.test.tsx": 309, "src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.test.tsx": 7498, @@ -8632,7 +8543,6 @@ "src/renderer/src/components/settings/mobile/pairing/MobilePairingSetupSection.test.tsx": 1414, "src/renderer/src/components/settings/mobile/MobilePane.test.tsx": 1581, "src/renderer/src/components/settings/advanced/network/NodeDistMirrorDialog.test.tsx": 2412, - "src/renderer/src/components/settings/notifications/NotificationHostToggles.test.tsx": 646, "src/renderer/src/components/settings/orchestration/OrchestrationPane.test.tsx": 2437, "src/renderer/src/components/settings/orchestration/OrchestrationSkillAgentCoverage.test.tsx": 320, "src/renderer/src/components/plugins/list/PluginsSettingsSection.lifecycle.test.tsx": 5402, @@ -8705,7 +8615,6 @@ "src/renderer/src/components/settings/runtime-environments/pairing/runtime-share-computer-button.test.tsx": 2092, "src/renderer/src/components/settings/form-controls/settings-form-option-filter.test.ts": 57, "src/renderer/src/components/settings/navigation/settings-project-list.test.ts": 99, - "src/renderer/src/components/settings/search/settings-search.test.ts": 53, "src/renderer/src/components/skills/setup/skill-setup-blocked-plan.test.tsx": 28488, "src/renderer/src/components/settings/source-control/source-control-action-card-layout.test.tsx": 4551, "src/renderer/src/components/settings/source-control/source-control-action-permission-draft.test.tsx": 6938, @@ -8849,7 +8758,6 @@ "src/renderer/src/components/sidebar/workspace-creation/folder/folder-workspace-source-selection.test.ts": 230, "src/renderer/src/components/sidebar/workspace-creation/folder/folder-workspace-composer-path-status.test.tsx": 2113, "src/renderer/src/components/sidebar/workspace-creation/folder/folder-workspace-composer-submit-host-platform.test.ts": 2264, - "src/renderer/src/components/sidebar/workspace-creation/folder/folder-workspace-composer-submit-surface.test.ts": 2453, "src/renderer/src/components/sidebar/workspace-creation/folder/folder-workspace-composer-submit.test.ts": 2665, "src/renderer/src/components/sidebar/folder-workspace-delete.test.ts": 788, "src/renderer/src/components/sidebar/workspace-creation/folder/folder-workspace-linked-startup-plan.test.ts": 1652, @@ -9131,7 +9039,6 @@ "src/renderer/src/components/tab-bar/SortableTab.update-depth-probe.test.tsx": 1257, "src/renderer/src/components/tab-bar/SortableTabContextMenu.test.tsx": 977, "src/renderer/src/components/tab-bar/TabBar.context-menu.test.ts": 8259, - "src/renderer/src/components/tab-bar/TabBar.os-file-drop.test.tsx": 1297, "src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts": 3772, "src/renderer/src/components/tab-bar/TabBar.windows-shell-remote-runtime.test.ts": 3732, "src/renderer/src/components/tab-bar/TabBar.windows-shell-ssh-host.test.ts": 2829, @@ -9900,7 +9807,6 @@ "src/renderer/src/components/wsl-move/wsl-move-orchestrator-status.test.tsx": 567, "src/renderer/src/components/wsl-move/wsl-move-scope-rows.test.ts": 156, "src/renderer/src/hooks/agent-hook-completion-background-turn-notifications.test.ts": 2279, - "src/renderer/src/hooks/agent-hook-completion-fresh-working.test.ts": 375, "src/renderer/src/hooks/agent-hook-completion-grok-notifications.test.ts": 679, "src/renderer/src/hooks/agent-hook-completion-notifications.test.ts": 1131, "src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts": 52, @@ -9920,7 +9826,6 @@ "src/renderer/src/hooks/composer-state/composer-target-state.jira-mapping.test.tsx": 2792, "src/renderer/src/hooks/composer-state/imported-project-group-selection.test.tsx": 1929, "src/renderer/src/hooks/composer-state/jira-mapped-owner-flow.integration.test.tsx": 4506, - "src/renderer/src/hooks/composer-state/multiple-create-reset.test.ts": 1959, "src/renderer/src/hooks/composer-state/provider-runtime-sync.test.ts": 1988, "src/renderer/src/hooks/composer-state/quick-creation-execution.test.ts": 2283, "src/renderer/src/hooks/composer-state/server-ssh-repo-connect.test.ts": 873, @@ -9937,9 +9842,7 @@ "src/renderer/src/hooks/ipc-events/agent-status-pane-routing-index.test.ts": 1892, "src/renderer/src/hooks/ipc-events/agent-status-pending-retry-gate.test.ts": 466, "src/renderer/src/hooks/ipc-events/agent-status-program-status-only.test.ts": 398, - "src/renderer/src/hooks/ipc-events/agent-status-reconnect-replay-notification.test.ts": 352, "src/renderer/src/hooks/ipc-events/agent-status-terminal-title-tab-write.test.ts": 2247, - "src/renderer/src/hooks/ipc-events/browser-request-ipc-bridge.fallback.test.ts": 776, "src/renderer/src/hooks/ipc-events/browser-request-ipc-bridge.owner.test.ts": 623, "src/renderer/src/hooks/ipc-events/browser-request-ipc-bridge.profile-switch.test.ts": 570, "src/renderer/src/hooks/ipc-events/browser-request-ipc-bridge.split.test.ts": 699, @@ -9967,7 +9870,6 @@ "src/renderer/src/hooks/ipc-events/worktree-event-lineage-refresh.test.ts": 2309, "src/renderer/src/hooks/ipc-events/worktree-event-runtime-activation.test.ts": 1934, "src/renderer/src/hooks/ipc-events/wsl-host-ipc-bridge-capability.test.ts": 1917, - "src/renderer/src/hooks/ipc-events/wsl-host-ipc-bridge-focus.test.ts": 1690, "src/renderer/src/hooks/ipc-tab-switch-group-order-hydration.test.ts": 797, "src/renderer/src/hooks/ipc-tab-switch-owner-selection.test.ts": 386, "src/renderer/src/hooks/ipc-tab-switch-structured-tab-cycle.test.ts": 760, @@ -10010,7 +9912,6 @@ "src/renderer/src/hooks/useEditorExternalWatch.test.ts": 2099, "src/renderer/src/hooks/useEphemeralVmRecipeOptions.test.tsx": 415, "src/renderer/src/hooks/useGitHubSlugMetadata.assignee-reuse.test.tsx": 233, - "src/renderer/src/hooks/useGitHubSlugMetadata.test.tsx": 223, "src/renderer/src/hooks/useImmediateMutation.test.ts": 618, "src/renderer/src/hooks/useInstalledAgentSkills.react.test.tsx": 2093, "src/renderer/src/hooks/useIpcEvents-agent-status-connection-attribution.test.ts": 5510, @@ -10062,7 +9963,6 @@ "src/renderer/src/i18n/language-catalog-timeout.test.tsx": 2025, "src/renderer/src/i18n/language-detection-reconciliation.test.tsx": 2927, "src/renderer/src/i18n/language-readiness.test.ts": 199, - "src/renderer/src/i18n/localized-catalog.test.ts": 284, "src/renderer/src/i18n/plugin-pack-plurals.test.ts": 191, "src/renderer/src/i18n/relative-time-format.test.ts": 53, "src/renderer/src/i18n/ui-intl-formatters.test.ts": 208, @@ -10119,12 +10019,10 @@ "src/renderer/src/lib/agent-session/structured-agent-session-ordinary-draft.test.tsx": 9907, "src/renderer/src/lib/agent-session/structured-agent-session-provisional-tab.test.ts": 3672, "src/renderer/src/lib/agent-skill-cli-prerequisite.test.ts": 219, - "src/renderer/src/lib/agent-startup-creation-continuation.test.ts": 1727, "src/renderer/src/lib/agent-startup-delayed-delivery-perf.test.ts": 1708, "src/renderer/src/lib/agent-status-evidence-clock.test.ts": 88, "src/renderer/src/lib/agent-status.test.ts": 172, "src/renderer/src/lib/ai-vault-antigravity-reference-host.test.ts": 678, - "src/renderer/src/lib/ai-vault-omp-cold-resume.test.ts": 1909, "src/renderer/src/lib/ai-vault-resume-command.drop-repin.test.ts": 541, "src/renderer/src/lib/ai-vault-resume-command.test.ts": 665, "src/renderer/src/lib/ai-vault-resume-platform.test.ts": 617, @@ -10149,7 +10047,6 @@ "src/renderer/src/lib/browser-link-profile-isolation.test.ts": 2511, "src/renderer/src/lib/browser-page-palette-activation.test.ts": 1983, "src/renderer/src/lib/browser-palette-page-entries.test.ts": 480, - "src/renderer/src/lib/browser-palette-search.test.ts": 467, "src/renderer/src/lib/browser-workspace-tab-activation.test.ts": 1925, "src/renderer/src/lib/bundled-skill-target.test.ts": 54, "src/renderer/src/lib/check-run-details-log-normalization-performance.test.ts": 846, @@ -10213,7 +10110,6 @@ "src/renderer/src/lib/host-mirror-handle-gap-resume.test.ts": 1811, "src/renderer/src/lib/host-mirror-handle-gap-snapshot-lifetime.test.ts": 10232, "src/renderer/src/lib/host-mirror-handle-gap-subscription-lifetime.test.ts": 4060, - "src/renderer/src/lib/host-mirror-handle-gap-teardown.test.ts": 1779, "src/renderer/src/lib/host-mirror-handle-gap-verdict-union.test.ts": 2256, "src/renderer/src/lib/host-mirrored-pane-resume-replay.test.ts": 1814, "src/renderer/src/lib/http-link-completion.test.ts": 1760, @@ -10238,7 +10134,6 @@ "src/renderer/src/lib/launch-agent-session-continuation-toast.test.tsx": 2347, "src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts": 2498, "src/renderer/src/lib/launch-cancel-tombstones-compat/structured-agent-session-launch-cancellation.test.ts": 2266, - "src/renderer/src/lib/launch-structured-agent-session.test.ts": 2628, "src/renderer/src/lib/launch-work-item-direct-accepted.test.tsx": 2754, "src/renderer/src/lib/launch-work-item-direct-defaults.test.ts": 1594, "src/renderer/src/lib/launch-work-item-direct-setup.test.tsx": 3435, @@ -10290,7 +10185,6 @@ "src/renderer/src/lib/pane-manager/pane-divider.test.ts": 63, "src/renderer/src/lib/pane-manager/pane-drag-reorder.test.ts": 123, "src/renderer/src/lib/pane-manager/pane-drag-window-blur.test.ts": 618, - "src/renderer/src/lib/pane-manager/pane-drop-no-op.test.ts": 276, "src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts": 94, "src/renderer/src/lib/pane-manager/pane-fit.test.ts": 166, "src/renderer/src/lib/pane-manager/pane-inline-image-recovery.test.ts": 61, @@ -10389,7 +10283,6 @@ "src/renderer/src/lib/resume-sleeping-agent-session-live-ssh-dedupe.test.ts": 2059, "src/renderer/src/lib/resume-sleeping-agent-session-provider-claim.test.ts": 1789, "src/renderer/src/lib/resume-sleeping-agent-session-replay.test.ts": 1946, - "src/renderer/src/lib/resume-sleeping-agent-session-retired-binding.test.ts": 2642, "src/renderer/src/lib/resume-sleeping-agent-session-slept-pane-recovery.test.ts": 1855, "src/renderer/src/lib/resume-sleeping-agent-session-stranded-sleep.test.ts": 1962, "src/renderer/src/lib/resume-sleeping-agent-session-suppress-navigation.test.ts": 1919, @@ -10448,7 +10341,6 @@ "src/renderer/src/lib/unread-badge-count-selector.test.ts": 1930, "src/renderer/src/lib/use-tab-agent-observed-signal-dispatch.test.tsx": 1971, "src/renderer/src/lib/use-tab-agent-opencode-native-title.test.ts": 2093, - "src/renderer/src/lib/use-tab-agent-process-signals.test.ts": 1930, "src/renderer/src/lib/use-tab-agent.test.ts": 1961, "src/renderer/src/lib/user-opened-local-path.test.ts": 325, "src/renderer/src/lib/wake-sleeping-agents-in-background.test.ts": 511, @@ -10550,7 +10442,6 @@ "src/renderer/src/runtime/runtime-client-events.test.ts": 64, "src/renderer/src/runtime/runtime-directory-page-client.test.ts": 268, "src/renderer/src/runtime/runtime-environment-pairing-refresh.test.ts": 109, - "src/renderer/src/runtime/runtime-environment-ssh-state-read-concurrency.test.ts": 1887, "src/renderer/src/runtime/runtime-environment-ssh-state.test.ts": 1810, "src/renderer/src/runtime/runtime-file-client-mutation-ownership.test.ts": 536, "src/renderer/src/runtime/runtime-file-client-search-listing.test.ts": 577, @@ -10570,12 +10461,10 @@ "src/renderer/src/runtime/runtime-path-existence-queue-pairing.test.ts": 507, "src/renderer/src/runtime/runtime-provider-accounts-client.test.ts": 343, "src/renderer/src/runtime/runtime-quick-open-version-compatibility.test.ts": 451, - "src/renderer/src/runtime/runtime-repo-base-ref-default-wsl-retry.test.ts": 498, "src/renderer/src/runtime/runtime-rpc-client.test.ts": 342, "src/renderer/src/runtime/runtime-terminal-artifact-write.test.ts": 567, "src/renderer/src/runtime/runtime-terminal-inspection.test.ts": 2033, "src/renderer/src/runtime/runtime-terminal-stream.test.ts": 449, - "src/renderer/src/runtime/server-browser-profile-reuse.test.ts": 2024, "src/renderer/src/runtime/server-held-panes.test.tsx": 2254, "src/renderer/src/runtime/server-layout-apply.test.ts": 2222, "src/renderer/src/runtime/server-layout-editor-handover.test.ts": 5075, @@ -10624,7 +10513,6 @@ "src/renderer/src/runtime/web-session-tabs-removed-frame-retires-live-publisher.test.ts": 854, "src/renderer/src/runtime/web-session-tabs-retired-epoch-census-repair.test.tsx": 2349, "src/renderer/src/runtime/web-session-tabs-stream-end-resubscribe.test.tsx": 2282, - "src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts": 1070, "src/renderer/src/runtime/web-session-tabs-sync-agent-status.test.ts": 987, "src/renderer/src/runtime/web-session-tabs-sync-browser-tabs.test.ts": 925, "src/renderer/src/runtime/web-session-tabs-sync-client-owned-page-content.test.ts": 990, @@ -10638,7 +10526,6 @@ "src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts": 2524, "src/renderer/src/runtime/web-session-tabs-sync-mirrored-draft-close.test.ts": 1833, "src/renderer/src/runtime/web-session-tabs-sync-reconnect-resubscribe.test.tsx": 2360, - "src/renderer/src/runtime/web-session-tabs-sync-remote-main-agent-verdict.test.ts": 1771, "src/renderer/src/runtime/web-session-tabs-sync-remote-status-title-flap.test.ts": 1715, "src/renderer/src/runtime/web-session-tabs-sync-sleep-activity.test.ts": 1010, "src/renderer/src/runtime/web-session-tabs-sync-snapshot-batch.test.ts": 1064, diff --git a/config/scripts/dependency-security-patches.test.mjs b/config/scripts/dependency-security-patches.test.mjs index 706902f206c..0d7d8c907c9 100644 --- a/config/scripts/dependency-security-patches.test.mjs +++ b/config/scripts/dependency-security-patches.test.mjs @@ -1,9 +1,5 @@ -import * as fs from 'node:fs' -import { mkdtempSync, rmSync } from 'node:fs' import { createRequire } from 'node:module' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, describe, expect, it } from 'vitest' +import { describe, expect, it } from 'vitest' function dependencyRequire(packages) { let require = createRequire(import.meta.url) @@ -21,21 +17,6 @@ const sprintfRequire = dependencyRequire([ 'global-agent', 'roarr' ]) -const globRequire = dependencyRequire(['i18next-cli']) -const minimatchCopies = [ - ['9.0.9', dependencyRequire(['electron-builder', 'app-builder-lib', '@electron/universal'])], - ['10.2.5', dependencyRequire(['i18next-cli', 'glob'])], - ['10.2.6', dependencyRequire(['electron-builder', 'app-builder-lib'])] -] -const tempDirs = [] - -function makeTree(dir, depth) { - fs.writeFileSync(join(dir, 'f'), '') - for (const child of depth ? ['a', 'b'] : []) { - fs.mkdirSync(join(dir, child)) - makeTree(join(dir, child), depth - 1) - } -} describe('installed dependency security patches', () => { it.each(['src/sprintf.js', 'dist/sprintf.min.js'])('bounds numeric precision in %s', (entry) => { @@ -51,57 +32,4 @@ describe('installed dependency security patches', () => { expect(sprintf('%.0f', 1.7)).toBe('2') expect(sprintf('%.0e', 1.7)).toBe('2e+0') }) - - it.each(minimatchCopies)('bounds **/.. pattern splitting in minimatch %s', (version, require) => { - const { Minimatch } = require('minimatch') - expect(require('minimatch/package.json').version).toBe(version) - const amplified = Array(10).fill('**/../a/b').join('/') - expect(new Minimatch(amplified, { optimizationLevel: 2, braceExpandMax: 64 }).set).toHaveLength( - 64 - ) - const mm = new Minimatch('**/../a/b/**/../c/d', { optimizationLevel: 2 }) - expect(mm.set).toHaveLength(4) - expect(mm.match('x/a/b/y/c/d')).toBe(true) - }) - - afterEach(() => { - for (const dir of tempDirs.splice(0)) { - rmSync(dir, { recursive: true, force: true }) - } - }) - - it('stops glob 13 directory reads after an abort', async () => { - const { glob } = globRequire('glob') - expect(globRequire('glob/package.json').version).toBe('13.0.6') - const cwd = mkdtempSync(join(tmpdir(), 'orca-glob-abort-')) - tempDirs.push(cwd) - makeTree(cwd, 5) - const controller = new AbortController() - let reads = 0 - // Settles after the latest read's callback has run, so follow-up reads are already counted. - let lastRead = Promise.resolve() - const countedFs = { - ...fs, - readdir: (...args) => { - if (++reads === 1) { - controller.abort(new Error('stop')) - } - const callback = args.pop() - let settle = () => {} - lastRead = new Promise((resolve) => { - settle = resolve - }) - fs.readdir(...args, (...result) => { - callback(...result) - settle() - }) - } - } - await expect(glob('**', { cwd, signal: controller.signal, fs: countedFs })).rejects.toThrow( - 'stop' - ) - await lastRead - expect(reads).toBe(1) - expect(await glob('**/f', { cwd })).toHaveLength(63) - }) }) diff --git a/config/scripts/vitest-node-runtime-files.mjs b/config/scripts/vitest-node-runtime-files.mjs index bd54f3a1309..1a234ec9ee1 100644 --- a/config/scripts/vitest-node-runtime-files.mjs +++ b/config/scripts/vitest-node-runtime-files.mjs @@ -44,7 +44,6 @@ const NODE_RUNTIME_INCLUDE = [ 'src/main/ai-vault/transcripts/session-document-consumer-replacement.test.ts', 'src/main/ai-vault-search/session-search-dependent-transcripts.test.ts', 'src/main/ai-vault-search/session-search-list-reparse.test.ts', - 'src/main/ai-vault-search/session-search-transcript-dependencies.test.ts', 'src/main/native-chat/structured-agent-cli-conversation-adoption.test.ts', 'src/main/runtime/orchestration-cli-subprocess.test.ts', 'src/main/runtime/orchestration-mailbox-delivery.test.ts', diff --git a/mobile/src/components/rich-markdown/editor-content.test.ts b/mobile/src/components/rich-markdown/editor-content.test.ts index 815e65f465f..ccd608d4694 100644 --- a/mobile/src/components/rich-markdown/editor-content.test.ts +++ b/mobile/src/components/rich-markdown/editor-content.test.ts @@ -55,58 +55,6 @@ describe('the editor document content', () => { expect(editor.posted).toEqual([{ type: 'change', markdown, generation: 1 }]) }) - it('shows an image an edit brings back without loading it again', async () => { - const loadImageSource = vi.fn(async () => 'data:image/png;base64,AAAA') - const editor = editorDocument(loadImageSource) - editor.handle.setMarkdown('![Diagram](images/diagram.png)', 1) - const pasted = document.createElement('img') - pasted.setAttribute('data-workspace-path', 'images/diagram.png') - editor.editor.append(pasted) - editor.type() - await vi.waitFor(() => expect(pasted.getAttribute('src')).toBe('data:image/png;base64,AAAA')) - expect(loadImageSource).toHaveBeenCalledOnce() - }) - - it('carries the generation it was set under into every change', () => { - const editor = editorDocument() - editor.handle.setMarkdown('first', 4) - editor.type() - editor.handle.setMarkdown('second', 5) - editor.type() - expect(editor.posted).toEqual([ - { type: 'change', markdown: 'first', generation: 4 }, - { type: 'change', markdown: 'second', generation: 5 } - ]) - }) - - it('reads a generation that is not a number as zero', () => { - const editor = editorDocument() - editor.handle.setMarkdown('body', Number.NaN) - editor.type() - expect(editor.posted).toEqual([{ type: 'change', markdown: 'body', generation: 0 }]) - }) - - it('posts the change on the spot, with no timer between the edit and the host', () => { - const editor = editorDocument() - vi.useFakeTimers() - try { - editor.handle.setMarkdown('body', 1) - editor.type() - expect(editor.posted).toHaveLength(1) - expect(vi.getTimerCount()).toBe(0) - } finally { - vi.useRealTimers() - } - }) - - it('says nothing while it is rewriting itself', () => { - // setMarkdown’s DOM rewrite must not echo host’s content as new user change. - const editor = editorDocument() - editor.handle.setMarkdown('# Title', 2) - expect(editor.posted).toEqual([]) - expect(editor.editor.innerHTML).toBe('

Title

') - }) - it('stops reporting, and disables every checkbox, once it is read-only', () => { const editor = editorDocument() editor.handle.setMarkdown('- [ ] Open\n- [x] Done', 1) diff --git a/mobile/src/components/use-new-workspace-runtime-context.test.ts b/mobile/src/components/use-new-workspace-runtime-context.test.ts index 0efb9f373a8..df5b2958bfe 100644 --- a/mobile/src/components/use-new-workspace-runtime-context.test.ts +++ b/mobile/src/components/use-new-workspace-runtime-context.test.ts @@ -83,62 +83,6 @@ describe('useNewWorkspaceRuntimeContext', () => { return { runtimeSettings, trustedOrcaHooks, availableProviders } } - it('publishes other source providers while the optional Jira probe is pending', async () => { - const client = new FakeSession('connected') - let answerJira: (response: RpcResponse) => void = () => {} - const pending = new Promise((resolve) => { - answerJira = resolve - }) - client.sendRequest.mockImplementation((method: string) => { - if (method === 'jira.status') { - return pending - } - if (method === 'settings.get') { - return Promise.resolve(reply({ settings: SETTINGS })) - } - if (method === 'ui.get') { - return Promise.resolve(reply(UI_WITH_TRUST)) - } - if (method === 'preflight.check') { - return Promise.resolve(reply({ glab: { installed: true } })) - } - return Promise.resolve(reply({ connected: true })) - }) - await act(async () => { - renderer = create(createElement(Harness, { client })) - }) - expect(currentContext().availableProviders).toEqual(['github', 'linear']) - await act(async () => answerJira(reply({ connected: true }))) - expect(currentContext().jiraConnection.connected).toBe(true) - }) - - // A null settings result must still reach the provider commit. - it.each([ - ['null', null], - ['absent', undefined], - ['without a settings member', {}] - ])('degrades a %s settings result to absent settings', async (_label, settingsResult) => { - // Jira has no availability gate here, so absent settings still offer it. - expect(await answer(settingsResult, UI_WITH_TRUST)).toEqual({ - runtimeSettings: null, - trustedOrcaHooks: TRUSTED_HOOKS, - availableProviders: ['github', 'jira'] - }) - }) - - // A null UI result must not skip the provider commit either. - it.each([ - ['null', null], - ['absent', undefined], - ['without a ui member', {}] - ])('degrades a %s ui result to untrusted hooks', async (_label, uiResult) => { - expect(await answer({ settings: SETTINGS }, uiResult)).toEqual({ - runtimeSettings: SETTINGS, - trustedOrcaHooks: {}, - availableProviders: ['github'] - }) - }) - // Stale trusted hooks would skip the setup-hook approval prompt; assert they are cleared. it('blanks the trust an earlier host published when the next ui result is null', async () => { expect((await answer({ settings: SETTINGS }, UI_WITH_TRUST)).trustedOrcaHooks).toEqual( @@ -161,14 +105,6 @@ describe('useNewWorkspaceRuntimeContext', () => { expect(currentContext().availableProviders).toEqual([]) }) - // Any server's relay change rebuilds every scoped client, so the client object is not the host. - it('keeps the selected host values when its client is rebuilt', async () => { - const published = await answer({ settings: SETTINGS }, UI_WITH_TRUST, 'runtime:a') - await swapToUnansweredClient('runtime:a') - expect(currentContext().runtimeSettings).toEqual(published.runtimeSettings) - expect(currentContext().trustedOrcaHooks).toEqual(TRUSTED_HOOKS) - }) - // A -> B -> A: the first A's Save callbacks must stay retired when A is picked again. it('ignores publishers from an earlier selection, including of the same host', async () => { let ui: unknown = UI_WITH_TRUST diff --git a/mobile/src/files/mobile-file-media-download.test.ts b/mobile/src/files/mobile-file-media-download.test.ts index 48771c5e843..10afd6b56be 100644 --- a/mobile/src/files/mobile-file-media-download.test.ts +++ b/mobile/src/files/mobile-file-media-download.test.ts @@ -2,11 +2,7 @@ import { Buffer } from 'buffer/index.js' import { Buffer as NodeBuffer } from 'node:buffer' import { describe, expect, it, vi } from 'vitest' import type { RpcResponse } from '../transport/types' -import { - downloadMobileFileMedia, - MOBILE_MEDIA_CHUNK_BYTES, - MOBILE_MEDIA_MAX_BYTES -} from './mobile-file-media-download' +import { downloadMobileFileMedia, MOBILE_MEDIA_CHUNK_BYTES } from './mobile-file-media-download' const media = { worktreeId: 'folder-workspace', @@ -46,22 +42,6 @@ function rig(replies: RpcResponse[]) { } describe('mobile media download', () => { - it.each(['forbidden', 'method_not_found'])( - 'explains when an older host refuses media stat: %s', - async (code) => { - const r = rig([ - { - id: '1', - ok: false, - error: { code, message: 'Unavailable' }, - _meta: { runtimeId: 'host' } - } - ]) - await expect(r.run()).rejects.toThrow('Update Orca on your desktop') - expect(r.client.sendRequest).toHaveBeenCalledTimes(1) - expect(r.sink.dispose).toHaveBeenCalledOnce() - } - ) it('stages bounded chunks byte-for-byte and verifies the file after downloading', async () => { const bytes = new Uint8Array(MOBILE_MEDIA_CHUNK_BYTES + 3).fill(123) const r = rig([ @@ -89,26 +69,6 @@ describe('mobile media download', () => { expect(r.progress).toHaveBeenLastCalledWith(bytes.length, bytes.length) expect(r.sink.dispose).not.toHaveBeenCalled() }) - it('refuses oversized files before requesting any bytes', async () => { - const r = rig([stat(MOBILE_MEDIA_MAX_BYTES + 1)]) - await expect(r.run()).rejects.toThrow('256 MB') - expect(r.client.sendRequest).toHaveBeenCalledTimes(1) - expect(r.sink.append).not.toHaveBeenCalled() - expect(r.sink.dispose).toHaveBeenCalledOnce() - }) - it.each([ - { contentBase64: 'AA==', bytesRead: 3, eof: true }, - { contentBase64: '', bytesRead: 0, eof: false }, - { contentBase64: 'AA==', bytesRead: 1, eof: true }, - { contentBase64: 'AAAAAA==', bytesRead: 4, eof: true }, - { contentBase64: '$invalid', bytesRead: 3, eof: true } - ])('discards a malformed or truncated transfer: %j', async (invalid) => { - const r = rig([stat(3), ok(invalid)]) - await expect(r.run()).rejects.toThrow() - expect(r.sink.append).not.toHaveBeenCalled() - expect(r.sink.finish).not.toHaveBeenCalled() - expect(r.sink.dispose).toHaveBeenCalledOnce() - }) it('refuses a file modified while it was downloaded', async () => { const r = rig([stat(3), chunk(new Uint8Array(3), true), stat(3, 2)]) await expect(r.run()).rejects.toThrow('File changed') diff --git a/mobile/src/hooks/mobile-dictation-desktop-start.test.ts b/mobile/src/hooks/mobile-dictation-desktop-start.test.ts deleted file mode 100644 index 4a6871b758d..00000000000 --- a/mobile/src/hooks/mobile-dictation-desktop-start.test.ts +++ /dev/null @@ -1,195 +0,0 @@ -// The microphone already owns the screen when desktop startup runs; failed current starts must release both, while stale starts must leave their replacement alone. -import { describe, expect, it, vi } from 'vitest' -import { startMobileDictationDesktopSession } from './mobile-dictation-desktop-start' -import type { RpcClient } from '../transport/rpc-client' - -const OK_RESPONSE = { ok: true, result: {} } as const - -type StartHarnessOptions = { - sendRequest?: (method: string) => Promise - commitRecordingStart?: () => boolean -} - -function createStartHarness(options: StartHarnessOptions = {}) { - let generation = 1 - let enabled = true - let activeId: string | null = 'dictation-a' - const setIdle = vi.fn() - const commitRecordingStart = vi.fn(options.commitRecordingStart ?? (() => true)) - const rollbackRecordingStart = vi.fn() - const sendRequest = vi.fn( - options.sendRequest ?? (async () => OK_RESPONSE) - ) as unknown as RpcClient['sendRequest'] - const client = { sendRequest } as RpcClient - - return { - options: { - client, - dictationId: 'dictation-a', - generation: 1, - getCurrentGeneration: () => generation, - getEnabled: () => enabled, - getActiveId: () => activeId, - clearActiveId: (dictationId: string) => { - if (activeId === dictationId) { - activeId = null - } - }, - setIdle, - commitRecordingStart, - rollbackRecordingStart - }, - setNewerStart: () => { - generation = 2 - activeId = 'dictation-b' - }, - setDisabled: () => { - enabled = false - }, - getActiveId: () => activeId, - setIdle, - sendRequest, - commitRecordingStart, - rollbackRecordingStart - } -} - -describe('startMobileDictationDesktopSession', () => { - it('cancels a start a newer one superseded while the desktop session opened', async () => { - let setNewerStart: () => void = () => {} - const harness = createStartHarness({ - sendRequest: async (method) => { - if (method === 'speech.dictation.start') { - setNewerStart() - } - return OK_RESPONSE - } - }) - setNewerStart = harness.setNewerStart - - await expect(startMobileDictationDesktopSession(harness.options)).resolves.toBe(false) - - expect(harness.setIdle).not.toHaveBeenCalled() - expect(harness.getActiveId()).toBe('dictation-b') - expect(harness.commitRecordingStart).not.toHaveBeenCalled() - expect(harness.sendRequest).toHaveBeenCalledWith( - 'speech.dictation.cancel', - { dictationId: 'dictation-a' }, - { failWhenDisconnected: true } - ) - }) - - it('returns to idle when a disable makes the start stale', async () => { - let setDisabled: () => void = () => {} - const harness = createStartHarness({ - sendRequest: async (method) => { - if (method === 'speech.dictation.start') { - setDisabled() - } - return OK_RESPONSE - } - }) - setDisabled = harness.setDisabled - - await expect(startMobileDictationDesktopSession(harness.options)).resolves.toBe(false) - - expect(harness.setIdle).toHaveBeenCalledOnce() - expect(harness.getActiveId()).toBeNull() - expect(harness.commitRecordingStart).not.toHaveBeenCalled() - }) - - it('closes the capture the hook opened when the desktop start fails', async () => { - const harness = createStartHarness({ - sendRequest: async (method) => { - if (method === 'speech.dictation.start') { - throw new Error('Desktop start failed') - } - return OK_RESPONSE - } - }) - - await expect(startMobileDictationDesktopSession(harness.options)).rejects.toThrow( - 'Desktop start failed' - ) - - // The hook opened capture before requesting a session; refusal must release it and the screen because no outer start catch does so. - expect(harness.rollbackRecordingStart).toHaveBeenCalledOnce() - expect(harness.sendRequest).toHaveBeenCalledWith( - 'speech.dictation.cancel', - { dictationId: 'dictation-a' }, - { failWhenDisconnected: true } - ) - }) - - it('leaves the capture alone when the failure is no longer the current start', async () => { - let setNewerStart: () => void = () => {} - const harness = createStartHarness({ - sendRequest: async (method) => { - if (method === 'speech.dictation.start') { - setNewerStart() - throw new Error('Desktop start failed') - } - return OK_RESPONSE - } - }) - setNewerStart = harness.setNewerStart - - await expect(startMobileDictationDesktopSession(harness.options)).resolves.toBe(false) - - // Capture has no start identity; rolling back stale A would stop B’s microphone and release B’s screen. - expect(harness.rollbackRecordingStart).not.toHaveBeenCalled() - // A still owns its desktop session and must cancel that alone. - expect(harness.sendRequest).toHaveBeenCalledWith( - 'speech.dictation.cancel', - { dictationId: 'dictation-a' }, - { failWhenDisconnected: true } - ) - }) - - it('commits recording before returning a current start to the hook', async () => { - const harness = createStartHarness() - - await expect(startMobileDictationDesktopSession(harness.options)).resolves.toBe(true) - - expect(harness.commitRecordingStart).toHaveBeenCalledOnce() - expect(harness.rollbackRecordingStart).not.toHaveBeenCalled() - }) - - it('cleans up the desktop session when native recording throws', async () => { - const harness = createStartHarness({ - commitRecordingStart: () => { - throw new Error('Audio focus request failed') - } - }) - - await expect(startMobileDictationDesktopSession(harness.options)).rejects.toThrow( - 'Audio focus request failed' - ) - - expect(harness.sendRequest).toHaveBeenCalledWith( - 'speech.dictation.cancel', - { dictationId: 'dictation-a' }, - { failWhenDisconnected: true } - ) - expect(harness.getActiveId()).toBeNull() - expect(harness.setIdle).toHaveBeenCalledOnce() - expect(harness.rollbackRecordingStart).toHaveBeenCalledOnce() - }) - - it('rejects and cleans up when the native recorder does not start', async () => { - const harness = createStartHarness({ commitRecordingStart: () => false }) - - await expect(startMobileDictationDesktopSession(harness.options)).rejects.toThrow( - 'Failed to start microphone recording' - ) - - expect(harness.sendRequest).toHaveBeenCalledWith( - 'speech.dictation.cancel', - { dictationId: 'dictation-a' }, - { failWhenDisconnected: true } - ) - expect(harness.getActiveId()).toBeNull() - expect(harness.setIdle).toHaveBeenCalledOnce() - expect(harness.rollbackRecordingStart).toHaveBeenCalledOnce() - }) -}) diff --git a/mobile/src/host-screen/host-workspace-list-action-error.test.tsx b/mobile/src/host-screen/host-workspace-list-action-error.test.tsx deleted file mode 100644 index 8326f8fc9e2..00000000000 --- a/mobile/src/host-screen/host-workspace-list-action-error.test.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { createElement, useState } from 'react' -import { - act, - create as createRenderer, - type ReactTestInstance, - type ReactTestRenderer -} from 'react-test-renderer' - -import { afterEach, describe, expect, it, vi } from 'vitest' - -// Banner and chevron motion run on Reanimated and Animated, which these mocks leave out. -vi.mock('../components/AutoHeight', () => ({ - Presence: ({ children }: { children: unknown }) => children || null, - AutoHeight: ({ children }: { children: unknown }) => children -})) -vi.mock('../components/DisclosureChevron', () => ({ - DisclosureChevron: ({ children }: { children: unknown }) => children -})) -vi.mock('react-native', () => ({ - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - RefreshControl: 'RefreshControl', - Animated: { SectionList: 'SectionList' }, - StyleSheet: { create: (styles: unknown) => styles }, - Text: 'Text', - View: 'View' -})) -vi.mock('lucide-react-native', () => ({ - ChevronDown: 'ChevronDown', - ChevronRight: 'ChevronRight', - Pin: 'Pin', - X: 'X' -})) -vi.mock('../components/AuthFailedBanner', () => ({ AuthFailedBanner: 'AuthFailedBanner' })) -vi.mock('../components/HostDiagnosticsLink', () => ({ HostDiagnosticsLink: 'HostDiagnosticsLink' })) -vi.mock('../components/MobileRepoIcon', () => ({ MobileRepoIcon: 'MobileRepoIcon' })) -vi.mock('../components/WorktreeListRow', () => ({ WorktreeListRow: 'WorktreeListRow' })) -vi.mock('../worktree/host-workspace-list-states', () => ({ - HostWorkspaceListStates: 'HostWorkspaceListStates' -})) - -import { colors } from '../theme/mobile-theme' -import { HostWorkspaceList } from './host-workspace-list' -import { INITIAL_MOBILE_VIEW } from '../worktree/workspace-view-settings' - -const renderers = new Set>() -function create(...args: Parameters): ReturnType { - const renderer = createRenderer(...args) - renderers.add(renderer) - return renderer -} -function unmountRenderers(): void { - act(() => { - for (const renderer of renderers) { - renderer.unmount() - } - }) - renderers.clear() -} - -// Render the real list banner: the screen suite mocks it away and cannot catch missing error copy or dismissal. -function listWith(fields: { actionError: string; setActionError: (value: string) => void }) { - const rows = [{ worktreeId: 'wt-1', repo: 'orca' }] - const sections = [{ key: 'all', title: '', data: rows }] - const controller = { - actions: { openWorktreeSession: () => {}, openNewWorktreeModal: () => {} }, - activeWorktreeScroll: { sectionListRef: { current: null }, onScrollToIndexFailed: () => {} }, - catalog: { refreshing: false, onRefresh: () => {} }, - connState: 'connected', - contentMaxWidth: 600, - displayWorktrees: rows, - // Use the embedded path to avoid unrelated floating-button dependencies. - embedded: true, - forceReconnectHost: () => {}, - hostId: 'host-1', - insets: { bottom: 0 }, - isReadOnly: false, - isWideLayout: false, - noticeParam: undefined, - now: 0, - reconnectAttempts: 0, - relayRecovery: { pairingRejected: false }, - routeNotice: '', - router: { push: () => {} }, - sectionsResult: { rawSections: sections, sections, repoById: new Map() }, - setDismissedNotice: () => {}, - settings: { activeFilterCount: 0, pendingViewChanges: { pending: [], stale: false } }, - state: { - actionError: fields.actionError, - setActionError: fields.setActionError, - catalogError: null, - collapsedGroups: new Set(), - view: { ...INITIAL_MOBILE_VIEW, groupBy: 'none' }, - search: '', - worktreesLoaded: true - } - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every child below the banner is mocked to a host string here, so what the list reads off the controller is exactly the fields named above. - return { controller, bottomBarHeight: 0 } as unknown as Parameters[0] -} - -function render(fields: { - actionError: string - setActionError: (value: string) => void -}): ReactTestRenderer { - function List(): ReturnType { - const [actionError, setActionError] = useState(fields.actionError) - return createElement( - HostWorkspaceList, - listWith({ - actionError, - setActionError: (value) => { - fields.setActionError(value) - setActionError(value) - } - }) - ) - } - const rendered: { tree: ReactTestRenderer | null } = { tree: null } - act(() => { - rendered.tree = create(createElement(List)) - }) - if (rendered.tree === null) { - throw new Error('the list did not render') - } - return rendered.tree -} - -function texts(tree: ReactTestRenderer): unknown[] { - return tree.root - .findAll((node) => String(node.type) === 'Text') - .map((node) => node.props.children) -} - -function dismiss(tree: ReactTestRenderer): ReactTestInstance { - const [only, ...rest] = tree.root.findAll( - (node) => - String(node.type) === 'Pressable' && node.props.accessibilityLabel === 'Dismiss notice' - ) - if (only === undefined || rest.length > 0) { - throw new Error(`expected one dismiss, found ${rest.length + Number(only !== undefined)}`) - } - return only -} - -describe('an action that did not happen', () => { - it('is on screen above the list, in the tone that says it failed', () => { - const tree = render({ actionError: 'Could not delete workspace.', setActionError: () => {} }) - expect(texts(tree)).toEqual(['Could not delete workspace.']) - const banner = tree.root.findAll( - (node) => String(node.type) === 'View' && Array.isArray(node.props.style) - )[0] - expect(banner?.props.style).toContainEqual({ borderBottomColor: colors.statusRed }) - const list = tree.root.find((node) => String(node.type) === 'SectionList') - expect(list.props.sections[0].data).toEqual([{ worktreeId: 'wt-1', repo: 'orca' }]) - expect(tree.root.findAll((node) => node === banner || node === list)).toEqual([banner, list]) - }) - - it('is dismissible, because the list and the confirm it re-opens have to stay reachable', () => { - const cleared: string[] = [] - const tree = render({ - actionError: 'Could not delete workspace.', - setActionError: (value) => cleared.push(value) - }) - act(() => { - dismiss(tree).props.onPress() - }) - expect(cleared).toEqual(['']) - expect(texts(tree)).not.toContain('Could not delete workspace.') - expect( - tree.root.find((node) => String(node.type) === 'SectionList').props.sections[0].data - ).toEqual([{ worktreeId: 'wt-1', repo: 'orca' }]) - }) - - it('paints nothing at all while there is none', () => { - const tree = render({ actionError: '', setActionError: () => {} }) - expect(texts(tree)).toEqual([]) - }) -}) - -afterEach(unmountRenderers) diff --git a/mobile/src/host-screen/host-workspace-list-row-memo.test.tsx b/mobile/src/host-screen/host-workspace-list-row-memo.test.tsx deleted file mode 100644 index 6d2f75cf1d6..00000000000 --- a/mobile/src/host-screen/host-workspace-list-row-memo.test.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { createElement, Fragment, type ReactNode } from 'react' -import { act, create as createRenderer, type ReactTestRenderer } from 'react-test-renderer' - -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { Section, WorkspaceListItem, Worktree } from '../worktree/workspace-list-types' - -const { rowRenders, agentRenders } = vi.hoisted(() => ({ - rowRenders: vi.fn(), - agentRenders: vi.fn() -})) - -// Banner and chevron motion run on Reanimated and Animated, which these mocks leave out. -vi.mock('../components/AutoHeight', () => ({ - Presence: ({ children }: { children: unknown }) => children || null, - AutoHeight: ({ children }: { children: unknown }) => children -})) -vi.mock('../components/DisclosureChevron', () => ({ - DisclosureChevron: ({ children }: { children: unknown }) => children -})) -vi.mock('react-native', () => ({ - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - RefreshControl: 'RefreshControl', - Animated: { - SectionList: ({ - sections, - keyExtractor, - renderItem - }: { - sections: Section[] - keyExtractor: (item: WorkspaceListItem) => string - renderItem: (info: { item: WorkspaceListItem }) => ReactNode - }) => - sections.flatMap((section) => - section.data.map((item) => - createElement(Fragment, { key: keyExtractor(item) }, renderItem({ item })) - ) - ) - }, - StyleSheet: { create: (styles: T) => styles }, - Text: 'Text', - View: 'View' -})) -vi.mock('lucide-react-native', () => ({})) -vi.mock('../platform/haptics', () => ({ triggerMediumImpact: vi.fn() })) -vi.mock('../components/AgentSpinner', () => ({ AgentSpinner: () => null })) -vi.mock('../components/MobileRepoIcon', () => ({ MobileRepoIcon: () => null })) -vi.mock('../components/WorktreeAgentList', () => ({ - WorktreeAgentList: (props: unknown) => { - agentRenders(props) - return null - } -})) -// Each row always renders glyphs, so glyph calls count row renders. -vi.mock('../components/WorktreeMetaGlyphs', () => ({ - prStateColor: () => '#000000', - WorktreeMetaGlyphs: () => { - rowRenders() - return null - } -})) - -import { buildSections } from '../worktree/workspace-list-sections' -import { NO_WORKSPACE_ROW_FILTERS } from '../../../src/shared/workspace-view/workspace-row-filters' -import { HostWorkspaceList } from './host-workspace-list' -import { INITIAL_MOBILE_VIEW } from '../worktree/workspace-view-settings' - -const renderers = new Set>() -function create(...args: Parameters): ReturnType { - const renderer = createRenderer(...args) - renderers.add(renderer) - return renderer -} -function unmountRenderers(): void { - act(() => { - for (const renderer of renderers) { - renderer.unmount() - } - }) - renderers.clear() -} - -function worktree(id: string, overrides: Partial = {}): Worktree { - return { - worktreeId: `repo-1::${id}`, - repoId: 'repo-1', - repo: 'orca', - branch: `feature/${id}`, - displayName: id, - path: id, - liveTerminalCount: 1, - hasAttachedPty: true, - unread: false, - isPinned: false, - linkedPR: null, - status: 'active', - ...overrides - } -} - -const noop = () => {} -const stable = { ref: { current: null }, repoById: new Map() } - -function listProps(worktrees: Worktree[]) { - // Reparse like every poll, producing fresh row objects even for unchanged data. - const polled: Worktree[] = JSON.parse(JSON.stringify(worktrees)) - const sections = buildSections(polled, 'name', NO_WORKSPACE_ROW_FILTERS, '', 'none', new Set()) - const controller = { - actions: { openWorktreeSession: noop, openNewWorktreeModal: noop }, - activeWorktreeScroll: { sectionListRef: stable.ref, onScrollToIndexFailed: noop }, - catalog: { refreshing: false, onRefresh: noop }, - connectionVerdict: { kind: 'normal', label: 'Connected' }, - connState: 'connected', - displayWorktrees: polled, - embedded: true, - insets: { bottom: 0 }, - isReadOnly: false, - now: 1_000, - reconnectAttempts: 0, - relayRecovery: { pairingRejected: false }, - sectionsResult: { rawSections: sections, sections, repoById: stable.repoById }, - settings: { - activeFilterCount: 0, - toggleWorktreeLineage: noop, - pendingViewChanges: { pending: [], stale: false } - }, - state: { - actionError: '', - setActionTarget: noop, - view: { ...INITIAL_MOBILE_VIEW, groupBy: 'none' }, - search: '', - worktreesLoaded: true - } - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the list reads only the controller fields above on this connected, embedded, error-free path. - return { controller, bottomBarHeight: 0 } as unknown as Parameters[0] -} - -describe('host list rows across polls', () => { - it('re-renders only the row whose worktree changed', () => { - const rows = [worktree('a'), worktree('b'), worktree('c')] - let tree: ReactTestRenderer | null = null - act(() => { - tree = create(createElement(HostWorkspaceList, listProps(rows))) - }) - rowRenders.mockClear() - - const changed = [rows[0]!, { ...rows[1]!, branch: 'feature/b-renamed' }, rows[2]!] - act(() => tree!.update(createElement(HostWorkspaceList, listProps(changed)))) - - expect(rowRenders).toHaveBeenCalledTimes(1) - expect( - tree!.root - .findAll((node) => String(node.type) === 'Text') - .flatMap((node) => node.props.children) - ).toContain('feature/b-renamed') - act(() => tree!.unmount()) - }) - it('re-renders only the workspace whose child roster changed', () => { - const row = worktree('a', { - agents: [ - { - paneKey: 'parent', - parentPaneKey: null, - state: 'working', - agentType: 'codex', - prompt: '', - taskTitle: null, - displayName: null, - lastAssistantMessage: null, - toolName: null, - toolInput: null, - interrupted: false, - stateStartedAt: 1, - updatedAt: 1, - subagents: [{ id: 'child', state: 'working', startedAt: 1 }] - } - ] - }) - let tree: ReactTestRenderer | null = null - act(() => { - tree = create(createElement(HostWorkspaceList, listProps([row, worktree('b')]))) - }) - rowRenders.mockClear() - agentRenders.mockClear() - const changed = { - ...row, - agents: [ - { - ...row.agents![0]!, - subagents: [{ id: 'child', state: 'waiting' as const, startedAt: 1 }] - } - ] - } - act(() => tree!.update(createElement(HostWorkspaceList, listProps([changed, worktree('b')])))) - expect(rowRenders).toHaveBeenCalledTimes(1) - expect(agentRenders).toHaveBeenCalledTimes(1) - expect(agentRenders.mock.calls[0]?.[0]).toMatchObject({ - agents: [{ paneKey: 'parent', subagents: [{ id: 'child', state: 'waiting', startedAt: 1 }] }] - }) - act(() => tree!.unmount()) - }) -}) - -afterEach(unmountRenderers) diff --git a/mobile/src/host-screen/host-worktree-child-navigation.test.tsx b/mobile/src/host-screen/host-worktree-child-navigation.test.tsx deleted file mode 100644 index beb3016882a..00000000000 --- a/mobile/src/host-screen/host-worktree-child-navigation.test.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { createElement } from 'react' -import { act, create as createRenderer } from 'react-test-renderer' - -import { afterEach, describe, expect, it, vi } from 'vitest' -import { - structuredAgentSessionPaneKey, - structuredAgentSessionTabId -} from '../../../src/shared/agent-session/view/structured-agent-session-projection' -import { MOBILE_DESKTOP_RELAY_RUNTIME_CAPABILITY } from '../../../src/shared/mobile-desktop-relay-contract' -import type { Worktree } from '../worktree/workspace-list-types' -import { useHostScreenState } from './use-host-screen-state' -import { useHostWorktreeActions } from './use-host-worktree-actions' -import { useSessionLeaveGuard } from '../session/session-leave-guard' -import { stubRpcClient } from '../transport/rpc-client-test-stub' -import type { RpcResponse } from '../transport/types' - -const renderers = new Set>() -function create(...args: Parameters): ReturnType { - const renderer = createRenderer(...args) - renderers.add(renderer) - return renderer -} -function unmountRenderers(): void { - act(() => { - for (const renderer of renderers) { - renderer.unmount() - } - }) - renderers.clear() -} - -vi.mock('expo-router', () => ({})) -vi.mock('react-native', () => ({ Platform: { OS: 'ios' } })) -vi.mock('../storage/preferences', () => ({ savePinnedIds: async () => {} })) -vi.mock('../transport/host-removal-lifecycle', () => ({ removeHostAndCloseClient: async () => {} })) -const leaveHostRoute = vi.hoisted(() => vi.fn()) -vi.mock('../host-route-exit', () => ({ leaveHostRoute })) - -const item: Worktree = { - worktreeId: 'folder-one', - workspaceKind: 'folder-workspace', - repoId: 'repo', - repo: 'app', - branch: '', - displayName: 'Folder', - path: '/workspace', - liveTerminalCount: 1, - hasAttachedPty: true, - unread: false, - isPinned: false, - linkedPR: null -} -const sessionId = '10000000-0000-4000-8000-000000000000' -const nativeTabId = structuredAgentSessionTabId(sessionId) -const terminalKey = `terminal:${sessionId}` -const nativeKey = structuredAgentSessionPaneKey(nativeTabId, sessionId) -describe('a workspace child opens its parent pane', () => { - it.each([ - [terminalKey, undefined], - [nativeKey, undefined], - [terminalKey, 'runtime:server'], - [nativeKey, 'runtime:server'] - ] as const)( - 'selects parent %s on %s through the workspace action', - async (paneKey, executionHost) => { - const push = vi.fn() - const replace = vi.fn() - const sendRequest = vi.fn( - async (_method: string, _params: unknown, _options?: unknown): Promise => ({ - id: 'activate', - ok: true, - result: {} - }) - ) - const client = stubRpcClient({ sendRequest }) - const row = { ...item, ...(executionHost ? { hostId: executionHost } : {}) } - const held: { open?: (item: Worktree, paneKey?: string) => void } = {} - function Probe() { - const state = useHostScreenState('host') - held.open = useHostWorktreeActions({ - client, - hostCapabilities: [MOBILE_DESKTOP_RELAY_RUNTIME_CAPABILITY], - connState: 'connected', - embedded: true, - fetchWorktrees: async () => {}, - forgetHostClient: () => {}, - hostId: 'host', - pathname: '/h/host/session/folder-one', - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: navigation reaches only push/replace; this double records both router methods. - router: { push, replace } as unknown as Parameters< - typeof useHostWorktreeActions - >[0]['router'], - state - }).openWorktreeSession - return null - } - let tree: ReturnType | undefined - await act(async () => { - tree = create(createElement(Probe)) - }) - await act(async () => { - held.open?.(row, paneKey) - }) - const target = replace.mock.calls[0]?.[0] - expect(target).toBe( - `/h/host/session/folder-one?name=Folder${executionHost ? `&executionHost=${encodeURIComponent(executionHost)}` : ''}&paneKey=${encodeURIComponent(paneKey)}` - ) - expect(sendRequest.mock.calls[0]?.slice(0, 2)).toEqual([ - 'worktree.activate', - { - worktree: 'id:folder-one', - notifyClients: false, - navigation: 'caller' - } - ]) - expect(sendRequest.mock.calls[0]?.[2]).toEqual( - executionHost ? { envelope: { executionHost } } : undefined - ) - expect(push).not.toHaveBeenCalled() - await act(async () => { - tree!.unmount() - }) - } - ) -}) - -async function tapWhileRecording( - pathname: string, - tap: (actions: ReturnType) => void -) { - const replace = vi.fn() - const sendRequest = vi.fn(async (): Promise => ({ - id: 'activate', - ok: true, - result: {} - })) - // The recording session publishes a guard that holds the leave. - const guard = vi.fn(() => true) - const held: { actions?: ReturnType } = {} - function Probe() { - useSessionLeaveGuard(guard) - held.actions = useHostWorktreeActions({ - client: stubRpcClient({ sendRequest }), - hostCapabilities: [MOBILE_DESKTOP_RELAY_RUNTIME_CAPABILITY], - connState: 'connected', - embedded: true, - fetchWorktrees: async () => {}, - forgetHostClient: () => {}, - hostId: 'host', - pathname, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: navigation reaches only push/replace; this double records both router methods. - router: { push: vi.fn(), replace } as unknown as Parameters< - typeof useHostWorktreeActions - >[0]['router'], - state: useHostScreenState('host') - }) - return null - } - await act(async () => { - create(createElement(Probe)) - }) - await act(async () => { - if (held.actions) { - tap(held.actions) - } - }) - return { replace, sendRequest, guard } -} - -describe('a sidebar tap while the open session is recording', () => { - it('stays without touching the other workspace', async () => { - const tap = await tapWhileRecording('/h/host/session/other', (actions) => - actions.openWorktreeSession(item) - ) - expect(tap.guard).toHaveBeenCalledTimes(1) - expect(tap.replace).not.toHaveBeenCalled() - expect(tap.sendRequest).not.toHaveBeenCalled() - }) - - it('keeps recording when the tap is on the open session itself', async () => { - const tap = await tapWhileRecording('/h/host/session/folder-one', (actions) => - actions.openWorktreeSession(item) - ) - expect(tap.guard).not.toHaveBeenCalled() - expect(tap.replace).not.toHaveBeenCalled() - }) - - it('stays on Back to hosts too', async () => { - const tap = await tapWhileRecording('/h/host/session/other', (actions) => actions.leaveHost()) - expect(tap.guard).toHaveBeenCalledTimes(1) - expect(leaveHostRoute).not.toHaveBeenCalled() - }) -}) - -afterEach(unmountRenderers) diff --git a/mobile/src/host-screen/workspace-options-sheet.test.tsx b/mobile/src/host-screen/workspace-options-sheet.test.tsx deleted file mode 100644 index 0ed57aeb9de..00000000000 --- a/mobile/src/host-screen/workspace-options-sheet.test.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' - -vi.mock('react-native', () => ({ - ActivityIndicator: 'ActivityIndicator', - Platform: { OS: 'android' }, - Pressable: 'Pressable', - StyleSheet: { create: (styles: T) => styles, hairlineWidth: 1 }, - Switch: 'Switch', - Text: 'Text', - View: 'View' -})) -vi.mock('lucide-react-native', () => ({ - Check: 'Check', - ChevronDown: 'ChevronDown', - ChevronLeft: 'ChevronLeft', - ChevronRight: 'ChevronRight', - X: 'X' -})) -vi.mock('../navigation/use-back-claim', () => ({ useBackClaim: () => {} })) -vi.mock('../platform/use-reduced-motion', () => ({ useReducedMotionEnabled: () => false })) -// The slide settles at once, so a back press unmounts the page in the same act. -vi.mock('react-native-reanimated', async () => - (await import('../test-support/reanimated-pager-stub')).reanimatedPagerStub() -) -// Stub the drawer's sheet machinery, not BottomDrawer: the option pager under test lives in that module. -vi.mock('../components/bottom-drawer/keyed-bottom-drawer', () => ({ - KeyedBottomDrawer: ({ children }: { children: () => unknown }) => children() -})) -vi.mock('../components/MobileSearchField', () => ({ MobileSearchField: 'MobileSearchField' })) -vi.mock('../components/MobileSwitch', () => ({ MobileSwitch: 'Switch' })) - -import { NO_WORKSPACE_ROW_FILTERS } from '../../../src/shared/workspace-view/workspace-row-filters' -import { INITIAL_MOBILE_VIEW } from '../worktree/workspace-view-settings' -import { WorkspaceOptionsSheet } from './workspace-options-sheet' - -let renderer: ReactTestRenderer | null = null - -afterEach(() => { - act(() => renderer?.unmount()) - renderer = null -}) - -type SheetOptions = { - newCardStyle: boolean - hideSleeping: boolean - hasFolderRow?: boolean - activeFilterCount?: number -} - -function buildController(options: SheetOptions) { - const settings = { - clearFilters: vi.fn(), - handleGroupChange: vi.fn(), - handleSortChange: vi.fn(), - setAgentActivityDisplayMode: vi.fn(), - setCardLayout: vi.fn(), - setFilter: vi.fn(), - setProjectOrderBy: vi.fn(), - toggleCardProperty: vi.fn(), - toggleRepoFilter: vi.fn(), - pendingViewChanges: { pending: [], stale: false } - } - const controller = { - activeFilterCount: options.activeFilterCount ?? 0, - projectOrderBy: 'manual', - displayWorktrees: [], - sectionsResult: { - uniqueRepos: [ - { key: 'r1', id: 'r1', name: 'orca', color: '#fff' }, - { key: 'r2', id: 'r2', name: 'docs', color: '#000' } - ] - }, - settings, - state: { - view: { - ...INITIAL_MOBILE_VIEW, - filters: { ...NO_WORKSPACE_ROW_FILTERS, hideSleepingWorkspaces: options.hideSleeping }, - groupBy: 'repo', - sortBy: 'smart', - sharesOtherClientFilter: true, - newCardStyle: options.newCardStyle - }, - hostLabelById: new Map(), - hostPlatform: null, - repoHostIdByRepoId: new Map([['r2', 'ssh:box']]), - serverWorkspaces: { hosts: [{ hostId: 'runtime:vm', label: 'VM' }], worktrees: [] }, - setShowWorkspaceOptions: vi.fn(), - showWorkspaceOptions: true, - worktrees: options.hasFolderRow ? [{ workspaceKind: 'folder-workspace' }] : [] - } - } - return { controller, settings } -} - -function render(options: SheetOptions) { - const { controller, settings } = buildController(options) - act(() => { - renderer = create( - createElement(WorkspaceOptionsSheet, { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the sheet reads only these members. - controller: controller as unknown as Parameters< - typeof WorkspaceOptionsSheet - >[0]['controller'] - }) - ) - }) - return { settings, state: controller.state } -} - -const texts = () => - renderer!.root - .findAll((node) => String(node.type) === 'Text') - .map((node) => node.props.children) - .filter((child): child is string => typeof child === 'string') - -const pressable = (label: string) => - renderer!.root.find( - (node) => String(node.type) === 'Pressable' && node.props.accessibilityLabel === label - ) - -const press = (label: string) => { - act(() => { - pressable(label).props.onPress() - }) -} - -const back = () => press('Back to Workspace options') - -const flip = (label: string, value: boolean) => { - act(() => { - renderer!.root - .find((node) => String(node.type) === 'Switch' && node.props.accessibilityLabel === label) - .props.onValueChange(value) - }) -} - -describe('the phone Workspace options sheet', () => { - it('swaps in Card display for the new card style and drops the exemption when nothing sleeps', () => { - render({ newCardStyle: true, hideSleeping: false }) - const shown = texts() - expect(shown).toContain('Card display') - expect(shown).not.toContain('Card layout') - expect(shown).not.toContain('Show properties') - expect(shown).not.toContain('Except default branch') - press('Card display') - expect(texts()).toContain('Orca CLI') - expect(texts()).toContain('Branch name') - expect(texts()).not.toContain('Ports') - }) - - it('names the branch choice for folder paths too once the desktop has folder workspaces', () => { - render({ newCardStyle: true, hideSleeping: false, hasFolderRow: true }) - press('Card display') - expect(texts()).toContain('Branch / folder path') - }) - - it('opens an option page from its row, stays there after a pick, and goes back', () => { - const { settings } = render({ newCardStyle: false, hideSleeping: false }) - expect(texts()).toContain('Card layout') - expect(texts()).toContain('Show properties') - expect(texts()).not.toContain('Card display') - press('Sort by, Agent Activity') - expect(texts()).toContain('Agents that need attention, then most recent activity.') - press('Recent') - expect(settings.handleSortChange).toHaveBeenCalledWith('recent') - expect(texts()).toContain('Name') - back() - expect(texts()).toContain('Filters') - }) - - it('writes each control to the shared value it names', () => { - const { settings } = render({ newCardStyle: false, hideSleeping: true }) - expect(texts()).toContain('Except default branch') - flip('Hide CLI-created', true) - expect(settings.setFilter).toHaveBeenCalledWith('hideCliCreatedWorkspaces', true) - press('PR') - expect(settings.handleGroupChange).toHaveBeenCalledWith('pr-status') - press('Card layout, Detailed') - press('Compact') - expect(settings.setCardLayout).toHaveBeenCalledWith('compact') - back() - press('Project order, Manual') - press('Recent') - expect(settings.setProjectOrderBy).toHaveBeenCalledWith('recent') - back() - press('Hosts, All hosts') - press('VM') - expect(settings.setFilter).toHaveBeenCalledWith('visibleHostIds', ['runtime:vm']) - back() - press('Projects, All projects') - press('docs') - expect(settings.toggleRepoFilter).toHaveBeenCalledWith('r2') - back() - press('Show properties, 8') - expect(texts()).not.toContain('Ports') - press('Automation') - expect(settings.toggleCardProperty).toHaveBeenCalledWith('automation') - expect(settings.toggleCardProperty).not.toHaveBeenCalledWith('ports') - press('Full list') - expect(settings.setAgentActivityDisplayMode).toHaveBeenCalledWith('full') - }) - it('resets filters only when some are active, and Done closes from the main sheet or a page', () => { - const { settings, state } = render({ - newCardStyle: false, - hideSleeping: false, - activeFilterCount: 2 - }) - expect(texts()).toContain('Synced across your devices') - press('Reset') - expect(settings.clearFilters).toHaveBeenCalledTimes(1) - press('Sort by, Agent Activity') - const pageDone = renderer!.root.findAll( - (node) => String(node.type) === 'Pressable' && node.props.accessibilityLabel === 'Done' - ) - act(() => pageDone.at(-1)?.props.onPress()) - expect(state.setShowWorkspaceOptions).toHaveBeenCalledWith(false) - act(() => renderer?.unmount()) - render({ newCardStyle: false, hideSleeping: false }) - expect(pressable('Reset').props.disabled).toBe(true) - }) -}) diff --git a/mobile/src/layout/side-safe-area.test.tsx b/mobile/src/layout/side-safe-area.test.tsx deleted file mode 100644 index 0069b3f6738..00000000000 --- a/mobile/src/layout/side-safe-area.test.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { act, create as createRenderer, type ReactTestRenderer } from 'react-test-renderer' - -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { ReactNode } from 'react' -import { getFoldSplit, type FoldSplit } from './fold-division' -import type { ReservedRegions } from '../../modules/orca-reserved-regions/src' -import type { EdgeInsets, Rect } from 'react-native-safe-area-context' - -const renderers = new Set>() -function create(...args: Parameters): ReturnType { - const renderer = createRenderer(...args) - renderers.add(renderer) - return renderer -} -function unmountRenderers(): void { - act(() => { - for (const renderer of renderers) { - renderer.unmount() - } - }) - renderers.clear() -} - -const geometry = vi.hoisted((): { regions: ReservedRegions; platform: { OS: string } } => ({ - regions: { divisions: [], occlusions: [] }, - platform: { OS: 'ios' } -})) -vi.mock('./reserved-regions', () => ({ useReservedRegions: () => geometry.regions })) -vi.mock('react-native', () => ({ - Platform: geometry.platform, - View: 'View', - StyleSheet: { create: (styles: T) => styles } -})) -vi.mock('react-native-safe-area-context', async () => { - const { createContext, useContext } = await import('react') - const SafeAreaInsetsContext = createContext(null) - const SafeAreaFrameContext = createContext(null) - return { - SafeAreaInsetsContext, - SafeAreaFrameContext, - SafeAreaProvider: ({ children }: { children: ReactNode }) => children, - useSafeAreaInsets: () => useContext(SafeAreaInsetsContext), - useSafeAreaFrame: () => useContext(SafeAreaFrameContext) - } -}) - -const { SafeAreaFrameContext, SafeAreaInsetsContext, useSafeAreaFrame, useSafeAreaInsets } = - await import('react-native-safe-area-context') -const { SideSafeArea, useWindowSafeAreaInsets } = await import('./side-safe-area') - -// Unequal sides, so a swapped edge shows. -const WINDOW = { top: 0, right: 48, bottom: 21, left: 59 } -const FRAME = { x: 0, y: 0, width: 852, height: 393 } - -function renderInLandscape(window = WINDOW, frame = FRAME, nested = false) { - const read: { screen?: EdgeInsets; frame?: Rect; modal?: EdgeInsets; fold?: FoldSplit | null } = - {} - function Probe() { - read.screen = useSafeAreaInsets() - read.frame = useSafeAreaFrame() - read.fold = getFoldSplit(geometry.regions.divisions, read.frame) - read.modal = useWindowSafeAreaInsets() - return null - } - let tree: ReactTestRenderer | undefined - act(() => { - tree = create( - - - - {nested ? ( - - - - ) : ( - - )} - - - - ) - }) - return { tree: tree!, read } -} - -describe('the app laid out clear of the side insets', () => { - it('pads the sides once, and screens below see the padded area', () => { - const { tree, read } = renderInLandscape() - const area = tree.root.find((node) => String(node.type) === 'View') - expect(Object.assign({}, ...[area.props.style].flat())).toMatchObject({ - paddingLeft: 59, - paddingRight: 48 - }) - expect(read.screen).toEqual({ top: 0, right: 0, bottom: 21, left: 0 }) - expect(read.frame).toEqual({ x: 59, y: 0, width: 745, height: 393 }) - }) - - it('gives a Modal, which opens a window of its own, the window insets', () => { - expect(renderInLandscape().read.modal).toEqual(WINDOW) - }) -}) - -afterEach(() => { - unmountRenderers() - geometry.regions = { divisions: [], occlusions: [] } - geometry.platform.OS = 'ios' -}) - -const DUO_FRAME = { x: 0, y: 0, width: 951, height: 669 } -const DUO_INSETS = { top: 0, right: 84, bottom: 34, left: 0 } -function duoGeometry() { - geometry.regions = { - divisions: [], - occlusions: [{ x: 867, y: 0, width: 84, height: 120 }], - windowSafeArea: { bounds: DUO_FRAME, insets: DUO_INSETS } - } -} -function topPadding(tree: ReactTestRenderer) { - return tree.root - .findAll((node) => String(node.type) === 'View') - .map((node) => Object.assign({}, ...[node.props.style].flat()).paddingTop ?? 0) -} - -it('keeps the full Duo height without adding a top band across nested areas', () => { - duoGeometry() - const { tree, read } = renderInLandscape(DUO_INSETS, DUO_FRAME, true) - expect(topPadding(tree)).toEqual([0, 0]) - expect(read.frame).toEqual({ x: 0, y: 0, width: 867, height: 669 }) - expect(read.screen).toEqual({ top: 0, right: 0, bottom: 34, left: 0 }) -}) - -it('adds no Duo edge spacing when the keyboard changes or a banner offsets the frame', () => { - duoGeometry() - geometry.regions.keyboardInset = 600 - expect(topPadding(renderInLandscape(DUO_INSETS, DUO_FRAME).tree)).toEqual([0]) - expect(topPadding(renderInLandscape(DUO_INSETS, { ...DUO_FRAME, y: 12 }).tree)).toEqual([0]) - expect(topPadding(renderInLandscape(DUO_INSETS, { ...DUO_FRAME, y: 30 }).tree)).toEqual([0]) -}) - -it.each(['web', 'android'])('does not apply the native Duo workaround on %s', (platform) => { - duoGeometry() - geometry.platform.OS = platform - expect(topPadding(renderInLandscape(DUO_INSETS, DUO_FRAME).tree)).toEqual([0]) -}) - -it('leaves portrait top-safe-area donation and ordinary iPad/phone geometry unchanged', () => { - geometry.regions = { - divisions: [], - occlusions: [{ x: 470, y: 0, width: 199, height: 84 }], - windowSafeArea: { - bounds: { x: 0, y: 0, width: 669, height: 951 }, - insets: { top: 84, right: 0, bottom: 34, left: 0 } - } - } - expect( - topPadding( - renderInLandscape( - { top: 84, right: 0, bottom: 34, left: 0 }, - { x: 0, y: 0, width: 669, height: 951 } - ).tree - ) - ).toEqual([0]) - geometry.regions = { divisions: [], occlusions: [] } - expect(topPadding(renderInLandscape().tree)).toEqual([0]) -}) - -it.each([ - { - division: { x: 0, y: 320, width: 951, height: 20 }, - expected: { axis: 'horizontal', start: 320, end: 340, size: 669 } - }, - { - division: { x: 430, y: 0, width: 10, height: 669 }, - expected: { axis: 'vertical', start: 430, end: 440, size: 867 } - } -])( - 'keeps the window hinge aligned without a synthetic top offset: $expected.axis', - ({ division, expected }) => { - duoGeometry() - geometry.regions.divisions = [division] - const { read, tree } = renderInLandscape(DUO_INSETS, DUO_FRAME, true) - expect(topPadding(tree)).toEqual([0, 0]) - expect(read.frame).toEqual({ x: 0, y: 0, width: 867, height: 669 }) - expect(read.fold).toEqual(expected) - } -) - -it('preserves genuine top insets for the screen and modal without a synthetic guard', () => { - duoGeometry() - const { tree, read } = renderInLandscape({ ...DUO_INSETS, top: 18 }, DUO_FRAME) - expect(topPadding(tree)).toEqual([0]) - expect(read.screen?.top).toBe(18) - expect(read.modal?.top).toBe(18) -}) diff --git a/mobile/src/mobile-web-shell/bridge-terminal-output-merge.test.ts b/mobile/src/mobile-web-shell/bridge-terminal-output-merge.test.ts index f0893f10795..f5ad99f1989 100644 --- a/mobile/src/mobile-web-shell/bridge-terminal-output-merge.test.ts +++ b/mobile/src/mobile-web-shell/bridge-terminal-output-merge.test.ts @@ -1,10 +1,7 @@ import { Buffer } from 'node:buffer' -import { describe, expect, it, vi } from 'vitest' -import * as byteCounter from '../../../src/shared/terminal/stream/terminal-stream-json-byte-length' -import { - BridgeTerminalOutputBacklog, - terminalStreamMaxPayloadBytes -} from './bridge-terminal-output-backlog' +import { describe, expect, it } from 'vitest' + +import { BridgeTerminalOutputBacklog } from './bridge-terminal-output-backlog' type Output = { type: 'data'; streamId: number; chunk: string } type Metadata = { type: 'resized'; streamId: number; cols: number; rows: number } @@ -81,24 +78,6 @@ function serializedFrames( } describe('terminal backlog merge framing', () => { - it.each([0, 1, 123_456, 1e21])( - 'matches the serializer for stable finite stream id %s', - (streamId) => { - const payloads = [ - output('first', streamId), - output('second', streamId), - output('third', streamId) - ] - for (const cap of [0, wireBytes(payloads[0]), 54, 80, 120]) { - for (const windowEmpty of [false, true]) { - expect(drain(payloads, cap, windowEmpty)).toEqual( - serializedFrames(payloads, cap, windowEmpty) - ) - } - } - } - ) - it('merges split surrogate pairs across empty chunks at the exact byte cap', () => { const payloads = [output('prefix\ud83d'), output(''), output(''), output('\ude00suffix')] const combined = output('prefix😀suffix') @@ -106,114 +85,4 @@ describe('terminal backlog merge framing', () => { expect(drain(payloads, cap, true)).toEqual([combined]) expect(drain(payloads, cap - 1, true)).toEqual(serializedFrames(payloads, cap - 1, true)) }) - - it('preserves escaped controls, quotes, backslashes, Unicode and lone surrogates', () => { - const payloads = [ - output('\u001b[31m\b\t\n\f\r\u0000'), - output('"\\café漢字😀'), - output('\ud800'), - output(''), - output('x\udc00'), - output('\ud800\ud800'), - output('\udc00\udc00') - ] - for (let cap = 0; cap < 180; cap++) { - expect(drain(payloads, cap, true)).toEqual(serializedFrames(payloads, cap, true)) - } - }) - - it('keeps metadata barriers and oversized-head behavior in either window state', () => { - const payloads: Payload[] = [ - output('before'), - { type: 'resized', streamId: 1, cols: 80, rows: 24 }, - output('x'.repeat(1000)), - output(''), - output('after') - ] - for (const windowEmpty of [false, true]) { - expect(drain(payloads, 100, windowEmpty)).toEqual( - serializedFrames(payloads, 100, windowEmpty) - ) - } - }) - - it('matches serialized frame boundaries on fragmented mixed output', () => { - const cells = ['plain', '\u001b[31m', '\n', '"\\', '漢字', '\ud83d', '', '\ude00', '\ud800'] - const payloads: Payload[] = [] - for (let index = 0; index < 300; index++) { - payloads.push(output(cells[index % cells.length], 123_456)) - if (index % 37 === 0) { - payloads.push({ type: 'resized', streamId: 123_456, cols: 80, rows: 24 }) - } - } - for (const cap of [38, 50, 80, 640, 4096]) { - for (const windowEmpty of [false, true]) { - expect(drain(payloads, cap, windowEmpty)).toEqual( - serializedFrames(payloads, cap, windowEmpty) - ) - } - } - }) - - it('counts fragmented output once instead of rescanning growing temporary strings', () => { - const chunks = Array.from({ length: 256 }, (_, index) => String(index).padEnd(1024, 'x')) - const measured = vi.spyOn(byteCounter, 'terminalStreamJsonByteLength') - try { - const frames = drain( - chunks.map((chunk) => output(chunk)), - terminalStreamMaxPayloadBytes('id'), - true - ) - expect(frames).toEqual([output(chunks.join(''))]) - const measuredUnits = measured.mock.calls.reduce((sum, [chunk]) => sum + chunk.length, 0) - expect(measuredUnits).toBeLessThanOrEqual(chunks.join('').length * 2) - } finally { - measured.mockRestore() - } - }) - - it('preserves output when partial drains compact the queue before more input arrives', () => { - const payloads: Payload[] = Array.from({ length: 10_000 }, (_, index) => - index % 37 === 0 - ? { type: 'resized', streamId: 1, cols: index + 1, rows: 24 } - : output(`${index}\u001b[0m😀\n`) - ) - const backlog = createBacklog() - const frames: unknown[] = [] - try { - for (const payload of payloads.slice(0, 5_000)) { - expect(backlog.hold(payload)).toBe(true) - } - while (backlog.pendingBytes > 50_000) { - frames.push(backlog.next(640, true)) - } - const remainingBytes = backlog.pendingBytes - const added = payloads.slice(5_000) - for (const payload of added) { - expect(backlog.hold(payload)).toBe(true) - } - expect(backlog.pendingBytes).toBe( - remainingBytes + added.reduce((total, payload) => total + wireBytes(payload), 0) - ) - while (backlog.held) { - frames.push(backlog.next(640, true)) - } - expect(frames).toEqual(serializedFrames(payloads, 640, true)) - expect(backlog.pendingBytes).toBe(0) - expect(backlog.next(640, true)).toBeNull() - - backlog.hold(output('after drain')) - expect(backlog.next(640, true)).toEqual(output('after drain')) - backlog.hold(output('a')) - backlog.hold({ type: 'resized', streamId: 1, cols: 2, rows: 24 }) - backlog.hold(output('b')) - expect(backlog.next(640, true)).toEqual(output('a')) - backlog.dispose() - backlog.hold(output('after reset')) - expect(backlog.held).toBe(true) - expect(backlog.next(640, true)).toEqual(output('after reset')) - } finally { - backlog.dispose() - } - }) }) diff --git a/mobile/src/mobile-web-shell/bridge/bridge-terminal-key.test.ts b/mobile/src/mobile-web-shell/bridge/bridge-terminal-key.test.ts index ba77ca16619..323850a3642 100644 --- a/mobile/src/mobile-web-shell/bridge/bridge-terminal-key.test.ts +++ b/mobile/src/mobile-web-shell/bridge/bridge-terminal-key.test.ts @@ -3,59 +3,12 @@ import { MOBILE_WEB_SHELL_GRANTS } from '../page-route-policy' import { createFakeBridgePortPair } from './bridge-port-pair-test-harness' import { BRIDGE_PROTOCOL_VERSION } from './bridge-envelope' import { - BridgeTerminalKeyIdSchema, - BridgeTerminalToolbarSchema, - BRIDGE_TERMINAL_KEY_IDS, - BRIDGE_TERMINAL_MODIFIER_IDS, - BRIDGE_TERMINAL_TOOLBAR_KEY_IDS, BRIDGE_TERMINAL_KEY_RAIL_CLAIM_NOTIFY, BRIDGE_TERMINAL_KEY_RAIL_GRANT, - type BridgeTerminalKeyId, - type BridgeTerminalKeyRail + type BridgeTerminalKeyId } from './bridge-terminal-key' -const ALL_RAIL_KEYS: BridgeTerminalKeyRail = [ - ...BRIDGE_TERMINAL_TOOLBAR_KEY_IDS, - ...BRIDGE_TERMINAL_KEY_IDS, - ...BRIDGE_TERMINAL_MODIFIER_IDS, - 'char:/', - 'char:@' -] - describe('the iPhone Duo key rail over the bridge', () => { - it('publishes and delivers every rail key through both bridge ends', async () => { - const pair = createFakeBridgePortPair({ terminalKeyRail: ALL_RAIL_KEYS }) - await pair.flush() - expect(pair.client.getShellSession()?.terminalKeyRail).toEqual(ALL_RAIL_KEYS) - const pressed: BridgeTerminalKeyId[] = [] - const release = pair.client.claimTerminalKeyRail((key) => pressed.push(key), 'all-keys') - await pair.flush() - for (const key of ALL_RAIL_KEYS) { - expect(pair.host.sendTerminalKey(key, 'all-keys')).toBe(true) - } - await pair.flush() - expect(pressed).toEqual(ALL_RAIL_KEYS) - release() - await pair.flush() - expect(pair.host.sendTerminalKey('ctrlC', 'all-keys')).toBe(false) - }) - it('tells the page which keys the rail holds, and moves that live as the window turns', async () => { - const pair = createFakeBridgePortPair({ terminalKeyRail: ['escape', 'tab', 'enter'] }) - await pair.flush() - expect(pair.client.getShellSession()?.terminalKeyRail).toEqual(['escape', 'tab', 'enter']) - const seen: BridgeTerminalKeyRail[] = [] - pair.client.onTerminalKeyRailUpdate((keys) => seen.push(keys)) - - pair.host.publishTerminalKeyRail(['escape']) - await pair.flush() - pair.host.publishTerminalKeyRail(['escape']) - await pair.flush() - pair.host.publishTerminalKeyRail([]) - await pair.flush() - expect(pair.client.getShellSession()?.terminalKeyRail).toEqual([]) - expect(seen).toEqual([['escape'], []]) - }) - it('hears the page claim the rail, and drops the claim when a document starts or ends', async () => { const pair = createFakeBridgePortPair({ terminalKeyRail: ['escape', 'arrowUp'] }) await pair.flush() @@ -170,25 +123,3 @@ it('carries toolbar state across both bridge ends and delivers toolbar actions o await pair.flush() expect(pair.toShell).toHaveLength(releaseEnd) }) - -it('accepts only single printable characters and known latches', () => { - expect(BridgeTerminalKeyIdSchema.safeParse('char:~').success).toBe(true) - expect(BridgeTerminalKeyIdSchema.safeParse('char:ab').success).toBe(false) - expect(BridgeTerminalKeyIdSchema.safeParse('char: ').success).toBe(false) - const toolbar = { - canSend: true, - canCompose: true, - keyboardVisible: false, - liveInputEnabled: true, - phoneDisplayMode: false, - runsAgent: true, - modifiers: { ctrl: 'off', alt: 'off', shift: 'off' } - } - expect(BridgeTerminalToolbarSchema.parse(toolbar)).toEqual(toolbar) - expect( - BridgeTerminalToolbarSchema.safeParse({ - ...toolbar, - modifiers: { ...toolbar.modifiers, ctrl: 'on' } - }).success - ).toBe(false) -}) diff --git a/mobile/src/mobile-web-shell/host-area-leave-session.test.tsx b/mobile/src/mobile-web-shell/host-area-leave-session.test.tsx deleted file mode 100644 index b2bded1c410..00000000000 --- a/mobile/src/mobile-web-shell/host-area-leave-session.test.tsx +++ /dev/null @@ -1,170 +0,0 @@ -// Session exit and missing-worktree bounces must land on one host route instead of stacking another host-area session. -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const harness = await vi.hoisted(async () => await import('./mobile-web-shell-screen-test-harness')) -const dependencies = vi.hoisted(() => harness.createScreenDependencies()) -const { screenModuleMocks } = await vi.hoisted( - async () => await import('./mobile-web-shell-screen-test-mocks') -) -const mocks = vi.hoisted(() => screenModuleMocks(dependencies)) -const device = vi.hoisted(() => ({ width: 390, height: 844, dismissTo: vi.fn() })) - -vi.mock('react-native', () => ({ - ...mocks['react-native'](), - useWindowDimensions: () => ({ width: device.width, height: device.height }) -})) -vi.mock('expo-router', () => { - const base = mocks['expo-router']() - return { - ...base, - useRouter: () => ({ ...base.useRouter(), dismissTo: device.dismissTo }) - } -}) -vi.mock('expo-clipboard', mocks['expo-clipboard']) -vi.mock('expo-haptics', mocks['expo-haptics']) -vi.mock('expo-document-picker', mocks['expo-document-picker']) -vi.mock('@orca/expo-two-way-audio', mocks['@orca/expo-two-way-audio']) -vi.mock('expo-keep-awake', mocks['expo-keep-awake']) -vi.mock('expo-image-picker', mocks['expo-image-picker']) -vi.mock('expo-file-system', mocks['expo-file-system']) -vi.mock('lucide-react-native', mocks['lucide-react-native']) -vi.mock('react-native-safe-area-context', mocks['react-native-safe-area-context']) -vi.mock('../../modules/orca-mobile-web-shell/src', mocks['../../modules/orca-mobile-web-shell/src']) -vi.mock('../app-update/use-wall-app-update', mocks['../app-update/use-wall-app-update']) -vi.mock('../transport/host-client-hooks', mocks['../transport/host-client-hooks']) -vi.mock('./use-page-host-snapshot', mocks['./use-page-host-snapshot']) -vi.mock('./use-mobile-web-shell-session', mocks['./use-mobile-web-shell-session']) - -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { clientFrame, createFakeRpcClient } from './bridge-host-test-fakes' -import { MobileWebShellScreen } from './MobileWebShellScreen' -import type { NativeStackState } from './native-stack-return' - -const HOST = '/h/host-1' -const DETAIL = '/h/host-1/session/wt-1' -const BOUNCE = '/h/host-1?notice=worktree-missing' -const IPAD = { width: 1180, height: 820 } -const PHONE = { width: 390, height: 844 } - -type Entry = string - -function rootStateOf(stack: readonly Entry[]): NativeStackState { - const routes = stack.map((entry, index) => { - const [path = '', search = ''] = entry.split('?') - const [, , hostId = '', area, worktreeId] = path.split('/') - const query = Object.fromEntries(new URLSearchParams(search)) - return area === undefined - ? { key: `entry-${index}`, name: '[hostId]/index', params: { hostId, ...query } } - : { - key: `entry-${index}`, - name: `[hostId]/${area}/[worktreeId]`, - params: { hostId, worktreeId: worktreeId ?? '', ...query } - } - }) - const hostStack = { key: 'host-stack', index: routes.length - 1, routes } - return { key: 'root-stack', index: 0, routes: [{ key: 'h', name: 'h', state: hostStack }] } -} - -// Model Expo Router 55: dismissTo pops to the nearest matching route with new params, or replaces the current entry when no match exists. -function applied(stack: readonly Entry[]): Entry[] { - const next = [...stack] - for (const [href] of dependencies.push.mock.calls) { - next.push(String(href)) - } - for (const [action] of dependencies.dispatch.mock.calls) { - next.splice(next.length - action.payload.count) - } - for (const [href] of device.dismissTo.mock.calls) { - const target = String(href) - const route = target.split('?')[0] - const found = next.findLastIndex((entry) => entry.split('?')[0] === route) - if (found === -1) { - next.splice(-1, 1, target) - } else { - next.splice(found, next.length - found, target) - } - } - return next -} - -async function hopFromSession( - viewport: { width: number; height: number }, - stack: readonly Entry[], - href: string -) { - device.width = viewport.width - device.height = viewport.height - dependencies.rootState = rootStateOf(stack) - dependencies.client = createFakeRpcClient() - dependencies.state = harness.readyState('session-one') - dependencies.pageRoutes = ['/h/[hostId]', '/h/[hostId]/session/[worktreeId]'] - const mounted: { tree: ReactTestRenderer | null } = { tree: null } - await act(async () => { - mounted.tree = create( - createElement(MobileWebShellScreen, { - hostId: 'host-1', - route: { pathname: DETAIL } - }) - ) - }) - const view = mounted.tree?.root.findAll((node) => String(node.type) === 'ShellViewProbe')[0] - await act(async () => { - view?.props.onBridgeMessage({ nativeEvent: { json: clientFrame({ type: 'ready' }) } }) - view?.props.onBridgeMessage({ - nativeEvent: { json: clientFrame({ type: 'notify', name: 'navigate', href }) } - }) - }) - act(() => mounted.tree?.unmount()) -} - -beforeEach(() => { - harness.resetScreenDependencies(dependencies) - device.dismissTo.mockReset() -}) - -afterEach(() => { - vi.clearAllMocks() -}) - -describe('leaving a wide detail session for its host route', () => { - it('returns to the host-area session below it, so Back does not reopen the session', async () => { - await hopFromSession(IPAD, [HOST, DETAIL], HOST) - expect(applied([HOST, DETAIL])).toEqual([HOST]) - }) - - it('carries the bounce notice to that host route, and still stacks nothing', async () => { - await hopFromSession(IPAD, [HOST, DETAIL], BOUNCE) - expect(applied([HOST, DETAIL])).toEqual([BOUNCE]) - }) - - it('lands on one host route from a cold deep link, with nothing below the session', async () => { - await hopFromSession(IPAD, [DETAIL], BOUNCE) - expect(applied([DETAIL])).toEqual([BOUNCE]) - }) -}) - -describe('the same hop on a phone', () => { - it.each([HOST, BOUNCE])('replaces a cold detail session with %s', async (href) => { - await hopFromSession(PHONE, [DETAIL], href) - expect(applied([DETAIL])).toEqual([href]) - expect(dependencies.push).not.toHaveBeenCalled() - }) - - it('updates the existing host notice without retaining the exited detail session', async () => { - await hopFromSession(PHONE, [HOST, DETAIL], BOUNCE) - expect(applied([HOST, DETAIL])).toEqual([BOUNCE]) - }) - - it('returns to the host route below rather than stacking a second one', async () => { - await hopFromSession(PHONE, [HOST, DETAIL], HOST) - expect(device.dismissTo).not.toHaveBeenCalled() - expect(applied([HOST, DETAIL])).toEqual([HOST]) - }) - - it('pushes a route the stack does not hold, such as another worktree', async () => { - const other = '/h/host-1/session/wt-2' - await hopFromSession(PHONE, [HOST, DETAIL], other) - expect(applied([HOST, DETAIL])).toEqual([HOST, DETAIL, other]) - }) -}) diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts b/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts index 24ef7f7fda0..9245e157032 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts +++ b/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts @@ -174,28 +174,6 @@ describe('an offline cold start after a grant-only manifest edit', () => { doubles.manifest = null }) - it('opens a same-build hit online without writing to the generation directory', async () => { - // Do not rewrite manifest.json while the native view may read it during page open. - const fileSystem = createFakeGenerationFileSystem() - await cacheGeneration(fileSystem, manifestGranting(GRANTS_A)) - const generationPaths = () => - fileSystem.paths().filter((path) => path.startsWith(`${HOST_KEY}/generations/`)) - const paths = generationPaths() - const settled = fileSystem.writes.length - doubles.manifest = M2 - - const online = await mountRoute(fileSystem) - - expect(online.state()).toMatchObject({ kind: 'ready', buildId: BUILD_ID }) - expect(online.routeGrants()).toEqual(GRANTS_A_PLUS_B) - const generationWrites = fileSystem.writes - .slice(settled) - .filter((path) => path.startsWith(`${HOST_KEY}/generations/`)) - expect(generationWrites).toEqual([]) - expect(generationPaths()).toEqual(paths) - await online.unmount() - }) - it('reads the grants the assets arrived with, whatever a later online read accepted', async () => { const fileSystem = createFakeGenerationFileSystem() await cacheGeneration(fileSystem, manifestGranting(GRANTS_A)) diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-update-failure-log.test.ts b/mobile/src/mobile-web-shell/mobile-web-shell-update-failure-log.test.ts index 9294f6f2957..cd783473a9d 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-update-failure-log.test.ts +++ b/mobile/src/mobile-web-shell/mobile-web-shell-update-failure-log.test.ts @@ -2,14 +2,10 @@ import { describe, expect, it } from 'vitest' import { computeMobileWebBundleId } from '../../../src/shared/mobile-web-bundle/manifest-contract' import type { MobileWebBundleFetchResult } from '../transport/web-bundle/mobile-web-bundle-fetch' import { createFakeGenerationFileSystem } from './generation-file-system-fake' -import { createGenerationStore, MAX_CACHED_HOSTS } from './generation-store' +import { createGenerationStore } from './generation-store' import { deriveHostCacheKey } from './host-cache-key' import type { MobileWebShellUpdateFailure } from './mobile-web-shell-update-failure' -import { - MAX_UPDATE_FAILURES, - MAX_UPDATE_FAILURES_PER_HOST, - appendUpdateFailure -} from './mobile-web-shell-update-failure-log' +import { appendUpdateFailure } from './mobile-web-shell-update-failure-log' const LOG = 'update-failures.json' @@ -26,45 +22,7 @@ function failure(hostId: string, at: number): MobileWebShellUpdateFailure { } } -function appendAll(entries: readonly MobileWebShellUpdateFailure[]): MobileWebShellUpdateFailure[] { - return entries.reduce(appendUpdateFailure, []) -} - describe('appendUpdateFailure', () => { - it('keeps five per host and evicts that host oldest-first', () => { - const kept = appendAll(Array.from({ length: 7 }, (_, at) => failure('host-1', at))) - expect(kept.map((entry) => entry.at)).toEqual([2, 3, 4, 5, 6]) - expect(kept).toHaveLength(MAX_UPDATE_FAILURES_PER_HOST) - }) - - it("never evicts one host's record for another host's failures below the ceiling", () => { - const kept = appendAll([ - failure('host-1', 0), - ...Array.from({ length: 9 }, (_, at) => failure('host-2', at + 1)) - ]) - expect(kept.filter((entry) => entry.hostId === 'host-1')).toHaveLength(1) - expect(kept.filter((entry) => entry.hostId === 'host-2')).toHaveLength(5) - }) - - it('holds a ceiling across hosts, evicting the oldest of all', () => { - const kept = appendAll( - Array.from({ length: MAX_UPDATE_FAILURES + 10 }, (_, at) => failure(`host-${at}`, at)) - ) - expect(kept).toHaveLength(MAX_UPDATE_FAILURES) - expect(kept[0]?.at).toBe(10) - }) - - it('keeps a full per-host history for every cached host', () => { - const entries = Array.from({ length: MAX_CACHED_HOSTS }, (_, host) => - Array.from({ length: MAX_UPDATE_FAILURES_PER_HOST }, (_, offset) => - failure(`host-${host}`, host * MAX_UPDATE_FAILURES_PER_HOST + offset) - ) - ).flat() - expect(appendAll(entries)).toEqual(entries) - const extra = failure('host-0', entries.length) - expect(appendAll([...entries, extra])).toEqual([...entries.slice(1), extra]) - }) - it('drops a build id that is not a digest rather than keep host text', () => { const [entry] = appendUpdateFailure([], { ...failure('host-1', 0), @@ -142,20 +100,4 @@ describe('the generation store keeps the log', () => { ) expect(await store.readUpdateFailures()).toEqual([failure('host-1', 1)]) }) - - it('never rejects: a disk that refuses the write costs the record, not the shell', async () => { - const fileSystem = createFakeGenerationFileSystem() - fileSystem.failWritesAt(LOG) - const store = createGenerationStore({ fileSystem }) - await expect(store.recordUpdateFailure(failure('host-1', 1))).resolves.toBeUndefined() - expect(await store.readUpdateFailures()).toEqual([]) - }) - - it('is not mistaken for a host directory', async () => { - const fileSystem = createFakeGenerationFileSystem() - const store = createGenerationStore({ fileSystem }) - await store.recordUpdateFailure(failure('host-1', 1)) - await store.sweepStagedGenerations() - expect(fileSystem.text(LOG)).not.toBeNull() - }) }) diff --git a/mobile/src/platform/use-native-device-verbs.test.tsx b/mobile/src/platform/use-native-device-verbs.test.tsx index 7f46fb91bb4..da46626ef78 100644 --- a/mobile/src/platform/use-native-device-verbs.test.tsx +++ b/mobile/src/platform/use-native-device-verbs.test.tsx @@ -119,33 +119,6 @@ function mount( } } -describe('the device handler the shell hands its host', () => { - it('sends a clipboard verb to the clipboard half', async () => { - const { serve, unmount } = mount('session-a') - await expect(serve('native.clipboard.read', { mime: 'text' })).resolves.toEqual({ - value: 'on the pasteboard' - }) - unmount() - }) - - it('sends a media verb to the media half, picker and all', async () => { - const { serve, unmount } = mount('session-a') - await expect( - serve('native.media.pick', { source: 'library', multiple: false }) - ).resolves.toEqual({ items: [] }) - expect(device.picker.launchImageLibraryAsync).toHaveBeenCalled() - unmount() - }) - - it('answers a release for a handle no pick ever minted, rather than refusing', async () => { - const { serve, unmount } = mount('session-a') - await expect(serve('native.media.release', { handle: 'media-9' })).resolves.toEqual({ - released: false - }) - unmount() - }) -}) - describe('a picker completing after its page session ends', () => { beforeEach(() => { device.files.clear() @@ -222,17 +195,4 @@ describe('the screen a page session leaves behind', () => { await new Promise((resolve) => setTimeout(resolve, 0)) expect(device.wakeTags).toEqual(['+orca-microphone', '-orca-microphone']) }) - - it('leaves the next session nothing of the last one to give back', async () => { - const first = mount('session-a') - await first.serve('native.audio.start', { sampleRate: 16_000 }) - first.unmount() - await new Promise((resolve) => setTimeout(resolve, 0)) - device.wakeTags.length = 0 - const second = mount('session-b') - second.unmount() - await new Promise((resolve) => setTimeout(resolve, 0)) - // A session with no capture must not deactivate an unheld wake tag. - expect(device.wakeTags).toEqual([]) - }) }) diff --git a/mobile/src/session/TerminalPaneView.test.tsx b/mobile/src/session/TerminalPaneView.test.tsx deleted file mode 100644 index db37b837614..00000000000 --- a/mobile/src/session/TerminalPaneView.test.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { act, create } from 'react-test-renderer' -import { expect, it, vi } from 'vitest' -import { mountFixture } from '../test-support/rpc-recording/recorder-fixture-shape' - -const hosts = vi.hoisted(() => { - const component = (name: string) => { - const Host = (props: { children?: ReactNode }) => props.children ?? null - Host.displayName = name - return Host - } - return { - View: component('View'), - Text: component('Text'), - Pressable: component('Pressable'), - TerminalWebView: component('TerminalWebView') - } -}) -vi.mock('react-native', () => ({ - AppState: { currentState: 'active', addEventListener: () => ({ remove: () => {} }) }, - View: hosts.View, - Text: hosts.Text, - Pressable: hosts.Pressable, - ActivityIndicator: hosts.View, - Animated: { View: hosts.View }, - StyleSheet: { - create: (value: T) => value, - absoluteFillObject: { position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 } - } -})) -vi.mock('../terminal/TerminalWebView', () => ({ TerminalWebView: hosts.TerminalWebView })) -vi.mock('../storage/preferences', () => ({ saveTerminalTextScale: () => Promise.resolve() })) -vi.mock('../browser/MobileBrowserPane', () => ({ MobileBrowserPane: () => null })) -vi.mock('./native-chat/MobileNativeChatOverlay', () => ({ MobileNativeChatOverlay: () => null })) -vi.mock('./MobileSessionFileReader', () => ({ FileReader: () => null })) -vi.mock('./MobileSessionMarkdownReader', () => ({ MarkdownReader: () => null })) -vi.mock('./mobile-session-styles', () => ({ styles: {} })) -vi.mock('../transport/use-host-profile', () => ({ useHostProfile: () => null })) -vi.mock('expo-router', async () => { - const { useEffect } = await import('react') - return { useFocusEffect: (effect: () => () => void) => useEffect(effect, [effect]) } -}) -import { TerminalPaneView } from './TerminalPaneView' -import { MobileSessionActiveContent } from './MobileSessionActiveContent' -import { terminalPaneViewport } from './mobile-terminal-pane-layout' - -type Props = Parameters[0] - -it.each([0, 1])( - 'keeps stacked pane %i visible above a tall keyboard and preserves its measured terminal frame', - (index) => { - const onFrame = vi.fn() - const base = mountFixture({ - handle: `term-${index}`, - active: true, - visible: true, - splitIndex: index, - splitDirection: 'column', - splitFrame: { width: 390, height: 500 }, - keyboardInset: 0, - keyboardLift: 0, - textScale: 1, - onFrame, - onRef: vi.fn() - }) - let renderer!: ReturnType - try { - act(() => { - renderer = create(createElement(TerminalPaneView, base)) - }) - const views = () => renderer.root.findAllByType(hosts.View) - expect(views()[2].props.style.height).toBe(222) - act(() => { - renderer.update( - createElement(TerminalPaneView, { ...base, keyboardInset: 300, keyboardLift: 150 }) - ) - }) - const [outer, clip, measured] = views() - const outerStyle = Object.assign({}, ...outer.props.style.filter(Boolean)) - expect(outerStyle.top).toBe(index * 100) - expect(outerStyle.height).toBe(100) - expect(clip.props.style.overflow).toBe('hidden') - expect(measured.props.style.height).toBe(222) - act(() => { - measured.props.onLayout({ nativeEvent: { layout: { width: 390, height: 222 } } }) - }) - expect(onFrame).toHaveBeenLastCalledWith(`term-${index}`, { width: 390, height: 222 }) - const terminal = renderer.root.findByType(hosts.TerminalWebView) - const terminalStyle = Object.assign({}, ...terminal.props.style.filter(Boolean)) - const bottom = - outerStyle.top + 28 + measured.props.style.height + terminalStyle.transform[0].translateY - expect(bottom).toBe(index * 100 + 100) - expect(bottom).toBeGreaterThan(outerStyle.top + 28) - expect(bottom).toBeLessThanOrEqual(200) - } finally { - act(() => renderer?.unmount()) - } - } -) - -it.each(['stacked', 'side-by-side'] as const)( - 'keeps visible %s terminal content above the keyboard independently of input focus', - (layout) => { - const frame = { width: 900, height: 500 } - const terminalHeight = layout === 'stacked' ? 222 : 472 - const visibleHeight = layout === 'stacked' ? 72 : 172 - const expectedLift = layout === 'stacked' ? 150 : 300 - const geometry = new Map() - for (const handle of ['a', 'b', 'c']) { - terminalPaneViewport(geometry, handle).terminalFrameRef.current = { - width: layout === 'stacked' ? 900 : 450, - height: terminalHeight - } - } - const controller = mountFixture[0]['controller']>( - { - sessionTabs: [], - terminals: ['a', 'b', 'c'].map((handle) => ({ handle, title: handle })), - activeHandle: 'a', - terminalSplitActive: true, - terminalSplitLayout: layout, - visibleTerminalHandles: ['a', 'b'], - terminalKeyboardMetrics: new Map([ - ['a', { rows: 10, rowPitch: 5, cursorY: 2, contentBottomRow: 2, altScreen: false }], - [ - 'b', - { - rows: 40, - rowPitch: terminalHeight / 40, - cursorY: 39, - contentBottomRow: 39, - altScreen: true - } - ] - ]), - getTerminalPaneViewport: (handle) => terminalPaneViewport(geometry, handle), - keyboardLift: 0, - activeTerminalKeyboardLift: 0, - notifyTerminalFrame: vi.fn(), - dictation: { isRecording: false }, - nativeChatSendError: { message: null, clear: vi.fn() } - } - ) - let renderer!: ReturnType - const render = (activeHandle: string, keyboardLift: number) => { - const element = createElement(MobileSessionActiveContent, { - controller: { ...controller, activeHandle, keyboardLift } - }) - act(() => { - if (renderer) { - renderer.update(element) - } else { - renderer = create(element) - } - }) - } - const terminalStyle = (handle: string) => { - const pane = renderer.root - .findAllByType(TerminalPaneView) - .find((item) => item.props.handle === handle)! - const terminal = pane.findByType(hosts.TerminalWebView) - return Object.assign({}, ...terminal.props.style.filter(Boolean)) - } - try { - render('a', 0) - act(() => { - renderer.root - .findAllByType(hosts.View)[0] - .props.onLayout({ nativeEvent: { layout: frame } }) - }) - for (const focused of ['a', 'b', 'a']) { - render(focused, 300) - expect(terminalStyle('a').transform).toBeUndefined() - expect(terminalStyle('b').transform).toEqual([{ translateY: -expectedLift }]) - // A pane never shown has not mounted its terminal yet. - expect(renderer.root.findAllByType(hosts.TerminalWebView)).toHaveLength(2) - for (const pane of renderer.root.findAllByType(TerminalPaneView).slice(0, 2)) { - const [outer, clip, measured] = pane.findAllByType(hosts.View) - const outerStyle = Object.assign({}, ...outer.props.style.filter(Boolean)) - expect(outerStyle.height - 28).toBe(visibleHeight) - expect(clip.props.style.overflow).toBe('hidden') - expect(measured.props.style.height).toBe(terminalHeight) - } - expect(terminalHeight + terminalStyle('b').transform[0].translateY).toBe(visibleHeight) - } - render('a', 0) - expect(terminalStyle('a').transform).toBeUndefined() - expect(terminalStyle('b').transform).toBeUndefined() - expect(renderer.root.findAllByType(hosts.TerminalWebView)).toHaveLength(2) - } finally { - act(() => renderer?.unmount()) - } - } -) diff --git a/mobile/src/session/ai-vault-resume-preparation.test.ts b/mobile/src/session/ai-vault-resume-preparation.test.ts deleted file mode 100644 index 0541daccedb..00000000000 --- a/mobile/src/session/ai-vault-resume-preparation.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { AiVaultSession } from '../../../src/shared/ai-vault/contracts/ai-vault-types' -import { - buildMobileAiVaultResumeLaunch, - resumeAiVaultSessionInTerminal -} from './ai-vault-resume-launch' -import { - prepareMobileAiVaultSessionResume, - RESUME_RPC_TIMEOUT_MS -} from './ai-vault-resume-preparation' - -const LEGACY_CODEX_HOME = '/Users/ada/Library/Application Support/orca/codex-runtime-home/home' -const PER_ACCOUNT_HOME = '/Users/ada/Library/Application Support/orca/codex-accounts/a/home' - -function legacySession(overrides: Partial = {}): AiVaultSession { - return { - id: 'codex:legacy-1', - executionHostId: 'local', - agent: 'codex', - sessionId: 'legacy-1', - title: 'Resume me', - cwd: '/Users/ada/repo', - branch: 'main', - model: null, - filePath: `${LEGACY_CODEX_HOME}/sessions/2026/07/20/rollout-a.jsonl`, - codexHome: LEGACY_CODEX_HOME, - createdAt: null, - updatedAt: null, - modifiedAt: '2026-07-20T00:00:00.000Z', - messageCount: 2, - totalTokens: 10, - previewMessages: [], - queuedMessageCount: 0, - subagentTranscriptCount: 0, - resumeCommand: '', - subagent: null, - ...overrides - } -} - -describe('prepareMobileAiVaultSessionResume', () => { - it.each([ - { - code: 'forbidden', - message: "Method 'aiVault.prepareSessionResume' is not available to mobile clients" - }, - { - code: 'method_not_found', - message: 'Unknown method: aiVault.prepareSessionResume' - } - ])('uses the legacy command and environment on old-host $code', async (error) => { - const legacy = legacySession() - const sendRequest = vi - .fn() - .mockResolvedValueOnce({ ok: false, error }) - .mockResolvedValueOnce({ - ok: true, - result: { tab: { type: 'terminal', id: 'tab-1', terminal: 'pty-1', title: 'Terminal' } } - }) - .mockResolvedValueOnce({ ok: true, result: { send: { accepted: true } } }) - - const prepared = await prepareMobileAiVaultSessionResume({ sendRequest }, legacy) - const launch = buildMobileAiVaultResumeLaunch({ - session: prepared, - workspacePlatform: 'darwin' - }) - await resumeAiVaultSessionInTerminal({ sendRequest }, 'worktree-1', launch) - - expect(prepared).toBe(legacy) - expect(sendRequest.mock.calls[1]?.[1]).not.toHaveProperty('envToDelete') - expect(sendRequest).toHaveBeenNthCalledWith( - 3, - 'terminal.send', - { - terminal: 'pty-1', - text: `cd '/Users/ada/repo' && CODEX_HOME='${LEGACY_CODEX_HOME}' codex '-c' 'disable_paste_burst=true' '-c' 'tui.resume_cwd=current' 'resume' 'legacy-1'`, - enter: true - }, - { timeoutMs: RESUME_RPC_TIMEOUT_MS } - ) - }) - - it('uses the real Codex home when a supported desktop requests it', async () => { - const legacy = legacySession() - const sendRequest = vi.fn().mockResolvedValue({ - ok: true, - result: { useRealCodexHome: true } - }) - - const prepared = await prepareMobileAiVaultSessionResume({ sendRequest }, legacy) - const launch = buildMobileAiVaultResumeLaunch({ - session: prepared, - workspacePlatform: 'darwin' - }) - - expect(sendRequest).toHaveBeenCalledWith( - 'aiVault.prepareSessionResume', - { - agent: 'codex', - filePath: legacy.filePath, - codexHome: LEGACY_CODEX_HOME, - executionHostId: 'local' - }, - { timeoutMs: RESUME_RPC_TIMEOUT_MS } - ) - expect(prepared).toEqual({ ...legacy, codexHome: null }) - expect(launch.command).toBe( - "cd '/Users/ada/repo' && codex '-c' 'disable_paste_burst=true' '-c' 'tui.resume_cwd=current' 'resume' 'legacy-1'" - ) - expect(launch.envToDelete).toEqual(['CODEX_HOME', 'ORCA_CODEX_HOME']) - }) - - it('preserves the legacy resume path when a supported desktop declines real-home use', async () => { - const legacy = legacySession() - const sendRequest = vi.fn().mockResolvedValue({ - ok: true, - result: { useRealCodexHome: false } - }) - - const prepared = await prepareMobileAiVaultSessionResume({ sendRequest }, legacy) - const launch = buildMobileAiVaultResumeLaunch({ - session: prepared, - workspacePlatform: 'darwin' - }) - - expect(prepared).toBe(legacy) - expect(launch.command).toContain(`CODEX_HOME='${LEGACY_CODEX_HOME}'`) - expect(launch.envToDelete).toBeUndefined() - }) - - it.each([ - { agent: 'claude' as const, codexHome: null }, - { agent: 'codex' as const, codexHome: '/Users/ada/.config/codex' }, - { - agent: 'codex' as const, - codexHome: PER_ACCOUNT_HOME, - executionHostId: 'ssh:server-1' as AiVaultSession['executionHostId'] - }, - { agent: 'codex' as const, codexHome: '\\\\wsl.localhost\\Ubuntu\\home\\ada\\.codex' } - ])('does not prepare unrepinnable session $agent at $codexHome', async (overrides) => { - const current = legacySession(overrides) - const sendRequest = vi.fn() - - await expect(prepareMobileAiVaultSessionResume({ sendRequest }, current)).resolves.toBe(current) - expect(sendRequest).not.toHaveBeenCalled() - }) - - it('repins a per-account session to the home the host substitutes', async () => { - const substituteHome = '/Users/ada/Library/Application Support/orca/codex-accounts/b/home' - const current = legacySession({ codexHome: PER_ACCOUNT_HOME }) - const sendRequest = vi.fn().mockResolvedValue({ - ok: true, - result: { useRealCodexHome: false, substituteCodexHome: substituteHome } - }) - - const prepared = await prepareMobileAiVaultSessionResume({ sendRequest }, current) - const launch = buildMobileAiVaultResumeLaunch({ - session: prepared, - workspacePlatform: 'darwin' - }) - - expect(sendRequest).toHaveBeenCalledWith( - 'aiVault.prepareSessionResume', - { - agent: 'codex', - filePath: current.filePath, - codexHome: PER_ACCOUNT_HOME, - executionHostId: 'local' - }, - { timeoutMs: RESUME_RPC_TIMEOUT_MS } - ) - expect(prepared.codexHome).toBe(substituteHome) - expect(launch.command).toContain(`CODEX_HOME='${substituteHome}'`) - }) - - it('keeps a per-account session home when an older host sends no repin', async () => { - const current = legacySession({ codexHome: PER_ACCOUNT_HOME }) - const sendRequest = vi.fn().mockResolvedValue({ - ok: true, - result: { useRealCodexHome: false } - }) - - await expect(prepareMobileAiVaultSessionResume({ sendRequest }, current)).resolves.toBe(current) - }) - - it('keeps a per-account session usable when the host cannot prepare at all', async () => { - const current = legacySession({ codexHome: PER_ACCOUNT_HOME }) - const sendRequest = vi.fn().mockResolvedValue({ - ok: false, - error: { code: 'method_not_found', message: 'Unknown method' } - }) - - await expect(prepareMobileAiVaultSessionResume({ sendRequest }, current)).resolves.toBe(current) - }) - - it.each([ - { - code: 'internal_error', - message: 'Retry resume after checking session folder permissions.' - }, - { - code: 'forbidden', - message: 'Session resume is forbidden' - } - ])('keeps genuine server error visible: $code', async (error) => { - const sendRequest = vi.fn().mockResolvedValue({ ok: false, error }) - - await expect( - prepareMobileAiVaultSessionResume({ sendRequest }, legacySession()) - ).rejects.toThrow(error.message) - }) - - it.each(['Request timed out: aiVault.prepareSessionResume', 'Connection interrupted'])( - 'keeps transport failure visible: %s', - async (message) => { - const failure = new Error(message) - const sendRequest = vi.fn().mockRejectedValue(failure) - - await expect( - prepareMobileAiVaultSessionResume({ sendRequest }, legacySession()) - ).rejects.toBe(failure) - } - ) -}) diff --git a/mobile/src/session/mobile-image-source-picker.test.ts b/mobile/src/session/mobile-image-source-picker.test.ts index a2c5f1d7189..e71d719b36b 100644 --- a/mobile/src/session/mobile-image-source-picker.test.ts +++ b/mobile/src/session/mobile-image-source-picker.test.ts @@ -1,5 +1,4 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { CLIPBOARD_IMAGE_MAX_SOURCE_BYTES } from '../../../src/shared/clipboard-image' vi.mock('expo-image-picker', () => ({ UIImagePickerPreferredAssetRepresentationMode: { Compatible: 'compatible' }, @@ -14,7 +13,6 @@ vi.mock('expo-file-system', () => ({ })) import { - ImageLibraryPermissionError, pickMobileImage, pickMobileImages, type PickedMobileImage @@ -23,7 +21,6 @@ import { const granted = { granted: true } as Awaited< ReturnType > -const denied = { granted: false } as typeof granted async function collectImages( images: AsyncIterable @@ -59,33 +56,6 @@ function fileFactory( describe('pickMobileImage', () => { afterEach(() => vi.restoreAllMocks()) - it('reads a photo URI without relying on React Native fetch', async () => { - const bytes = new Uint8Array([0, 1, 2, 3]) - const file = fileFactory(bytes) - const launchLibrary = vi.fn().mockResolvedValue({ - canceled: false, - assets: [{ uri: 'file:///x.jpg', fileSize: bytes.length }] - }) - const fetchSpy = vi - .spyOn(globalThis, 'fetch') - .mockRejectedValue(new Error('Network request failed')) - const result = await pickMobileImage('library', { - requestLibraryPermission: vi.fn().mockResolvedValue(granted), - launchLibrary, - createFile: file.createFile - }) - - expect(result).toEqual({ - base64: Buffer.from(bytes).toString('base64'), - uri: 'file:///x.jpg' - }) - expect(launchLibrary).toHaveBeenCalledWith( - expect.objectContaining({ base64: false, preferredAssetRepresentationMode: 'compatible' }) - ) - expect(fetchSpy).not.toHaveBeenCalled() - expect(file.close).toHaveBeenCalledTimes(1) - }) - it('returns every selected library photo in order', async () => { const bytesByUri = new Map([ ['file:///a.jpg', new Uint8Array([1])], @@ -137,76 +107,6 @@ describe('pickMobileImage', () => { ) }) - it('throws when photo library permission is denied', async () => { - await expect( - pickMobileImage('library', { - requestLibraryPermission: vi.fn().mockResolvedValue(denied), - launchLibrary: vi.fn() - }) - ).rejects.toBeInstanceOf(ImageLibraryPermissionError) - }) - - it('returns null when the library picker is cancelled', async () => { - const result = await pickMobileImage('library', { - requestLibraryPermission: vi.fn().mockResolvedValue(granted), - launchLibrary: vi.fn().mockResolvedValue({ canceled: true, assets: null }) - }) - - expect(result).toBeNull() - }) - - it('reads any picked file into base64 and keeps its name for the files source', async () => { - const bytes = new Uint8Array([1, 2, 3, 4]) - const file = fileFactory(bytes) - const launchFiles = vi.fn().mockResolvedValue({ - canceled: false, - assets: [ - { - uri: 'file:///book.txt', - name: '柴静-看见.txt', - mimeType: 'text/plain', - size: bytes.length - } - ] - }) - - const result = await pickMobileImage('files', { - launchFiles, - createFile: file.createFile - }) - - expect(launchFiles).toHaveBeenCalledWith(expect.objectContaining({ type: '*/*' })) - expect(result).toEqual({ - base64: Buffer.from(bytes).toString('base64'), - uri: 'file:///book.txt', - fileName: '柴静-看见.txt' - }) - expect(file.close).toHaveBeenCalledTimes(1) - }) - - it('returns null when the files picker is cancelled', async () => { - const result = await pickMobileImage('files', { - launchFiles: vi.fn().mockResolvedValue({ canceled: true, assets: null }) - }) - - expect(result).toBeNull() - }) - - it('rejects an oversized asset before opening it', async () => { - const file = fileFactory(new Uint8Array([1])) - await expect( - pickMobileImage('files', { - launchFiles: vi.fn().mockResolvedValue({ - canceled: false, - assets: [{ uri: 'file:///huge.png', size: CLIPBOARD_IMAGE_MAX_SOURCE_BYTES + 1 }] - }), - createFile: file.createFile - }) - ).rejects.toThrow('Clipboard image is too large') - expect(file.createFile).not.toHaveBeenCalled() - expect(file.open).not.toHaveBeenCalled() - }) - it('closes the file handle when reading fails', async () => { const file = fileFactory(new Uint8Array(), { fileSize: 4, readError: new Error('read failed') }) await expect( diff --git a/mobile/src/session/mobile-markdown-document-lifetime.test.ts b/mobile/src/session/mobile-markdown-document-lifetime.test.ts deleted file mode 100644 index 39014b6c6c5..00000000000 --- a/mobile/src/session/mobile-markdown-document-lifetime.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { hookMount, performHookAction } from '../test-support/rpc-recording/hook-mount' -import { mountFixture } from '../test-support/rpc-recording/recorder-fixture-shape' -import type { RpcResponse } from '../transport/types' -import type { MarkdownDocState, MobileSessionTab } from './mobile-session-route-types' -import { useMobileSessionDocumentReaders } from './use-mobile-session-document-readers' -import { useMobileSessionCloseActions } from './use-mobile-session-close-actions' - -type ReaderScope = Parameters[0] -type CloseScope = Parameters[0] - -function success(content: string): RpcResponse { - return { - id: 'read', - ok: true, - result: { content, version: 'version', isDirty: false, editable: false }, - _meta: { runtimeId: 'host' } - } -} - -function mountDocuments(readReply: () => Promise) { - let docs = new Map() - const tabs: { current: MobileSessionTab[] } = { current: [] } - const setDocs: ReaderScope['setMarkdownDocs'] = (update) => { - docs = typeof update === 'function' ? update(docs) : update - } - const closeReply = vi.fn(async (): Promise => success('')) - const client = { - sendRequest: vi.fn(async (method: string): Promise => - method === 'markdown.readTab' || method === 'files.read' ? readReply() : closeReply() - ) - } - let readers: ReturnType | undefined - let close: ReturnType | undefined - const hook = hookMount(() => { - readers = useMobileSessionDocumentReaders( - mountFixture({ - worktreeId: 'folder-workspace', - markdownDocsRef: { - get current() { - return docs - } - }, - client, - setMarkdownDocs: setDocs, - setFileDocs: () => {} - }) - ) - close = useMobileSessionCloseActions( - mountFixture({ - worktreeId: 'folder-workspace', - client, - sessionTabsRef: tabs, - setSessionTabs: () => {}, - setFileDocs: () => {}, - setMarkdownDocs: setDocs, - closedTabTombstonesRef: { current: new Map() }, - activeSessionTabIdRef: { current: null }, - selectedSessionTabIdRef: { current: null }, - pendingSelectionRef: { current: null }, - activeSessionTabTypeRef: { current: null }, - activeHandleRef: { current: null }, - setActiveSessionTabId: () => {}, - setActiveHandle: () => {} - }) - ) - }) - hook.mount() - const tab = (id: string) => - mountFixture>({ - type: 'markdown', - id, - relativePath: id + '.md', - isDirty: false - }) - return { - get docs() { - return docs - }, - closeReply, - unmount: hook.unmount, - read(id: string) { - const target = tab(id) - tabs.current = [...tabs.current.filter((current) => current.id !== id), target] - return performHookAction(() => readers?.readMarkdownTab(target)) - }, - close(id: string) { - return performHookAction(() => close?.handleCloseSessionTab(tab(id))) - }, - dirty(id: string) { - const doc = docs.get(id) - if (doc?.status !== 'ready') { - throw new Error('Missing ready document') - } - docs.set(id, { ...doc, localContent: 'phone draft', isDirty: true }) - } - } -} - -function delayedReply() { - let resolve: (reply: RpcResponse) => void = () => {} - const promise = new Promise((done) => { - resolve = done - }) - return { promise, resolve } -} - -describe('mobile Markdown document lifetime', () => { - it('releases only the closed clean document and preserves other cached documents', async () => { - const readReply = vi - .fn() - .mockResolvedValueOnce(success('# a')) - .mockResolvedValueOnce(success('# b')) - .mockResolvedValueOnce(success('# c')) - const h = mountDocuments(readReply) - try { - await h.read('a') - await h.read('b') - await h.read('c') - expect(h.docs.get('a')).toMatchObject({ status: 'ready', content: '# a' }) - expect(h.docs.get('b')).toMatchObject({ status: 'ready', content: '# b' }) - expect(h.docs.get('c')).toMatchObject({ status: 'ready', content: '# c' }) - const a = h.docs.get('a') - const c = h.docs.get('c') - await h.close('b') - expect([...h.docs.keys()]).toEqual(['a', 'c']) - expect(h.docs.get('a')).toBe(a) - expect(h.docs.get('c')).toBe(c) - await h.close('a') - expect([...h.docs.keys()]).toEqual(['c']) - expect(h.docs.get('c')).toBe(c) - await h.close('c') - expect(h.docs.size).toBe(0) - } finally { - h.unmount() - } - }) - - it.each(['success', 'failure', 'disk fallback'])( - 'keeps a closed read released after a late %s', - async (kind) => { - const delayed = delayedReply() - const readReply = vi - .fn() - .mockReturnValueOnce(delayed.promise) - .mockResolvedValue(success('disk')) - const h = mountDocuments(readReply) - try { - const reading = h.read('closed') - await h.close('closed') - delayed.resolve( - kind === 'success' - ? success('late') - : { - id: 'read', - ok: false, - error: { - code: kind === 'disk fallback' ? 'renderer_unavailable' : 'tab_not_found', - message: 'Unavailable' - }, - _meta: { runtimeId: 'host' } - } - ) - await reading - expect(h.docs.size).toBe(0) - if (kind === 'disk fallback') { - expect(readReply).toHaveBeenCalledTimes(2) - } - } finally { - h.unmount() - } - } - ) - - it('retains the document when the host refuses close', async () => { - const h = mountDocuments(async () => success('kept')) - try { - await h.read('kept') - const doc = h.docs.get('kept') - h.closeReply.mockResolvedValue({ - id: 'close', - ok: false, - error: { code: 'refused', message: 'Refused' }, - _meta: { runtimeId: 'host' } - }) - await h.close('kept') - expect(h.docs.get('kept')).toBe(doc) - } finally { - h.unmount() - } - }) - - it('preserves unsaved phone drafts after close', async () => { - const h = mountDocuments(async () => success('original')) - try { - await h.read('draft') - h.dirty('draft') - const draft = h.docs.get('draft') - await h.close('draft') - expect(h.docs.get('draft')).toBe(draft) - } finally { - h.unmount() - } - }) - - it('keeps a newer read when an older request finishes later', async () => { - const old = delayedReply() - const h = mountDocuments( - vi.fn().mockReturnValueOnce(old.promise).mockResolvedValue(success('new')) - ) - try { - const first = h.read('same') - await h.read('same') - const current = h.docs.get('same') - old.resolve(success('old')) - await first - expect(h.docs.get('same')).toBe(current) - } finally { - h.unmount() - } - }) -}) diff --git a/mobile/src/session/mobile-structured-session-status-feed.test.ts b/mobile/src/session/mobile-structured-session-status-feed.test.ts index 285e9548a76..0a38b3663b7 100644 --- a/mobile/src/session/mobile-structured-session-status-feed.test.ts +++ b/mobile/src/session/mobile-structured-session-status-feed.test.ts @@ -57,32 +57,6 @@ describe("the phone's status stream", () => { } } - it('opens one stream per client however many chats read it', () => { - const feed = mobileStructuredSessionStatusFeed(client) - feed.subscribe(() => {}) - mobileStructuredSessionStatusFeed(client).subscribe(() => {}) - - expect(client.subscribe).toHaveBeenCalledOnce() - expect(client.subscribe).toHaveBeenCalledWith( - 'agentSession.subscribeStatus', - {}, - expect.any(Function) - ) - }) - - it("follows the host's Stopping from the snapshot through each status frame", () => { - const listener = vi.fn() - mobileStructuredSessionStatusFeed(client).subscribe(listener) - - frames[0]?.({ type: 'snapshot', sessions: [summary(true)] }) - expect(read()?.stopping).toBe(true) - - frames[0]?.({ type: 'status', session: summary(false) }) - expect(read()).toMatchObject({ status: 'working' }) - expect(read()).not.toHaveProperty('stopping') - expect(listener).toHaveBeenCalledTimes(2) - }) - it('keeps a retrying host owned and starting until readiness, then revokes ownership on disconnect', () => { mobileStructuredSessionStatusFeed(client).subscribe(() => {}) const starting = { ...summary(false), hostExecutionPhase: 'starting' as const } @@ -95,43 +69,6 @@ describe("the phone's status stream", () => { expect(read()?.hostExecutionOwned).toBeUndefined() expect(read()?.hostExecutionPhase).toBeUndefined() }) - - it('drops Stopping when the host ends the stream, and opens it again on the next connection', () => { - mobileStructuredSessionStatusFeed(client).subscribe(() => {}) - frames[0]?.({ type: 'snapshot', sessions: [summary(true)] }) - - frames[0]?.({ type: 'end' }) - - expect(read()).not.toHaveProperty('stopping') - connection('connected') - expect(client.subscribe).toHaveBeenCalledTimes(2) - }) - - it('drops Stopping, keeping the rest, once the phone loses contact', () => { - mobileStructuredSessionStatusFeed(client).subscribe(() => {}) - frames[0]?.({ type: 'snapshot', sessions: [summary(true)] }) - - connection('reconnecting') - - expect(read()).toMatchObject({ sessionId: 'session-1', status: 'working' }) - expect(read()).not.toHaveProperty('stopping') - expect(read()).not.toHaveProperty('hostExecutionOwned') - }) - - it('reads a host that refuses the method to phones as one without it, asking once per connection', () => { - mobileStructuredSessionStatusFeed(client).subscribe(() => {}) - - frames[0]?.(FORBIDDEN) - connection('connected') - mobileStructuredSessionStatusFeed(client).subscribe(() => {}) - expect(client.subscribe).toHaveBeenCalledOnce() - expect(read()).toBeUndefined() - - // A new connection may reach a host updated meanwhile. - connection('reconnecting') - connection('connected') - expect(client.subscribe).toHaveBeenCalledTimes(2) - }) }) // Match physical registry ordering: drop ended streams before delivering their last frame. diff --git a/mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts b/mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts deleted file mode 100644 index 1259f6a7c5c..00000000000 --- a/mobile/src/session/native-chat/MobileNativeChatView.history-paging.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { act } from 'react-test-renderer' -import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' -import { - installChatViewHooks, - assistantTurn, - hostType, - list, - render, - root, - scrollEvent, - update -} from './MobileNativeChatView.harness.test-fixture' -import { - pageScroller, - platform, - scrollToEnd, - scrollToOffset -} from './MobileNativeChatView.render-stubs.test-fixture' - -vi.mock('react-native', async () => { - const { reactNativeStub } = await import('./MobileNativeChatView.render-stubs.test-fixture') - return reactNativeStub() -}) -vi.mock('react-native-safe-area-context', () => ({ - useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }) -})) -vi.mock('react-native-gesture-handler', async () => { - const { gestureHandlerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture') - return gestureHandlerStub() -}) -vi.mock('lucide-react-native', () => ({ - ArrowDown: 'ArrowDown', - ChevronsDownUp: 'ChevronsDownUp', - ChevronsUpDown: 'ChevronsUpDown', - Square: 'Square' -})) -vi.mock('./transcript/MobileNativeChatMessage', () => ({ - MobileNativeChatMessage: 'ChatMessage' -})) -vi.mock('./transcript/MobileNativeChatLiveLine', () => ({ - MobileNativeChatLiveLine: 'LiveStatus' -})) -vi.mock('./input/MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' })) -vi.mock('./input/MobileNativeChatPermission', () => ({ - MobileNativeChatPermission: 'ChatPermission' -})) -vi.mock('./input/MobileNativeChatQuestion', () => ({ - MobileNativeChatQuestion: 'ChatQuestion' -})) -vi.mock('../../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' })) -vi.mock('./composer/MobileNativeChatComposer', async () => { - const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture') - return composerStub() -}) - -describe('MobileNativeChatView: history paging and anchoring', () => { - installChatViewHooks() - - it('stops tail-follow before loading earlier history can resize the list', async () => { - const folded = [assistantTurn('a1', 'History')] - const onLoadEarlier = vi.fn() - await render({ folded, hasMore: true, onLoadEarlier }) - scrollToEnd.mockClear() - - act(() => { - list().props.onScrollBeginDrag?.({}) - list().props.onScroll({ - nativeEvent: { - contentOffset: { y: 40 }, - contentSize: { height: 1_200 }, - layoutMeasurement: { height: 500 } - } - }) - list().props.onContentSizeChange(320, 950) - }) - - expect(onLoadEarlier).toHaveBeenCalledOnce() - expect(scrollToEnd).not.toHaveBeenCalled() - expect(scrollToOffset).not.toHaveBeenCalled() - }) - - it('resumes following when the drag that paged in history ends at the tail', async () => { - vi.useFakeTimers() - try { - const shortChat = { - contentOffset: { y: 0 }, - contentSize: { height: 400 }, - layoutMeasurement: { height: 500 } - } - const folded = [assistantTurn('a1', 'Short history')] - const onLoadEarlier = vi.fn() - await render({ folded, hasMore: true, onLoadEarlier }) - - act(() => { - list().props.onScrollBeginDrag?.({}) - list().props.onScroll({ nativeEvent: shortChat }) - list().props.onScrollEndDrag?.({ nativeEvent: shortChat }) - }) - await act(async () => { - vi.advanceTimersByTime(200) - }) - act(() => list().props.onContentSizeChange(320, 950)) - - expect(onLoadEarlier).toHaveBeenCalledOnce() - expect(scrollToOffset).toHaveBeenLastCalledWith({ animated: false, offset: 950 }) - expect( - root().findAll((node) => node.props.accessibilityLabel === 'Scroll to latest') - ).toHaveLength(0) - } finally { - vi.useRealTimers() - } - }) - - it('pages in history only from a scroll the reader made', async () => { - const onLoadEarlier = vi.fn() - await render({ folded: [assistantTurn('a1', 'Latest')], hasMore: true, onLoadEarlier }) - const nearTop = { - nativeEvent: { - contentOffset: { y: 40 }, - contentSize: { height: 520 }, - layoutMeasurement: { height: 500 } - } - } - - act(() => list().props.onScroll(nearTop)) - expect(onLoadEarlier).not.toHaveBeenCalled() - - act(() => { - list().props.onScrollBeginDrag?.({}) - list().props.onScroll(nearTop) - }) - expect(onLoadEarlier).toHaveBeenCalledOnce() - }) - - it('anchors the first visible row, which the header above it is not', async () => { - const folded = [assistantTurn('a1', 'History')] - await render({ folded, hasMore: true }) - expect(list().props.maintainVisibleContentPosition).toEqual({ minIndexForVisible: 1 }) - - await update({ folded, hasMore: false }) - expect(list().props.maintainVisibleContentPosition).toEqual({ minIndexForVisible: 0 }) - }) - - describe('on the page, whose list is anchored at its end', () => { - const page = () => root().find((node) => hostType(node) === 'ScrollView') - const jumpControl = () => - root().findAll((node) => node.props.accessibilityLabel === 'Scroll to latest') - - beforeEach(() => { - platform.OS = 'web' - Object.assign(pageScroller, { scrollTop: 0, scrollHeight: 20_000, clientHeight: 500 }) - }) - - afterEach(() => { - platform.OS = 'ios' - }) - - function readerScrollsTo(y: number) { - act(() => { - pageScroller.scrollTop = y - page().props.onScroll(scrollEvent(y, pageScroller.scrollHeight)) - }) - } - - it('stops following once the reader scrolls off the end, and follows again back at it', async () => { - await render({ folded: [assistantTurn('a1', 'Latest')] }) - - readerScrollsTo(-600) - expect(jumpControl()).toHaveLength(1) - readerScrollsTo(0) - expect(jumpControl()).toHaveLength(0) - - readerScrollsTo(-600) - act(() => jumpControl()[0]!.props.onPress()) - expect(pageScroller.scrollTop).toBe(0) - act(() => page().props.onScroll(scrollEvent(0, pageScroller.scrollHeight))) - expect(jumpControl()).toHaveLength(0) - }) - - it('opens another chat at its newest message, not where the reader left the last one', async () => { - await render({ folded: [assistantTurn('a1', 'Chat A')] }) - readerScrollsTo(-600) - - await update({ folded: [assistantTurn('b1', 'Chat B')], sendSurfaceId: 'tab-b' }) - - expect(pageScroller.scrollTop).toBe(0) - expect(jumpControl()).toHaveLength(0) - }) - - it('asks for older history ahead of the top, and after a failure only the retry asks', async () => { - const onLoadEarlier = vi.fn() - const folded = [assistantTurn('a1', 'Latest')] - await render({ folded, hasMore: true, onLoadEarlier }) - expect(onLoadEarlier).not.toHaveBeenCalled() - - readerScrollsTo(-15_000) - expect(onLoadEarlier).toHaveBeenCalled() - - onLoadEarlier.mockClear() - await update({ folded, hasMore: true, olderPages: 'loading', onLoadEarlier }) - readerScrollsTo(-19_500) - await update({ folded, hasMore: true, olderPages: 'failed', onLoadEarlier }) - readerScrollsTo(-19_500) - expect(onLoadEarlier).not.toHaveBeenCalled() - - const retry = root().find( - (node) => hostType(node) === 'Pressable' && node.props.accessibilityRole === 'button' - ) - act(() => retry.props.onPress()) - expect(onLoadEarlier).toHaveBeenCalledOnce() - }) - - it('fills a chat that opens shorter than its reach without a scroll', async () => { - const onLoadEarlier = vi.fn() - pageScroller.scrollHeight = 600 - await render({ folded: [assistantTurn('a1', 'Latest')], hasMore: true, onLoadEarlier }) - expect(onLoadEarlier).toHaveBeenCalled() - }) - }) -}) - -vi.mock('../MobileQueuedMessageComposer', () => ({ - MobileQueuedMessageComposer: 'QueuedMessageComposer' -})) diff --git a/mobile/src/session/native-chat/composer/mobile-attachment-operation-lifetime.test.ts b/mobile/src/session/native-chat/composer/mobile-attachment-operation-lifetime.test.ts index 7c003b592de..244aeedd364 100644 --- a/mobile/src/session/native-chat/composer/mobile-attachment-operation-lifetime.test.ts +++ b/mobile/src/session/native-chat/composer/mobile-attachment-operation-lifetime.test.ts @@ -153,63 +153,3 @@ it.each(['files', 'images'] as const)( } } ) - -it('retains unique image identity across a real hook remount and removes only the chosen photo', async () => { - const client: RpcClient = { - sendRequest: vi.fn(async (method: string): Promise => ({ - id: 'r', - ok: true, - result: - method === 'clipboard.startImageUpload' - ? { uploadId: 'upload' } - : method === 'clipboard.commitImageUpload' - ? '/stored/photo.png' - : null, - _meta: { runtimeId: 'r' } - })), - subscribe: () => () => {}, - updateTerminalSubscriptionViewport: () => {}, - getState: () => 'connected', - getReconnectAttempt: () => 0, - getLastConnectedAt: () => null, - onStateChange: () => () => {}, - notifyForeground: () => {}, - close: () => {} - } - picker.pickImages.mockResolvedValue([{ base64: 'YQ==', uri: 'file:///photo.png' }]) - let hook!: ReturnType - function Probe() { - hook = useMobileNativeChatImageAttachments({ - client, - agent: 'codex', - activeHandleRef: { current: null }, - deviceTokenRef: { current: null }, - getActiveWorktreeConnectionId: async () => null, - connState: 'connected', - scopeKey: 'retained', - enabled: true, - structuredNativeChat: true, - showToast: vi.fn(), - onSendError: vi.fn(), - baseSend: vi.fn().mockResolvedValue('accepted'), - readSeededLaunchDraft: () => null - }) - return null - } - let mounted!: ReturnType - await act(async () => { - mounted = create(createElement(Probe)) - }) - await act(async () => hook.attachImage('library')) - const first = hook.attachments[0]!.id - act(() => mounted.unmount()) - await act(async () => { - mounted = create(createElement(Probe)) - }) - await act(async () => hook.attachImage('library')) - const second = hook.attachments[1]!.id - expect(second).not.toBe(first) - act(() => hook.removeAttachment(second)) - expect(hook.attachments.map(({ id }) => id)).toEqual([first]) - act(() => mounted.unmount()) -}) diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.test.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.test.ts deleted file mode 100644 index b78fd07f183..00000000000 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { prepareQueuedMessageEditText } from '../../../../../src/shared/queued-message-edit-command' -import { describe, expect, it } from 'vitest' -import { deriveComposerAutocomplete } from '../../../../../src/shared/native-chat/input/native-chat-composer-autocomplete' -import type { NativeChatSkillDiscoverySnapshot } from '../../../../../src/shared/native-chat/input/native-chat-picker-items' -import { - boundedMenuItems, - draftAfterSkillEdit, - narrowedMenuItems, - leadingSkillToken, - mobileComposerCatalog, - mobileComposerSkillTokens, - type MobileComposerStructuredCatalog -} from './mobile-native-chat-composer-menu' - -function mobileComposerSlashItems(input: { - agent: string - editing?: boolean - structured: MobileComposerStructuredCatalog | undefined - query: string - discovery?: NativeChatSkillDiscoverySnapshot -}) { - const catalog = mobileComposerCatalog(input) - const draft = `/${input.query}` - const result = deriveComposerAutocomplete( - draft, - draft.length, - catalog.commands, - catalog.discovery.skills, - catalog.profile, - catalog.discovery, - null, - catalog.sessionSkills, - input.editing - ? (name) => prepareQueuedMessageEditText(input.agent, input.structured?.reported)(`/${name}`) - : undefined - ) - return result.mode === 'slash' ? boundedMenuItems(result.items) : [] -} - -const reported = [ - { name: 'compact', kind: 'command' as const, description: 'Compact the chat' }, - { name: 'review-pr', kind: 'skill' as const, description: 'Review a pull request' }, - { name: 'research', kind: 'skill' as const } -] - -describe('mobileComposerSlashItems', () => { - it("offers the host's commands, then the session's own and its skills with their descriptions", () => { - const items = mobileComposerSlashItems({ - agent: 'claude', - structured: { reported, conversationCommands: ['clear', 'compact'] }, - query: '' - }) - expect(items.map((item) => [item.kind, item.token, item.description ?? null])).toEqual([ - ['command', '/model', 'Choose the model'], - ['command', '/effort', 'Choose reasoning effort'], - ['command', '/clear', 'Clear conversation context'], - ['command', '/compact', 'Compact conversation context'], - ['skill', '/research', null], - ['skill', '/review-pr', 'Review a pull request'] - ]) - }) - - it('names skills the way the agent invokes them', () => { - const items = mobileComposerSlashItems({ - agent: 'codex', - structured: { reported, conversationCommands: [] }, - query: 'review' - }) - expect(items.map((item) => item.token)).toEqual(['$review-pr']) - }) - - it('falls back to the host commands, with no skills, before the session reports', () => { - const items = mobileComposerSlashItems({ - agent: 'claude', - structured: { reported: undefined, conversationCommands: ['compact'] }, - query: '' - }) - expect(items.some((item) => item.token === '/compact')).toBe(true) - expect(items.some((item) => item.kind === 'skill')).toBe(false) - }) - - it("lists the host's scanned skills until the session reports its own", () => { - const discovery: NativeChatSkillDiscoverySnapshot = { - status: 'ready', - skills: [ - { - id: 'triage', - name: 'triage', - description: 'Triage an issue', - providers: ['agent-skills'], - sourceKind: 'repo', - sourceLabel: 'Repo', - rootPath: '/repo/.agents/skills', - directoryPath: '/repo/.agents/skills/triage', - skillFilePath: '/repo/.agents/skills/triage/SKILL.md', - installed: true, - updatedAt: null - } - ] - } - const tokens = (structured: MobileComposerStructuredCatalog | undefined) => - mobileComposerSlashItems({ agent: 'claude', structured, query: '', discovery }) - .filter((item) => item.kind === 'skill') - .map((item) => item.token) - expect(tokens(undefined)).toEqual(['/triage']) - expect(tokens({ reported: undefined, conversationCommands: [] })).toEqual(['/triage']) - expect(tokens({ reported, conversationCommands: [] })).toEqual(['/research', '/review-pr']) - }) - - it('keeps a terminal-backed chat on its curated commands', () => { - const items = mobileComposerSlashItems({ agent: 'claude', structured: undefined, query: '' }) - expect(items.length).toBeGreaterThan(0) - expect(items.every((item) => item.kind === 'command')).toBe(true) - }) -}) - -describe('mobileComposerSkillTokens', () => { - it('knows every skill the session loaded, though the menu draws only the first rows', () => { - const many = Array.from({ length: 60 }, (_, index) => ({ - name: `skill-${String(index).padStart(2, '0')}`, - kind: 'skill' as const - })) - const structured = { reported: many, conversationCommands: [] } - const drawn = mobileComposerSlashItems({ agent: 'claude', structured, query: '' }) - expect(drawn.filter((item) => item.kind === 'skill')).toHaveLength(10) - const tokens = mobileComposerSkillTokens(mobileComposerCatalog({ agent: 'claude', structured })) - expect(tokens.size).toBe(60) - expect(tokens.has('/skill-59')).toBe(true) - }) -}) - -describe('leadingSkillToken', () => { - const skills = new Set(['/review-pr', '$review-pr']) - - it('reads a known skill followed by a space at the start of the draft', () => { - expect(leadingSkillToken('/review-pr focus on auth', skills)).toBe('/review-pr') - expect(leadingSkillToken('/review-pr ', skills)).toBe('/review-pr') - }) - - it('reads nothing until the token is closed, or for a command or prose', () => { - expect(leadingSkillToken('/review-pr', skills)).toBeNull() - expect(leadingSkillToken('/compact now', skills)).toBeNull() - expect(leadingSkillToken(' /review-pr x', skills)).toBeNull() - }) -}) - -describe('draftAfterSkillEdit', () => { - it('takes the whole skill out when a delete reaches into it', () => { - expect(draftAfterSkillEdit('/review-pr ', '/review-pr', '/review-pr')).toBe('') - expect(draftAfterSkillEdit('/review-pr focus', '/review-prfocus', '/review-pr')).toBe('focus') - expect(draftAfterSkillEdit('/review-pr focus', '/review-p focus', '/review-pr')).toBe('focus') - }) - - it('keeps every other edit as typed', () => { - expect(draftAfterSkillEdit('/review-pr focus', '/review-pr focus!', '/review-pr')).toBe( - '/review-pr focus!' - ) - expect(draftAfterSkillEdit('/review-pr focus', '/review-pr focu', '/review-pr')).toBe( - '/review-pr focu' - ) - expect(draftAfterSkillEdit('/review-pr x', '/review-pXr x', '/review-pr')).toBe('/review-pXr x') - }) -}) - -describe('narrowedMenuItems', () => { - const items = mobileComposerSlashItems({ - agent: 'claude', - structured: { reported, conversationCommands: [] }, - query: '' - }) - - it('keeps one kind when the + sheet asks for it', () => { - expect(narrowedMenuItems(items, 'skill').map((item) => [item.kind, item.token])).toEqual([ - ['skill', '/research'], - ['skill', '/review-pr'] - ]) - }) - - it('shows everything when that kind is empty, as for a chat at rest', () => { - const commandsOnly = items.filter((item) => item.kind === 'command') - expect(narrowedMenuItems(commandsOnly, 'skill')).toEqual(commandsOnly) - }) -}) - -it('keeps edit-valid provider commands and namesake skills without changing the ordinary catalog', () => { - const input = { - agent: 'claude', - structured: { - reported: [ - { name: 'model', kind: 'skill' as const, path: '/model/SKILL.md' }, - { name: 'review', kind: 'command' as const }, - { name: 'goal', kind: 'command' as const } - ], - conversationCommands: ['clear' as const, 'compact' as const] - }, - query: '' - } - const ordinary = mobileComposerSlashItems(input) - const editing = mobileComposerSlashItems({ ...input, editing: true }) - expect(editing.map(({ kind, name }) => `${kind}:${name}`)).toEqual([ - 'command:review', - 'command:goal', - 'skill:model' - ]) - expect(ordinary.map(({ name }) => name)).toContain('compact') - expect(ordinary.map(({ kind, name }) => `${kind}:${name}`)).toContain('command:model') -}) diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.test.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.test.ts index e961e53790a..92f3390e5f7 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.test.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.test.ts @@ -2,10 +2,7 @@ import { describe, expect, it } from 'vitest' import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { findLandedImagePreviewEchoes, - findLandedUnconfirmedSends, - migrateImagePreviewMessageIds, - type PendingImagePreviewEcho, - type UnconfirmedSend + type PendingImagePreviewEcho } from './mobile-native-chat-draft-reconcile' function userText(id: string, text: string): NativeChatMessage { @@ -27,56 +24,6 @@ function pending( } describe('mobile native chat image preview reconciliation', () => { - it('reconciles a trailing-marker echo and hands its preview to that echo', () => { - const messages = [ - userText('source', '[Image: source: /tmp/a.png]'), - userText('prompt', 'look at this[Image #1]') - ] - const preview = { - ...pending('pending', ['file:///a.jpg']), - text: 'look at this' - } - const unconfirmed: UnconfirmedSend = { - draftKey: 'draft', - pendingKey: 'pending-key', - text: 'look at this', - normalizedText: 'look at this', - baselineTailMessageId: null, - deadline: null - } - - expect( - findLandedImagePreviewEchoes(messages, [{ ...preview, afterMessageId: 'prompt' }]) - ).toEqual([]) - expect(findLandedUnconfirmedSends(messages, [unconfirmed])).toEqual([unconfirmed]) - expect(findLandedImagePreviewEchoes(messages, [preview])).toEqual([ - { pendingId: 'pending', messageId: 'prompt', images: ['file:///a.jpg'] } - ]) - }) - - it('binds an image echo to the row it was glued into with a following send', () => { - // Adjacent TUI sends can land as concatenated text; image-preview retirement must still recognize that authoritative echo. - const messages = [ - userText('source', '[Image: source: /tmp/a.png]'), - userText('prompt', 'look at this[Image #1] is it still working?') - ] - const preview = { ...pending('pending', ['file:///a.jpg']), text: 'look at this' } - - expect(findLandedImagePreviewEchoes(messages, [preview])).toEqual([ - { pendingId: 'pending', messageId: 'prompt', images: ['file:///a.jpg'] } - ]) - }) - - it('does not bind an image echo to a row that merely shares a word', () => { - const messages = [ - userText('source', '[Image: source: /tmp/a.png]'), - userText('prompt', 'totally different[Image #1]') - ] - const preview = { ...pending('pending', ['file:///a.jpg']), text: 'look at this' } - - expect(findLandedImagePreviewEchoes(messages, [preview])).toEqual([]) - }) - it('does not bind a glued image echo to an ordinary row with the same prefix', () => { const messages = [ userText('ordinary', 'look at this later'), @@ -90,43 +37,6 @@ describe('mobile native chat image preview reconciliation', () => { ]) }) - it('reconciles a middle-marker echo without changing its rendered whitespace', () => { - const messages = [ - userText('source', '[Image: source: /tmp/a.png]'), - userText('prompt', 'look [Image #1] here') - ] - const preview = { ...pending('pending', ['file:///a.jpg']), text: 'look here' } - const unconfirmed: UnconfirmedSend = { - draftKey: 'draft', - pendingKey: 'pending-key', - text: 'look here', - normalizedText: 'look here', - baselineTailMessageId: null, - deadline: null - } - - expect(findLandedUnconfirmedSends(messages, [unconfirmed])).toEqual([unconfirmed]) - expect(findLandedImagePreviewEchoes(messages, [preview])).toEqual([ - { pendingId: 'pending', messageId: 'prompt', images: ['file:///a.jpg'] } - ]) - }) - - it('reconciles multiple transcript text blocks with desktop separators', () => { - const prompt: NativeChatMessage = { - ...userText('prompt', 'unused'), - blocks: [ - { type: 'text', text: 'look' }, - { type: 'image-ref', path: '/tmp/a.png' }, - { type: 'text', text: '[Image #1] here' } - ] - } - const preview = { ...pending('pending', ['file:///a.jpg']), text: 'look here' } - - expect(findLandedImagePreviewEchoes([prompt], [preview])).toEqual([ - { pendingId: 'pending', messageId: 'prompt', images: ['file:///a.jpg'] } - ]) - }) - it('keeps separate adjacent image-only sends independently reconcilable', () => { const landed = findLandedImagePreviewEchoes( [ @@ -180,49 +90,4 @@ describe('mobile native chat image preview reconciliation', () => { { pendingId: 'pending', messageId: 'new-prompt', images: ['file:///new.jpg'] } ]) }) - - it('moves an early standalone preview to the later folded prompt id', () => { - const sessionKey = 'host\0worktree\0tab\0session' - const previous = { [sessionKey]: { source: ['file:///a.jpg'] } } - const messages = [ - userText('source', '[Image: source: /tmp/a.png]'), - userText('prompt', '[Image #1]') - ] - - expect(migrateImagePreviewMessageIds(previous, sessionKey, messages)).toEqual({ - [sessionKey]: { prompt: ['file:///a.jpg'] } - }) - }) - - it('moves an early standalone preview to a trailing-marker prompt id', () => { - const sessionKey = 'host\0worktree\0tab\0session' - const previous = { [sessionKey]: { source: ['file:///a.jpg'] } } - const messages = [ - userText('source', '[Image: source: /tmp/a.png]'), - userText('prompt', 'look[Image #1]') - ] - - expect(migrateImagePreviewMessageIds(previous, sessionKey, messages)).toEqual({ - [sessionKey]: { prompt: ['file:///a.jpg'] } - }) - }) - - it('moves a preview when the prompt marker is in a later text block', () => { - const sessionKey = 'host\0worktree\0tab\0session' - const previous = { [sessionKey]: { source: ['file:///a.jpg'] } } - const prompt: NativeChatMessage = { - ...userText('prompt', 'unused'), - blocks: [ - { type: 'text', text: 'look' }, - { type: 'text', text: '[Image #1] here' } - ] - } - - expect( - migrateImagePreviewMessageIds(previous, sessionKey, [ - userText('source', '[Image: source: /tmp/a.png]'), - prompt - ]) - ).toEqual({ [sessionKey]: { prompt: ['file:///a.jpg'] } }) - }) }) diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts index 6a55c8f4d9b..c4bf647ba36 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts @@ -1,25 +1,13 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' import { afterEach, describe, expect, it } from 'vitest' -import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' + import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' import { - clearMobileNativeChatDraftForSend, - readMobileNativeChatDraftEditGeneration, - readMobileNativeChatPickedSkills, - editMobileNativeChatDraft, - readMobileNativeChatSkillOrigin, - recordMobileNativeChatSkillPick, - restoreMobileNativeChatPickedSkills, resetMobileNativeChatDraftStoreForTests, - setMobileNativeChatDraftText, useMobileNativeChatDraft } from './mobile-native-chat-draft-store' -import { - readMobileNativeChatDraftInputs, - updateMobileNativeChatDraftInputs, - resetMobileNativeChatDraftInputsForTests -} from './mobile-native-chat-draft-inputs' +import { resetMobileNativeChatDraftInputsForTests } from './mobile-native-chat-draft-inputs' type DraftState = ReturnType & { composerText: string } @@ -85,48 +73,6 @@ describe('mobile native-chat draft store', () => { expect(currentState().composerText).toBe('ping') }) - it('keeps main picked refs across Send-clear but revokes them on a real token edit', () => { - const scope = 'sent-skills' - setMobileNativeChatDraftText(scope, '$review draft') - updateMobileNativeChatDraftInputs(scope, () => ({ - images: [], - files: [], - skills: [{ name: 'review', path: '/remote/SKILL.md' }] - })) - const generation = readMobileNativeChatDraftEditGeneration(scope) - clearMobileNativeChatDraftForSend(scope, generation - 1, '$review draft') - expect(readMobileNativeChatDraftInputs(scope).skills).toHaveLength(1) - clearMobileNativeChatDraftForSend(scope, generation, '$review draft') - expect(readMobileNativeChatPickedSkills(scope)).toEqual([ - { name: 'review', path: '/remote/SKILL.md' } - ]) - editMobileNativeChatDraft(scope, 'different text') - expect(readMobileNativeChatPickedSkills(scope)).toEqual([]) - }) - - it('keeps a typed draft after the chat screen unmounts and remounts', async () => { - await mount('a') - act(() => currentState().setComposerText('half-written prompt')) - act(() => renderer?.unmount()) - - await mount('a') - expect(currentState().composerText).toBe('half-written prompt') - }) - - it('keeps each tab its own draft across a remount', async () => { - await mount('a') - act(() => currentState().setComposerText('for a')) - act(() => renderer?.unmount()) - - await mount('b') - expect(currentState().composerText).toBe('') - act(() => currentState().setComposerText('for b')) - act(() => renderer?.unmount()) - - await mount('a') - expect(currentState().composerText).toBe('for a') - }) - it('lets an old send clear its unchanged scope after another tab was edited', async () => { await mount('a') act(() => currentState().setComposerText('for a')) @@ -143,74 +89,4 @@ describe('mobile native-chat draft store', () => { await mount('a') expect(currentState().composerText).toBe('') }) - - it('redraws only the draft subscriber per keystroke, not the screen that owns the drafts', async () => { - let screenRenders = 0 - let shown = '' - let owner: ReturnType | null = null - function Composer({ draftKey }: { draftKey: string | null }): null { - shown = useMobileNativeChatDraft(draftKey) - return null - } - const messages: NativeChatMessage[] = [] - function Screen(): ReturnType { - screenRenders += 1 - owner = useMobileNativeChatDrafts({ - hostId: 'host', - worktreeId: 'worktree', - tabId: 'a', - sessionId: 'session-a', - messages, - transcriptSettled: true - }) - return createElement(Composer, { draftKey: owner.draftKey }) - } - await act(async () => { - renderer = create(createElement(Screen)) - }) - const rendersBeforeTyping = screenRenders - - act(() => owner?.setComposerText('typed')) - - expect(shown).toBe('typed') - expect(screenRenders).toBe(rendersBeforeTyping) - }) -}) - -describe('picked skills', () => { - afterEach(() => resetMobileNativeChatDraftStoreForTests()) - - it('forgets a pick once an edit deletes its token, even if the name is typed again', () => { - const review = { name: 'review', path: '/a/review/SKILL.md' } - setMobileNativeChatDraftText('draft', 'fix $review then ') - recordMobileNativeChatSkillPick('draft', { token: '$review', path: review.path }) - editMobileNativeChatDraft('draft', 'fix it then ') - editMobileNativeChatDraft('draft', 'fix it then $review') - expect(readMobileNativeChatPickedSkills('draft')).toEqual([]) - }) - - it('keeps a picked skill as the send origin through a send clear, until an edit stops it leading', () => { - editMobileNativeChatDraft('draft', '/triage ') - recordMobileNativeChatSkillPick('draft', { token: '/triage' }) - editMobileNativeChatDraft('draft', '/triage the flaky test') - setMobileNativeChatDraftText('draft', '') - setMobileNativeChatDraftText('draft', '/triage the flaky test') - expect(readMobileNativeChatSkillOrigin('draft')).toBe('/triage') - editMobileNativeChatDraft('draft', 'the flaky test') - expect(readMobileNativeChatSkillOrigin('draft')).toBeNull() - }) - - it('replaces the file a re-picked token named, and gives a rejected draft its files back', () => { - const first = { name: 'review', path: '/a/review/SKILL.md' } - const second = { name: 'review', path: '/b/review/SKILL.md' } - editMobileNativeChatDraft('draft', '$review ') - recordMobileNativeChatSkillPick('draft', { token: '$review', path: first.path }) - recordMobileNativeChatSkillPick('draft', { token: '$review', path: second.path }) - expect(readMobileNativeChatPickedSkills('draft')).toEqual([second]) - - editMobileNativeChatDraft('draft', 'something else') - setMobileNativeChatDraftText('draft', '$review ') - restoreMobileNativeChatPickedSkills('draft', [second]) - expect(readMobileNativeChatPickedSkills('draft')).toEqual([second]) - }) }) diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.test.tsx b/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.test.tsx deleted file mode 100644 index c60c71dee65..00000000000 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.test.tsx +++ /dev/null @@ -1,214 +0,0 @@ -// Keep pending actions per conversation and preserve child-work disclosure across temporarily empty rosters. - -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { AgentChildWorkView } from '../../../../../src/shared/agent-status/child-work/agent-status-child-work-view' -import type { AgentSessionBackgroundTaskState } from '../../../../../src/shared/agent-session/agent-session-background-task-wire' -import { agentChildRowContextForSessionStream } from '../../../../../src/shared/agent-status/presentation/agent-child-row-stream-context' -import { structuredSessionBackgroundTasksView } from '../../../../../src/shared/structured-session-background-tasks-view' -import { useMobileNativeChatComposerTray } from './use-mobile-native-chat-composer-tray' -import type { MobileStructuredBackgroundTasks } from '../../use-mobile-structured-background-tasks' - -vi.mock('../../../components/DragReorderList', () => ({ DragReorderList: 'DragReorderList' })) -vi.mock('react-native-reanimated', async () => { - const { useRef } = await import('react') - return { - default: { ScrollView: 'ScrollView' }, - useAnimatedRef: () => useRef(null), - useSharedValue: (value: unknown) => useRef({ value }).current - } -}) - -vi.mock('react-native', () => ({ - Dimensions: { get: () => ({ width: 430, height: 900 }) }, - useWindowDimensions: () => ({ fontScale: 1 }), - Pressable: 'Pressable', - ScrollView: 'ScrollView', - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - Text: 'Text', - View: 'View' -})) - -vi.mock('lucide-react-native', () => ({ - Activity: 'Activity', - AlertCircle: 'AlertCircle', - Bot: 'Bot', - ChevronDown: 'ChevronDown', - CircleHelp: 'CircleHelp', - SquareTerminal: 'SquareTerminal', - Workflow: 'Workflow', - CornerDownRight: 'CornerDownRight', - ListEnd: 'ListEnd', - MoreHorizontal: 'MoreHorizontal', - Pause: 'Pause', - Pencil: 'Pencil', - Play: 'Play', - Send: 'Send', - Trash2: 'Trash2' -})) - -vi.mock('../../../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' })) -vi.mock('../../../components/AgentStateDot', () => ({ - AGENT_WORKING_COLOR: '#eab308', - AgentStateDot: 'AgentStateDot' -})) -vi.mock('../../../hooks/use-now', () => ({ useNow: () => 100_000 })) - -const CARD = { - messageId: 'waiting-1', - position: 1, - text: 'next', - line: 'next', - state: 'waiting' as const, - hold: 'turn' as const, - needsAttention: false, - caption: null, - attribution: null -} - -const CHILD: AgentChildWorkView = { - id: 'a', - providerId: 'task-a', - kind: 'agent', - description: 'review a', - state: 'working', - membership: 'live', - firstObservedAt: 90_000, - observedAt: 99_000, - stoppable: true, - invocation: { invocationId: 'spawn-a', generation: 1 } -} -const RUNNING: AgentSessionBackgroundTaskState = { state: 'monitoring', children: [CHILD] } - -function tasks( - sessionKey: string, - state: AgentSessionBackgroundTaskState | null -): MobileStructuredBackgroundTasks { - return { - sessionKey, - view: structuredSessionBackgroundTasksView(state, null), - rowContext: agentChildRowContextForSessionStream(true, 0), - stop: vi.fn(async () => ({ status: 'rejected' as const })) - } -} - -const NEVER_RESUMES = (): Promise => new Promise(() => undefined) - -function Slot({ - sessionKey, - onResume = NEVER_RESUMES, - backgroundTasks = null -}: { - sessionKey: string - onResume?: () => Promise - backgroundTasks?: MobileStructuredBackgroundTasks | null -}): React.JSX.Element { - const tray = useMobileNativeChatComposerTray({ - queued: { cards: [CARD], pause: { reason: 'stopped' }, onResume, sessionKey }, - backgroundTasks - }) - return createElement('View', null, tray.content) -} - -describe('useMobileNativeChatComposerTray', () => { - let renderer: ReactTestRenderer | null = null - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - }) - - it("never lets one conversation's pending Resume disable another's", async () => { - const pending = vi.fn(() => new Promise(() => undefined)) - const mounted = create(createElement('View')) - renderer = mounted - const resume = () => - mounted.root.findByProps({ accessibilityLabel: 'Resume sending the queued messages' }) - await act(async () => { - mounted.update(createElement(Slot, { sessionKey: 'session-a', onResume: pending })) - }) - await act(async () => resume().props.onPress()) - expect(resume().props.disabled).toBe(true) - await act(async () => { - mounted.update(createElement(Slot, { sessionKey: 'session-b', onResume: pending })) - }) - expect(resume().props.disabled).toBe(false) - }) - - function stripRows(mounted: ReactTestRenderer) { - return mounted.root.findAll((node) => node.props.testID === 'background-task-row') - } - - function openStrip(mounted: ReactTestRenderer): void { - const header = mounted.root - .findByProps({ testID: 'background-tasks-strip' }) - .find( - (node) => - String(node.type) === 'Pressable' && node.props.accessibilityState?.expanded !== undefined - ) - act(() => header.props.onPress()) - } - - it('keeps the strip open across a moment with nothing running', async () => { - const mounted = create(createElement('View')) - renderer = mounted - await act(async () => { - mounted.update( - createElement(Slot, { sessionKey: 's-a', backgroundTasks: tasks('s-a', RUNNING) }) - ) - }) - openStrip(mounted) - expect(stripRows(mounted)).toHaveLength(1) - await act(async () => { - mounted.update( - createElement(Slot, { sessionKey: 's-a', backgroundTasks: tasks('s-a', null) }) - ) - }) - expect(stripRows(mounted)).toHaveLength(0) - await act(async () => { - mounted.update( - createElement(Slot, { sessionKey: 's-a', backgroundTasks: tasks('s-a', RUNNING) }) - ) - }) - expect(stripRows(mounted)).toHaveLength(1) - }) - - it("starts another conversation's strip closed", async () => { - const mounted = create(createElement('View')) - renderer = mounted - await act(async () => { - mounted.update( - createElement(Slot, { sessionKey: 's-a', backgroundTasks: tasks('s-a', RUNNING) }) - ) - }) - openStrip(mounted) - expect(stripRows(mounted)).toHaveLength(1) - await act(async () => { - mounted.update( - createElement(Slot, { sessionKey: 's-b', backgroundTasks: tasks('s-b', RUNNING) }) - ) - }) - expect(stripRows(mounted)).toHaveLength(0) - }) - - it('gives the strip and the cards of one conversation their own keys', async () => { - const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined) - const mounted = create(createElement('View')) - renderer = mounted - await act(async () => { - mounted.update( - createElement(Slot, { sessionKey: 's-a', backgroundTasks: tasks('s-a', RUNNING) }) - ) - }) - const duplicateKeys = consoleError.mock.calls.filter((call) => - String(call[0]).includes('same key') - ) - consoleError.mockRestore() - expect(duplicateKeys).toEqual([]) - }) -}) - -vi.mock('../../MobileQueuedMessageComposer', () => ({ - MobileQueuedMessageComposer: 'QueuedMessageComposer' -})) diff --git a/mobile/src/session/native-chat/mobile-native-chat-stopped-before-start.test.tsx b/mobile/src/session/native-chat/mobile-native-chat-stopped-before-start.test.tsx deleted file mode 100644 index 25e4845f8d9..00000000000 --- a/mobile/src/session/native-chat/mobile-native-chat-stopped-before-start.test.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { createElement } from 'react' -import { AlertTriangle } from 'lucide-react-native' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { agentJournalSubmissionKey } from '../../../../src/shared/agent-session/journal/agent-session-journal-item-key' -import type { - AgentJournalItemBody, - AgentJournalRenderItem, - AgentJournalSubmission -} from '../../../../src/shared/agent-session/journal/agent-session-journal-types' -import type { AgentJournalTurnScope } from '../../../../src/shared/agent-session/journal/agent-session-journal-turn-scope' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import { agentSessionFailureFact } from '../../../../src/shared/agent-session/failures/agent-session-failure' -import { agentSessionFailureWords } from '../../../../src/shared/agent-session/failures/agent-session-failure-words' -import { AGENT_SESSION_GOAL_CONTROL_COPY } from '../../../../src/shared/agent-session/view/agent-session-goal-control-copy' -import { DISPATCH_REJECTED_CANCELLED } from '../../../../src/shared/agent-session/requests/structured-agent-session-dispatch-rejection' -import { projectStructuredAgentSessionMessages } from '../../../../src/shared/agent-session/view/structured-agent-session-message-projection' -import { selectStructuredAgentSettledTurns } from '../../../../src/shared/agent-session/view/structured-agent-session-turn-timing' - -vi.mock('react-native', async () => { - const React = await import('react') - const Text = ({ children, ...props }: { children?: React.ReactNode }): React.ReactNode => - React.createElement('Text', props, children) - return { - ActivityIndicator: 'ActivityIndicator', - Animated: { - Text, - Value: class { - setValue(): void {} - }, - loop: (animation: unknown) => animation, - sequence: () => ({ start: vi.fn(), stop: vi.fn() }), - timing: () => ({ start: vi.fn(), stop: vi.fn() }) - }, - Image: 'Image', - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - Text, - View: ({ children, ...props }: { children?: React.ReactNode }) => - React.createElement('View', props, children), - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 } - } -}) -vi.mock('expo-clipboard', () => ({ setStringAsync: vi.fn() })) -vi.mock('lucide-react-native', () => ({ - AlertTriangle: 'AlertTriangle', - ArrowUp: 'ArrowUp', - Bot: 'Bot', - Brain: 'Brain', - Check: 'Check', - ChevronDown: 'ChevronDown', - ChevronRight: 'ChevronRight', - Copy: 'Copy', - Eye: 'Eye', - FileMinus2: 'FileMinus2', - FilePen: 'FilePen', - FilePlus2: 'FilePlus2', - Folder: 'Folder', - Globe: 'Globe', - ListChecks: 'ListChecks', - MessageSquareMore: 'MessageSquareMore', - Pencil: 'Pencil', - Plug: 'Plug', - Search: 'Search', - SquareChevronRight: 'SquareChevronRight', - SquareTerminal: 'SquareTerminal', - Wrench: 'Wrench' -})) -vi.mock('../../components/MobileMarkdown', () => ({ MobileMarkdown: () => null })) -vi.mock('./transcript/MobileNativeChatMessageActionsSheet', () => ({ - MobileNativeChatMessageActionsSheet: () => null -})) - -import { MobileMarkdown } from '../../components/MobileMarkdown' -import { MobileNativeChatMessage } from './transcript/MobileNativeChatMessage' -import { useMobileNativeChatTurnDisclosure } from './transcript/use-mobile-native-chat-turn-disclosure' - -const THREAD: AgentJournalTurnScope = { kind: 'thread' } -let sequence = 0 - -function item( - itemId: string, - body: AgentJournalItemBody, - turnScope: AgentJournalTurnScope = THREAD -): AgentJournalRenderItem { - sequence += 1 - return { itemId, revision: 0, sequence, observedAt: sequence, body, turnScope } -} - -const sent = (id: string, text: string) => - item(agentJournalSubmissionKey(id), { - kind: 'message', - role: 'user', - blocks: [{ type: 'text', text }] - }) - -function submission( - clientMessageId: string, - overrides: Partial = {} -): AgentJournalSubmission { - return { - clientMessageId, - fence: 1, - payloadFingerprint: clientMessageId, - dispatchState: 'accepted', - providerItemId: null, - reason: null, - submittedAt: 1, - resolvedAt: 2, - ...overrides - } -} - -const items = [ - sent('warm-up', 'warm up'), - item('t1', { - kind: 'turn', - turnId: 't1', - state: 'completed', - outcome: 'success', - userItemId: agentJournalSubmissionKey('warm-up'), - startedAt: 1_000, - completedAt: 4_000 - }), - item( - 't1-answer', - { kind: 'message', role: 'assistant', blocks: [{ type: 'text', text: 'Warmed up.' }] }, - { kind: 'turn', turnItemId: 't1' } - ), - sent('never-ran', 'look around') -] -const submissions = [ - submission('warm-up'), - submission('never-ran', { dispatchState: 'rejected', reason: DISPATCH_REJECTED_CANCELLED }) -] - -type Disclosure = ReturnType -let disclosure: Disclosure | null = null - -function Harness({ messages }: { messages: readonly NativeChatMessage[] }): null { - disclosure = useMobileNativeChatTurnDisclosure({ - messages, - enabled: true, - isWorking: false, - settledTurns: selectStructuredAgentSettledTurns(items, submissions), - turnJournal: { items, submissions }, - workingStartedAt: null, - scopeKey: 'host\0worktree\0tab-a' - }) - return null -} - -describe('a send a Stop took back before the agent started it, on the phone', () => { - let renderer: ReactTestRenderer | null = null - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - }) - - it.each(['pauseUnconfirmed', 'stopUnconfirmed'] as const)( - 'renders the host %s warning once after projection', - (stopNotice) => { - const messages = projectStructuredAgentSessionMessages( - [ - item('stop-outcome', { - kind: 'status', - tone: 'warning', - ...agentSessionFailureWords(agentSessionFailureFact('cancelUnconfirmed'), { - surface: 'row', - stopNotice - }) - }) - ], - [], - [] - ) - expect(messages).toHaveLength(1) - act(() => { - renderer = create(createElement(MobileNativeChatMessage, { message: messages[0]! })) - }) - expect( - renderer!.root.findAll((node) => String(node.type) === 'Text').map((node) => node.children) - ).toEqual([[AGENT_SESSION_GOAL_CONTROL_COPY[stopNotice]]]) - expect(renderer!.root.findAllByType(AlertTriangle)).toHaveLength(1) - } - ) - - it('stays where it was sent, with the stop row after it and no turn status of its own', () => { - const messages = projectStructuredAgentSessionMessages(items, [], submissions) - expect(messages.map((message) => message.id)).toEqual([ - agentJournalSubmissionKey('warm-up'), - 't1-answer', - agentJournalSubmissionKey('never-ran'), - `stopped-before-start:${agentJournalSubmissionKey('never-ran')}` - ]) - - act(() => { - renderer = create(createElement(Harness, { messages })) - }) - const statuses = messages.map( - (message, index) => disclosure?.resolveRow(index, message).turnStatus ?? null - ) - expect(statuses[0]?.workedSeconds).toBe(3) - expect(statuses.slice(2)).toEqual([null, null]) - - act(() => renderer?.unmount()) - act(() => { - renderer = create(createElement(MobileNativeChatMessage, { message: messages[3]! })) - }) - expect(renderer!.root.findAllByType(MobileMarkdown)).toEqual([]) - expect( - renderer!.root.findAll((node) => String(node.type) === 'Text').map((node) => node.children) - ).toEqual([['Stopped manually']]) - }) -}) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.paced-reveal.test.ts b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.paced-reveal.test.ts deleted file mode 100644 index 5e961a3c98b..00000000000 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.paced-reveal.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' -import { - createNativeChatReplyRevealTracker, - trackNativeChatReplyRows -} from '../../../../../src/shared/native-chat/transcript/native-chat-reply-reveals' -import { NATIVE_CHAT_TEXT_REVEAL_DELAY_MS } from '../../../../../src/shared/native-chat/transcript/native-chat-text-reveal' -import { - MobileNativeChatReplyRevealsContext, - type MobileNativeChatReplyReveals -} from './mobile-native-chat-reply-reveals' -import { - createMobileNativeChatGrowthHold, - MobileNativeChatGrowthHoldContext -} from '../../mobile-native-chat-growth-hold' - -vi.mock('react-native', () => ({ - Image: 'Image', - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - Text: 'Text', - View: 'View', - StyleSheet: { create: (value: unknown) => value, hairlineWidth: 1 } -})) -vi.mock('lucide-react-native', () => ({ Copy: 'Copy', Check: 'Check' })) -vi.mock('../../../components/MobileMarkdown', () => ({ MobileMarkdown: 'MobileMarkdown' })) -vi.mock('../../../platform/clipboard', () => ({ - useClipboardWriter: () => ({ writeText: vi.fn() }) -})) -vi.mock('./MobileNativeChatMessageActionsSheet', () => ({ - MobileNativeChatMessageActionsSheet: 'MessageActionsSheet' -})) -vi.mock('./MobileNativeChatReasoningRow', () => ({ MobileNativeChatReasoningRow: 'Reasoning' })) -vi.mock('./MobileNativeChatTurnStatus', () => ({ MobileNativeChatTurnStatus: 'TurnStatus' })) -vi.mock('./MobileNativeChatToolRun', () => ({ ToolRun: 'ToolRun' })) - -import { MobileNativeChatMessage } from './MobileNativeChatMessage' - -const DELAY = NATIVE_CHAT_TEXT_REVEAL_DELAY_MS -const first = 'word '.repeat(40) -const second = first + 'more '.repeat(40) - -describe('a growing reply on the phone', () => { - let renderer: ReactTestRenderer | undefined - let chat: MobileNativeChatReplyReveals - let growth = createMobileNativeChatGrowthHold() - - function render(text: string | string[], working = true, runningTool = false): void { - const message: NativeChatMessage = { - id: 'a1', - role: 'assistant', - source: 'transcript', - timestamp: null, - blocks: (typeof text === 'string' ? [text] : text).map((part) => ({ - type: 'text', - text: part - })) - } - if (runningTool) { - message.blocks.push({ type: 'tool-call', name: 'Read', input: {}, state: 'running' }) - } - const row = createElement(MobileNativeChatMessage, { - message, - mayStillGrow: working, - activeTurnIsWorking: true - }) - const revealing = createElement( - MobileNativeChatReplyRevealsContext.Provider, - { value: chat }, - row - ) - const tree = createElement( - MobileNativeChatGrowthHoldContext.Provider, - { value: growth }, - revealing - ) - act(() => { - if (renderer) { - renderer.update(tree) - } else { - renderer = create(tree) - } - }) - } - function contents(): string[] { - return renderer!.root - .findAll((node) => String(node.type) === 'MobileMarkdown') - .map((node) => String(node.props.content)) - } - function drawn(): number { - return contents().join('').length - } - function advance(ms: number): void { - act(() => { - vi.advanceTimersByTime(ms) - }) - } - function remount(text: string | string[]): void { - act(() => renderer?.unmount()) - renderer = undefined - render(text) - } - - beforeEach(() => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - vi.spyOn(performance, 'now').mockImplementation(() => Date.now()) - vi.stubGlobal('requestAnimationFrame', (step: () => void) => setTimeout(step, 16)) - vi.stubGlobal('cancelAnimationFrame', (frame: number) => clearTimeout(frame)) - const tracker = createNativeChatReplyRevealTracker() - trackNativeChatReplyRows(tracker, ['u1'], new Set(['u1'])) - const reveals = trackNativeChatReplyRows(tracker, ['u1', 'a1'], new Set(['u1', 'a1'])) - chat = { ...reveals, reducedMotion: false } - }) - afterEach(() => { - act(() => renderer?.unmount()) - renderer = undefined - growth = createMobileNativeChatGrowthHold() - vi.unstubAllGlobals() - vi.restoreAllMocks() - vi.useRealTimers() - }) - - it('reveals each chunk over time, and a remounted row resumes instead of replaying', () => { - render(first) - expect(drawn()).toBe('word'.length) - advance(DELAY / 2) - expect(drawn()).toBeGreaterThan('word'.length) - expect(drawn()).toBeLessThan(first.length) - advance(DELAY) - expect(drawn()).toBe(first.length) - - render(second) - advance(DELAY / 2) - const before = drawn() - expect(before).toBeGreaterThan(first.length) - expect(before).toBeLessThan(second.length) - remount(second) - expect(drawn()).toBeGreaterThanOrEqual(before) - expect(drawn()).toBeLessThan(second.length) - advance(DELAY) - expect(drawn()).toBe(second.length) - }) - - it('waits while the page holds growth, then catches up in one step', () => { - growth.set(true) - render(first) - const shown = drawn() - advance(DELAY * 2) - expect(drawn()).toBe(shown) - act(() => growth.set(false)) - advance(16) - expect(drawn()).toBe(first.length) - }) - - it.each([false, true])('paces a second prose block after the first caught up: %s', (caughtUp) => { - render(first) - advance(caughtUp ? DELAY : DELAY / 2) - render([first, second]) - if (!caughtUp) { - expect(contents()[0].length).toBeLessThan(first.length) - } - expect(contents()[1].length).toBeLessThan(second.length) - advance(DELAY / 2) - const before = drawn() - expect(before).toBeGreaterThan(first.length) - expect(before).toBeLessThan(first.length + second.length) - remount([first, second]) - expect(drawn()).toBeGreaterThanOrEqual(before) - expect(drawn()).toBeLessThan(first.length + second.length) - advance(DELAY) - expect(contents()).toEqual([first, second]) - }) - - it('does not replay read text when a question ends while the row is unmounted', () => { - render(first) - advance(DELAY) - render(first, false) - expect(chat.drawn.has('a1')).toBe(false) - act(() => renderer?.unmount()) - renderer = undefined - render(first) - expect(drawn()).toBe(first.length) - render(second) - expect(drawn()).toBeLessThan(second.length) - advance(DELAY) - expect(drawn()).toBe(second.length) - }) - - it('draws completed prose in full on first mount beside a running tool', () => { - render(first, true, true) - expect(contents()).toEqual([first]) - const tool = renderer!.root.find((node) => String(node.type) === 'ToolRun') - expect(tool.props.blocks[0].state).toBe('running') - }) - - it('draws the text as it arrives with reduced motion', () => { - chat = { ...chat, reducedMotion: true } - render(second) - expect(drawn()).toBe(second.length) - }) -}) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.reasoning.test.ts b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.reasoning.test.ts deleted file mode 100644 index 472de0757b9..00000000000 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.reasoning.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' -import type { NativeChatResolvedPrompt } from '../../../../../src/shared/native-chat/native-chat-resolution-receipt' - -vi.mock('react-native', async () => { - const React = await import('react') - const Text = ({ children, ...props }: { children?: ReactNode }): unknown => - React.createElement('Text', props, children) - return { - AppState: { currentState: 'active', addEventListener: () => ({ remove: () => {} }) }, - ActivityIndicator: 'ActivityIndicator', - Animated: { - Text, - Value: class { - setValue(): void {} - }, - loop: (animation: unknown) => animation, - sequence: () => ({ start: vi.fn(), stop: vi.fn() }), - timing: () => ({ start: vi.fn(), stop: vi.fn() }) - }, - Image: 'Image', - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - ScrollView: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('ScrollView', props, children), - Text, - View: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('View', props, children), - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - useWindowDimensions: () => ({ width: 400, height: 800 }) - } -}) -vi.mock('expo-clipboard', () => ({ setStringAsync: vi.fn() })) -vi.mock('lucide-react-native', () => ({ - AlertCircle: 'AlertCircle', - AlertTriangle: 'AlertTriangle', - ArrowUp: 'ArrowUp', - Bot: 'Bot', - Brain: 'Brain', - Check: 'Check', - ChevronDown: 'ChevronDown', - ChevronRight: 'ChevronRight', - Copy: 'Copy', - Eye: 'Eye', - FileMinus2: 'FileMinus2', - FilePen: 'FilePen', - FilePlus2: 'FilePlus2', - Folder: 'Folder', - Globe: 'Globe', - ListChecks: 'ListChecks', - MessageSquareMore: 'MessageSquareMore', - Pencil: 'Pencil', - Plug: 'Plug', - Search: 'Search', - SquareChevronRight: 'SquareChevronRight', - SquareTerminal: 'SquareTerminal', - Wrench: 'Wrench' -})) -vi.mock('../../../components/MobileMarkdown', () => ({ MobileMarkdown: 'MobileMarkdown' })) -vi.mock('./MobileNativeChatMessageActionsSheet', () => ({ - MobileNativeChatMessageActionsSheet: 'MessageActionsSheet' -})) - -import { MobileNativeChatMessage } from './MobileNativeChatMessage' -import { findAllByHostType } from '../../../test-support/react-test-renderer-host-nodes' - -describe('MobileNativeChatMessage', () => { - let renderer: ReactTestRenderer | null = null - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - }) - - function render( - message: NativeChatMessage, - props: { - fontScale?: number - structuredActivityUi?: boolean - activeTurnIsWorking?: boolean - trailingRun?: boolean - foldedAway?: boolean - turnExpanded?: boolean - turnStatus?: { - startedAt: number | null - thinking: boolean - workedSeconds: number | null - } | null - onToggleTurn?: () => void - reasoningIsLive?: boolean - unsentNotice?: string - receipt?: NativeChatResolvedPrompt - userMessageOpen?: boolean - onToggleUserMessage?: (messageId: string) => void - awaiting?: boolean - } = {} - ): ReactTestRenderer { - act(() => { - renderer = create(createElement(MobileNativeChatMessage, { message, ...props })) - }) - return renderer! - } - - const textIn = (node: ReactTestInstance): string[] => - findAllByHostType(node, 'Text').map((text) => String(text.children.join(''))) - - describe('a reasoning row', () => { - const reasoning = (fields: Partial = {}): NativeChatMessage => ({ - id: 'r1', - role: 'reasoning', - blocks: [{ type: 'text', text: 'Weighing two approaches' }], - timestamp: 1_000, - source: 'transcript', - state: 'completed', - completedAt: 4_000, - ...fields - }) - const toggleOf = (tree: ReactTestRenderer): ReactTestInstance => - tree.root.find( - (node) => String(node.type) === 'Pressable' && node.props.accessibilityRole === 'button' - ) - const markdownIn = (tree: ReactTestRenderer): ReactTestInstance[] => - tree.root.findAll((node) => String(node.type) === 'MobileMarkdown') - - it('starts collapsed to its headline, with its text unmounted', () => { - const tree = render(reasoning()) - expect(textIn(tree.root)).toContain('Thought for 3s') - expect(toggleOf(tree).props.accessibilityState).toEqual({ expanded: false }) - expect(toggleOf(tree).props.accessibilityLabel).toBe('Reasoning: Thought for 3s') - expect(toggleOf(tree).props.hitSlop).toBe(6) - expect(markdownIn(tree)).toHaveLength(0) - }) - - it('leads its headline with the brain, as desktop does', () => { - const [first] = toggleOf(render(reasoning())).children - expect(typeof first === 'string' ? first : first?.type).toBe('Brain') - }) - - it('mounts its text once opened', () => { - const tree = render(reasoning()) - act(() => toggleOf(tree).props.onPress()) - expect(toggleOf(tree).props.accessibilityState).toEqual({ expanded: true }) - expect(markdownIn(tree).map((node) => node.props.content)).toEqual([ - 'Weighing two approaches' - ]) - }) - - it('draws nothing while the live line discloses it, or when blank', () => { - expect( - render(reasoning({ state: 'running' }), { - activeTurnIsWorking: true, - reasoningIsLive: true - }).toJSON() - ).toBeNull() - expect(render(reasoning({ blocks: [{ type: 'text', text: ' \n ' }] })).toJSON()).toBeNull() - }) - - it("still draws its turn's bar while the live line discloses it", () => { - const tree = render(reasoning({ state: 'running' }), { - activeTurnIsWorking: true, - reasoningIsLive: true, - turnStatus: { startedAt: 1_000, thinking: true, workedSeconds: null } - }) - expect(tree.root.findAll((node) => String(node.type) === 'Pressable')).toHaveLength(0) - expect(textIn(tree.root).some((text) => text.startsWith('Working for'))).toBe(true) - }) - - it('draws any other open block in its working turn as Reasoning', () => { - const child = render(reasoning({ state: 'running', agentId: 'sub-1' }), { - activeTurnIsWorking: true - }) - expect(textIn(child.root)).toContain('Reasoning') - expect(toggleOf(child).props.accessibilityLabel).toBe('Reasoning') - }) - - it('says only what the host saw', () => { - expect(textIn(render(reasoning({ state: 'running' })).root)).toContain('Thought') - const unknown = render(reasoning({ state: undefined, completedAt: undefined })) - expect(textIn(unknown.root)).toContain('Reasoning') - expect(toggleOf(unknown).props.accessibilityLabel).toBe('Reasoning') - expect(textIn(render(reasoning({ completedAt: 1_300 })).root)).toContain('Thought for 1s') - }) - }) -}) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.subagent-group.test.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.subagent-group.test.tsx deleted file mode 100644 index 0c5406b3260..00000000000 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.subagent-group.test.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { - NativeChatMessage, - NativeChatSubagentEntry -} from '../../../../../src/shared/native-chat/native-chat-types' -import { Harness, Result, userMessage } from './use-mobile-native-chat-turn-disclosure.test-fixture' - -vi.mock('react-native', async () => { - const React = await import('react') - const Text = ({ children, ...props }: { children?: ReactNode }): ReactNode => - React.createElement('Text', props, children) - return { - Image: 'Image', - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - ScrollView: 'ScrollView', - Text, - View: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('View', props, children), - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - Animated: { Value: class {}, timing: () => ({ start: vi.fn() }) } - } -}) -vi.mock('expo-clipboard', () => ({ setStringAsync: vi.fn() })) -vi.mock('lucide-react-native', () => ({ - Bot: 'Bot', - Brain: 'Brain', - Check: 'Check', - ChevronDown: 'ChevronDown', - ChevronRight: 'ChevronRight', - Eye: 'Eye', - FileMinus2: 'FileMinus2', - FilePen: 'FilePen', - FilePlus2: 'FilePlus2', - Folder: 'Folder', - Globe: 'Globe', - ListChecks: 'ListChecks', - MessageSquareMore: 'MessageSquareMore', - Pencil: 'Pencil', - Plug: 'Plug', - Search: 'Search', - SquareChevronRight: 'SquareChevronRight', - SquareTerminal: 'SquareTerminal', - Wrench: 'Wrench' -})) -vi.mock('../../../components/MobileMarkdown', () => ({ MobileMarkdown: 'MobileMarkdown' })) -vi.mock('./MobileNativeChatMessageActionsSheet', () => ({ - MobileNativeChatMessageActionsSheet: 'MessageActionsSheet' -})) -vi.mock('../../../hooks/use-now', () => ({ useNow: () => 70_000 })) - -import { MobileNativeChatMessage } from './MobileNativeChatMessage' - -function roster( - agents: NativeChatSubagentEntry[], - sentence: string | null = 'Kicked off 2 subagents' -): NativeChatMessage { - return { - id: 'spawn', - role: 'system', - blocks: [ - ...(sentence === null ? [] : [{ type: 'text' as const, text: sentence }]), - { type: 'subagent-group', groupId: 'group-1', agents } - ], - timestamp: null, - source: 'transcript' - } -} - -const WORKING: NativeChatSubagentEntry[] = [ - { id: 'a', label: 'review', state: 'working', startedAt: 10_000 }, - { id: 'b', label: 'tests', state: 'working', startedAt: 10_000 } -] -const FINISHED: NativeChatSubagentEntry[] = [ - { id: 'a', label: 'review', state: 'completed', startedAt: 0, settledAt: 60_000, tokens: 9_000 }, - { id: 'b', label: 'tests', state: 'failed', startedAt: 0, settledAt: 62_000, tokens: 3_000 } -] - -// The View mock is a component around a host node; match the host alone so nothing counts twice. -function byTestId(node: ReactTestInstance, testID: string): boolean { - return typeof node.type === 'string' && node.props.testID === testID -} - -function textOf(node: ReactTestInstance): string { - return node.children.map((child) => (typeof child === 'string' ? child : textOf(child))).join('') -} - -describe('mobile transcript roster row', () => { - let renderer: ReactTestRenderer | null = null - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - }) - - function render( - message: NativeChatMessage, - props: Record = {} - ): ReactTestRenderer { - act(() => { - renderer = create(createElement(MobileNativeChatMessage, { message, ...props })) - }) - return renderer! - } - - const markdown = (tree: ReactTestRenderer): unknown[] => - tree.root.findAll((node) => String(node.type) === 'MobileMarkdown').map((n) => n.props.content) - const aside = (tree: ReactTestRenderer): string[] => - tree.root.findAll((node) => String(node.type) === 'Text').map(textOf) - const group = (tree: ReactTestRenderer): ReactTestInstance[] => - tree.root.findAll((node) => byTestId(node, 'subagent-group')) - - it("draws the host's live group in place of its frozen sentence", () => { - const tree = render(roster(WORKING)) - expect(markdown(tree)).toEqual([]) - expect(group(tree)).toHaveLength(1) - expect(textOf(group(tree)[0]!)).toBe('Kicked off 2 subagents2 working · 1m 0s') - }) - - it('reads the settled verdict, run length and tokens', () => { - const tree = render(roster(FINISHED, 'Ran 2 subagents (1 failed)')) - expect(markdown(tree)).toEqual([]) - expect(textOf(group(tree)[0]!)).toBe('Ran 2 subagents1 failed · 1m 2s · 12k tokens') - }) - - it('speaks the header whole, its clock only once the group stops counting', () => { - const label = (tree: ReactTestRenderer): unknown => - group(tree)[0]!.findAll((node) => String(node.type) === 'Pressable')[0]!.props - .accessibilityLabel - expect(label(render(roster(WORKING)))).toBe('Kicked off 2 subagents · 2 working') - const alerted: NativeChatSubagentEntry[] = [ - ...WORKING, - { id: 'c', label: 'lint', state: 'failed', startedAt: 0, settledAt: 5_000 } - ] - expect(label(render(roster(alerted)))).toBe('Kicked off 3 subagents · 2 working +1 failed') - expect(label(render(roster(FINISHED)))).toBe('Ran 2 subagents · 1 failed · 1m 2s · 12k tokens') - }) - - it('keeps the sentence when there is no group it can draw', () => { - const textOnly: NativeChatMessage = { - id: 'spawn', - role: 'system', - blocks: [{ type: 'text', text: 'Ran 2 subagents' }], - timestamp: null, - source: 'transcript' - } - expect(aside(render(textOnly))).toEqual(['Ran 2 subagents']) - const childless = render(roster([], 'Ran 2 subagents')) - expect(aside(childless)).toEqual(['Ran 2 subagents']) - expect(group(childless)).toEqual([]) - }) - - it('keeps real text beside a group', () => { - const tree = render(roster(WORKING, 'Delegating the review.')) - expect(aside(tree)[0]).toBe('Delegating the review.') - expect(group(tree)).toHaveLength(1) - }) - - it('stays visible once its turn settles, outside the tool-run collapse', () => { - const tree = render(roster(FINISHED, null), { - structuredActivityUi: true, - activeTurnIsWorking: false, - turnExpanded: false - }) - expect(group(tree)).toHaveLength(1) - }) - - it('lists each child when the transcript holds it open, and asks to toggle by group id', () => { - const onToggleSubagentGroup = vi.fn() - const closed = render(roster(FINISHED), { onToggleSubagentGroup }) - expect(closed.root.findAll((node) => byTestId(node, 'subagent-group-entry'))).toEqual([]) - const header = group(closed)[0]!.findAll((node) => String(node.type) === 'Pressable')[0]! - act(() => header.props.onPress()) - expect(onToggleSubagentGroup).toHaveBeenCalledWith('group-1') - - const open = render(roster(FINISHED), { - subagentGroupsOpen: new Set(['group-1']), - onToggleSubagentGroup - }) - expect(open.root.findAll((node) => byTestId(node, 'subagent-group-entry')).map(textOf)).toEqual( - ['reviewcompleted · 9k', 'testsfailed · 3k'] - ) - }) -}) - -describe('transcript-held roster disclosure', () => { - let renderer: ReactTestRenderer | null = null - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - }) - - it('opens a group by id and hands the open set to roster rows only', () => { - const messages = [userMessage('ask'), roster(WORKING)] - act(() => { - renderer = create(createElement(Harness, { messages, enabled: true, isWorking: false })) - }) - const disclosure = () => renderer!.root.findByType(Result).props.disclosure - const rowFor = (index: number) => disclosure().resolveRow(index, messages[index]!) - expect(rowFor(0).subagentGroupsOpen).toBeUndefined() - expect(rowFor(1).subagentGroupsOpen?.has('group-1')).toBe(false) - act(() => rowFor(1).onToggleSubagentGroup('group-1')) - expect(rowFor(1).subagentGroupsOpen?.has('group-1')).toBe(true) - }) -}) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.web.test.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.web.test.tsx deleted file mode 100644 index 2bb562b401b..00000000000 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.web.test.tsx +++ /dev/null @@ -1,210 +0,0 @@ -// @vitest-environment happy-dom -import { MOBILE_WEB_APP_NATIVE_PARITY_STYLE } from '../../../../../config/scripts/build-mobile-web-app-bundle.mjs' -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { MobileNativeChatMessage } from './MobileNativeChatMessage' -import { MobileNativeChatRowOverlaysContext } from './mobile-native-chat-row-overlays' -import type * as ReactNative from 'react-native' -import type * as ReactNativeSvg from 'react-native-svg' -import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' - -vi.mock('react-native', () => vi.importActual('react-native-web')) -// Use the same SVG web host selected by the page bundler, overriding the native setup stand-in. -vi.mock('react-native-svg', async () => { - const { createRequire } = await import('node:module') - const { resolve } = await import('node:path') - // Anchor the mobile-only dependency here, even when the mocker runs from root dependencies. - const fromMobile = createRequire(resolve('package.json')) - return vi.importActual( - fromMobile.resolve('react-native-svg/lib/module/elements.web.js') - ) -}) -vi.mock('../../../components/MobileMarkdown', () => ({ MobileMarkdown: () => null })) -vi.mock('../../../components/MobileSourceCopyButton', () => ({ - MobileSourceCopyButton: () => null -})) - -const cases = [ - [ - 'codex', - 'notification:guardianWarning', - 'Codex reported a safety review warning. View details.' - ], - ['claude', 'message:system:permission_denied', 'Claude denied permission. View details.'] -] as const -let root: Root -let container: HTMLDivElement -let policy: HTMLDivElement -const openActions = vi.fn() -const overlays = { openActions, openImage: () => {} } -const text = `${'安全 '.repeat(1500)}EXPLANATION_SENTINEL` -const head = `{"partial":"${'é'.repeat(4500)}PAYLOAD_SENTINEL` - -beforeEach(() => { - policy = document.createElement('div') - policy.innerHTML = MOBILE_WEB_APP_NATIVE_PARITY_STYLE - document.head.append(policy) - container = document.createElement('div') - document.body.append(container) - root = createRoot(container) - openActions.mockClear() -}) -afterEach(() => { - act(() => root.unmount()) - container.remove() - policy.remove() - window.getSelection()?.removeAllRanges() -}) -function render(provider: string, kind: string) { - const notice: NativeChatMessage = { - id: 'notice', - role: 'system', - source: 'transcript', - timestamp: null, - blocks: [ - { - type: 'text', - tone: provider === 'claude' ? 'error' : 'warning', - text, - providerFrame: { - provider, - kind, - payload: { head, byteLength: 99999, digest: 'synthetic', truncated: true } - } - } - ] - } - act(() => - root.render( - - - - - ) - ) - const button = container.querySelector('[role="button"]') - if (!(button instanceof HTMLElement)) { - throw new Error('Missing notice disclosure') - } - return button -} -function touch(node: Element, type: 'touchstart' | 'touchend') { - const finger = { identifier: 1, target: node, pageX: 10, pageY: 10, clientX: 10, clientY: 10 } - node.dispatchEvent( - Object.assign(new Event(type, { bubbles: true, cancelable: true }), { - touches: type === 'touchstart' ? [finger] : [], - changedTouches: [finger] - }) - ) -} -function select(node: Element) { - const range = document.createRange() - const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT) - const first = walker.nextNode()! - let last = first - while (walker.nextNode()) { - last = walker.currentNode - } - range.setStart(first, 0) - range.setEnd(last, last.textContent!.length) - window.getSelection()!.removeAllRanges() - window.getSelection()!.addRange(range) - document.dispatchEvent(new Event('selectionchange')) -} -function tap(node: HTMLElement) { - act(() => { - touch(node, 'touchstart') - touch(node, 'touchend') - node.click() - }) -} - -it.each(cases)( - 'keeps %s summary selection separate from activation in the web page', - (provider, kind, heading) => { - const button = render(provider, kind) - const summary = Array.from(button.querySelectorAll('div')).find( - (node) => node.textContent === heading - )! - const control = Array.from(container.querySelectorAll('div')).find( - (node) => node.childElementCount === 0 && node.textContent === 'Ordinary warning control' - )! - expect(getComputedStyle(document.body).userSelect).toBe('none') - expect(getComputedStyle(control).userSelect).toBe('text') - expect(getComputedStyle(summary).userSelect).toBe('text') - expect(container.querySelectorAll('[role="button"]')).toHaveLength(1) - expect(button.getAttribute('aria-label')).toBe(heading) - expect(button.tabIndex).toBe(0) - expect(button.getAttribute('aria-expanded')).toBe('false') - expect(container.textContent!.includes('EXPLANATION_SENTINEL')).toBe(false) - act(() => { - touch(summary, 'touchstart') - select(summary) - touch(summary, 'touchend') - summary.click() - }) - expect(window.getSelection()!.toString()).toBe(heading) - expect(button.getAttribute('aria-expanded')).toBe('false') - expect(container.textContent!.includes('EXPLANATION_SENTINEL')).toBe(false) - expect(openActions).not.toHaveBeenCalled() - window.getSelection()!.removeAllRanges() - tap(button) - expect(button.getAttribute('aria-expanded')).toBe('true') - expect(container.textContent!.includes('EXPLANATION_SENTINEL')).toBe(true) - const detail = Array.from(container.querySelectorAll('div')).find( - (node) => node.childElementCount === 0 && node.textContent?.startsWith(text) - )! - expect(detail.textContent).toContain(text) - expect(detail.textContent).toContain(head) - expect(detail.textContent).toContain( - 'Provider response truncated (original size: 99999 bytes).' - ) - expect(getComputedStyle(detail).userSelect).toBe('text') - act(() => select(detail)) - expect(window.getSelection()!.toString()).toContain('PAYLOAD_SENTINEL') - expect(button.getAttribute('aria-expanded')).toBe('true') - expect(container.textContent!.includes('EXPLANATION_SENTINEL')).toBe(true) - window.getSelection()!.removeAllRanges() - tap(button) - expect(button.getAttribute('aria-expanded')).toBe('false') - expect(container.textContent!.includes('EXPLANATION_SENTINEL')).toBe(false) - act(() => { - expect(button.tagName).toBe('BUTTON') - // happy-dom does not synthesize a native button's keyboard-origin click. - button.dispatchEvent(new MouseEvent('click', { bubbles: true, detail: 0 })) - }) - expect(button.getAttribute('aria-expanded')).toBe('true') - expect(container.textContent!.includes('EXPLANATION_SENTINEL')).toBe(true) - tap(button) - expect(button.getAttribute('aria-expanded')).toBe('false') - expect(container.textContent!.includes('EXPLANATION_SENTINEL')).toBe(false) - } -) - -it.each(cases)( - 'rotates the %s caret wrapper without transforming SVG or paths', - (provider, kind) => { - const button = render(provider, kind) - const svg = button.querySelector('svg')! - const wrapper = svg.parentElement! - expect(svg.getAttribute('width')).toBe('16') - expect(svg.getAttribute('height')).toBe('16') - expect(svg.querySelector('path')?.getAttribute('d')).toBeTruthy() - expect(getComputedStyle(wrapper).transform).toMatch(/^(none)?$/) - tap(button) - expect(getComputedStyle(wrapper).transform).toBe('rotate(90deg)') - for (const node of [svg, ...svg.querySelectorAll('path')]) { - expect(getComputedStyle(node).transform).toMatch(/^(none)?$/) - expect(node.hasAttribute('transform')).toBe(false) - } - tap(button) - expect(getComputedStyle(wrapper).transform).toMatch(/^(none)?$/) - } -) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.test.ts b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.test.ts index 43bf961fa12..52ae2dd631a 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.test.ts +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.test.ts @@ -39,17 +39,11 @@ vi.mock('react-native', async () => { vi.mock('lucide-react-native', () => ({ ChevronRight: 'ChevronRight' })) import { MobileNativeChatTurnStatus } from './MobileNativeChatTurnStatus' -import { - findAllByHostType, - findByHostType -} from '../../../test-support/react-test-renderer-host-nodes' +import { findAllByHostType } from '../../../test-support/react-test-renderer-host-nodes' const labels = (node: ReactTestInstance): string[] => findAllByHostType(node, 'Text').map((text) => String(text.children.join(''))) -const spinners = (node: ReactTestInstance): ReactTestInstance[] => - findAllByHostType(node, 'ActivityIndicator') - let renderer: ReactTestRenderer | null = null beforeEach(() => { @@ -88,56 +82,6 @@ describe('MobileNativeChatTurnStatus', () => { return renderer! } - it('counts up from the first second while the turn runs, with no spinner', () => { - const tree = render({ startedAt: Date.now() }) - expect(labels(tree.root)).toEqual(['Working for 0s']) - act(() => { - vi.advanceTimersByTime(12_000) - }) - expect(labels(tree.root)).toEqual(['Working for 12s']) - expect(spinners(tree.root)).toHaveLength(0) - }) - - it('settles to a tappable "Worked for" row that toggles the turn', () => { - const onToggleExpanded = vi.fn() - const tree = render({ startedAt: Date.now(), workedSeconds: 184, onToggleExpanded }) - expect(labels(tree.root)).toEqual(['Worked for 3m 4s']) - const button = findByHostType(tree.root, 'Pressable') - expect(button.props.accessibilityLabel).toBe('Toggle turn details') - expect(button.props.accessibilityState).toEqual({ expanded: false }) - act(() => button.props.onPress()) - expect(onToggleExpanded).toHaveBeenCalledOnce() - }) - - it('heads a turn a crash cut off as worked, and a turn the user stopped as interrupted', () => { - const crashed = render({ - startedAt: Date.now(), - workedSeconds: 12, - verdict: 'interruption', - onToggleExpanded: vi.fn() - }) - expect(labels(crashed.root)).toEqual(['Worked for 12s']) - act(() => crashed.unmount()) - const stopped = render({ - startedAt: Date.now(), - workedSeconds: 12, - verdict: 'cancellation', - onToggleExpanded: vi.fn() - }) - expect(labels(stopped.root)).toEqual(['Interrupted after 12s']) - }) - - it('stays a plain row when the settled turn has nothing to disclose', () => { - const tree = render({ startedAt: Date.now(), workedSeconds: 5 }) - expect(findAllByHostType(tree.root, 'Pressable')).toHaveLength(0) - expect(labels(tree.root)).toEqual(['Worked for 5s']) - }) - - it('holds no interval once the turn has settled', () => { - render({ startedAt: Date.now(), workedSeconds: 5 }) - expect(vi.getTimerCount()).toBe(0) - }) - it('stops clock callbacks in the background and catches up immediately on resume', () => { const tree = render({ startedAt: Date.now() }) act(() => { diff --git a/mobile/src/session/native-chat/visual/mobile-native-chat-open-file.test.ts b/mobile/src/session/native-chat/visual/mobile-native-chat-open-file.test.ts deleted file mode 100644 index 339268dc52b..00000000000 --- a/mobile/src/session/native-chat/visual/mobile-native-chat-open-file.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { RpcResponse } from '../../../transport/types' -import type { UnvalidatedRpcRequestPort } from '../../../transport/unvalidated-rpc-request-port' -import { openMobileNativeChatFileTap } from './mobile-native-chat-open-file' - -function ok(result: unknown): RpcResponse { - return { id: 'rpc', ok: true, result, _meta: { runtimeId: 'runtime-1' } } -} - -// Drains the resolve round trip without counting its internal awaits. -const settle = (): Promise => new Promise((resolve) => setTimeout(resolve, 0)) - -function activationState(activated: boolean) { - return { - activated, - activationSeq: 1, - latestActivationSeq: 1, - sourceTerminalHandle: 'terminal-1', - activeTerminalHandle: 'terminal-1', - activeTabType: 'terminal' - } -} - -function baseOptions(client: UnvalidatedRpcRequestPort) { - return { - client, - hostId: 'host-1', - worktreeId: 'wt-1', - pushPreviewRoute: vi.fn(), - openBrowser: vi.fn(), - triggerOpenFeedback: vi.fn(), - fetchSessionTabs: vi.fn(), - getSessionTabs: () => [], - getActiveSessionTabId: () => null, - getActivationState: activationState, - switchSessionTab: vi.fn(), - scheduleDelayedAction: vi.fn(), - onOpenFailed: vi.fn() - } -} - -function worktreeFileResolution(relativePath: string) { - return ok({ - worktree: 'wt-1', - relativePath, - absolutePath: `/repo/${relativePath}`, - exists: true, - isDirectory: false, - openTarget: { - kind: 'worktree-file', - provider: 'local', - relativePath, - absolutePath: `/repo/${relativePath}` - } - }) -} - -describe('openMobileNativeChatFileTap', () => { - it('resolves against the worktree root: no terminal handle and no cwd', async () => { - const sendRequest = vi.fn(async () => worktreeFileResolution('src/app.ts')) - const options = baseOptions({ sendRequest }) - - openMobileNativeChatFileTap({ ...options, pathText: 'src/app.ts' }) - await settle() - - expect(sendRequest).toHaveBeenCalledWith( - 'files.resolveTerminalPath', - { worktree: 'id:wt-1', pathText: 'src/app.ts', crossWorkspace: true }, - { timeoutMs: 10_000 } - ) - }) - - it('carries the native-chat session identity into resolution and grant refresh', async () => { - const sendRequest = vi.fn(async () => - ok({ - worktree: 'wt-1', - relativePath: null, - absolutePath: '/Users/ada/orca-plans/result.html', - exists: true, - isDirectory: false, - openTarget: { - kind: 'absolute-file', - provider: 'local', - absolutePath: '/Users/ada/orca-plans/result.html', - grantId: 'grant-1', - readOnly: true - } - }) - ) - const options = baseOptions({ sendRequest }) - - openMobileNativeChatFileTap({ - ...options, - pathText: '~/orca-plans/result.html', - nativeChatContext: { tabId: 'tab-1', sessionId: 'session-1' } - }) - await settle() - - expect(sendRequest).toHaveBeenCalledWith( - 'files.resolveTerminalPath', - { - worktree: 'id:wt-1', - pathText: '~/orca-plans/result.html', - crossWorkspace: true, - nativeChatContext: { tabId: 'tab-1', sessionId: 'session-1' } - }, - { timeoutMs: 10_000 } - ) - expect(options.pushPreviewRoute).toHaveBeenCalledWith({ - pathname: '/h/[hostId]/files/preview/[worktreeId]', - params: expect.objectContaining({ - source: 'terminalArtifact', - absolutePath: '/Users/ada/orca-plans/result.html', - grantId: 'grant-1', - nativeChatTab: 'tab-1', - nativeChatSession: 'session-1' - }) - }) - }) - - it('parses a :line:col citation and opens the mobile preview route', async () => { - const sendRequest = vi.fn(async () => worktreeFileResolution('src/app.ts')) - const options = baseOptions({ sendRequest }) - - openMobileNativeChatFileTap({ ...options, pathText: 'src/app.ts:120:7' }) - await settle() - - expect(sendRequest).toHaveBeenCalledWith( - 'files.resolveTerminalPath', - { worktree: 'id:wt-1', pathText: 'src/app.ts', crossWorkspace: true }, - { timeoutMs: 10_000 } - ) - expect(options.triggerOpenFeedback).toHaveBeenCalledTimes(1) - expect(options.pushPreviewRoute).toHaveBeenCalledWith({ - pathname: '/h/[hostId]/files/preview/[worktreeId]', - params: expect.objectContaining({ - source: 'worktree', - relativePath: 'src/app.ts', - line: '120', - column: '7' - }) - }) - expect(options.onOpenFailed).not.toHaveBeenCalled() - }) - - it('surfaces a resolve miss instead of a silent no-op', async () => { - const sendRequest = vi.fn(async () => - ok({ - worktree: 'wt-1', - relativePath: null, - absolutePath: null, - exists: false, - isDirectory: false - }) - ) - const options = baseOptions({ sendRequest }) - - openMobileNativeChatFileTap({ ...options, pathText: 'gone/missing.ts' }) - await settle() - - expect(options.onOpenFailed).toHaveBeenCalledTimes(1) - expect(options.pushPreviewRoute).not.toHaveBeenCalled() - expect(options.triggerOpenFeedback).not.toHaveBeenCalled() - }) - - it('surfaces a rejected resolve request', async () => { - const sendRequest = vi.fn(async () => { - throw new Error('Request timed out') - }) - const options = baseOptions({ sendRequest }) - - openMobileNativeChatFileTap({ ...options, pathText: 'src/app.ts' }) - await settle() - - expect(options.onOpenFailed).toHaveBeenCalledTimes(1) - }) - - it('opens a plain path through files.open with tab activation', async () => { - const responses = [worktreeFileResolution('src/app.ts'), ok({ opened: true })] - const sendRequest = vi.fn(async (method: string) => { - const response = responses.shift() - if (!response) { - throw new Error(`no scripted response for ${method}`) - } - return response - }) - const openedTab = { id: 'tab-2', relativePath: 'src/app.ts' } - const switchSessionTab = vi.fn() - const options = { - ...baseOptions({ sendRequest }), - getSessionTabs: () => [openedTab], - getActiveSessionTabId: () => 'terminal-tab', - switchSessionTab, - scheduleDelayedAction: vi.fn((callback: () => void) => callback()) - } - - vi.useFakeTimers() - try { - openMobileNativeChatFileTap({ ...options, pathText: 'src/app.ts' }) - await vi.runAllTimersAsync() - - expect(sendRequest).toHaveBeenCalledWith( - 'files.open', - { worktree: 'id:wt-1', relativePath: 'src/app.ts' }, - { timeoutMs: 15_000 } - ) - expect(switchSessionTab).toHaveBeenCalledWith(openedTab) - expect(options.onOpenFailed).not.toHaveBeenCalled() - } finally { - vi.useRealTimers() - } - }) -}) diff --git a/mobile/src/session/native-chat/visual/mobile-native-chat-visual-read.test.ts b/mobile/src/session/native-chat/visual/mobile-native-chat-visual-read.test.ts deleted file mode 100644 index 76862305bec..00000000000 --- a/mobile/src/session/native-chat/visual/mobile-native-chat-visual-read.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { RpcClient } from '../../../transport/rpc-client' -import type { RpcResponse } from '../../../transport/types' -import { - cachedMobileNativeChatVisual, - readMobileNativeChatVisual, - resetMobileNativeChatVisualCacheForTest, - type MobileNativeChatVisualSource -} from './mobile-native-chat-visual-read' - -const REVISION_A = 'a'.repeat(32) -const REVISION_B = 'b'.repeat(32) - -function success(result: unknown): RpcResponse { - return { id: '1', ok: true, result } -} - -function failure(code: string): RpcResponse { - return { id: '1', ok: false, error: { code, message: code } } -} - -function sourceWith(sendRequest: ReturnType): MobileNativeChatVisualSource { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the reader calls only sendRequest; the rest of RpcClient is connection lifecycle it never touches. - const client = { sendRequest } as unknown as RpcClient - return { client, sessionId: 'session-1' } -} - -afterEach(() => { - resetMobileNativeChatVisualCacheForTest() -}) - -describe('readMobileNativeChatVisual', () => { - it('reads the visual by session and bare file name, then caches it', async () => { - const sendRequest = vi - .fn() - .mockResolvedValue(success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' })) - const source = sourceWith(sendRequest) - - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'ready', - html: '

', - revision: REVISION_A - }) - expect(sendRequest).toHaveBeenCalledWith( - 'agentSession.readVisual', - { sessionId: 'session-1', file: 'chart.html' }, - expect.objectContaining({ timeoutMs: expect.any(Number) }) - ) - expect(cachedMobileNativeChatVisual(source, 'chart.html')).toEqual({ - html: '

', - revision: REVISION_A - }) - }) - - it('revalidates with the held revision and keeps the cached bytes on unchanged', async () => { - const sendRequest = vi - .fn() - .mockResolvedValueOnce(success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' })) - .mockResolvedValueOnce( - success({ ok: true, revision: REVISION_A, sizeBytes: 3, unchanged: true }) - ) - const source = sourceWith(sendRequest) - await readMobileNativeChatVisual(source, 'chart.html') - - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'ready', - html: '

', - revision: REVISION_A - }) - expect(sendRequest.mock.calls[1]?.[1]).toEqual({ - sessionId: 'session-1', - file: 'chart.html', - knownRevision: REVISION_A - }) - }) - - it('replaces the cached bytes when the host has a new revision', async () => { - const sendRequest = vi - .fn() - .mockResolvedValueOnce(success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' })) - .mockResolvedValueOnce( - success({ ok: true, revision: REVISION_B, sizeBytes: 4, html: '


' }) - ) - const source = sourceWith(sendRequest) - await readMobileNativeChatVisual(source, 'chart.html') - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'ready', - html: '
', - revision: REVISION_B - }) - expect(cachedMobileNativeChatVisual(source, 'chart.html')).toEqual({ - html: '
', - revision: REVISION_B - }) - }) - - it('drops the cached bytes when the host refuses a visual it served before', async () => { - const source = sourceWith( - vi - .fn() - .mockResolvedValueOnce( - success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' }) - ) - .mockResolvedValueOnce(success({ ok: false, error: 'not_found' })) - ) - await readMobileNativeChatVisual(source, 'chart.html') - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'refused' - }) - expect(cachedMobileNativeChatVisual(source, 'chart.html')).toBeNull() - }) - - it('reads a host verdict and an older host as refused', async () => { - for (const response of [ - success({ ok: false, error: 'not_found' }), - success({ ok: false, error: 'some_future_error' }), - failure('method_not_found'), - failure('forbidden') - ]) { - resetMobileNativeChatVisualCacheForTest() - const source = sourceWith(vi.fn().mockResolvedValue(response)) - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'refused' - }) - } - }) - - it('reads an error reply and a malformed reply as no verdict, keeping the cache', async () => { - for (const response of [ - failure('runtime_error'), - success({ ok: true, revision: 'not-hex', html: '' }), - success('') - ]) { - resetMobileNativeChatVisualCacheForTest() - const sendRequest = vi - .fn() - .mockResolvedValueOnce( - success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' }) - ) - .mockResolvedValueOnce(response) - const source = sourceWith(sendRequest) - await readMobileNativeChatVisual(source, 'chart.html') - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'unreachable' - }) - expect(cachedMobileNativeChatVisual(source, 'chart.html')).not.toBeNull() - } - }) - - it('reads a transport failure as unreachable, never as refused', async () => { - const source = sourceWith(vi.fn().mockRejectedValue(new Error('socket closed'))) - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'unreachable' - }) - }) - - it('treats an unchanged answer for a revision it never sent as unreachable and drops the entry', async () => { - const source = sourceWith( - vi.fn().mockResolvedValue(success({ ok: true, revision: REVISION_A, unchanged: true })) - ) - await expect(readMobileNativeChatVisual(source, 'chart.html')).resolves.toEqual({ - kind: 'unreachable' - }) - expect(cachedMobileNativeChatVisual(source, 'chart.html')).toBeNull() - }) - - it('shares one request between concurrent reads of the same visual', async () => { - let resolve: (response: RpcResponse) => void = () => {} - const sendRequest = vi.fn( - () => - new Promise((settle) => { - resolve = settle - }) - ) - const source = sourceWith(sendRequest) - const first = readMobileNativeChatVisual(source, 'chart.html') - const second = readMobileNativeChatVisual(source, 'chart.html') - resolve(success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' })) - await expect(Promise.all([first, second])).resolves.toEqual([ - { kind: 'ready', html: '

', revision: REVISION_A }, - { kind: 'ready', html: '

', revision: REVISION_A } - ]) - expect(sendRequest).toHaveBeenCalledTimes(1) - }) - - it('keys the cache by client and session, so another host or chat never sees this one', async () => { - const source = sourceWith( - vi - .fn() - .mockResolvedValue(success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' })) - ) - await readMobileNativeChatVisual(source, 'chart.html') - expect(cachedMobileNativeChatVisual({ ...source, sessionId: 'other' }, 'chart.html')).toBeNull() - const otherHost = sourceWith(vi.fn()).client - expect(cachedMobileNativeChatVisual({ ...source, client: otherHost }, 'chart.html')).toBeNull() - }) - - it('evicts the oldest visuals past the entry bound', async () => { - const source = sourceWith( - vi - .fn() - .mockResolvedValue(success({ ok: true, revision: REVISION_A, sizeBytes: 3, html: '

' })) - ) - for (let index = 0; index < 17; index += 1) { - await readMobileNativeChatVisual(source, `v${index}.html`) - } - expect(cachedMobileNativeChatVisual(source, 'v0.html')).toBeNull() - expect(cachedMobileNativeChatVisual(source, 'v16.html')).not.toBeNull() - }) -}) diff --git a/mobile/src/session/shell-terminal-key-rail.web.test.tsx b/mobile/src/session/shell-terminal-key-rail.web.test.tsx index c46b4bcfc2d..61c95d972b6 100644 --- a/mobile/src/session/shell-terminal-key-rail.web.test.tsx +++ b/mobile/src/session/shell-terminal-key-rail.web.test.tsx @@ -110,24 +110,6 @@ function mount() { return { Page, frames, deliver, owner, terminal, onKey } } -it('keeps the native scroll view mounted across toolbar-only updates without releasing ownership', () => { - const { Page, frames, deliver, owner } = mount() - const scroll = native!.root.findByType(ScrollView) - for (const update of [ - { keyboardVisible: true }, - { liveInputEnabled: false }, - { phoneDisplayMode: true }, - { canSend: false }, - { modifiers: { ctrl: 'once', alt: 'off', shift: 'off' } as const } - ]) { - const toolbar = { ...initialToolbar, ...update } - act(() => page!.update(createElement(Page, { toolbar }))) - expect(frames).toEqual([[true, owner, toolbar]]) - deliver() - expect(native!.root.findByType(ScrollView)).toBe(scroll) - } -}) - it('updates pane ownership without releasing the rail and still releases when covered', () => { const { Page, frames, deliver, owner, terminal, onKey } = mount() act(() => page!.update(createElement(Page, { toolbar: initialToolbar, focusedTerminal: 'two' }))) diff --git a/mobile/src/session/use-mobile-session-image-attachments.test.tsx b/mobile/src/session/use-mobile-session-image-attachments.test.tsx deleted file mode 100644 index 17367d7ca28..00000000000 --- a/mobile/src/session/use-mobile-session-image-attachments.test.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { AGENT_TUI_CLEAR_INPUT_MAX } from '../../../src/shared/agent-launch/startup/agent-tui-input-clear' -import { createElement } from 'react' -import { resetMobileNativeChatDraftInputsForTests } from './native-chat/composer/mobile-native-chat-draft-inputs' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { FakeSession } from '../transport/mobile-endpoint-supervisor-test-fakes' -import { resetMobileNativeChatStaleInputForTests } from './native-chat/mobile-native-chat-stale-input' -import { resetMobileNativeChatTerminalWritesForTests } from './native-chat/mobile-native-chat-terminal-write-lock' -import { useMobileSessionImageAttachments } from './use-mobile-session-image-attachments' - -vi.mock('../platform/media-picker', () => ({ - useMediaPicker: () => ({ - pickImages: async function* () { - yield { base64: 'AAAA', uri: 'file:///a.jpg' } - } - }) -})) - -type HookArgs = Parameters[0] -type Hook = ReturnType - -function baseArgs(overrides: Partial> = {}) { - const client = new FakeSession('connected') - client.sendRequest.mockImplementation(async (method) => { - if (method === 'clipboard.startImageUpload') { - return { id: 'start', ok: false, error: { code: 'method_not_found', message: 'no' } } - } - if (method === 'clipboard.saveImageAsTempFile') { - return { id: 'save', ok: true, result: '/tmp/a.png' } - } - if (method === 'terminal.send') { - return { id: 'send', ok: true, result: { send: { accepted: true } } } - } - throw new Error(`unexpected request: ${method}`) - }) - return { - client, - agent: 'claude', - activeHandle: 'term-1', - activeHandleRef: { current: 'term-1' }, - canSend: true, - connState: 'connected', - deviceTokenRef: { current: null }, - nativeChatScopeKey: 'scope-1', - nativeChatInputLeaseReady: false, - nativeChatAgentConfirmed: true, - getActiveWorktreeConnectionId: async () => 'conn-1', - beforeTerminalSend: async () => true, - nativeChatBaseSend: vi.fn().mockResolvedValue('accepted'), - structuredNativeChat: true, - nativeChatSessionId: 'session-1', - readSeededLaunchDraft: () => null, - showToast: vi.fn(), - onNativeChatSendError: vi.fn(), - onSuccess: vi.fn(), - onError: vi.fn(), - ...overrides - } satisfies HookArgs -} - -describe('useMobileSessionImageAttachments', () => { - let renderer: ReactTestRenderer | null = null - let hook: Hook - - function Harness({ args }: { args: HookArgs }): null { - hook = useMobileSessionImageAttachments(args) - return null - } - - beforeEach(() => { - resetMobileNativeChatDraftInputsForTests() - vi.useFakeTimers() - resetMobileNativeChatStaleInputForTests() - resetMobileNativeChatTerminalWritesForTests() - }) - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - vi.useRealTimers() - }) - - async function renderWithImage(args: HookArgs): Promise { - act(() => { - renderer = create(createElement(Harness, { args })) - }) - await act(async () => { - await hook.nativeChatImages.attachImage('library') - }) - expect(hook.nativeChatImages.attachments).toEqual([ - { id: expect.any(String), path: '/tmp/a.png', previewUri: 'file:///a.jpg' } - ]) - } - - async function send(): Promise { - let accepted = false - await act(async () => { - const pending = hook.nativeChatImages.sendNativeChat('look') - await vi.advanceTimersByTimeAsync(300) - accepted = await pending - }) - return accepted - } - - it('sends structured session images without a terminal input lease', async () => { - const args = baseArgs({ activeHandle: null, activeHandleRef: { current: null } }) - await renderWithImage(args) - const image = hook.nativeChatImages.attachments[0] - - expect(await send()).toBe(true) - expect(args.nativeChatBaseSend).toHaveBeenCalledWith( - 'look', - ['file:///a.jpg'], - expect.any(Number), - [image] - ) - expect(args.client.sendRequest.mock.calls.map(([method]) => method)).toEqual([ - 'clipboard.startImageUpload', - 'clipboard.saveImageAsTempFile' - ]) - expect(hook.nativeChatImages.attachments).toEqual([]) - }) - - it.each([false, true])('gates terminal image delivery on input lease %s', async (leaseReady) => { - const args = baseArgs({ - nativeChatInputLeaseReady: leaseReady, - structuredNativeChat: false - }) - await renderWithImage(args) - - expect(await send()).toBe(leaseReady) - const sends = args.client.sendRequest.mock.calls.filter( - ([method]) => method === 'terminal.send' - ) - if (leaseReady) { - expect(sends).toEqual([ - [ - 'terminal.send', - { terminal: 'term-1', text: AGENT_TUI_CLEAR_INPUT_MAX, enter: false }, - expect.any(Object) - ], - [ - 'terminal.send', - { terminal: 'term-1', text: '\x1b[200~/tmp/a.png\x1b[201~ ', enter: false }, - expect.any(Object) - ] - ]) - expect(args.nativeChatBaseSend).toHaveBeenCalledWith( - 'look', - ['file:///a.jpg'], - expect.any(Number), - undefined, - expect.any(Number) - ) - expect(hook.nativeChatImages.attachments).toEqual([]) - } else { - expect(sends).toEqual([]) - expect(args.nativeChatBaseSend).not.toHaveBeenCalled() - expect(args.onNativeChatSendError).toHaveBeenCalledWith('Message not sent (disconnected)') - expect(hook.nativeChatImages.attachments).toHaveLength(1) - } - }) - - it('keeps a structured session image pending when the connection drops', async () => { - const args = baseArgs() - await renderWithImage(args) - act(() => { - renderer?.update(createElement(Harness, { args: { ...args, connState: 'connecting' } })) - }) - - expect(await send()).toBe(false) - expect(args.nativeChatBaseSend).not.toHaveBeenCalled() - expect(args.onNativeChatSendError).toHaveBeenCalledWith('Message not sent (disconnected)') - expect(hook.nativeChatImages.attachments).toHaveLength(1) - expect(args.client.sendRequest.mock.calls.map(([method]) => method)).toEqual([ - 'clipboard.startImageUpload', - 'clipboard.saveImageAsTempFile' - ]) - }) - - it('keeps a terminal image pending until the live agent is confirmed', async () => { - const args = baseArgs({ - nativeChatInputLeaseReady: true, - nativeChatAgentConfirmed: false, - structuredNativeChat: false - }) - await renderWithImage(args) - - expect(await send()).toBe(false) - expect(args.nativeChatBaseSend).not.toHaveBeenCalled() - expect(args.onNativeChatSendError).toHaveBeenCalledWith('Message not sent (disconnected)') - expect(hook.nativeChatImages.attachments).toHaveLength(1) - expect(args.client.sendRequest.mock.calls.map(([method]) => method)).toEqual([ - 'clipboard.startImageUpload', - 'clipboard.saveImageAsTempFile' - ]) - }) -}) diff --git a/mobile/src/session/use-mobile-structured-session-opening.test.tsx b/mobile/src/session/use-mobile-structured-session-opening.test.tsx deleted file mode 100644 index cc4446706dd..00000000000 --- a/mobile/src/session/use-mobile-structured-session-opening.test.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import type * as OpenMobileStructuredSessionModule from './open-mobile-structured-session' -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, expect, it, vi } from 'vitest' -import type { - StructuredSessionOpenSource, - StructuredSessionOpenResult -} from '../../../src/shared/open-structured-agent-session' -import { createFakeRpcClient } from '../mobile-web-shell/bridge-host-test-fakes' -import { useMobileStructuredSessionOpening } from './use-mobile-structured-session-opening' -import { - activateMobileUiLocale, - mobileI18n, - setMobilePseudoLocalization -} from '../i18n/mobile-i18n' -import { pseudoLocalizeString } from '../../../src/shared/pseudo-localization' -const mocks = vi.hoisted(() => ({ - executionHost: 'local', - open: vi.fn(), - push: vi.fn(), - replace: vi.fn() -})) -vi.mock('../navigation/workspace-execution-host', () => ({ - useWorkspaceExecutionHost: () => mocks.executionHost -})) -vi.mock('../navigation/workspace-route-handoff', () => ({ - useWorkspaceRouteHandoff: () => ({ push: mocks.push, replace: mocks.replace }) -})) -vi.mock('./open-mobile-structured-session', async (original) => ({ - ...(await original()), - openMobileStructuredSession: mocks.open -})) -const source: StructuredSessionOpenSource = { - kind: 'notification', - sessionId: 'root', - worktreeId: 'workspace' -} -const opened: StructuredSessionOpenResult = { - kind: 'opened', - target: { sessionId: 'live', worktreeId: 'workspace' }, - tab: { id: 'actual', sessionId: 'live', agent: 'codex' } -} -afterEach(async () => { - vi.clearAllMocks() - setMobilePseudoLocalization(false) - mobileI18n.removeResourceBundle('fr', 'translation') - await activateMobileUiLocale('en') -}) -it.each( - (['stable', 'newer-pick', 'execution-host', 'unmount'] as const).flatMap((mode) => - (['opened', 'failed'] as const).map((outcome) => ({ mode, outcome })) - ) -)('only the owning phone view applies $outcome completion: $mode', async ({ mode, outcome }) => { - await activateMobileUiLocale('en') - mocks.executionHost = 'local' - const deferred = Promise.withResolvers() - mocks.open.mockReturnValue(deferred.promise) - const selected = { current: 'source' } - const pending = { current: null } - const switchSessionTab = vi.fn() - const scope: Parameters[0] = { - hostId: 'host', - worktreeId: 'workspace', - client: createFakeRpcClient(), - connState: 'connected', - showToast: vi.fn(), - switchSessionTab, - activeSessionTabIdRef: { current: 'source' }, - selectedSessionTabIdRef: selected, - pendingSelectionRef: pending, - sessionTabsRef: { - current: [ - { - type: 'terminal', - id: 'source', - title: 'Source', - terminal: 'terminal-source', - isActive: true - } - ] - } - } - let open: ((source: StructuredSessionOpenSource) => Promise) | undefined - function Probe() { - open = useMobileStructuredSessionOpening(scope) - return null - } - let tree: ReactTestRenderer | undefined - await act(async () => { - tree = create(createElement(Probe)) - }) - try { - let completion: Promise | undefined - await act(async () => { - completion = open?.( - outcome === 'opened' - ? source - : { - kind: 'sender', - party: { address: 'orca_session_id:root', terminalHandle: null, orcaSessionId: null }, - messageIds: ['message'], - fallback: { sessionId: 'root', worktreeId: 'workspace', agent: 'codex' }, - displayName: 'Worker custom' - } - ) - }) - if (mode === 'newer-pick') { - selected.current = 'newer-user-pick' - } - if (mode === 'execution-host') { - mocks.executionHost = 'ssh:another' - await act(async () => tree?.update(createElement(Probe))) - } - if (mode === 'unmount') { - await act(async () => tree?.unmount()) - } - mobileI18n.addResourceBundle('fr', 'translation', { - 'session.structuredOpen.unreachable': 'Conversation de {{agent}} : réessayez.' - }) - await activateMobileUiLocale('fr') - setMobilePseudoLocalization(true) - await act(async () => { - deferred.resolve( - outcome === 'opened' - ? opened - : { - kind: 'failed', - failure: { reason: 'unreachable', beforeEffects: false } - } - ) - await completion - }) - expect(switchSessionTab).toHaveBeenCalledTimes( - mode === 'stable' && outcome === 'opened' ? 1 : 0 - ) - expect(scope.showToast).toHaveBeenCalledTimes(mode === 'stable' && outcome === 'failed' ? 1 : 0) - expect(mocks.push).not.toHaveBeenCalled() - if (mode === 'stable' && outcome === 'failed') { - expect(scope.showToast).toHaveBeenCalledWith( - pseudoLocalizeString('Conversation de Worker custom : réessayez.', { expand: true }), - 3_000 - ) - expect(mocks.open).toHaveBeenCalledWith( - expect.objectContaining({ - source: expect.objectContaining({ - fallback: { sessionId: 'root', worktreeId: 'workspace', agent: 'codex' } - }) - }) - ) - } - if (mode === 'stable' && outcome === 'opened') { - expect(switchSessionTab).toHaveBeenCalledWith( - expect.objectContaining({ id: 'actual', sessionId: 'live' }), - { activate: false } - ) - } - } finally { - await act(async () => tree?.unmount()) - } -}) - -it('routes a removed workspace notification to the existing host notice without a tab or duplicate toast', async () => { - mocks.executionHost = 'local' - mocks.open.mockResolvedValue({ - kind: 'failed', - failure: { reason: 'workspace-missing', beforeEffects: true } - }) - const scope: Parameters[0] = { - hostId: 'host', - worktreeId: 'workspace', - client: createFakeRpcClient(), - connState: 'connected', - showToast: vi.fn(), - switchSessionTab: vi.fn(), - activeSessionTabIdRef: { current: null }, - selectedSessionTabIdRef: { current: null }, - pendingSelectionRef: { current: null }, - sessionTabsRef: { current: [] } - } - let open: ((value: StructuredSessionOpenSource) => Promise) | undefined - function Probe() { - open = useMobileStructuredSessionOpening(scope) - return null - } - let tree: ReactTestRenderer | undefined - try { - await act(async () => { - tree = create(createElement(Probe)) - }) - await act(async () => { - await open?.(source) - }) - expect(mocks.replace).toHaveBeenCalledExactlyOnceWith('/h/host?notice=worktree-missing') - expect(mocks.push).not.toHaveBeenCalled() - expect(scope.switchSessionTab).not.toHaveBeenCalled() - expect(scope.showToast).not.toHaveBeenCalled() - } finally { - await act(async () => tree?.unmount()) - } -}) diff --git a/mobile/src/session/use-mobile-terminal-paste.test.ts b/mobile/src/session/use-mobile-terminal-paste.test.ts index a7557986912..ec328234c14 100644 --- a/mobile/src/session/use-mobile-terminal-paste.test.ts +++ b/mobile/src/session/use-mobile-terminal-paste.test.ts @@ -1,7 +1,7 @@ import { terminalInputSend } from '../terminal/mobile-terminal-operations' import { describe, expect, it, vi } from 'vitest' import type { RpcClient } from '../transport/rpc-client' -import type { ConnectionState } from '../transport/types' + import { useMobileTerminalPaste } from './use-mobile-terminal-paste' // Call memo factories directly: this hook-only harness has no mounted render to cache across. @@ -21,121 +21,6 @@ vi.mock('./mobile-clipboard-image', async (importOriginal) => ({ saveMobileClipboardImageAsTempFile: async () => '/tmp/shot.png' })) -describe('mobile clipboard image followed by typing', () => { - it.each(['omp', 'claude', 'codex', null, 'unknown'])( - 'separates a pasted image from subsequent %s input', - async (agent) => { - const sendRequest = vi - .fn() - .mockResolvedValue({ ok: true, result: { send: { accepted: true } } }) - const client: RpcClient = { - sendRequest, - subscribe: () => () => {}, - updateTerminalSubscriptionViewport: vi.fn(), - getState: () => 'connected', - getReconnectAttempt: () => 0, - getLastConnectedAt: () => null, - onStateChange: () => () => {}, - notifyForeground: vi.fn(), - close: vi.fn() - } - const paste = useMobileTerminalPaste({ - sendTerminalUserInput: terminalInputSend.request, - agent, - activeHandle: 'term-1', - activeHandleRef: { current: 'term-1' }, - activeSessionTabTypeRef: { current: 'terminal' }, - canSend: true, - client, - clientRef: { current: client }, - connState: 'connected', - connStateRef: { current: 'connected' }, - deviceTokenRef: { current: null }, - flushPendingLiveInputBeforeExternalSend: async () => true, - getActiveWorktreeConnectionId: async () => null, - onError: vi.fn(), - onSuccess: vi.fn(), - ptyModesRef: { current: new Map() }, - refreshCanPaste: vi.fn(), - showToast: vi.fn() - }) - await paste() - expect(sendRequest).toHaveBeenCalledWith('terminal.send', { - terminal: 'term-1', - text: `\x1b[200~${agent === 'omp' ? '@' : ''}/tmp/shot.png\x1b[201~ `, - enter: false - }) - } - ) - - it.each(['terminal switch', 'non-terminal view', 'disconnect'])( - 'drops a completed image paste after a pending input flush observes %s', - async (change) => { - const sendRequest = vi.fn() - const client: RpcClient = { - sendRequest, - subscribe: () => () => {}, - updateTerminalSubscriptionViewport: vi.fn(), - getState: () => 'connected', - getReconnectAttempt: () => 0, - getLastConnectedAt: () => null, - onStateChange: () => () => {}, - notifyForeground: vi.fn(), - close: vi.fn() - } - const activeHandleRef = { current: 'term-1' } - const activeSessionTabTypeRef = { current: 'terminal' } - const connStateRef: { current: ConnectionState } = { current: 'connected' } - let releaseFlush!: (ready: boolean) => void - let flushStarted!: () => void - const flushing = new Promise((resolve) => { - flushStarted = resolve - }) - const onSuccess = vi.fn() - const paste = useMobileTerminalPaste({ - sendTerminalUserInput: terminalInputSend.request, - agent: null, - activeHandle: 'term-1', - activeHandleRef, - activeSessionTabTypeRef, - canSend: true, - client, - clientRef: { current: client }, - connState: 'connected', - connStateRef, - deviceTokenRef: { current: null }, - flushPendingLiveInputBeforeExternalSend: () => { - flushStarted() - return new Promise((resolve) => { - releaseFlush = resolve - }) - }, - getActiveWorktreeConnectionId: async () => null, - onError: vi.fn(), - onSuccess, - ptyModesRef: { current: new Map() }, - refreshCanPaste: vi.fn(), - showToast: vi.fn() - }) - const pending = paste() - await flushing - if (change === 'terminal switch') { - activeHandleRef.current = 'term-2' - } - if (change === 'non-terminal view') { - activeSessionTabTypeRef.current = 'agent-session' - } - if (change === 'disconnect') { - connStateRef.current = 'disconnected' - } - releaseFlush(true) - await pending - expect(sendRequest).not.toHaveBeenCalled() - expect(onSuccess).not.toHaveBeenCalled() - } - ) -}) - describe('mobile clipboard text paste', () => { it('frames a multi-line paste in an alt-screen app the way desktop does', async () => { clipboard.text = 'line 1\nline 2 \x1b[201~' diff --git a/mobile/src/source-control/mobile-create-pr-action.test.ts b/mobile/src/source-control/mobile-create-pr-action.test.ts deleted file mode 100644 index 876fac04847..00000000000 --- a/mobile/src/source-control/mobile-create-pr-action.test.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { HostedReviewCreationBlockedReason } from '../../../src/shared/source-control/hosted-review/hosted-review-contracts' -import type { MobileHostedReviewEligibilityReply } from './hosted-review-reply-schema' -import { - buildMobileCreatePrAction, - type MobileCreatePrEligibilityState -} from './mobile-create-pr-action' - -function eligibility( - overrides: Partial = {} -): MobileHostedReviewEligibilityReply { - return { - provider: 'github', - review: null, - canCreate: true, - blockedReason: null, - nextAction: null, - reviewLookupOutcome: 'not_found', - defaultBaseRef: 'main', - title: 'feature', - body: '', - ...overrides - } -} - -function action( - overrides: { - branch?: string | null - eligibility?: MobileHostedReviewEligibilityReply | null - busyAction?: string | null - } = {} -) { - const onCreatePr = vi.fn() - const descriptor = buildMobileCreatePrAction({ - branch: Object.hasOwn(overrides, 'branch') ? overrides.branch : 'feature', - eligibilityState: - overrides.eligibility === null - ? { kind: 'error' } - : { kind: 'ready', eligibility: overrides.eligibility ?? eligibility() }, - busyAction: overrides.busyAction ?? null, - onCreatePr - }) - return { descriptor, onCreatePr } -} - -describe('buildMobileCreatePrAction', () => { - it('enables create when eligibility can create', () => { - const { descriptor, onCreatePr } = action() - - expect(descriptor).toMatchObject({ - visible: true, - label: 'Create Pull Request', - disabled: false, - pushFirst: false - }) - descriptor.onPress() - expect(onCreatePr).toHaveBeenCalledWith(false) - }) - - it('lets a dirty branch in when Create PR can commit it first, like desktop', () => { - const descriptor = buildMobileCreatePrAction({ - branch: 'feature', - eligibilityState: { - kind: 'ready', - eligibility: eligibility({ canCreate: false, blockedReason: 'dirty' }) - }, - busyAction: null, - prepareInputs: { - stagedCount: 1, - hasStageableChanges: false, - hasMessage: true, - hasUnresolvedConflicts: false, - upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 } - }, - onCreatePr: vi.fn() - }) - expect(descriptor.disabled).toBe(false) - }) - - it('uses merge-request copy for GitLab', () => { - const { descriptor } = action({ - eligibility: eligibility({ provider: 'gitlab' }) - }) - - expect(descriptor.label).toBe('Create Merge Request') - }) - - it('enables push-first creation when the branch needs push', () => { - const { descriptor, onCreatePr } = action({ - eligibility: eligibility({ - canCreate: false, - blockedReason: 'needs_push', - nextAction: 'push' - }) - }) - - expect(descriptor).toMatchObject({ visible: true, disabled: false, pushFirst: true }) - descriptor.onPress() - expect(onCreatePr).toHaveBeenCalledWith(true) - }) - - it.each([ - 'dirty', - 'no_upstream', - 'needs_sync', - 'default_branch', - 'auth_required' - ])('shows a disabled hinted action for locally actionable %s', (blockedReason) => { - const { descriptor } = action({ - eligibility: eligibility({ canCreate: false, blockedReason }) - }) - - expect(descriptor.visible).toBe(true) - expect(descriptor.disabled).toBe(true) - expect(descriptor.hint).toBeTruthy() - }) - - it('falls back to a disabled hinted action for other reachable blocked reasons', () => { - const { descriptor } = action({ - eligibility: eligibility({ canCreate: false, blockedReason: 'fork_head_unsupported' }) - }) - - expect(descriptor).toMatchObject({ visible: true, disabled: true }) - expect(descriptor.hint).toContain('fork') - }) - - it.each([ - 'existing_review', - 'unsupported_provider', - 'detached_head', - null - ])('keeps a disabled status row for %s', (blockedReason) => { - const { descriptor } = action({ - eligibility: eligibility({ canCreate: false, blockedReason }) - }) - - expect(descriptor).toMatchObject({ visible: true, disabled: true }) - expect(descriptor.hint).toBeTruthy() - }) - - it('keeps an unavailable row after errors but hides missing branches', () => { - expect(action({ eligibility: null }).descriptor).toMatchObject({ - visible: true, - disabled: true, - label: 'Review status unavailable' - }) - expect(action({ branch: null }).descriptor.visible).toBe(false) - }) - - it('shows an in-flight spinner and disables the current action', () => { - const { descriptor } = action({ busyAction: 'create-pr' }) - - expect(descriptor).toMatchObject({ visible: true, disabled: true, loading: true }) - }) - - it('disables without a spinner during unrelated git work', () => { - const { descriptor, onCreatePr } = action({ busyAction: 'stage-all' }) - - expect(descriptor).toMatchObject({ visible: true, disabled: true, loading: false }) - descriptor.onPress() - expect(onCreatePr).not.toHaveBeenCalled() - }) - - it('keeps a disabled status row for providers without review creation', () => { - const { descriptor } = action({ - eligibility: eligibility({ provider: 'unsupported' }) - }) - - expect(descriptor).toMatchObject({ - visible: true, - disabled: true, - label: 'Review creation unavailable for this provider' - }) - }) - - it('keeps a creatable button visible but disabled while a newer eligibility loads', () => { - const onCreatePr = vi.fn() - const descriptor = buildMobileCreatePrAction({ - branch: 'feature', - eligibilityState: { kind: 'loading', eligibility: eligibility({ canCreate: true }) }, - busyAction: null, - onCreatePr - }) - - expect(descriptor).toMatchObject({ visible: true, disabled: true, loading: false }) - }) -}) - -// Cold eligibility must reserve the Create PR row’s footprint so arriving data cannot shift a list being read. -describe('cold-mount layout stability (issue #8411)', () => { - const coldMount: MobileCreatePrEligibilityState[] = [ - { kind: 'loading', eligibility: null }, - { kind: 'ready', eligibility: eligibility({ canCreate: true }) } - ] - - it('reserves the button row while the first eligibility request is in flight', () => { - const descriptors = coldMount.map((eligibilityState) => - buildMobileCreatePrAction({ - branch: 'feature', - eligibilityState, - busyAction: null, - onCreatePr: vi.fn() - }) - ) - - const footprint = descriptors.map(({ visible }) => visible) - - expect(footprint).toEqual([true, true]) - }) - - it.each([ - [ - 'existing review', - { - kind: 'ready', - eligibility: eligibility({ canCreate: false, blockedReason: 'existing_review' }) - } satisfies MobileCreatePrEligibilityState - ], - [ - 'unsupported provider', - { - kind: 'ready', - eligibility: eligibility({ provider: 'unsupported' }) - } satisfies MobileCreatePrEligibilityState - ], - ['eligibility error', { kind: 'error' } satisfies MobileCreatePrEligibilityState] - ])('keeps the cold-load footprint when %s resolves', (_label, resolvedState) => { - const footprint = [coldMount[0], resolvedState].map((eligibilityState) => { - const descriptor = buildMobileCreatePrAction({ - branch: 'feature', - eligibilityState, - busyAction: null, - onCreatePr: vi.fn() - }) - return descriptor.visible - }) - - expect(footprint).toEqual([true, true]) - }) - - it('does not offer a tappable action against unresolved eligibility', () => { - const descriptor = buildMobileCreatePrAction({ - branch: 'feature', - eligibilityState: { kind: 'loading', eligibility: null }, - busyAction: null, - onCreatePr: vi.fn() - }) - - expect(descriptor.disabled).toBe(true) - }) -}) diff --git a/mobile/src/source-control/mobile-source-control-primary-action.test.ts b/mobile/src/source-control/mobile-source-control-primary-action.test.ts deleted file mode 100644 index fdfc03d5c02..00000000000 --- a/mobile/src/source-control/mobile-source-control-primary-action.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - buildMobileBranchRemoteAction, - buildMobileSourceControlPrimaryAction, - type MobileSourceControlPrimaryActionArgs, - type MobileSourceControlPrimaryActionHandlers -} from './mobile-source-control-primary-action' -import type { MobileGitStatusResult } from './mobile-git-status' - -function handlers(): MobileSourceControlPrimaryActionHandlers { - return { - commit: vi.fn(async () => true), - stageAll: vi.fn(async () => undefined), - runActionSheetGitSequence: vi.fn(async () => undefined), - runActionSheetGitSync: vi.fn(async () => undefined) - } -} - -function status(overrides: Partial = {}): MobileGitStatusResult { - return { - entries: [], - conflictOperation: 'unknown', - branch: 'feature', - head: 'abc123', - upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 }, - ...overrides - } -} - -function args(overrides: Partial = {}) { - return { - status: status(), - hasUnresolvedConflicts: false, - stageablePaths: [], - stagedCount: 0, - unstagedCount: 0, - commitMessage: '', - busyAction: null, - branchCompareResult: null, - handlers: handlers(), - ...overrides - } -} - -describe('buildMobileSourceControlPrimaryAction', () => { - it('selects Stage all for unstaged work and dispatches the stage runner', () => { - const h = handlers() - const action = buildMobileSourceControlPrimaryAction( - args({ - stageablePaths: ['a.ts'], - unstagedCount: 1, - handlers: h - }) - ) - expect(action.label).toBe('Stage all') - expect(action.disabled).toBe(false) - action.onPress() - expect(h.stageAll).toHaveBeenCalledTimes(1) - }) - - it('selects Commit for staged work with a message and dispatches commit', () => { - const h = handlers() - const action = buildMobileSourceControlPrimaryAction( - args({ - stagedCount: 1, - commitMessage: 'Ship it', - handlers: h - }) - ) - expect(action.label).toBe('Commit 1 file') - expect(action.disabled).toBe(false) - action.onPress() - expect(h.commit).toHaveBeenCalledTimes(1) - }) - - it('selects Publish Branch only when a current branch exists', () => { - expect( - buildMobileSourceControlPrimaryAction( - args({ status: status({ upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 } }) }) - ).label - ).toBe('Publish Branch') - const detached = buildMobileSourceControlPrimaryAction( - args({ - status: status({ - branch: undefined, - upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 } - }) - }) - ) - expect(detached.label).toBe('Commit') - expect(detached.disabled).toBe(true) - }) - - it('does not republish a branch whose review is already merged, like desktop', () => { - const action = buildMobileSourceControlPrimaryAction( - args({ - status: status({ upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 } }), - review: { prState: 'merged', isPRStateLoading: false, reviewNumber: 7, pushTarget: null } - }) - ) - expect(action.disabled).toBe(true) - expect(action.accessibilityHint).toBe('Nothing to commit. The review is already merged.') - }) - - it('dispatches force push with lease when the shared decision requires it', () => { - const h = handlers() - const action = buildMobileSourceControlPrimaryAction( - args({ - status: status({ - upstreamStatus: { - hasUpstream: true, - ahead: 10, - behind: 2, - behindCommitsArePatchEquivalent: true - } - }), - branchCompareResult: { - entries: [], - summary: { - status: 'ready', - errorMessage: undefined, - baseRef: 'main', - baseOid: 'base', - compareRef: 'HEAD', - changedFiles: 0, - commitsAhead: 3, - headOid: 'abc', - mergeBase: 'def' - } - }, - handlers: h - }) - ) - expect(action.label).toBe('Force Push') - expect(action.requiresForceWithLease).toBe(true) - action.onPress() - expect(h.runActionSheetGitSequence).toHaveBeenCalledWith('force-push', [ - { method: 'git.push', params: { forceWithLease: true } } - ]) - }) - - it('leaves a force push to desktop when the review has its own push target', () => { - const h = handlers() - const action = buildMobileSourceControlPrimaryAction( - args({ - status: status({ - upstreamStatus: { - hasUpstream: true, - ahead: 2, - behind: 2, - behindCommitsArePatchEquivalent: true - } - }), - review: { - prState: 'open', - isPRStateLoading: false, - reviewNumber: 7, - pushTarget: { remoteName: 'fork', branchName: 'feature' } - }, - handlers: h - }) - ) - expect(action.requiresForceWithLease).toBe(true) - expect(action.disabled).toBe(true) - action.onPress() - expect(h.runActionSheetGitSequence).not.toHaveBeenCalled() - }) - - it('disables the button for unresolved entries even during a conflict operation', () => { - const action = buildMobileSourceControlPrimaryAction( - args({ - status: status({ conflictOperation: 'merge' }), - hasUnresolvedConflicts: true, - stagedCount: 1, - commitMessage: 'Resolve' - }) - ) - expect(action.label).toBe('Commit 1 file') - expect(action.disabled).toBe(true) - expect(action.accessibilityHint).toBe('Resolve conflicts before committing.') - }) - - it('does not block solely because a conflict operation exists without unresolved entries', () => { - const action = buildMobileSourceControlPrimaryAction( - args({ - status: status({ conflictOperation: 'merge' }), - hasUnresolvedConflicts: false, - stagedCount: 1, - commitMessage: 'Resolve' - }) - ) - expect(action.disabled).toBe(false) - }) -}) - -describe('buildMobileBranchRemoteAction', () => { - it('offers the branch remote step past local changes, forcing after a rebase', async () => { - const h = handlers() - const rebased = buildMobileBranchRemoteAction( - args({ - status: status({ - upstreamStatus: { - hasUpstream: true, - ahead: 3, - behind: 3, - behindCommitsArePatchEquivalent: true - } - }), - stagedCount: 1, - commitMessage: 'wip', - handlers: h - }) - ) - expect(rebased?.label).toBe('Force Push') - await rebased?.run() - expect(h.runActionSheetGitSequence).toHaveBeenCalledWith('force-push', [ - { method: 'git.push', params: { forceWithLease: true } } - ]) - expect(buildMobileBranchRemoteAction(args())).toBeNull() - }) -}) diff --git a/mobile/src/source-control/source-control-reply-schema.test.ts b/mobile/src/source-control/source-control-reply-schema.test.ts deleted file mode 100644 index 5c82f33843a..00000000000 --- a/mobile/src/source-control/source-control-reply-schema.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { collectSalvageDrops } from '../../../src/shared/zod-salvage' -import { - gitBranchCompareResultSchema, - gitCommitCompareResultSchema -} from './git-compare-reply-schema' -import { gitHistoryResultSchema } from './git-history-reply-schema' -import { - buildMobileSourceControlSections, - countStagedEntries, - countUnstagedEntries, - isMobileGitStageableEntry -} from './mobile-git-status' -import { gitStatusHostPayloadSchema, gitStatusProjectionSchema } from './git-status-reply-schema' -import { hostedReviewEligibilitySchema } from './hosted-review-reply-schema' - -describe('source-control reply schemas', () => { - it('decodes a reply from a host that sends members this client does not declare', () => { - // Strict schemas would reject new host fields or silently drop dirty rows. - const fromNewerHost = { - entries: [ - { path: 'src/app.ts', status: 'modified', area: 'staged', submoduleRoot: 'vendor/x' } - ], - conflictOperation: 'unknown', - branch: 'feature', - didHitLimit: false - } - const parsed = gitStatusHostPayloadSchema.safeParse(fromNewerHost) - expect(parsed.success).toBe(true) - expect(parsed.data?.entries).toHaveLength(1) - expect(parsed.data?.entries[0]?.submoduleRoot).toBe('vendor/x') - expect(parsed.data?.didHitLimit).toBe(false) - }) - - it('keeps a commit whose timestamp the host sent as null', () => { - // Number-or-absent would drop null timestamps. - const parsed = gitHistoryResultSchema.safeParse({ - items: [{ id: 'a'.repeat(40), parentIds: [], subject: '', timestamp: null }] - }) - expect(parsed.data?.items).toHaveLength(1) - }) - - it('passes an eligibility token through that the shared union does not list', () => { - const parsed = hostedReviewEligibilitySchema.safeParse({ - provider: 'gitlab', - reviewLookupOutcome: 'none' - }) - expect(parsed.data?.reviewLookupOutcome).toBe('none') - }) - - it('writes every projected member out, absent ones included', () => { - // Projection preserves keys as undefined; optional-key omission would change the established shape. - const projected = gitStatusProjectionSchema.parse({ - entries: [{ path: 'src/app.ts', status: 'modified', area: 'staged' }] - }) - expect(projected && Object.keys(projected).sort()).toEqual([ - 'branch', - 'conflictOperation', - 'entries', - 'head', - 'upstreamStatus' - ]) - expect(projected?.entries[0] && Object.keys(projected.entries[0]).sort()).toEqual([ - 'added', - 'area', - 'conflictKind', - 'conflictStatus', - 'conflictStatusSource', - 'oldPath', - 'path', - 'removed', - 'status', - 'submodule', - 'submoduleRoot' - ]) - }) - - it('reports a dropped entry instead of thinning the list in silence', () => { - const salvaged = collectSalvageDrops(() => - gitStatusHostPayloadSchema.safeParse({ - entries: [{ path: 'src/app.ts', status: 'modified', area: 'staged' }, { path: 42 }] - }) - ) - expect(salvaged.value.success).toBe(true) - expect(salvaged.droppedCount).toBe(1) - expect(salvaged.droppedPaths).toEqual(['1']) - }) -}) - -// Unknown routing enums must degrade to values readers already handle, preserving renderable replies. - -const STATUS_ENTRY = { path: 'src/app.ts', status: 'modified', area: 'staged' } - -describe('an enum arm this build does not know', () => { - it('degrades a compare summary status rather than refusing the whole compare', () => { - const parsed = gitBranchCompareResultSchema.safeParse({ - summary: { baseRef: 'origin/main', changedFiles: 1, status: 'shallow-base' } - }) - expect(parsed.data?.summary.status).toBe('error') - const absent = gitBranchCompareResultSchema.safeParse({ - summary: { baseRef: 'origin/main', changedFiles: 1 } - }) - expect(absent.success).toBe(false) - const wrongType = gitBranchCompareResultSchema.safeParse({ - summary: { baseRef: 'origin/main', changedFiles: 1, status: 7 } - }) - expect(wrongType.success).toBe(false) - }) - - it('degrades a branch entry status rather than dropping the row', () => { - const parsed = gitBranchCompareResultSchema.safeParse({ - summary: { baseRef: 'origin/main', changedFiles: 1, status: 'ready' }, - entries: [{ path: 'src/app.ts', status: 'typechange' }] - }) - expect(parsed.data?.entries?.[0]?.status).toBe('modified') - const absent = gitBranchCompareResultSchema.safeParse({ - summary: { baseRef: 'origin/main', changedFiles: 1, status: 'ready' }, - entries: [{ path: 'src/app.ts' }] - }) - expect(absent.data?.entries).toHaveLength(0) - }) - - it('degrades an optional changed-file status rather than dropping the row', () => { - const parsed = gitCommitCompareResultSchema.safeParse({ - entries: [{ path: 'src/app.ts', status: 'typechange' }] - }) - expect(parsed.data?.entries[0]?.status).toBe('modified') - const absent = gitCommitCompareResultSchema.safeParse({ entries: [{ path: 'src/app.ts' }] }) - expect(absent.data?.entries[0]?.status).toBeUndefined() - }) - - it('degrades a working-tree entry status rather than dropping the row', () => { - const parsed = gitStatusHostPayloadSchema.safeParse({ - entries: [{ ...STATUS_ENTRY, status: 'typechange' }] - }) - expect(parsed.data?.entries[0]?.status).toBe('modified') - const absent = gitStatusHostPayloadSchema.safeParse({ - entries: [{ path: 'src/app.ts', area: 'staged' }] - }) - expect(absent.data?.entries).toHaveLength(0) - }) - - it('degrades the same status inside the normalized projection', () => { - const projected = gitStatusProjectionSchema.parse({ - entries: [{ ...STATUS_ENTRY, status: 'typechange' }] - }) - expect(projected?.entries[0]?.status).toBe('modified') - }) - - it('keeps a row whose conflict tokens it does not know, reading them as absent', () => { - const parsed = gitStatusHostPayloadSchema.safeParse({ - entries: [ - { - ...STATUS_ENTRY, - conflictKind: 'both_renamed', - conflictStatus: 'resolved_upstream', - conflictStatusSource: 'relay' - } - ] - }) - expect(parsed.data?.entries).toHaveLength(1) - expect(parsed.data?.entries[0]?.conflictKind).toBeUndefined() - expect(parsed.data?.entries[0]?.conflictStatus).toBeUndefined() - expect(parsed.data?.entries[0]?.conflictStatusSource).toBeUndefined() - }) - - it('passes a hosted-review provider through instead of degrading it', () => { - // Provider round-trips; enum fallback would rewrite the host’s token. - const parsed = hostedReviewEligibilitySchema.safeParse({ provider: 'codeberg' }) - expect(parsed.data?.provider).toBe('codeberg') - expect(hostedReviewEligibilitySchema.safeParse({}).success).toBe(false) - expect(hostedReviewEligibilitySchema.safeParse({ provider: 7 }).success).toBe(false) - }) - - it('keeps a row whose staging area it does not know, in no section', () => { - // Unknown area withholds actions but keeps the row in conflict gating. - const parsed = gitStatusHostPayloadSchema.safeParse({ - entries: [{ ...STATUS_ENTRY, area: 'stashed', conflictStatus: 'unresolved' }] - }) - const entries = parsed.data?.entries ?? [] - expect(entries).toHaveLength(1) - expect(entries[0]?.area).toBeUndefined() - expect(entries.some((entry) => entry.conflictStatus === 'unresolved')).toBe(true) - expect(buildMobileSourceControlSections(entries, 'changes-first')).toHaveLength(0) - expect(isMobileGitStageableEntry(entries[0] ?? STATUS_ENTRY)).toBe(false) - expect(countStagedEntries(entries)).toBe(0) - expect(countUnstagedEntries(entries)).toBe(0) - }) - - it('reads an explicit null entries list as an empty compare', () => { - // Explicit null entries must reach the consumer’s empty-list fallback. - const parsed = gitBranchCompareResultSchema.safeParse({ - summary: { baseRef: 'origin/main', changedFiles: 0, status: 'ready' }, - entries: null - }) - expect(parsed.success).toBe(true) - expect(parsed.data?.entries ?? []).toEqual([]) - }) -}) diff --git a/mobile/src/tasks/composer-linked-work-item.test.ts b/mobile/src/tasks/composer-linked-work-item.test.ts deleted file mode 100644 index 7b26e64226b..00000000000 --- a/mobile/src/tasks/composer-linked-work-item.test.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - buildGitHubLinkedWorkItem, - buildGitLabLinkedWorkItem, - buildLinearLinkedWorkItem, - buildSmartNameSelection, - resolveComposerBranchPick, - resolveComposerCreateSelection, - resolveWorkItemAutoName, - shouldApplyAutoName -} from './composer-linked-work-item' - -describe('linked work item builders', () => { - it('maps a GitHub PR into a linked work item', () => { - const linked = buildGitHubLinkedWorkItem({ - type: 'pr', - number: 42, - title: 'Fix bug', - url: 'https://github.com/o/r/pull/42', - repoId: 'repo-1' - }) - expect(linked).toMatchObject({ provider: 'github', type: 'pr', number: 42, repoId: 'repo-1' }) - }) - - it('maps a GitLab MR into a linked work item', () => { - const linked = buildGitLabLinkedWorkItem({ - type: 'mr', - number: 7, - title: 'Add feature', - url: 'https://gitlab.com/g/p/-/merge_requests/7', - repoId: 'repo-2' - }) - expect(linked).toMatchObject({ provider: 'gitlab', type: 'mr', number: 7, repoId: 'repo-2' }) - }) - - it('maps a Linear issue with identifier, workspace, and org key', () => { - const linked = buildLinearLinkedWorkItem({ - identifier: 'ENG-9', - title: 'Ship it', - url: 'https://linear.app/acme/issue/ENG-9', - workspaceId: 'ws-1' - }) - expect(linked).toMatchObject({ - provider: 'linear', - type: 'issue', - number: 0, - linearIdentifier: 'ENG-9', - linearWorkspaceId: 'ws-1', - linearOrganizationUrlKey: 'acme' - }) - }) -}) - -describe('shouldApplyAutoName', () => { - it('applies when the name is empty or the previous auto-name', () => { - expect(shouldApplyAutoName({ currentName: '', lastAutoName: '' })).toBe(true) - expect(shouldApplyAutoName({ currentName: 'fix-bug', lastAutoName: 'fix-bug' })).toBe(true) - }) - - it('applies when the name is a lookup query (URL / #N)', () => { - expect(shouldApplyAutoName({ currentName: '#42', lastAutoName: 'x' })).toBe(true) - expect(shouldApplyAutoName({ currentName: 'ENG-9', lastAutoName: 'x' })).toBe(false) - }) - - it('keeps a deliberately typed name', () => { - expect(shouldApplyAutoName({ currentName: 'my custom name', lastAutoName: 'other' })).toBe( - false - ) - }) -}) - -describe('resolveWorkItemAutoName', () => { - it('slugifies the title subject', () => { - expect( - resolveWorkItemAutoName({ - type: 'issue', - number: 3, - title: 'Fix the Login Bug', - provider: 'github' - }) - ).toBe('fix-the-login-bug') - }) -}) - -describe('buildSmartNameSelection', () => { - const base = (over: Record) => ({ - provider: 'github' as const, - type: 'pr' as const, - number: 12, - title: 'T', - url: 'u', - ...over - }) - - it('maps GitHub PR / issue kinds and numbers the label', () => { - expect(buildSmartNameSelection({ linkedWorkItem: base({}), baseBranch: undefined })).toEqual({ - kind: 'github-pr', - label: '#12 T', - url: 'u' - }) - expect( - buildSmartNameSelection({ linkedWorkItem: base({ type: 'issue' }), baseBranch: undefined }) - ).toMatchObject({ kind: 'github-issue' }) - }) - - it('maps GitLab MR / issue kinds', () => { - expect( - buildSmartNameSelection({ - linkedWorkItem: base({ provider: 'gitlab', type: 'mr' }), - baseBranch: undefined - }) - ).toMatchObject({ kind: 'gitlab-mr' }) - expect( - buildSmartNameSelection({ - linkedWorkItem: base({ provider: 'gitlab', type: 'issue' }), - baseBranch: undefined - }) - ).toMatchObject({ kind: 'gitlab-issue' }) - }) - - it('maps Linear with a bare title label', () => { - expect( - buildSmartNameSelection({ - linkedWorkItem: base({ provider: 'linear', type: 'issue', number: 0, title: 'ENG-9 Ship' }), - baseBranch: undefined - }) - ).toEqual({ kind: 'linear', label: 'ENG-9 Ship', url: 'u' }) - }) - - it('falls back to a branch pill', () => { - expect(buildSmartNameSelection({ linkedWorkItem: null, baseBranch: 'main' })).toEqual({ - kind: 'branch', - label: 'main' - }) - }) - - it('returns null when nothing is selected', () => { - expect(buildSmartNameSelection({ linkedWorkItem: null, baseBranch: undefined })).toBeNull() - }) -}) - -describe('resolveComposerCreateSelection', () => { - const baseCreateArgs = { - branch: null, - reuseEligibleBranch: null, - reuseSelectedBranch: false, - branchCreateIntent: false, - name: '' - } - - it('prefers a linked work item and passes resolved base fields', () => { - const selection = resolveComposerCreateSelection({ - ...baseCreateArgs, - linkedWorkItem: { - provider: 'github', - type: 'pr', - number: 5, - title: 'T', - url: 'u', - repoId: 'repo-1' - }, - base: { baseBranch: 'main', compareBaseRef: 'origin/main', branchNameOverride: 'pr-5' } - }) - expect(selection).toMatchObject({ - kind: 'work-item', - baseBranch: 'main', - compareBaseRef: 'origin/main', - branchNameOverride: 'pr-5' - }) - }) - - it('marks reuse when the eligible branch is toggled on', () => { - const selection = resolveComposerCreateSelection({ - ...baseCreateArgs, - linkedWorkItem: null, - base: { baseBranch: 'feature', branchNameOverride: 'feature' }, - branch: { refName: 'feature', localBranchName: 'feature' }, - reuseEligibleBranch: 'feature', - reuseSelectedBranch: true - }) - expect(selection).toEqual({ - kind: 'branch', - baseBranch: 'feature', - refName: 'feature', - localBranchName: 'feature', - reuse: true, - branchNameOverride: 'feature' - }) - }) - - it('returns a new-branch selection when create-branch intent is set', () => { - expect( - resolveComposerCreateSelection({ - ...baseCreateArgs, - linkedWorkItem: null, - base: {}, - branchCreateIntent: true, - name: 'feature/login' - }) - ).toEqual({ kind: 'new-branch', branchName: 'feature/login' }) - }) - - it('returns null with no work item, no branch base, and no intent', () => { - expect( - resolveComposerCreateSelection({ ...baseCreateArgs, linkedWorkItem: null, base: {} }) - ).toBeNull() - }) -}) - -describe('resolveComposerBranchPick', () => { - it('auto-names and enables reuse for an unused local branch', () => { - const pick = resolveComposerBranchPick({ - refName: 'feature', - localBranchName: 'feature', - currentName: '', - lastAutoName: '', - worktreeBranches: [] - }) - expect(pick.base).toEqual({ baseBranch: 'feature', branchNameOverride: 'feature' }) - expect(pick).toMatchObject({ - reuseEligibleBranch: 'feature', - reuseSelectedBranch: true, - name: 'feature' - }) - }) - - it('does not reuse a branch already checked out elsewhere', () => { - const pick = resolveComposerBranchPick({ - refName: 'feature', - localBranchName: 'feature', - currentName: '', - lastAutoName: '', - worktreeBranches: ['refs/heads/feature'] - }) - expect(pick.reuseEligibleBranch).toBe('feature') - expect(pick.reuseSelectedBranch).toBe(true) - expect(pick.base.branchNameOverride).toBeUndefined() - }) -}) diff --git a/mobile/src/tasks/task-source-search-reply-schema.test.ts b/mobile/src/tasks/task-source-search-reply-schema.test.ts deleted file mode 100644 index e6eec30cfaa..00000000000 --- a/mobile/src/tasks/task-source-search-reply-schema.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - taskGitHubWorkItemListSchema, - taskGitLabWorkItemListSchema, - taskLinearIssueListSchema, - taskWorkItemLookupSchema -} from './task-source-search-reply-schema' - -const githubRow = { - id: 'issue:1', - type: 'issue', - number: 1, - title: 'one', - state: 'open', - url: '', - labels: [], - updatedAt: '2020-01-01T00:00:00.000Z', - author: null -} - -const linearRow = { - id: 'issue-1', - identifier: 'ENG-1', - title: 'A Linear issue', - url: '', - state: { name: 'Todo', type: 'unstarted', color: '#000' }, - team: { id: 'team-1', key: 'ENG', name: 'Engineering' }, - labels: [], - priority: 0, - updatedAt: '2020-01-01T00:00:00.000Z', - workspaceId: 'linear-workspace' -} - -describe('the provider lists require items, and a row requires its labels', () => { - it('reads the representative GitHub search row whole', () => { - const parsed = taskGitHubWorkItemListSchema.safeParse({ items: [githubRow] }) - expect(parsed.success && parsed.data).toMatchObject({ items: [githubRow] }) - }) - - it('reads the representative GitLab row whole', () => { - const row = { ...githubRow, id: 'issue:2', number: 2, title: 'two', state: 'opened' } - const result = { items: [row], page: 1, perPage: 1, totalCount: 0, totalPages: 2 } - const parsed = taskGitLabWorkItemListSchema.safeParse(result) - expect(parsed.success && parsed.data).toEqual(result) - }) - - it('names a list with no items, which the task screen mapped unguarded', () => { - expect(taskGitHubWorkItemListSchema.safeParse({}).success).toBe(false) - expect(taskGitLabWorkItemListSchema.safeParse({ error: { type: 'quota' } }).success).toBe(false) - }) - - it('drops a row with no labels, which both label editors filter unguarded', () => { - const { labels: _labels, ...noLabels } = githubRow - const parsed = taskGitHubWorkItemListSchema.safeParse({ items: [githubRow, noLabels] }) - expect(parsed.success && parsed.data).toMatchObject({ items: [githubRow] }) - }) - - it('drops a row whose labels are not an array, rather than handing filter a string', () => { - const parsed = taskGitHubWorkItemListSchema.safeParse({ - items: [{ ...githubRow, labels: 'bug' }] - }) - expect(parsed.success && parsed.data).toMatchObject({ items: [] }) - }) - - it('drops a non-string label and keeps the row, because every reader compares it as text', () => { - const parsed = taskGitHubWorkItemListSchema.safeParse({ - items: [{ ...githubRow, labels: ['bug', 7] }] - }) - expect(parsed.success && parsed.data).toMatchObject({ items: [{ labels: ['bug'] }] }) - }) - - it('keeps items alongside an in-band provider error, as the representative reply does', () => { - const parsed = taskGitLabWorkItemListSchema.safeParse({ - items: [githubRow], - error: { type: 'not_found', message: 'missing' } - }) - expect(parsed.success && parsed.data).toMatchObject({ - error: { type: 'not_found', message: 'missing' } - }) - }) - - it('passes the source banner members through without typing them', () => { - const parsed = taskGitHubWorkItemListSchema.safeParse({ - items: [], - sources: { issues: 'upstream' }, - issueSourceFellBack: true - }) - expect(parsed.success && parsed.data).toMatchObject({ - sources: { issues: 'upstream' }, - issueSourceFellBack: true - }) - }) - - it('drops a row that is not an object rather than failing the page', () => { - const parsed = taskGitHubWorkItemListSchema.safeParse({ items: [githubRow, 'nope'] }) - expect(parsed.success && parsed.data).toMatchObject({ items: [githubRow] }) - }) -}) - -describe('a work-item row preserves author as a tri-state', () => { - it('keeps an explicit null, which the row renders as "no author"', () => { - const parsed = taskGitHubWorkItemListSchema.safeParse({ items: [githubRow] }) - expect(JSON.stringify(parsed)).toContain('"author":null') - }) - - it('keeps absence absent rather than collapsing it to null', () => { - const { author: _author, ...noAuthor } = githubRow - const parsed = taskGitHubWorkItemListSchema.safeParse({ items: [noAuthor] }) - const row = parsed.success ? parsed.data.items[0] : undefined - expect(row && 'author' in row).toBe(false) - }) -}) - -describe('the Linear issue list takes both shapes the picker has always accepted', () => { - it('reads a bare array, which linear.searchIssues answers', () => { - const parsed = taskLinearIssueListSchema.safeParse([linearRow]) - expect(parsed.success && parsed.data).toEqual([linearRow]) - }) - - it('reads an items envelope, which linear.listIssues answers', () => { - const parsed = taskLinearIssueListSchema.safeParse({ items: [linearRow] }) - expect(parsed.success && parsed.data).toEqual([linearRow]) - }) - - it('drops an issue with no id, which nothing could key a row on', () => { - const { id: _id, ...noId } = linearRow - expect(taskLinearIssueListSchema.safeParse([noId])).toMatchObject({ success: true, data: [] }) - }) - - // Missing unguarded mapping/sort inputs must drop a row instead of crashing or producing NaN. - it('drops an issue with no state, which createLinearTask reads as state.name', () => { - const { state: _state, ...noState } = linearRow - expect(taskLinearIssueListSchema.safeParse([noState])).toMatchObject({ data: [] }) - }) - - it('drops an issue with no team, which createLinearTask reads as team.name', () => { - const { team: _team, ...noTeam } = linearRow - expect(taskLinearIssueListSchema.safeParse([noTeam])).toMatchObject({ data: [] }) - }) - - it('drops an issue whose state carries no name, not just one with no state at all', () => { - const parsed = taskLinearIssueListSchema.safeParse([ - { ...linearRow, state: { type: 'unstarted', color: '#000' } } - ]) - expect(parsed).toMatchObject({ data: [] }) - }) - - it('drops an issue whose team carries no name', () => { - const parsed = taskLinearIssueListSchema.safeParse([ - { ...linearRow, team: { id: 'team-1', key: 'ENG' } } - ]) - expect(parsed).toMatchObject({ data: [] }) - }) - - it('drops an issue with no priority, which would make the reviewer comparator NaN', () => { - const { priority: _priority, ...noPriority } = linearRow - expect(taskLinearIssueListSchema.safeParse([noPriority])).toMatchObject({ data: [] }) - }) - - it.each(['identifier', 'title', 'url', 'updatedAt', 'labels'] as const)( - 'drops an issue with no %s, which the host type declares non-optional', - (member) => { - const { [member]: _dropped, ...without } = linearRow - expect(taskLinearIssueListSchema.safeParse([without])).toMatchObject({ data: [] }) - } - ) - - it('keeps the rest of the page when one row drops', () => { - const { state: _state, ...noState } = linearRow - const parsed = taskLinearIssueListSchema.safeParse([linearRow, noState]) - expect(parsed.success && parsed.data).toEqual([linearRow]) - }) - - it('names a payload that is neither shape, where main threw its own copy', () => { - expect(taskLinearIssueListSchema.safeParse({ issues: [] }).success).toBe(false) - expect(taskLinearIssueListSchema.safeParse('nope').success).toBe(false) - expect(taskLinearIssueListSchema.safeParse(null).success).toBe(false) - }) -}) - -describe('a single work-item lookup', () => { - it('keeps the host null for an item that does not resolve', () => { - expect(taskWorkItemLookupSchema.safeParse(null)).toMatchObject({ success: true, data: null }) - }) - - it('reads the corrected lookup row whole', () => { - expect(taskWorkItemLookupSchema.safeParse(githubRow)).toMatchObject({ success: true }) - }) - - // A malformed single lookup must fail the reply; silently returning it would crash the label editor. - it('names a lookup row with no labels rather than resolving the paste', () => { - const { labels: _labels, ...noLabels } = githubRow - expect(taskWorkItemLookupSchema.safeParse(noLabels).success).toBe(false) - }) - - it('names a payload that is not an item at all', () => { - expect(taskWorkItemLookupSchema.safeParse('twelve').success).toBe(false) - }) -}) diff --git a/mobile/src/tasks/use-mobile-tasks-task-pagination-actions.test.tsx b/mobile/src/tasks/use-mobile-tasks-task-pagination-actions.test.tsx deleted file mode 100644 index 383abda8201..00000000000 --- a/mobile/src/tasks/use-mobile-tasks-task-pagination-actions.test.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { createElement, useState } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' - -vi.mock('./mobile-tasks-dependencies', async () => { - const react = await import('react') - return { - useCallback: react.useCallback, - useMemo: react.useMemo, - useState: react.useState, - ...(await import('../../../src/shared/work-items')) - } -}) -vi.mock('./mobile-tasks-legacy-foundation', () => ({ - buildPartialRepositoryNotice: () => 'partial' -})) -vi.mock('./mobile-task-list-operations', () => ({ linearAccountConnect: {} })) - -import { useMobileTasksTaskPaginationActions } from './use-mobile-tasks-task-pagination-actions' -import type { TaskListLoadingModel } from './use-mobile-tasks-task-list-loading' - -const client = { id: 'client' } -type PageRow = { key: string; updatedAt?: string; source?: { repoId: string } } - -const firstPage: PageRow[] = [{ key: 'github:repo-1:issue:40', updatedAt: '2026-10-02T00:00:00Z' }] -const oldSelectionPage = [{ key: 'github:repo-1:issue:4', updatedAt: '2026-10-01T00:00:00Z' }] - -function buildModel( - fetchGitHubItemsPage: () => Promise, - githubCountedTotalPages: number | null = 2 -) { - return { - client, - clientRef: { current: client }, - connState: 'connected', - fetchGitHubItemsPage: vi.fn(fetchGitHubItemsPage), - githubCurrentPage: 0, - githubPages: [firstPage], - githubPaginationLoading: false, - githubCountedTotalPages, - loadGenerationRef: { current: 1 }, - selectedHostedRepos: [{ id: 'repo-1' }], - tasksSupported: true, - taskUiReady: true, - setError: vi.fn(), - setGithubCurrentPage: vi.fn(), - setGithubCountedTotalPages: vi.fn(), - setGithubLoadingTargetPage: vi.fn(), - setGithubPages: vi.fn(), - setGithubPaginationLoading: vi.fn(), - setItems: vi.fn() - } -} - -let renderer: ReactTestRenderer | null = null -let changePage: ((page: number) => Promise) | null = null -let canLoadUncountedNextPage = false -let totalPages = 0 - -function mount(model: ReturnType): void { - function Harness(): null { - const [countedTotalPages, setCountedTotalPages] = useState(model.githubCountedTotalPages) - model.setGithubCountedTotalPages.mockImplementation(setCountedTotalPages) - const actions = useMobileTasksTaskPaginationActions( - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the fixture carries every member the page handler reads. - { ...model, githubCountedTotalPages: countedTotalPages } as unknown as TaskListLoadingModel - ) - changePage = actions.handleGitHubPageChange - canLoadUncountedNextPage = actions.githubCanLoadUncountedNextPage - totalPages = actions.githubTotalPages - return null - } - act(() => { - renderer = create(createElement(Harness)) - }) -} - -afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - changePage = null -}) - -describe('GitHub task page change', () => { - it('asks for the target page by number and shows it', async () => { - const model = buildModel(async () => ({ - items: oldSelectionPage, - failedCount: 0, - errorTypes: [] - })) - mount(model) - await act(async () => { - await changePage?.(1) - }) - expect(model.fetchGitHubItemsPage).toHaveBeenCalledWith( - client, - model.selectedHostedRepos, - 1, - expect.any(Function) - ) - expect(model.setGithubPages).toHaveBeenCalledWith([firstPage, oldSelectionPage]) - expect(model.setItems).toHaveBeenCalledWith(oldSelectionPage) - }) - - it('drops a page that lands after the list reloaded for a new selection', async () => { - let land: (page: unknown) => void = () => {} - const model = buildModel( - () => - new Promise((resolve) => { - land = resolve - }) - ) - mount(model) - let pending: Promise | undefined - act(() => { - pending = changePage?.(1) - }) - model.loadGenerationRef.current += 1 - await act(async () => { - land({ items: oldSelectionPage, failedCount: 0, errorTypes: [] }) - await pending - }) - expect(model.setGithubPages).not.toHaveBeenCalled() - expect(model.setItems).not.toHaveBeenCalled() - expect(model.setGithubPaginationLoading).toHaveBeenLastCalledWith(false) - }) - - it('explains a clean empty next page and withdraws speculative Next', async () => { - const model = buildModel(async () => ({ items: [], failedCount: 0, errorTypes: [] }), null) - model.githubPages = [ - Array.from({ length: 72 }, (_, index) => ({ - key: `github:repo-1:issue:${index}`, - source: { repoId: 'repo-1' } - })) - ] - mount(model) - expect(canLoadUncountedNextPage).toBe(true) - await act(async () => { - await changePage?.(1) - }) - expect(model.setError).toHaveBeenCalledWith('No more results on page 2.') - expect(canLoadUncountedNextPage).toBe(false) - expect(model.setItems).not.toHaveBeenCalled() - }) - - it('explains an all-repo failure and keeps the page retryable', async () => { - const model = buildModel(async () => ({ items: [], failedCount: 1, errorTypes: [] })) - mount(model) - await act(async () => { - await changePage?.(1) - }) - expect(model.setError).toHaveBeenCalledWith('Page 2 could not be loaded from GitHub.') - expect(model.setGithubCountedTotalPages).not.toHaveBeenCalled() - expect(model.setItems).not.toHaveBeenCalled() - }) - - it('keeps a real count when an empty page is returned', async () => { - const model = buildModel(async () => ({ items: [], failedCount: 0, errorTypes: [] }), 3) - mount(model) - await act(async () => { - await changePage?.(1) - }) - expect(model.setError).toHaveBeenCalledWith('No more results on page 2.') - expect(totalPages).toBe(3) - expect(model.setItems).not.toHaveBeenCalled() - }) - - describe('without a count, as in the default all-repos view', () => { - function rows(repoId: string, count: number): PageRow[] { - return Array.from({ length: count }, (_, index) => ({ - key: `github:${repoId}:issue:${index}`, - source: { repoId } - })) - } - - function lastPageOffersNext(page: PageRow[]): boolean { - const model = buildModel(async () => ({ items: [], failedCount: 0, errorTypes: [] }), null) - model.githubPages = [page] - model.selectedHostedRepos = [{ id: 'repo-a' }, { id: 'repo-b' }] - mount(model) - return canLoadUncountedNextPage - } - - it('offers the next page when one repo filled its share and another did not', () => { - expect(lastPageOffersNext([...rows('repo-a', 36), ...rows('repo-b', 5)])).toBe(true) - }) - - it('offers no next page when no repo filled its share', () => { - expect(lastPageOffersNext([...rows('repo-a', 20), ...rows('repo-b', 5)])).toBe(false) - }) - }) -}) diff --git a/mobile/src/terminal/document/selection-range.test.ts b/mobile/src/terminal/document/selection-range.test.ts index 0ad5d095b2e..982c481718a 100644 --- a/mobile/src/terminal/document/selection-range.test.ts +++ b/mobile/src/terminal/document/selection-range.test.ts @@ -4,9 +4,6 @@ import { Terminal } from '@xterm/xterm' import { afterEach, describe, expect, it, vi } from 'vitest' import { createTerminalDocumentScope, type TerminalDocumentScope } from './document-scope' import { seedWordSelection } from './selection-range' -import { armLongPress, dispatcherShouldBlockSurface } from './tap-dispatch' -import { handleDragMove, stopEdgeScroll } from './selection-overlay' -import { terminalDocumentDouble } from './document-terminal-double.test-support' const terminals: Terminal[] = [] @@ -79,16 +76,6 @@ describe('word selection uses the rendered xterm cell range', () => { expect(f.selected()).toBe('🙂') }) - it('keeps ASCII behavior for a terminal seam without cell readers', async () => { - const f = await fixture('hello world') - const line = f.term.buffer.active.getLine(0)! - const getLine = vi.spyOn(f.term.buffer.active, 'getLine').mockReturnValue(line) - vi.spyOn(line, 'getCell').mockReturnValue(undefined) - seedWordSelection(f.scope, 8, 0) - expect(f.select).toHaveBeenLastCalledWith(6, 0, 5) - expect(getLine).toHaveBeenCalledWith(0) - }) - it('keeps an absolute scrollback row and tolerates an evicted line', async () => { const f = await fixture('한글\r\nsecond\r\nthird\r\nfourth') expect(f.term.buffer.active.viewportY).toBeGreaterThan(0) @@ -133,45 +120,3 @@ describe('Unicode selection boundaries across every rendered column', () => { expect(f.selected()).toBe('next') }) }) - -it('keeps the Unicode seed while the long-press finger owns a forward or backward drag', async () => { - const f = await fixture('가 한글 next') - const surface = document.createElement('div') - f.scope.term = { - ...terminalDocumentDouble().terminal, - cols: f.term.cols, - rows: f.term.rows, - buffer: f.scope.term!.buffer, - select: f.select, - _core: { _renderService: { dimensions: { css: { cell: { width: 8, height: 15 } } } } } - } - Object.assign(f.scope, { - surface, - selectionOverlay: document.createElement('div'), - handleStart: document.createElement('div'), - handleEnd: document.createElement('div'), - selMenu: document.createElement('div'), - viewportRect: () => ({ left: 0, top: 50, width: 480, height: 200 }), - currentScale: 1, - userScale: 1 - }) - f.scope.touchDispatch.mode = 'surface' - f.scope.touchDispatch.touchId = 7 - f.scope.tapCandidate = { x: 36, y: 57, t: Date.now(), identifier: 7 } - vi.useFakeTimers() - armLongPress(f.scope, new Touch({ identifier: 7, target: surface, clientX: 36, clientY: 57 })) - vi.advanceTimersByTime(500) - expect(f.selected()).toBe('한글') - expect(f.scope.sel?.anchor).toEqual({ col: 3, row: 0 }) - expect(f.scope.sel?.focus).toEqual({ col: 6, row: 0 }) - expect(f.scope.touchDispatch).toMatchObject({ mode: 'select-drag', touchId: 7 }) - expect(dispatcherShouldBlockSurface(f.scope)).toBe(true) - expect(f.scope.tapCandidate).toBeNull() - - handleDragMove(f.scope, 'end', 11 * 8 + 4, 57) - expect(f.selected()).toBe('한글 next') - handleDragMove(f.scope, 'end', 4, 57) - expect(f.select).toHaveBeenLastCalledWith(0, 0, 4) - expect(f.selected()).toBe('가 한') - stopEdgeScroll(f.scope) -}) diff --git a/mobile/src/terminal/terminal-live-pending-flush-state.test.ts b/mobile/src/terminal/terminal-live-pending-flush-state.test.ts index 56d0d1ee65a..f4ce91838f3 100644 --- a/mobile/src/terminal/terminal-live-pending-flush-state.test.ts +++ b/mobile/src/terminal/terminal-live-pending-flush-state.test.ts @@ -1,24 +1,12 @@ import { describe, expect, it, vi } from 'vitest' -import { - isTerminalLiveInputWithinByteLimit, - TERMINAL_LIVE_INPUT_MAX_BYTES -} from './terminal-live-input' +import { TERMINAL_LIVE_INPUT_MAX_BYTES } from './terminal-live-input' import { cancelTerminalLivePendingFlush, createTerminalLivePendingFlushState, - queueTerminalLiveControlSend, queueTerminalLiveMirrorSend, waitForTerminalLivePendingFlush } from './terminal-live-pending-flush-state' -describe('terminal live pending flush state', () => { - it('Given no in-flight flush When waiting for the barrier Then allows control input', async () => { - const state = createTerminalLivePendingFlushState() - - await expect(waitForTerminalLivePendingFlush(state)).resolves.toBe(true) - }) -}) - describe('terminal live mirror send queue', () => { it.each(['throw', 'reject', 'refuse', 'too-large'] as const)( 'retires dependent input after a send fails by %s', @@ -63,100 +51,6 @@ describe('terminal live mirror send queue', () => { } ) - it.each(['a', '😀'])( - 'keeps coalesced %s input within the live sender byte limit', - async (glyph) => { - const state = createTerminalLivePendingFlushState() - const payloads: string[] = [] - const received: string[] = [] - let release = (_accepted: boolean): void => {} - const sender = async (_handle: string, payload: string): Promise => { - payloads.push(payload) - if (payloads.length === 1) { - return new Promise((resolve) => { - release = resolve - }) - } - const accepted = isTerminalLiveInputWithinByteLimit(payload) - if (accepted) { - received.push(payload) - } - return accepted - } - const part = glyph.repeat( - TERMINAL_LIVE_INPUT_MAX_BYTES / 2 / new TextEncoder().encode(glyph).length - ) - const first = queueTerminalLiveMirrorSend(state, 'terminal-1', 'held', sender) - const queued = Array.from({ length: 3 }, () => - queueTerminalLiveMirrorSend(state, 'terminal-1', part, sender) - ) - release(true) - await expect(Promise.all([first, ...queued])).resolves.toEqual([true, true, true, true]) - expect(received.join('')).toBe(part.repeat(3)) - expect(payloads).toHaveLength(3) - expect(payloads.every((payload) => isTerminalLiveInputWithinByteLimit(payload))).toBe(true) - } - ) - - it('Given high RTT When more input queues Then pending bytes share one follow-up send', async () => { - const state = createTerminalLivePendingFlushState() - const payloads: string[] = [] - let resolveFirstSend: (value: boolean) => void = () => {} - const sender = async (_handle: string, payload: string): Promise => { - payloads.push(payload) - if (payloads.length === 1) { - return new Promise((resolve) => { - resolveFirstSend = resolve - }) - } - return true - } - - const first = queueTerminalLiveMirrorSend(state, 'terminal-1', 'a', sender) - const second = queueTerminalLiveMirrorSend(state, 'terminal-1', 'b', sender) - const third = queueTerminalLiveMirrorSend(state, 'terminal-1', 'c', sender) - await Promise.resolve() - - expect(payloads).toEqual(['a']) - resolveFirstSend(true) - await expect(Promise.all([first, second, third])).resolves.toEqual([true, true, true]) - expect(payloads).toEqual(['a', 'bc']) - }) - - it('batches repeated controls behind an in-flight send, apart from the text before them', async () => { - const state = createTerminalLivePendingFlushState() - const writes: string[] = [] - let release = (): void => {} - const sender = (_handle: string, payload: string): Promise => { - writes.push(payload) - return writes.length === 1 - ? new Promise((resolve) => { - release = () => resolve(true) - }) - : Promise.resolve(true) - } - const text = queueTerminalLiveMirrorSend(state, 'terminal-1', 'ls', sender) - void queueTerminalLiveMirrorSend(state, 'terminal-1', ' -la', sender) - const controls = ['\x1b[A', '\x1b[A', '\r'].map((bytes) => - queueTerminalLiveControlSend(state, 'terminal-1', bytes, sender) - ) - - release() - - await expect(text).resolves.toBe(true) - await expect(Promise.all(controls)).resolves.toEqual([true, true, true]) - expect(writes).toEqual(['ls', ' -la', '\x1b[A\x1b[A\r']) - }) - - it('Given a settled mirror send When it was the newest Then the state resets to null', async () => { - const state = createTerminalLivePendingFlushState() - - await queueTerminalLiveMirrorSend(state, 'terminal-1', 'payload', async () => true) - await Promise.resolve() - - expect(state.current).toBeNull() - }) - it('cancels flush waiters before the retired transport acknowledges', async () => { const state = createTerminalLivePendingFlushState() let releaseRetired = (_sent: boolean): void => {} @@ -201,21 +95,4 @@ describe('terminal live mirror send queue', () => { await control } }) - - it('Given queued input When the queue is cancelled Then unsent input is dropped', async () => { - const state = createTerminalLivePendingFlushState() - let resolveSend: (value: boolean) => void = () => {} - const sender = async (): Promise => - new Promise((resolve) => { - resolveSend = resolve - }) - const active = queueTerminalLiveMirrorSend(state, 'terminal-1', 'a', sender) - const pending = queueTerminalLiveMirrorSend(state, 'terminal-1', 'b', sender) - - cancelTerminalLivePendingFlush(state) - - await expect(Promise.all([active, pending])).resolves.toEqual([false, false]) - expect(state.current).toBeNull() - resolveSend(true) - }) }) diff --git a/mobile/src/terminal/terminal-text-field-submit-binding.web.test.ts b/mobile/src/terminal/terminal-text-field-submit-binding.web.test.ts index 040611cf93c..f77388310db 100644 --- a/mobile/src/terminal/terminal-text-field-submit-binding.web.test.ts +++ b/mobile/src/terminal/terminal-text-field-submit-binding.web.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it, vi } from 'vitest' import type { TextInput } from 'react-native' import { hostOs } from '../platform/host-os' -import { bindTerminalTextFieldSubmit } from './terminal-text-field-submit-binding' import { bindTerminalTextFieldSubmit as bindOnWeb } from './terminal-text-field-submit-binding.web' vi.mock('../platform/host-os', () => ({ hostOs: vi.fn(() => 'android') })) @@ -23,19 +22,6 @@ function sendBeforeInput(field: HTMLElement, inputType: string): InputEvent { return event } -describe('the native submit binding', () => { - it('binds nothing, because the editor action already fires onSubmitEditing', () => { - const onSubmit = vi.fn() - const field = mountField() - - const unbind = bindTerminalTextFieldSubmit(asField(field), onSubmit) - sendBeforeInput(field, 'insertLineBreak') - - expect(onSubmit).not.toHaveBeenCalled() - expect(() => unbind()).not.toThrow() - }) -}) - describe('the web sibling', () => { it('submits on the line break the browser reports, and cancels it', () => { const onSubmit = vi.fn() @@ -48,17 +34,6 @@ describe('the web sibling', () => { expect(event.defaultPrevented).toBe(true) }) - it('submits a multiline field on its paragraph break', () => { - const onSubmit = vi.fn() - const field = document.createElement('textarea') - document.body.append(field) - bindOnWeb(asField(field), onSubmit) - - sendBeforeInput(field, 'insertParagraph') - - expect(onSubmit).toHaveBeenCalledTimes(1) - }) - // IME candidate acceptance emits insertCompositionText, so it must not submit terminal input. it('leaves a composition commit alone', () => { const onSubmit = vi.fn() @@ -71,16 +46,6 @@ describe('the web sibling', () => { expect(event.defaultPrevented).toBe(false) }) - it('ignores ordinary typing', () => { - const onSubmit = vi.fn() - const field = mountField() - bindOnWeb(asField(field), onSubmit) - - sendBeforeInput(field, 'insertText') - - expect(onSubmit).not.toHaveBeenCalled() - }) - it('stops listening once the field is unbound', () => { const onSubmit = vi.fn() const field = mountField() @@ -90,21 +55,6 @@ describe('the web sibling', () => { expect(onSubmit).not.toHaveBeenCalled() }) - - it('tolerates a field that never mounted', () => { - expect(() => bindOnWeb(asField(null), vi.fn())()).not.toThrow() - }) - - it('leaves a node that is not a field alone', () => { - const onSubmit = vi.fn() - const div = document.createElement('div') - document.body.append(div) - - bindOnWeb(asField(div), onSubmit) - sendBeforeInput(div, 'insertLineBreak') - - expect(onSubmit).not.toHaveBeenCalled() - }) }) describe.each(['android', 'ios'] as const)('live terminal keyboard chords on %s', (os) => { @@ -198,13 +148,6 @@ describe.each(['android', 'ios'] as const)('live terminal keyboard chords on %s' expect(onChord).not.toHaveBeenCalled() }) - it('leaves standalone web keyboard handling unchanged', () => { - const { key, onChord } = setup(true, 'web') - expect(key('Tab').defaultPrevented).toBe(false) - expect(key('c', { ctrlKey: true }).defaultPrevented).toBe(false) - expect(onChord).not.toHaveBeenCalled() - }) - it('does not swallow keys when the current terminal cannot send', () => { const { key } = setup(false) expect(key('Tab').defaultPrevented).toBe(false) diff --git a/mobile/src/terminal/use-terminal-live-input-focus.test.ts b/mobile/src/terminal/use-terminal-live-input-focus.test.ts deleted file mode 100644 index e9fb43162fc..00000000000 --- a/mobile/src/terminal/use-terminal-live-input-focus.test.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { createElement, type RefObject } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { - TerminalLiveInputFocusTarget, - TerminalLiveInputFocusTimerRef -} from './terminal-live-input' -import { useTerminalLiveInputFocus } from './use-terminal-live-input-focus' - -type HarnessProps = { - readonly activeHandleRef: RefObject - readonly canSend: boolean - readonly inputRef: RefObject - readonly keyboardHeight?: number - readonly lifecycleIdentity: object | null - readonly lifecycleKey: string - readonly reopenFocusedInputWhenKeyboardHidden: boolean - readonly timerRef: TerminalLiveInputFocusTimerRef -} - -type FocusHandlers = ReturnType - -function createFocusTarget(initiallyFocused = false): TerminalLiveInputFocusTarget & { - readonly blur: ReturnType - readonly focus: ReturnType -} { - let focused = initiallyFocused - return { - blur: vi.fn(() => { - focused = false - }), - focus: vi.fn(() => { - focused = true - }), - isFocused: () => focused - } -} - -function createTimerRef(): TerminalLiveInputFocusTimerRef { - return { current: null } -} - -function createHarness(initialProps: HarnessProps): { - readonly handlers: () => FocusHandlers - readonly render: (props: HarnessProps) => void - readonly unmount: () => void -} { - let handlers: FocusHandlers | null = null - let renderer: ReactTestRenderer | null = null - - function Harness(props: HarnessProps): null { - handlers = useTerminalLiveInputFocus({ - ...props, - keyboardHeight: props.keyboardHeight ?? 0 - }) - return null - } - - act(() => { - renderer = create(createElement(Harness, initialProps)) - }) - if (!handlers || !renderer) { - throw new Error('terminal live input focus harness did not render') - } - - return { - handlers: () => { - if (!handlers) { - throw new Error('terminal live input focus harness is not mounted') - } - return handlers - }, - render: (props) => { - act(() => renderer?.update(createElement(Harness, props))) - }, - unmount: () => { - act(() => renderer?.unmount()) - handlers = null - } - } -} - -function connectedProps( - inputRef: RefObject, - timerRef = createTimerRef(), - activeHandleRef: RefObject = { current: 'terminal-a' } -): HarnessProps { - return { - activeHandleRef, - canSend: true, - inputRef, - lifecycleIdentity: null, - lifecycleKey: 'host-a:worktree-a:connected', - reopenFocusedInputWhenKeyboardHidden: true, - timerRef - } -} - -describe('terminal live input focus hook', () => { - afterEach(() => { - vi.useRealTimers() - }) - - it('defers initial terminal surface focus until the WebView touch has completed', () => { - vi.useFakeTimers() - const input = createFocusTarget() - const harness = createHarness(connectedProps({ current: input })) - - harness.handlers().handleTerminalTap('terminal-a') - expect(input.focus).not.toHaveBeenCalled() - - vi.runOnlyPendingTimers() - expect(input.focus).toHaveBeenCalledTimes(1) - harness.unmount() - }) - - it('reacquires focus after successive terminal tap notifications', () => { - vi.useFakeTimers() - const input = createFocusTarget() - const harness = createHarness(connectedProps({ current: input })) - - harness.handlers().handleTerminalTap('terminal-a') - vi.runOnlyPendingTimers() - harness.handlers().handleTerminalTap('terminal-a') - vi.runAllTimers() - - expect(input.blur).toHaveBeenCalledTimes(1) - expect(input.focus).toHaveBeenCalledTimes(2) - harness.unmount() - }) - it('preserves the focused iPad responder when a hardware keyboard keeps keyboard height at zero', () => { - vi.useFakeTimers() - const input = createFocusTarget(true) - const inputRef = { current: input } - const harness = createHarness({ - ...connectedProps(inputRef), - reopenFocusedInputWhenKeyboardHidden: false - }) - - harness.handlers().handleTerminalTap('terminal-a') - vi.runAllTimers() - - expect(input.blur).not.toHaveBeenCalled() - expect(input.focus).toHaveBeenCalledTimes(1) - harness.unmount() - }) - - it('cancels focus when navigation reuses the mounted route', () => { - vi.useFakeTimers() - const oldInput = createFocusTarget() - const inputRef: RefObject = { current: oldInput } - const timerRef = createTimerRef() - const harness = createHarness(connectedProps(inputRef, timerRef)) - harness.handlers().handleTerminalTap('terminal-a') - const newInput = createFocusTarget() - inputRef.current = newInput - harness.render({ - ...connectedProps(inputRef, timerRef), - lifecycleKey: 'host-a:worktree-b:connected' - }) - vi.runOnlyPendingTimers() - expect(oldInput.focus).not.toHaveBeenCalled() - expect(newInput.focus).not.toHaveBeenCalled() - - harness.handlers().handleTerminalTap('terminal-a') - vi.runOnlyPendingTimers() - expect(newInput.focus).toHaveBeenCalledTimes(1) - harness.unmount() - }) - - it('drops pending focus when reconnect reuses the mounted route', () => { - vi.useFakeTimers() - const oldInput = createFocusTarget() - const oldInputRef = { current: oldInput } - const timerRef = createTimerRef() - const harness = createHarness(connectedProps(oldInputRef, timerRef)) - harness.handlers().handleTerminalTap('terminal-a') - harness.render({ - ...connectedProps(oldInputRef, timerRef), - canSend: false, - lifecycleIdentity: {}, - lifecycleKey: 'host-a:worktree-a:disconnected' - }) - vi.runOnlyPendingTimers() - expect(oldInput.focus).not.toHaveBeenCalled() - - const replacementInput = createFocusTarget() - oldInputRef.current = replacementInput - harness.render({ - ...connectedProps(oldInputRef, timerRef), - lifecycleIdentity: {}, - lifecycleKey: 'host-a:worktree-a:connected' - }) - harness.handlers().handleTerminalTap('terminal-a') - vi.runOnlyPendingTimers() - expect(replacementInput.focus).toHaveBeenCalledTimes(1) - harness.unmount() - }) - - it('cancels focus when a retained screen blurs', () => { - vi.useFakeTimers() - const input = createFocusTarget() - const timerRef = createTimerRef() - const harness = createHarness(connectedProps({ current: input }, timerRef)) - - harness.handlers().handleTerminalTap('terminal-a') - harness.handlers().resetLiveInputFocus() - vi.runOnlyPendingTimers() - - expect(timerRef.current).toBeNull() - expect(input.blur).toHaveBeenCalledTimes(1) - expect(input.focus).not.toHaveBeenCalled() - harness.unmount() - }) - - it('does not let stale handle state focus a replacement terminal', () => { - vi.useFakeTimers() - const firstInput = createFocusTarget() - const inputRef: RefObject = { current: firstInput } - const timerRef = createTimerRef() - const activeHandleRef = { current: 'terminal-a' as string | null } - const harness = createHarness(connectedProps(inputRef, timerRef, activeHandleRef)) - harness.handlers().handleTerminalTap('terminal-a') - - const replacementInput = createFocusTarget() - inputRef.current = replacementInput - activeHandleRef.current = 'terminal-b' - harness.render(connectedProps(inputRef, timerRef, activeHandleRef)) - vi.runOnlyPendingTimers() - - expect(firstInput.focus).not.toHaveBeenCalled() - expect(replacementInput.focus).not.toHaveBeenCalled() - harness.handlers().handleTerminalTap('terminal-b') - vi.runOnlyPendingTimers() - expect(replacementInput.focus).toHaveBeenCalledTimes(1) - harness.unmount() - }) - - it('keeps the native focus target immediate outside the WebView tap path', () => { - const input = createFocusTarget() - const harness = createHarness(connectedProps({ current: input })) - - harness.handlers().focusLiveInput() - - expect(input.focus).toHaveBeenCalledTimes(1) - harness.unmount() - }) -}) diff --git a/mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts b/mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts index c7b9c6a245e..d5077b38d36 100644 --- a/mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts +++ b/mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts @@ -1,9 +1,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { RelayOuterError } from './mobile-relay-e2ee-link' + import { bundle, dependencies, - FakeLogicalClient, FakeRelaySession, FakeSession, host, @@ -54,50 +53,6 @@ describe('mobile endpoint supervisor nudges', () => { expect(vi.getTimerCount()).toBe(0) }) - it('restarts a disconnected Relay immediately on an app-resume retry', async () => { - const logical = new FakeLogicalClient('disconnected', 'relay') - const openRelay = vi - .fn() - .mockReturnValueOnce(new FakeRelaySession('disconnected', new RelayOuterError(4408))) - .mockReturnValueOnce(new FakeRelaySession('connected')) - const deps = dependencies({ - openRelay, - openDirect: vi.fn(() => new FakeSession('disconnected')), - randomBytes: () => new Uint8Array([128, 0]) - }) - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - - await supervisor.start() - await vi.advanceTimersByTimeAsync(0) - expect(openRelay).toHaveBeenCalledOnce() - - supervisor.nudge('app-resume') - await vi.advanceTimersByTimeAsync(0) - - expect(openRelay).toHaveBeenCalledTimes(2) - expect(logical.getState()).toBe('connected') - expect(logical.getActivePath()).toBe('relay') - supervisor.stop() - }) - - it('leaves physical relay probing to the session watchdog', async () => { - const logical = new FakeLogicalClient('connected', 'relay') - const deps = dependencies() - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - - supervisor.nudge('focus') - await vi.advanceTimersByTimeAsync(0) - expect(logical.sendRequest).not.toHaveBeenCalled() - expect(logical.suspendActiveSession).not.toHaveBeenCalled() - expect(deps.openRelay).not.toHaveBeenCalled() - - supervisor.nudge('focus') - await vi.advanceTimersByTimeAsync(0) - expect(logical.sendRequest).not.toHaveBeenCalled() - supervisor.stop() - }) - it('withdraws the racing relay dial when direct authenticated while it was in flight', async () => { // Withdraw if direct wins before relay authentication; never close the winning session. const directSession = new FakeSession('connecting') diff --git a/mobile/src/transport/mobile-relay-background-lifecycle.test.ts b/mobile/src/transport/mobile-relay-background-lifecycle.test.ts deleted file mode 100644 index fcc2bfbe53d..00000000000 --- a/mobile/src/transport/mobile-relay-background-lifecycle.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - dependencies, - FakeLogicalClient, - FakeRelaySession, - FakeSession, - host, - relay -} from './mobile-endpoint-supervisor-test-fakes' -import { MobileEndpointSupervisor } from './mobile-endpoint-supervisor' - -vi.mock('react-native', () => ({ Platform: { OS: 'ios' } })) -vi.mock('expo-secure-store', () => ({ WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'when-unlocked' })) -vi.mock('expo-crypto', () => ({ getRandomBytes: (length: number) => new Uint8Array(length) })) - -describe('mobile Relay background lifecycle', () => { - beforeEach(() => { - vi.useFakeTimers() - vi.setSystemTime(new Date('2026-07-13T12:00:00Z')) - }) - - afterEach(() => vi.useRealTimers()) - - it('retains a relay session across a quick background and foreground', async () => { - const logical = new FakeLogicalClient('connected', 'relay') - const deps = dependencies() - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - - supervisor.setForeground(false) - await vi.advanceTimersByTimeAsync(29_999) - expect(logical.suspendActiveSession).not.toHaveBeenCalled() - expect(logical.getState()).toBe('connected') - - supervisor.setForeground(true) - await vi.advanceTimersByTimeAsync(1) - - expect(logical.suspendActiveSession).not.toHaveBeenCalled() - expect(deps.openRelay).not.toHaveBeenCalled() - expect(logical.getActivePath()).toBe('relay') - supervisor.stop() - }) - - it('nudges retained relay availability with the tolerant focus policy', async () => { - const logical = new FakeLogicalClient('disconnected', 'lan') - const session = new FakeRelaySession('connected') - const deps = dependencies({ - openDirect: vi.fn(() => new FakeSession('disconnected')), - openRelay: vi.fn(() => session) - }) - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - expect(logical.getActivePath()).toBe('relay') - - supervisor.nudge('focus') - expect(session.notifyForeground).not.toHaveBeenCalled() - - supervisor.setForeground(false) - await vi.advanceTimersByTimeAsync(5_000) - supervisor.setForeground(true) - expect(session.notifyForeground).toHaveBeenCalledExactlyOnceWith('focus') - - // Past the grace the relay is already released, so there is nothing to probe. - supervisor.setForeground(false) - await vi.advanceTimersByTimeAsync(30_000) - supervisor.setForeground(true) - expect(session.notifyForeground).toHaveBeenCalledExactlyOnceWith('focus') - supervisor.stop() - }) - - it('releases a retained relay after 30 seconds and recovers only on foreground', async () => { - const logical = new FakeLogicalClient('connected', 'relay') - const deps = dependencies() - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - - supervisor.setForeground(false) - await vi.advanceTimersByTimeAsync(29_999) - expect(logical.suspendActiveSession).not.toHaveBeenCalled() - - await vi.advanceTimersByTimeAsync(1) - expect(logical.suspendActiveSession).toHaveBeenCalledOnce() - expect(logical.getState()).toBe('disconnected') - expect(deps.openRelay).not.toHaveBeenCalled() - - supervisor.setForeground(true) - await vi.waitFor(() => expect(logical.migrateTo).toHaveBeenCalledOnce()) - expect(logical.getActivePath()).toBe('relay') - supervisor.stop() - }) - - it('enforces an overdue grace on foreground when the background timer was suspended', async () => { - const logical = new FakeLogicalClient('connected', 'relay') - const deps = dependencies() - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - - supervisor.setForeground(false) - vi.setSystemTime(Date.now() + 30_000) - expect(logical.suspendActiveSession).not.toHaveBeenCalled() - - supervisor.setForeground(true) - expect(logical.suspendActiveSession).toHaveBeenCalledOnce() - await vi.waitFor(() => expect(deps.openRelay).toHaveBeenCalledOnce()) - expect(logical.getState()).toBe('connected') - supervisor.stop() - }) - - it('recovers after a retained relay fails while backgrounded', async () => { - const logical = new FakeLogicalClient('connected', 'relay') - const deps = dependencies() - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - - supervisor.setForeground(false) - await vi.advanceTimersByTimeAsync(5_000) - logical.publishState('disconnected') - - expect(logical.suspendActiveSession).toHaveBeenCalledOnce() - expect(vi.getTimerCount()).toBe(0) - expect(deps.openRelay).not.toHaveBeenCalled() - - supervisor.setForeground(true) - await vi.waitFor(() => expect(deps.openRelay).toHaveBeenCalledOnce()) - expect(logical.getState()).toBe('connected') - supervisor.stop() - }) - - it('does not retain or suspend a healthy direct session in the background', async () => { - const logical = new FakeLogicalClient('connected', 'tailscale') - const deps = dependencies() - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - - supervisor.setForeground(false) - await vi.advanceTimersByTimeAsync(30_000) - - expect(logical.suspendActiveSession).not.toHaveBeenCalled() - expect(logical.getState()).toBe('connected') - expect(deps.openRelay).not.toHaveBeenCalled() - supervisor.stop() - }) - - it('resumes a lease rotation that came due during the background grace', async () => { - const logical = new FakeLogicalClient('disconnected', 'lan') - const openRelay = vi.fn(() => new FakeRelaySession('connected', null, Date.now() + 90_000)) - const deps = dependencies({ - openDirect: vi.fn(() => new FakeSession('disconnected')), - openRelay - }) - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - await supervisor.start() - - await vi.advanceTimersByTimeAsync(40_000) - supervisor.setForeground(false) - await vi.advanceTimersByTimeAsync(20_000) - expect(openRelay).toHaveBeenCalledOnce() - expect(logical.getState()).toBe('connected') - - supervisor.setForeground(true) - await vi.waitFor(() => expect(deps.writeBundle).toHaveBeenCalledTimes(2)) - expect(logical.migrateTo).toHaveBeenCalledTimes(2) - expect(openRelay).toHaveBeenCalledTimes(2) - expect(logical.getActivePath()).toBe('relay') - expect(logical.getState()).toBe('connected') - supervisor.stop() - }) - - it('arms lease rotation when confirmation persistence finishes during the grace', async () => { - let finishWrite: (() => void) | undefined - const writeStarted = new Promise((resolve) => { - finishWrite = resolve - }) - const logical = new FakeLogicalClient('disconnected', 'lan') - const openRelay = vi.fn(() => new FakeRelaySession('connected', null, Date.now() + 90_000)) - const deps = dependencies({ - openDirect: vi.fn(() => new FakeSession('disconnected')), - openRelay, - writeBundle: vi.fn(() => writeStarted) - }) - const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps) - const starting = supervisor.start() - await vi.waitFor(() => expect(deps.writeBundle).toHaveBeenCalledOnce()) - - supervisor.setForeground(false) - finishWrite?.() - await starting - supervisor.setForeground(true) - await vi.advanceTimersByTimeAsync(59_999) - expect(openRelay).toHaveBeenCalledOnce() - await vi.advanceTimersByTimeAsync(1) - - await vi.waitFor(() => expect(deps.writeBundle).toHaveBeenCalledTimes(2)) - expect(logical.migrateTo).toHaveBeenCalledTimes(2) - expect(openRelay).toHaveBeenCalledTimes(2) - expect(logical.getActivePath()).toBe('relay') - expect(logical.getState()).toBe('connected') - supervisor.stop() - }) -}) diff --git a/mobile/src/transport/relay-host-reachability-verdict.test.ts b/mobile/src/transport/relay-host-reachability-verdict.test.ts deleted file mode 100644 index a3496fc428b..00000000000 --- a/mobile/src/transport/relay-host-reachability-verdict.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' - -vi.mock('./mobile-e2ee-v2-client-session', () => ({ - MobileE2EEV2ClientSession: { create: () => ({}) } -})) - -vi.mock('./mobile-e2ee-v2-physical-channel', () => ({ - MobileE2EEAuthenticationError: class extends Error {}, - MobileE2EEV2PhysicalChannel: class { - start = vi.fn() - handleMessage = vi.fn(async () => {}) - sendText = vi.fn(() => true) - sendBinary = vi.fn(() => true) - dispose = vi.fn() - } -})) - -import { MOBILE_RELAY_CLOSE_CODE } from '../../../src/shared/remote-runtime/relay/mobile-relay-close-codes' -import { classifyConnection } from './connection-health' -import { RelayOuterError } from './mobile-relay-e2ee-link' -import { RelayReconnectController } from './mobile-relay-reconnect-controller' -import { RelayDirectorHttpError } from './mobile-relay-resume-director' -import { relayHostReachabilityForFailure } from './relay-host-reachability-latch' -import type { RelayHostReachability } from './relay-host-reachability' - -// Mirrors LogicalClientConnectionPath.update, the one consumer: it publishes -// only on change, so the latch itself need not dedupe. -function changesReportedTo(reported: RelayHostReachability[]) { - let last: RelayHostReachability | null = null - return (value: RelayHostReachability) => { - if (value !== last) { - last = value - reported.push(value) - } - } -} - -function controllerReportingTo(report: (reachability: RelayHostReachability) => void) { - const controller = new RelayReconnectController( - { - now: () => 0, - randomBytes: () => new Uint8Array([0, 0]), - setTimer: () => 0, - clearTimer: () => {} - }, - () => {} - ) - controller.reportRecoveryTo({ - setRecoveryAttempt: () => {}, - setPairingRejected: () => {}, - setRelayHostReachability: report - }) - return controller -} - -const hostOffline = () => new RelayOuterError(MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE) - -describe('relayHostReachabilityForFailure', () => { - it('reads the cell close code off a failed dial', () => { - expect(relayHostReachabilityForFailure(hostOffline())).toBe('host-offline') - expect( - relayHostReachabilityForFailure( - new RelayOuterError(MOBILE_RELAY_CLOSE_CODE.BAD_OUTER_CREDENTIAL) - ) - ).toBe('credential-refused') - expect(relayHostReachabilityForFailure(new RelayOuterError(1006))).toBe('unreachable') - }) - - it('treats a director 401 as the same refusal as a cell 4401', () => { - expect(relayHostReachabilityForFailure(new RelayDirectorHttpError(401, null))).toBe( - 'credential-refused' - ) - expect(relayHostReachabilityForFailure(new RelayDirectorHttpError(503, 30_000))).toBe( - 'connecting' - ) - }) - - it('has no verdict for errors that carry no relay code', () => { - expect(relayHostReachabilityForFailure(new TypeError('Network request failed'))).toBe( - 'connecting' - ) - expect(relayHostReachabilityForFailure(null)).toBe('connecting') - }) -}) - -describe('RelayReconnectController relay host reachability', () => { - // Two matching offline closes must explain the outage before hours of generic relay retry copy accumulate. - it('reports host-offline on the second consecutive 4404, not the first', () => { - const reported: RelayHostReachability[] = [] - const controller = controllerReportingTo(changesReportedTo(reported)) - - controller.registerFailure(hostOffline(), false) - expect(reported).toEqual(['connecting']) - - controller.registerFailure(hostOffline(), false) - expect(reported).toEqual(['connecting', 'host-offline']) - }) - - // Bank verdicts before the credential gate returns early. - it('still latches credential-refused while the credential gate holds', () => { - const reported: RelayHostReachability[] = [] - const controller = controllerReportingTo(changesReportedTo(reported)) - const refused = () => new RelayOuterError(MOBILE_RELAY_CLOSE_CODE.BAD_OUTER_CREDENTIAL) - - controller.registerFailure(refused(), false) - controller.registerFailure(refused(), false) - expect(reported.at(-1)).toBe('credential-refused') - }) - - it('ignores a failed replacement dial while an authenticated relay session is live', () => { - const reported: RelayHostReachability[] = [] - const controller = controllerReportingTo(changesReportedTo(reported)) - controller.registerFailure(hostOffline(), false) - controller.setActiveSession({ getFailure: () => null, notifyForeground: () => {} }) - controller.registerFailure(hostOffline(), false) - controller.registerFailure(hostOffline(), false) - expect(reported).toEqual(['connecting']) - }) - - it('clears the verdict when a relay session authenticates', () => { - const reported: RelayHostReachability[] = [] - const controller = controllerReportingTo(changesReportedTo(reported)) - controller.registerFailure(hostOffline(), false) - controller.registerFailure(hostOffline(), false) - controller.setActiveSession({ getFailure: () => null, notifyForeground: () => {} }) - expect(reported).toEqual(['connecting', 'host-offline', 'connecting']) - }) - - it('clears the verdict when a direct session proves the desktop is up', () => { - const reported: RelayHostReachability[] = [] - const controller = controllerReportingTo(changesReportedTo(reported)) - controller.registerFailure(new RelayOuterError(1006), false) - controller.registerFailure(new RelayOuterError(1006), false) - controller.resetForDirectConnection() - expect(reported).toEqual(['connecting', 'unreachable', 'connecting']) - }) -}) - -describe('classifyConnection with a relay host verdict', () => { - const base = { - state: 'connecting' as const, - reconnectAttempts: 1, - lastConnectedAt: null, - pendingPath: 'relay' as const, - hostName: 'Host 1' - } - - it('names the offline desktop instead of "Connecting via Relay…"', () => { - expect(classifyConnection({ ...base, relayHostReachability: 'host-offline' })).toEqual({ - kind: 'unreachable', - label: 'Host 1 is offline', - reason: 'never-connected', - detail: "Check it's awake, Orca is running, and you're signed in" - }) - }) - - it('names the refused credential and its real remedy, not a desktop sign-in', () => { - expect(classifyConnection({ ...base, relayHostReachability: 'credential-refused' })).toEqual({ - kind: 'unreachable', - label: 'Relay access expired for Host 1', - reason: 'never-connected', - detail: 'Re-pair with your desktop' - }) - }) - - it('blames the connection, softly, on a transport close', () => { - expect(classifyConnection({ ...base, relayHostReachability: 'unreachable' })).toEqual({ - kind: 'warning', - label: "Can't reach Relay", - detail: 'Check your connection' - }) - }) - - it('keeps narrating Relay while the verdict is still connecting', () => { - expect(classifyConnection({ ...base, relayHostReachability: 'connecting' }).label).toBe( - 'Connecting via Relay…' - ) - }) - - it('does not depend on the relay path still being pending', () => { - expect( - classifyConnection({ - ...base, - state: 'reconnecting', - pendingPath: null, - relayHostReachability: 'credential-refused' - }).label - ).toBe('Relay access expired for Host 1') - }) - - it('reads as stale once this session had been connected', () => { - expect( - classifyConnection({ - ...base, - lastConnectedAt: 1, - nowMs: 2, - relayHostReachability: 'host-offline' - }) - ).toMatchObject({ reason: 'stale' }) - }) - - it('falls back to a generic host name', () => { - expect( - classifyConnection({ ...base, hostName: undefined, relayHostReachability: 'host-offline' }) - .label - ).toBe('Host is offline') - }) - - it('never appends the Tailscale hint', () => { - expect( - classifyConnection({ ...base, endpoint: '100.64.0.1', relayHostReachability: 'host-offline' }) - ).not.toHaveProperty('hint') - }) - - it('yields to a revoked pairing and a live connection', () => { - expect( - classifyConnection({ ...base, pairingRejected: true, relayHostReachability: 'host-offline' }) - .kind - ).toBe('auth-failed') - expect( - classifyConnection({ ...base, state: 'connected', relayHostReachability: 'host-offline' }) - .label - ).toBe('Connected') - }) -}) diff --git a/mobile/src/transport/relay-host-signed-out-verdict.test.ts b/mobile/src/transport/relay-host-signed-out-verdict.test.ts deleted file mode 100644 index 286b4df74f3..00000000000 --- a/mobile/src/transport/relay-host-signed-out-verdict.test.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' - -vi.mock('./mobile-e2ee-v2-client-session', () => ({ - MobileE2EEV2ClientSession: { create: () => ({}) } -})) - -vi.mock('./mobile-e2ee-v2-physical-channel', () => ({ - MobileE2EEAuthenticationError: class extends Error {}, - MobileE2EEV2PhysicalChannel: class { - start = vi.fn() - handleMessage = vi.fn(async () => {}) - sendText = vi.fn(() => true) - sendBinary = vi.fn(() => true) - dispose = vi.fn() - } -})) - -import { RELAY_HOST_CLOSE_REASON } from '../../../src/shared/relay-host-close-reason' -import { MOBILE_RELAY_CLOSE_CODE } from '../../../src/shared/remote-runtime/relay/mobile-relay-close-codes' -import { classifyConnection, verdictDisplayLabel } from './connection-health' -import { MobileRelayE2eeLink, RelayOuterError } from './mobile-relay-e2ee-link' -import { LogicalClientConnectionPath } from './logical-client-connection-path' -import { RelayReconnectController } from './mobile-relay-reconnect-controller' -import type { RelayHostReachability } from './relay-host-reachability' - -const SIGNED_OUT_LABEL = 'Sign-in required on Host 1' -const SIGNED_OUT_DETAIL = 'Sign in to Orca on your desktop to reconnect' - -class FakeSocket { - static readonly OPEN = 1 - readonly OPEN = FakeSocket.OPEN - readyState = FakeSocket.OPEN - bufferedAmount = 0 - onopen: (() => void) | null = null - onmessage: ((event: { data: unknown }) => void) | null = null - onerror: (() => void) | null = null - onclose: ((event: { code: number; reason: string }) => void) | null = null - send = vi.fn() - close = vi.fn() -} - -function linkOver( - socket: FakeSocket, - onHostCloseReason: (reason: string) => void, - onError: (error: Error) => void -): MobileRelayE2eeLink { - return new MobileRelayE2eeLink({ - endpoint: { cellUrl: 'https://relay-c1.onorca.dev', relayHostId: 'AbCdEf0123_-xyZ9' }, - credential: 'credential', - expectedCredentialKind: 'resume', - deviceToken: 'device-token', - desktopPublicKeyB64: 'desktop-key', - onAuthenticated: vi.fn(), - onText: vi.fn(), - onBinary: vi.fn(), - onHostCloseReason, - onError, - createSocket: () => socket as unknown as WebSocket - }) -} - -describe('relay close reason on the phone', () => { - it('reports the cell close reason and still fails with 4404', () => { - const socket = new FakeSocket() - const onHostCloseReason = vi.fn() - const onError = vi.fn() - linkOver(socket, onHostCloseReason, onError) - - socket.onclose?.({ - code: MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE, - reason: RELAY_HOST_CLOSE_REASON.SIGNED_OUT - }) - - expect(onHostCloseReason).toHaveBeenCalledWith(RELAY_HOST_CLOSE_REASON.SIGNED_OUT) - expect(onError).toHaveBeenCalledOnce() - expect(onError).toHaveBeenCalledWith( - new RelayOuterError( - MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE, - false, - RELAY_HOST_CLOSE_REASON.SIGNED_OUT - ) - ) - }) - - // An old cell sends its constant, and every other close sends nothing. - it('reports nothing for a reason it does not know', () => { - const socket = new FakeSocket() - const onHostCloseReason = vi.fn() - linkOver(socket, onHostCloseReason, vi.fn()) - - socket.onclose?.({ - code: MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE, - reason: 'relay connection rejected' - }) - - expect(onHostCloseReason).not.toHaveBeenCalled() - }) - - // Retain close reason whichever arrives first: hello refusal or close. - it('still reports the reason when the hello refusal lands first', async () => { - const socket = new FakeSocket() - const onHostCloseReason = vi.fn() - linkOver(socket, onHostCloseReason, vi.fn()) - - socket.onmessage?.({ - data: JSON.stringify({ - type: 'relay-hello', - ok: false, - code: MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE - }) - }) - await Promise.resolve() - await Promise.resolve() - socket.onclose?.({ - code: MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE, - reason: RELAY_HOST_CLOSE_REASON.SIGNED_OUT - }) - - expect(onHostCloseReason).toHaveBeenCalledWith(RELAY_HOST_CLOSE_REASON.SIGNED_OUT) - }) -}) - -describe('the signed-out signal on the logical client', () => { - it('publishes on change and retires when any path reaches connected', () => { - const path = new LogicalClientConnectionPath(() => false) - const changes = vi.fn() - path.subscribe(changes) - - path.setRelayHostReachability('signed-out') - path.setRelayHostReachability('signed-out') - expect(path.getRelayHostReachability()).toBe('signed-out') - expect(changes).toHaveBeenCalledTimes(1) - - path.clearAfterConnected() - expect(path.getRelayHostReachability()).toBe('connecting') - }) -}) - -describe('the sign-out the cell reported outranks a later plain 4404', () => { - function controllerReporting(reported: RelayHostReachability[]) { - const controller = new RelayReconnectController( - { - now: () => 0, - randomBytes: () => new Uint8Array([0, 0]), - setTimer: () => 0, - clearTimer: () => {} - }, - vi.fn() - ) - controller.reportRecoveryTo({ - setRecoveryAttempt: () => {}, - setPairingRejected: () => {}, - setRelayHostReachability: (value) => reported.push(value) - }) - return controller - } - - // A 4404 with no reason is the cell forgetting why, not the desktop signing in. - it('keeps the sign-out through later unlabelled host-offline closes', () => { - const reported: RelayHostReachability[] = [] - const controller = controllerReporting(reported) - - controller.assertHostReachability('signed-out') - expect(reported).toEqual(['connecting', 'signed-out']) - controller.registerFailure(new RelayOuterError(MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE), false) - controller.registerFailure(new RelayOuterError(MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE), false) - - expect(reported).toEqual(['connecting', 'signed-out']) - }) - - it('retires on a connection like every other verdict', () => { - const reported: RelayHostReachability[] = [] - const controller = controllerReporting(reported) - - controller.assertHostReachability('signed-out') - controller.setActiveSession({ getFailure: () => null, notifyForeground: () => {} }) - - expect(reported).toEqual(['connecting', 'signed-out', 'connecting']) - }) -}) - -describe('RelayReconnectController cadence', () => { - // Signed-out reason changes copy only; preserve the host-offline retry cadence. - it('keeps the host-offline retry delay for a 4404', () => { - const delays: number[] = [] - const controller = new RelayReconnectController( - { - now: () => 0, - randomBytes: () => new Uint8Array([0, 0]), - setTimer: (callback, delay) => { - delays.push(delay) - return 1 as unknown as ReturnType - }, - clearTimer: () => {} - }, - vi.fn() - ) - - controller.registerFailure(new RelayOuterError(MOBILE_RELAY_CLOSE_CODE.HOST_OFFLINE)) - - expect(delays.at(-1)).toBe(5_000) - }) -}) - -describe('classifyConnection with a signed-out desktop', () => { - const base = { - reconnectAttempts: 0, - lastConnectedAt: null, - relayHostReachability: 'signed-out' as const, - hostName: 'Host 1' - } - - it('says so from the first failed dial instead of "Connecting via Relay…"', () => { - const verdict = classifyConnection({ - ...base, - state: 'connecting', - pendingPath: 'relay' - }) - - expect(verdict).toEqual({ - kind: 'unreachable', - label: SIGNED_OUT_LABEL, - reason: 'never-connected', - detail: SIGNED_OUT_DETAIL - }) - expect(verdictDisplayLabel(verdict)).toBe(SIGNED_OUT_LABEL) - }) - - it('replaces "Can\'t reach desktop" on the direct path too', () => { - expect( - classifyConnection({ ...base, state: 'reconnecting', reconnectAttempts: 20 }).label - ).toBe(SIGNED_OUT_LABEL) - }) -}) diff --git a/mobile/src/transport/web-bundle/mobile-web-bundle-compat.test.ts b/mobile/src/transport/web-bundle/mobile-web-bundle-compat.test.ts deleted file mode 100644 index 711a91c7459..00000000000 --- a/mobile/src/transport/web-bundle/mobile-web-bundle-compat.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { MOBILE_WEB_BUNDLE_CAPABILITY } from '../../../../src/shared/mobile-web-bundle/mobile-web-bundle-capability' -import { - evaluateMobileWebBundleCompat, - type MobileWebBundleCompatManifest, - type MobileWebBundleCompatVerdict, - type MobileWebBundleHostStatus -} from './mobile-web-bundle-compat' - -const CAPABLE: readonly string[] = ['browser.screencast.v1', MOBILE_WEB_BUNDLE_CAPABILITY] - -// Reply members are present but possibly undefined, so model a host answering neither with both keys. -const ANSWERED_NEITHER: MobileWebBundleHostStatus = { - protocolVersion: undefined, - minCompatibleMobileVersion: undefined -} - -function manifest( - overrides: Partial = {} -): MobileWebBundleCompatManifest { - return { - schemaVersion: 1, - runtimeProtocolVersion: 3, - minCompatibleRuntimeProtocolVersion: 2, - pageVersion: 1, - ...overrides - } -} - -function evaluate(input: { - hostCapabilities?: readonly string[] - hostStatus?: MobileWebBundleHostStatus - manifest?: MobileWebBundleCompatManifest | null -}): MobileWebBundleCompatVerdict { - return evaluateMobileWebBundleCompat({ - hostCapabilities: input.hostCapabilities ?? CAPABLE, - hostStatus: input.hostStatus ?? { protocolVersion: 3, minCompatibleMobileVersion: 2 }, - manifest: input.manifest === undefined ? manifest() : input.manifest - }) -} - -describe('evaluateMobileWebBundleCompat', () => { - it('opens a bundle whose window contains the host', () => { - expect(evaluate({})).toEqual({ kind: 'ok', manifestChecked: true }) - }) - - it('answers the capability question before a manifest exists', () => { - expect(evaluate({ manifest: null })).toEqual({ kind: 'ok', manifestChecked: false }) - expect(evaluate({ hostCapabilities: [], manifest: null })).toEqual({ - kind: 'blocked', - reason: 'bundle-unavailable' - }) - }) - - it('blocks a host that ships no bundle', () => { - expect(evaluate({ hostCapabilities: ['browser.screencast.v1'] })).toEqual({ - kind: 'blocked', - reason: 'bundle-unavailable' - }) - }) - - it('blocks a manifest schema this shell does not know', () => { - expect(evaluate({ manifest: manifest({ schemaVersion: 2 }) })).toMatchObject({ - kind: 'blocked', - reason: 'bundle-shell-too-old', - schemaVersion: 2 - }) - expect(evaluate({ manifest: manifest({ schemaVersion: 0 }) })).toMatchObject({ - reason: 'bundle-shell-too-old', - schemaVersion: 0 - }) - }) - - it('blocks a host older than the bundle it serves', () => { - expect( - evaluate({ - hostStatus: { protocolVersion: 1, minCompatibleMobileVersion: 0 }, - manifest: manifest({ minCompatibleRuntimeProtocolVersion: 2 }) - }) - ).toEqual({ - kind: 'blocked', - reason: 'bundle-incompatible', - side: 'desktop', - hostProtocolVersion: 1, - requiredHostProtocolVersion: 2 - }) - }) - - it('blocks a bundle older than the host expects', () => { - expect( - evaluate({ - hostStatus: { protocolVersion: 9, minCompatibleMobileVersion: 4 }, - manifest: manifest({ runtimeProtocolVersion: 3, minCompatibleRuntimeProtocolVersion: 0 }) - }) - ).toEqual({ - kind: 'blocked', - reason: 'bundle-incompatible', - side: 'mobile', - bundleRuntimeProtocolVersion: 3, - requiredBundleRuntimeProtocolVersion: 4 - }) - }) - - it('reports the missing capability first when the host also fails every later check', () => { - expect( - evaluate({ - hostCapabilities: [], - hostStatus: { protocolVersion: 0, minCompatibleMobileVersion: 99 }, - manifest: manifest({ schemaVersion: 7, minCompatibleRuntimeProtocolVersion: 5 }) - }) - ).toEqual({ kind: 'blocked', reason: 'bundle-unavailable' }) - }) - - it('reports an unknown schema before reading the protocol window inside it', () => { - expect( - evaluate({ - hostStatus: { protocolVersion: 0, minCompatibleMobileVersion: 99 }, - manifest: manifest({ schemaVersion: 2, minCompatibleRuntimeProtocolVersion: 5 }) - }) - ).toMatchObject({ reason: 'bundle-shell-too-old' }) - }) - - it('reports the desktop side before the mobile side when both windows miss', () => { - expect( - evaluate({ - hostStatus: { protocolVersion: 1, minCompatibleMobileVersion: 99 }, - manifest: manifest({ runtimeProtocolVersion: 3, minCompatibleRuntimeProtocolVersion: 5 }) - }) - ).toMatchObject({ reason: 'bundle-incompatible', side: 'desktop' }) - }) - - it('treats an omitted host protocolVersion as the oldest host that could have answered', () => { - expect( - evaluate({ - hostStatus: ANSWERED_NEITHER, - manifest: manifest({ minCompatibleRuntimeProtocolVersion: 1 }) - }) - ).toEqual({ - kind: 'blocked', - reason: 'bundle-incompatible', - side: 'desktop', - hostProtocolVersion: 0, - requiredHostProtocolVersion: 1 - }) - }) - - it('treats an omitted host minCompatibleMobileVersion as no floor at all', () => { - expect( - evaluate({ - hostStatus: ANSWERED_NEITHER, - manifest: manifest({ runtimeProtocolVersion: 0, minCompatibleRuntimeProtocolVersion: 0 }) - }) - ).toEqual({ kind: 'ok', manifestChecked: true }) - }) - - it('opens at the boundary of both windows, so equality is not a block', () => { - expect( - evaluate({ - hostStatus: { protocolVersion: 2, minCompatibleMobileVersion: 3 }, - manifest: manifest({ runtimeProtocolVersion: 3, minCompatibleRuntimeProtocolVersion: 2 }) - }) - ).toEqual({ kind: 'ok', manifestChecked: true }) - expect( - evaluate({ - hostStatus: { protocolVersion: 1, minCompatibleMobileVersion: 3 }, - manifest: manifest({ runtimeProtocolVersion: 3, minCompatibleRuntimeProtocolVersion: 2 }) - }) - ).toMatchObject({ reason: 'bundle-incompatible', side: 'desktop' }) - expect( - evaluate({ - hostStatus: { protocolVersion: 2, minCompatibleMobileVersion: 4 }, - manifest: manifest({ runtimeProtocolVersion: 3, minCompatibleRuntimeProtocolVersion: 2 }) - }) - ).toMatchObject({ reason: 'bundle-incompatible', side: 'mobile' }) - }) -}) - -// Pin the page-version floor independently of manifest-schema support. -describe('the page floor', () => { - const tooOld = (pageVersion: number) => ({ - kind: 'blocked', - reason: 'bundle-incompatible', - side: 'desktop', - pageVersion, - requiredPageVersion: 1 - }) - - it('walls a desktop whose page is older than this shell, as a desktop to update', () => { - expect(evaluate({ manifest: manifest({ pageVersion: 0 }) })).toEqual(tooOld(0)) - }) - - it('reads a manifest with no page version as older than the floor', () => { - const { schemaVersion, runtimeProtocolVersion, minCompatibleRuntimeProtocolVersion } = - manifest() - const unversioned = { - schemaVersion, - runtimeProtocolVersion, - minCompatibleRuntimeProtocolVersion - } - expect(evaluate({ manifest: unversioned })).toEqual(tooOld(0)) - }) - - it('opens a page at the floor and one above it', () => { - expect(evaluate({ manifest: manifest({ pageVersion: 1 }) })).toEqual({ - kind: 'ok', - manifestChecked: true - }) - expect(evaluate({ manifest: manifest({ pageVersion: 7 }) })).toEqual({ - kind: 'ok', - manifestChecked: true - }) - }) -}) diff --git a/mobile/src/transport/web-bundle/mobile-web-bundle-fetch-slow-link.test.ts b/mobile/src/transport/web-bundle/mobile-web-bundle-fetch-slow-link.test.ts index 20a6d2c67ab..5c461876712 100644 --- a/mobile/src/transport/web-bundle/mobile-web-bundle-fetch-slow-link.test.ts +++ b/mobile/src/transport/web-bundle/mobile-web-bundle-fetch-slow-link.test.ts @@ -20,8 +20,6 @@ vi.mock('expo-crypto', async () => { // Use a small grid for cheap payload work; model slow-link cost with time rather than bytes. const RANGE_GRID = 1024 const ONE_WAY_LATENCY_MS = 150 -/** One 384 KiB window sealed at ~40 KB/s, the slow relayed path #29697 reported. */ -const WINDOW_TRANSFER_MS = 17_000 function toHex(bytes: Uint8Array): string { return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('') @@ -159,20 +157,6 @@ describe('fetchMobileWebBundle over a slow link', () => { vi.useRealTimers() }) - it('finishes when every reply lands within the stall budget but four queued ones do not', async () => { - // Each window lands 17s after the one before it, so the fourth read in flight waits ~68s. - const files = bundleFiles() - const host = slowLinkHost(files, WINDOW_TRANSFER_MS) - - const outcome = await settle(fetchMobileWebBundle({ client: host.client })) - - expect(outcome.error).toBeUndefined() - expect(outcome.value?.assets.size).toBe(4) - for (const [path, content] of Object.entries(files)) { - expect(outcome.value?.assets.get(path)).toEqual(content) - } - }) - it('fails as a lost link once no window has landed for the stall budget', async () => { const host = slowLinkHost(bundleFiles(), null) const startedAt = Date.now() diff --git a/mobile/src/worktree/agent-row-display.test.ts b/mobile/src/worktree/agent-row-display.test.ts deleted file mode 100644 index 5ca1203fed0..00000000000 --- a/mobile/src/worktree/agent-row-display.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { RuntimeWorktreeAgentRow } from '../../../src/shared/runtime-types' -import type { AGENT_TURN_OUTCOMES } from '../../../src/shared/agent-status/lifecycle/agent-turn-outcome' -import { isAgentStatusFresh } from '../../../src/shared/agent-status/contracts/agent-status-freshness' -import { - agentDisplayLabel, - agentStateLabel, - agentDotState, - agentRowTimeAt, - formatTimeAgo -} from './agent-row-display' - -type Outcome = (typeof AGENT_TURN_OUTCOMES)[number] -const mainAgentDone = (outcome: Outcome, stateStartedAt = 0) => ({ - mainAgent: { state: 'done' as const, outcome, stateStartedAt } -}) - -function row(overrides: Partial = {}): RuntimeWorktreeAgentRow { - return { - paneKey: 'p', - parentPaneKey: null, - state: 'working', - agentType: 'claude', - prompt: '', - taskTitle: null, - displayName: null, - lastAssistantMessage: null, - toolName: null, - toolInput: null, - interrupted: false, - stateStartedAt: 0, - updatedAt: 0, - ...overrides - } -} - -describe('agentDotState', () => { - it('maps known states through and unknown to idle', () => { - expect(agentDotState(row({ state: 'working', updatedAt: 0 }), 0)).toBe('working') - expect( - agentDotState(row({ state: 'working', workingMode: 'monitoring', updatedAt: 0 }), 0) - ).toBe('monitoring') - expect(agentDotState(row({ state: 'blocked', updatedAt: 0 }), 0)).toBe('blocked') - expect(agentDotState(row({ state: 'waiting', updatedAt: 0 }), 0)).toBe('waiting') - expect(agentDotState(row({ state: 'done', updatedAt: 0 }), 0)).toBe('done') - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: unparsed rows can carry a future host state. - expect(agentDotState(row({ state: 'unknown-state' as never }), 0)).toBe('idle') - }) - - it('lets the shared verdict mark win over the row state, label included', () => { - expect(agentDotState(row({ state: 'working', ...mainAgentDone('failure') }), 0)).toBe('failed') - expect(agentDisplayLabel(row({ state: 'done', ...mainAgentDone('unconfirmed') }), 0)).toBe( - 'Couldn’t confirm' - ) - for (const [outcome, label] of [ - ['cancellation', 'Interrupted'], - ['interruption', 'Failed'], - ['failure', 'Failed'] - ] as const) { - expect( - agentStateLabel(agentDotState(row({ state: 'done', ...mainAgentDone(outcome) }), 0)) - ).toBe(label) - } - }) - - it('decays a stale active state to idle, matching desktop', () => { - const stale = 1_800_001 - expect(agentDotState(row({ state: 'working', updatedAt: 0, hasLivePty: true }), stale)).toBe( - 'unverifiable' - ) - expect(agentDotState(row({ state: 'working', updatedAt: 0 }), stale)).toBe('idle') - expect(agentDotState(row({ state: 'blocked', updatedAt: 0 }), stale)).toBe('idle') - expect(agentDotState(row({ state: 'waiting', updatedAt: 0 }), stale)).toBe('idle') - expect(agentDotState(row({ state: 'working', updatedAt: 0 }), 1_800_000)).toBe('working') - expect(agentDotState(row({ state: 'done', updatedAt: 0 }), stale)).toBe('done') - expect( - agentDotState(row({ state: 'done', updatedAt: 0, ...mainAgentDone('interruption') }), stale) - ).toBe('failed') - }) - - // Title renewal updates the row timestamp without renewing status evidence. - it('ages a status by its evidence, not by a later title renewal, as desktop does', () => { - const now = 100 * 60 * 1000 - const renewed = { - state: 'blocked' as const, - updatedAt: now, - evidenceObservedAt: now - 31 * 60 * 1000 - } - expect(isAgentStatusFresh(renewed, now)).toBe(false) - expect(agentDotState(renewed, now)).toBe('idle') - }) -}) - -describe('agentRowTimeAt', () => { - it('dates a main agent that failed while its subagents run by its own failure', () => { - expect( - agentRowTimeAt( - row({ state: 'working', stateStartedAt: 100, ...mainAgentDone('failure', 900) }) - ) - ).toBe(900) - }) - - it('dates every other row by when its state began', () => { - expect( - agentRowTimeAt( - row({ state: 'working', stateStartedAt: 100, ...mainAgentDone('success', 900) }) - ) - ).toBe(100) - expect( - agentRowTimeAt(row({ state: 'done', stateStartedAt: 100, ...mainAgentDone('failure', 900) })) - ).toBe(100) - expect(agentRowTimeAt(row({ state: 'working', stateStartedAt: 100 }))).toBe(100) - }) -}) - -describe('agentDisplayLabel', () => { - it('prefers last message, then prompt, then state label', () => { - expect(agentDisplayLabel(row({ lastAssistantMessage: 'hello there' }), 0)).toBe('hello there') - expect(agentDisplayLabel(row({ lastAssistantMessage: ' ', prompt: 'do the thing' }), 0)).toBe( - 'do the thing' - ) - expect(agentDisplayLabel(row({ state: 'working', prompt: '', updatedAt: 0 }), 0)).toBe( - 'Working' - ) - expect( - agentDisplayLabel(row({ state: 'working', prompt: '{ "tool": "x"', updatedAt: 0 }), 0) - ).toBe('Working') - expect( - agentDisplayLabel( - row({ state: 'working', workingMode: 'monitoring', prompt: '', updatedAt: 0 }), - 0 - ) - ).toBe('Monitoring background tasks') - }) - - it("says Stopping while the host says a person's Stop is ending the turn", () => { - const stopping = row({ - lastAssistantMessage: 'hello there', - mainAgent: { state: 'working', stopping: true, stateStartedAt: 0 } - }) - expect(agentDisplayLabel(stopping, 0)).toBe('Stopping…') - expect(agentDotState(stopping, 0)).toBe('working') - expect(agentDisplayLabel({ ...stopping, conversationName: 'Orca PR rebase' }, 0)).toBe( - 'Stopping… · Orca PR rebase' - ) - }) - - it('leads with the host-sent conversation name, like the desktop row', () => { - const named = row({ state: 'done', conversationName: 'Orca PR rebase', prompt: 'rebase it' }) - expect(agentDisplayLabel(named, 0)).toBe('Orca PR rebase') - expect(agentDisplayLabel({ ...named, lastAssistantMessage: 'PR is closed' }, 0)).toBe( - 'Orca PR rebase · PR is closed' - ) - }) - - it('falls back to the decayed state label when stale', () => { - expect(agentDisplayLabel(row({ state: 'working', prompt: '', updatedAt: 0 }), 1_800_001)).toBe( - 'Idle' - ) - }) - - it('keeps the task text on a silent row and names the silence when there is none', () => { - const silent = row({ state: 'working', updatedAt: 0, hasLivePty: true }) - expect(agentDisplayLabel({ ...silent, prompt: 'build' }, 40 * 60_000)).toBe('build') - expect(agentDisplayLabel(silent, 40 * 60_000)).toBe('No recent update') - }) -}) - -describe('formatTimeAgo', () => { - const now = 10_000_000 - it('formats across thresholds', () => { - expect(formatTimeAgo(now - 30_000, now)).toBe('just now') - expect(formatTimeAgo(now - 5 * 60_000, now)).toBe('5m') - expect(formatTimeAgo(now - 3 * 3_600_000, now)).toBe('3h') - expect(formatTimeAgo(now - 2 * 86_400_000, now)).toBe('2d') - }) -}) diff --git a/src/main/acp/acp-session-reopen-failure.test.ts b/src/main/acp/acp-session-reopen-failure.test.ts index 1a75b3ec747..dfbc3bc1e3c 100644 --- a/src/main/acp/acp-session-reopen-failure.test.ts +++ b/src/main/acp/acp-session-reopen-failure.test.ts @@ -9,28 +9,6 @@ import { AcpAgentError, AcpRequestTimeoutError } from './acp-errors' import { AcpScriptedAgent } from './acp-scripted-agent.test-support' import { agentSessionFailureFact } from '../../shared/agent-session/failures/agent-session-failure' import { agentSessionFailureWords } from '../../shared/agent-session/failures/agent-session-failure-words' -import { acpReopenTakeover } from './acp-session-reopen-failure' - -it.each(['ServerUnavailable', 'CatalogNotReady'])( - 'retains the saved session on a temporary %s load answer', - (errorName) => { - const error = new AcpAgentError(-32603, 'not ready', { errorName }) - const warn = vi.fn() - expect(() => - acpReopenTakeover( - error, - { - key: 'saved-key', - sessionId: 'saved-id', - mayBeUnsaved: () => false, - unannouncedLosses: () => [] - }, - { over: false, now: 1, warn } - ) - ).toThrow(error) - expect(warn).not.toHaveBeenCalled() - } -) afterEach(async () => { vi.useRealTimers() diff --git a/src/main/agent-hooks/hook-status-session-tabs-republish.test.ts b/src/main/agent-hooks/hook-status-session-tabs-republish.test.ts deleted file mode 100644 index 10c4cfc208a..00000000000 --- a/src/main/agent-hooks/hook-status-session-tabs-republish.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { AgentHookServer } from './server' -import { installHookStatusSessionTabsRepublish } from './hook-status-session-tabs-republish' -import { AGENT_STATUS_STALE_AFTER_MS } from '../../shared/agent-status/contracts/agent-status-freshness' -import { - createMobileSessionTabsAgentStatusHeartbeat, - SESSION_TABS_AGENT_STATUS_HEARTBEAT_INTERVAL_MS -} from '../runtime/mobile-session-tabs-agent-status-heartbeat' - -const PANE = 'tab-provider:11111111-1111-4111-8111-111111111111' - -function providerOnly(server: AgentHookServer, transcriptPath: string): void { - server.ingestRemote( - { - paneKey: PANE, - tabId: 'tab-provider', - worktreeId: 'repo::/worktree', - providerSession: { key: 'session_id', id: 'pi-session', transcriptPath }, - providerSessionOnly: true, - payload: { state: 'done', prompt: '', agentType: 'pi' } - }, - null - ) -} - -describe('hook status session-tabs republish', () => { - afterEach(() => { - vi.useRealTimers() - }) - - it('delivers provider-only changes and authority retirement from the owner mutation stream', () => { - const server = new AgentHookServer() - const touch = vi.fn() - const uninstall = installHookStatusSessionTabsRepublish(server, () => ({ - getTerminalWorktreeIdForHandle: () => null, - getTerminalWorktreeIdForPaneKey: () => null, - scheduleMobileSessionTabsAgentStatusHeartbeatForWorktree: vi.fn(), - touchMobileSessionTabsForWorktree: touch - })) - try { - providerOnly(server, '/sessions/first.jsonl') - expect(touch).toHaveBeenLastCalledWith('repo::/worktree') - - touch.mockClear() - providerOnly(server, '/sessions/first.jsonl') - expect(touch).not.toHaveBeenCalled() - - providerOnly(server, '/sessions/replaced.jsonl') - expect(touch).toHaveBeenCalledTimes(1) - - touch.mockClear() - server.retirePaneAuthority(PANE) - expect(touch).toHaveBeenCalledTimes(1) - expect(touch).toHaveBeenCalledWith('repo::/worktree') - } finally { - uninstall() - } - }) - - it('deduplicates the old and new ownership of one moved row', () => { - const server = new AgentHookServer() - const touch = vi.fn() - providerOnly(server, '/sessions/first.jsonl') - const uninstall = installHookStatusSessionTabsRepublish(server, () => ({ - getTerminalWorktreeIdForHandle: () => null, - getTerminalWorktreeIdForPaneKey: () => null, - scheduleMobileSessionTabsAgentStatusHeartbeatForWorktree: vi.fn(), - touchMobileSessionTabsForWorktree: touch - })) - try { - server.transferPaneAuthority(PANE, 'tab-new:22222222-2222-4222-8222-222222222222') - expect(touch).toHaveBeenCalledTimes(1) - expect(touch).toHaveBeenCalledWith('repo::/worktree') - } finally { - uninstall() - } - }) - - it('renews mobile freshness across its lease through a bounded heartbeat cadence', () => { - vi.useFakeTimers() - vi.setSystemTime(1_000) - const server = new AgentHookServer() - const publications: number[] = [] - const rowMutations = vi.fn() - const enrichedStatuses = vi.fn() - const semanticStatuses = vi.fn() - let heartbeat: ReturnType - const runtime = { - getTerminalWorktreeIdForHandle: () => null, - getTerminalWorktreeIdForPaneKey: () => null, - scheduleMobileSessionTabsAgentStatusHeartbeatForWorktree: (worktreeId: string) => - heartbeat.scheduleWorktreeHeartbeat(worktreeId), - touchMobileSessionTabsForWorktree: (worktreeId: string) => { - heartbeat.observeWorktreeRefresh(worktreeId) - publications.push(Date.now()) - } - } - heartbeat = createMobileSessionTabsAgentStatusHeartbeat( - () => [], - (worktreeId) => runtime.touchMobileSessionTabsForWorktree(worktreeId) - ) - const uninstall = installHookStatusSessionTabsRepublish(server, () => runtime) - server.subscribeStatusRowMutations(rowMutations) - server.subscribeEnrichedStatus(enrichedStatuses) - server.subscribeStatusChanges(semanticStatuses) - const observation = { - paneKey: PANE, - tabId: 'tab-provider', - worktreeId: 'repo::/worktree', - payload: { state: 'working' as const, prompt: 'active', agentType: 'codex' as const } - } - - try { - server.ingestTerminalStatus(observation) - for (let minute = 1; minute <= 31; minute += 1) { - vi.advanceTimersByTime(60_000) - server.ingestTerminalStatus(observation) - vi.runOnlyPendingTimers() - } - - expect(Date.now()).toBeGreaterThan(1_000 + AGENT_STATUS_STALE_AFTER_MS) - const renewed = server.getStatusSnapshot()[0] - expect(renewed?.state).toBe('working') - expect(Date.now() - renewed!.receivedAt).toBeLessThan(AGENT_STATUS_STALE_AFTER_MS) - expect(publications).toEqual([ - 1_000, - 1_000 + SESSION_TABS_AGENT_STATUS_HEARTBEAT_INTERVAL_MS, - 1_000 + SESSION_TABS_AGENT_STATUS_HEARTBEAT_INTERVAL_MS * 2 - ]) - expect(rowMutations).toHaveBeenCalledTimes(1) - expect(enrichedStatuses).toHaveBeenCalledTimes(1) - expect(semanticStatuses).toHaveBeenCalledTimes(1) - } finally { - uninstall() - heartbeat.dispose() - server.stop() - } - }) -}) diff --git a/src/main/ai-vault-search/session-search-index-consumer.ts b/src/main/ai-vault-search/session-search-index-consumer.ts index cdbc5beb724..d7d18805ced 100644 --- a/src/main/ai-vault-search/session-search-index-consumer.ts +++ b/src/main/ai-vault-search/session-search-index-consumer.ts @@ -12,7 +12,6 @@ import { fileIdentity } from './session-search-file-cursor' import type { SessionSearchFileWrite } from './session-search-index-writer' import type { SessionSearchResumableCursor, SessionSearchStore } from './session-search-store' -// File-read assumptions are checked by session-search-transcript-dependencies.test.ts. export const SELF_CONTAINED_TRANSCRIPT_AGENTS = { claude: true, qoder: true, diff --git a/src/main/ai-vault-search/session-search-transcript-dependencies.test.ts b/src/main/ai-vault-search/session-search-transcript-dependencies.test.ts deleted file mode 100644 index 5f7d4389487..00000000000 --- a/src/main/ai-vault-search/session-search-transcript-dependencies.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import * as fs from 'node:fs' -import * as fsPromises from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterAll, afterEach, beforeAll, expect, it, vi } from 'vitest' -import { AI_VAULT_AGENTS } from '../../shared/ai-vault/contracts/ai-vault-types' -import * as transcriptFs from '../native-chat/wsl-transcript-fs-access' -import { parseAgentSessionFile } from '../ai-vault/session-scanner-agent-parser' -import { AI_VAULT_AGENT_SOURCES } from '../ai-vault/session-scanner-agent-sources' -import { discoverFiles } from '../ai-vault/session-scanner-discovery' -import { - writeEveryAgentVault, - type EveryAgentVault -} from '../ai-vault/session-scanner-every-agent-fixture' -import { geminiJsonlFixture } from '../ai-vault/session-scanner-incremental-fixtures' -import { parseAgentSessionFileCached } from '../ai-vault/parse-cache/session-scanner-parse-cache' -import { resetSessionParseCacheForTests } from '../ai-vault/parse-cache/session-parse-cache-store' -import { - registerTranscriptConsumer, - resetTranscriptConsumersForTests, - type TranscriptMessage -} from '../ai-vault/transcripts/session-transcript-consumers' -import { SELF_CONTAINED_TRANSCRIPT_AGENTS } from './session-search-index-consumer' - -vi.mock('node:fs', async (importOriginal) => { - const original = await importOriginal() - // Recursive spies alter native stream prototypes and prevent readers from finishing. - const tracked = { - readFile: vi.fn(original.readFile), - readFileSync: vi.fn(original.readFileSync), - open: vi.fn(original.open), - openSync: vi.fn(original.openSync), - createReadStream: vi.fn(original.createReadStream) - } - return { ...original, ...tracked, default: { ...original.default, ...tracked } } -}) -vi.mock('node:fs/promises', { spy: true }) -vi.mock('../native-chat/wsl-transcript-fs-access', { spy: true }) - -let root: string -let vault: EveryAgentVault - -beforeAll(async () => { - root = await fsPromises.mkdtemp(join(tmpdir(), 'ss-transcript-dependencies-')) - vault = await writeEveryAgentVault(root) - const gemini = geminiJsonlFixture() - await fsPromises.writeFile( - join(vault.roots.geminiSessionsDir, gemini.fileName), - `${gemini.seedLines.join('\n')}\n` - ) -}) - -afterEach(() => { - vi.clearAllMocks() - resetTranscriptConsumersForTests() - resetSessionParseCacheForTests() -}) - -afterAll(async () => { - await fsPromises.rm(root, { recursive: true, force: true }) -}) - -function contentReadPaths(): unknown[] { - return [ - ...vi.mocked(fsPromises.readFile).mock.calls.map(([path]) => path), - ...vi.mocked(fsPromises.open).mock.calls.map(([path]) => path), - ...vi.mocked(fs.readFile).mock.calls.map(([path]) => path), - ...vi.mocked(fs.readFileSync).mock.calls.map(([path]) => path), - ...vi.mocked(fs.open).mock.calls.map(([path]) => path), - ...vi.mocked(fs.openSync).mock.calls.map(([path]) => path), - ...vi.mocked(fs.createReadStream).mock.calls.map(([path]) => path), - ...vi.mocked(transcriptFs.wslGatedReadFile).mock.calls.map(([path]) => path), - ...vi.mocked(transcriptFs.openTranscriptFile).mock.calls.map(([path]) => path), - ...vi.mocked(transcriptFs.openTranscriptReadStream).mock.calls.map(([path]) => path), - ...vi.mocked(transcriptFs.readTranscriptSlice).mock.calls.map(([path]) => path) - ] -} - -it.each(AI_VAULT_AGENTS.filter((agent) => SELF_CONTAINED_TRANSCRIPT_AGENTS[agent]))( - '%s reads indexed content only from its candidate transcript', - async (agent) => { - const source = AI_VAULT_AGENT_SOURCES[agent] - if (!source) { - throw new Error(`${agent} needs a real transcript fixture before enabling file-stat reuse`) - } - expect(source.contentDependencyPath).toBeUndefined() - const discoveries = await Promise.all( - source.rootDirs(vault.roots, []).map((rootDir) => - discoverFiles({ - ...source, - rootDir, - agent, - limit: 10, - issues: [], - extensions: [...source.extensions] - }) - ) - ) - const files = discoveries.flatMap((discovery) => discovery.files) - expect(files.length).toBeGreaterThan(0) - - for (const file of files) { - const candidate = { agent, file, codexHome: null } - for (const read of ['direct', 'cached'] as const) { - const messages: TranscriptMessage[] = [] - const sink = { active: true, push: (message: TranscriptMessage) => messages.push(message) } - resetTranscriptConsumersForTests() - resetSessionParseCacheForTests() - registerTranscriptConsumer({ - beginRead: () => ({ message: sink.push, finish: () => undefined }) - }) - vi.clearAllMocks() - - const session = await (read === 'direct' - ? parseAgentSessionFile(candidate, process.platform, sink) - : parseAgentSessionFileCached(candidate, process.platform)) - - expect(session).not.toBeNull() - expect(messages.length).toBeGreaterThan(0) - const paths = contentReadPaths() - expect(paths).toContain(file.path) - // Include failed optional reads: an absent sidecar can appear on a later scan. - expect( - paths.filter((path) => path !== file.path), - `${read}: ${file.path}` - ).toEqual([]) - } - } - } -) diff --git a/src/main/ai-vault/session-scanner-discovery-wsl-gate.test.ts b/src/main/ai-vault/session-scanner-discovery-wsl-gate.test.ts deleted file mode 100644 index 5142dbc130b..00000000000 --- a/src/main/ai-vault/session-scanner-discovery-wsl-gate.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { Dirent } from 'node:fs' -import type * as NodeFsPromisesModule from 'node:fs/promises' -import type { AiVaultScanIssue } from '../../shared/ai-vault/contracts/ai-vault-types' - -const fsMocks = vi.hoisted(() => ({ readdir: vi.fn(), stat: vi.fn() })) - -vi.mock('node:fs/promises', async (importOriginal) => ({ - ...(await importOriginal()), - readdir: fsMocks.readdir, - stat: fsMocks.stat -})) - -import { - resetWslTranscriptFsGateForTests, - WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS, - WslTranscriptFsError -} from '../native-chat/wsl-transcript-fs-gate' -import { discoverFiles, walkSessionFiles } from './session-scanner-discovery' - -const SLOW_MESSAGE = - 'WSL transcript files are temporarily unavailable because filesystem access is taking too long. Try again shortly or restart Orca if the issue continues.' - -// UNC child serialization reads every Dirent kind flag. -function dirent(name: string): Dirent { - return { - name, - isBlockDevice: () => false, - isCharacterDevice: () => false, - isDirectory: () => false, - isFIFO: () => false, - isFile: () => true, - isSocket: () => false, - isSymbolicLink: () => false - } as Dirent -} - -let releaseStall: (() => void) | undefined - -function stalls(): Promise { - return new Promise((resolve) => { - releaseStall = () => resolve(undefined as T) - }) -} - -beforeEach(() => { - resetWslTranscriptFsGateForTests() - fsMocks.readdir.mockReset() - fsMocks.stat.mockReset() - fsMocks.readdir.mockResolvedValue([]) - releaseStall = undefined -}) - -// Settle stalled calls to release permits before the next test. -afterEach(async () => { - if (!vi.isFakeTimers()) { - return - } - releaseStall?.() - await vi.advanceTimersByTimeAsync(0) - vi.useRealTimers() -}) - -describe('walkSessionFiles WSL gate refusals', () => { - it('rethrows gate refusals instead of reporting an empty tree', async () => { - const refusal = new WslTranscriptFsError('timeout', 'slow share') - await expect( - walkSessionFiles('\\\\wsl.localhost\\Ubuntu\\home\\ada\\.codex\\sessions', 'codex', [], { - extensions: new Set(['.jsonl']), - readDirectory: async () => { - throw refusal - } - }) - ).rejects.toBe(refusal) - }) - - it('still treats ordinary readdir failures as an empty tree', async () => { - await expect( - walkSessionFiles('/missing/root', 'codex', [], { - extensions: new Set(['.jsonl']), - readDirectory: async () => { - throw Object.assign(new Error('no such directory'), { code: 'ENOENT' }) - } - }) - ).resolves.toEqual([]) - }) -}) - -describe('discoverFiles containment for a stalled WSL root', () => { - const UNC_ROOT = '\\\\wsl.localhost\\Ubuntu\\home\\ada\\.codex\\sessions' - - it('contains a stalled readdir to one root so a Promise.all fan-out still resolves', async () => { - vi.useFakeTimers() - { - fsMocks.readdir.mockImplementation((dir: string) => - dir === UNC_ROOT ? stalls() : Promise.resolve([]) - ) - const issues: AiVaultScanIssue[] = [] - const discovery = discoverFiles({ - rootDir: UNC_ROOT, - limit: 10, - agent: 'codex', - issues, - extensions: ['.jsonl'] - }) - const healthy = discoverFiles({ - rootDir: '/home/ada/.codex/sessions', - limit: 10, - agent: 'codex', - issues: [], - extensions: ['.jsonl'] - }) - - await vi.advanceTimersByTimeAsync(WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS + 1) - - await expect(Promise.all([discovery, healthy])).resolves.toEqual([ - { agent: 'codex', rootDir: UNC_ROOT, files: [] }, - { agent: 'codex', rootDir: '/home/ada/.codex/sessions', files: [] } - ]) - expect(issues).toEqual([ - { - agent: 'codex', - path: UNC_ROOT, - message: SLOW_MESSAGE, - messageCode: 'wslTranscriptFsSlow' - } - ]) - } - }) - - it('reports every stalled file without spending one deadline per file', async () => { - vi.useFakeTimers() - { - const names = ['a', 'b', 'c', 'd', 'e'].map((name) => dirent(`${name}.jsonl`)) - fsMocks.readdir.mockResolvedValue(names) - fsMocks.stat.mockImplementation(stalls) - const issues: AiVaultScanIssue[] = [] - const discovery = discoverFiles({ - rootDir: UNC_ROOT, - limit: 10, - agent: 'codex', - issues, - extensions: ['.jsonl'] - }) - - // Route quarantine makes later stats fail fast; only the first stalled operations reach deadlines. - await vi.advanceTimersByTimeAsync(WSL_TRANSCRIPT_FS_SCAN_TIMEOUT_MS * 2 + 2) - - await expect(discovery).resolves.toEqual({ agent: 'codex', rootDir: UNC_ROOT, files: [] }) - expect(issues).toHaveLength(names.length) - expect(issues.every((issue) => issue.path.startsWith(UNC_ROOT))).toBe(true) - } - }) -}) - -it('execution admission preserves native access errors while confirmed missing roots remain absent', async () => { - for (const code of ['EACCES', 'ENOENT']) { - const failure = Object.assign(new Error(code), { code }) - const search = walkSessionFiles('/account/sessions', 'codex', [], { - extensions: new Set(['.jsonl']), - strictErrors: true, - readDirectory: async () => { - throw failure - } - }) - await (code === 'ENOENT' - ? expect(search).resolves.toEqual([]) - : expect(search).rejects.toBe(failure)) - } -}) diff --git a/src/main/ai-vault/structured-session-ownership-shell-commands.test.ts b/src/main/ai-vault/structured-session-ownership-shell-commands.test.ts deleted file mode 100644 index 765e9d2580d..00000000000 --- a/src/main/ai-vault/structured-session-ownership-shell-commands.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest' -import type { AgentSessionProviderHandle } from '../../shared/agent-session/lifecycle/agent-session-provider-handle' -import { setStructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-registry' -import { assertLegacyAiVaultResumeCommandAllowed } from './structured-session-ownership' -import { installOwnership, PROVIDER_SESSION } from './structured-session-ownership.test-support' - -const OPENCODE_ID = 'ses_2f9aB3cD4eF5gH6iJ7kL8mN9oP' -const UUID = '019fd622-3c4d-7e5f-a061-7c8d9e0f1a2b' -const OTHER_UUID = '019fd633-4d5e-7f60-b172-8d9e0f1a2b3c' -const PI_FILE = `/Users/me/.pi/agent/sessions/--Users-me-repo--/2026-10-09T10-00-00-000Z_${UUID}.jsonl` - -type Agent = { - name: string - own: () => void - /** A resume of the owned conversation, without its program. */ - resumeArgs: string - /** `--flag=` forms, each completed by a quoted target. */ - inlineFlags: string[] - otherId: string -} - -const acp = (agent: 'opencode' | 'grok' | 'omp', nativeId: string): AgentSessionProviderHandle => ({ - transport: 'acp', - agent, - nativeId -}) - -const AGENTS: Agent[] = [ - { - name: 'claude', - own: () => installOwnership({ provider: 'claude' }), - resumeArgs: `-r ${PROVIDER_SESSION}`, - inlineFlags: ['--resume=', '-r='], - otherId: OTHER_UUID - }, - { - name: 'codex', - own: () => installOwnership(), - resumeArgs: `resume ${PROVIDER_SESSION}`, - inlineFlags: [], - otherId: OTHER_UUID - }, - { - name: 'opencode', - own: () => installOwnership({ handle: acp('opencode', OPENCODE_ID) }), - resumeArgs: `-s ${OPENCODE_ID}`, - inlineFlags: ['--session=', '-s='], - otherId: 'ses_other' - }, - { - name: 'grok', - own: () => installOwnership({ handle: acp('grok', UUID) }), - resumeArgs: `-r ${UUID}`, - inlineFlags: ['--resume=', '-r=', '--load='], - otherId: OTHER_UUID - }, - { - name: 'omp', - own: () => installOwnership({ handle: acp('omp', UUID) }), - resumeArgs: `--resume ${UUID}`, - inlineFlags: ['--resume=', '-r=', '--session='], - otherId: OTHER_UUID - }, - { - name: 'pi', - own: () => - installOwnership({ handle: { transport: 'jsonl-rpc', agent: 'pi', nativeId: PI_FILE } }), - resumeArgs: `--session ${UUID}`, - inlineFlags: ['--session='], - otherId: OTHER_UUID - } -] - -const allowed = (command: string) => - expect( - assertLegacyAiVaultResumeCommandAllowed(command, async () => undefined), - command - ).resolves.toBeUndefined() -const refused = (command: string) => - expect( - assertLegacyAiVaultResumeCommandAllowed(command, async () => undefined), - command - ).rejects.toThrow('agent_session_conflict') - -function ownedId(agent: Agent): string { - return agent.resumeArgs.split(' ').at(-1)! -} - -afterEach(() => setStructuredAgentSessionHost(null)) - -describe.each(AGENTS)('a $name resume read as a shell reads it', (agent) => { - const resume = `${agent.name} ${agent.resumeArgs}` - - it('ends at a separator glued to a word', async () => { - agent.own() - for (const command of [ - `${resume}; echo done`, - `${resume};`, - `${resume}|tee x`, - `${resume}&`, - `${resume}&&echo done`, - `cd /x&&${resume}`, - `cd /x||${resume}`, - `cd /x;${resume}`, - `cd /x|${resume}`, - `echo hi 2>&1;${resume} 2>&1` - ]) { - await refused(command) - } - }) - - it('keeps a quoted separator inside its word', async () => { - agent.own() - await refused(`${agent.name} "a;b" 'c|d' ${agent.resumeArgs}`) - await allowed(`echo "x;${resume}" 'y&&${resume}'`) - }) - - it('removes the quotes around a value after `=`', async () => { - agent.own() - for (const flag of agent.inlineFlags) { - await refused(`${agent.name} ${flag}"${ownedId(agent)}"`) - await refused(`${agent.name} ${flag}'${ownedId(agent)}'`) - await allowed(`${agent.name} ${flag}"${agent.otherId}"`) - } - }) -}) - -// Agent names count only where the shell would run them, including text sent from agent prompts. -describe.each(AGENTS)('a $name resume in command position', (agent) => { - const resume = `${agent.name} ${agent.resumeArgs}` - - it('is refused behind assignments, a path or a launcher', async () => { - agent.own() - for (const command of [ - `FOO=1 BAR='a b' ${resume}`, - `/usr/local/bin/${resume}`, - `${agent.name[0]!.toUpperCase()}${agent.name.slice(1)} ${agent.resumeArgs}`, - `env -i FOO=1 ${resume}`, - `exec ${resume}`, - `command ${resume}`, - `sudo -E ${resume}`, - `npx ${resume}`, - `bunx ${resume}`, - `pnpm dlx ${resume}`, - `& 'C:\\Program Files\\${agent.name}\\${agent.name}.exe' ${agent.resumeArgs}`, - `cd /d "C:/x" && ${resume}`, - `echo hi\n${resume}` - ]) { - await refused(command) - } - }) - - it('is allowed as another program’s argument or prompt text', async () => { - agent.own() - for (const command of [ - `Fix the ${agent.name} ${agent.resumeArgs} handling`, - `echo ${resume}`, - `git commit -m wip ${resume}`, - `rg -n ${resume}` - ]) { - await allowed(command) - } - }) -}) - -it('allows a prompt that names a Pi flag while a Pi chat owns a conversation', async () => { - AGENTS.find(({ name }) => name === 'pi')!.own() - await allowed('Fix the Pi --resume flag') - await allowed('The pi -c path is broken.\nPlease fix pi --continue too') -}) diff --git a/src/main/bitbucket/pull-request-creation.test.ts b/src/main/bitbucket/pull-request-creation.test.ts deleted file mode 100644 index 4340aea378a..00000000000 --- a/src/main/bitbucket/pull-request-creation.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { createBitbucketPullRequest } from './pull-request-creation' -import { _resetBitbucketRepoRefCache } from './repository-ref' - -const { gitExecFileAsyncMock } = vi.hoisted(() => ({ - gitExecFileAsyncMock: vi.fn() -})) - -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal>()), - gitExecFileAsync: gitExecFileAsyncMock -})) - -vi.mock('../source-control/pull-request-template', () => ({ - readHostedPullRequestTemplate: vi.fn(async () => 'Template body') -})) - -const OLD_ENV = process.env -const OLD_FETCH = globalThis.fetch - -const CREATE_INPUT = { - provider: 'bitbucket', - base: 'main', - head: 'feature/login', - title: 'Add login' -} as const - -function createdPullRequestResponse(): Response { - return Response.json({ - id: 42, - title: 'Add login', - state: 'OPEN', - updated_on: '2026-08-11T00:00:00Z', - links: { html: { href: 'https://bitbucket.org/team/repo/pull-requests/42' } }, - source: { branch: { name: 'feature/login' } }, - destination: { branch: { name: 'main' } } - }) -} - -describe('Bitbucket pull request creation', () => { - beforeEach(() => { - process.env = { ...OLD_ENV, ORCA_BITBUCKET_ACCESS_TOKEN: 'bb-token' } - gitExecFileAsyncMock.mockReset() - gitExecFileAsyncMock.mockResolvedValue({ - stdout: 'https://bitbucket.org/team/repo.git\n', - stderr: '' - }) - _resetBitbucketRepoRefCache() - }) - - afterEach(() => { - process.env = OLD_ENV - globalThis.fetch = OLD_FETCH - _resetBitbucketRepoRefCache() - }) - - it('posts the Bitbucket source/destination create body to the repository endpoint', async () => { - const fetchMock = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { - const url = new URL(String(input)) - expect(url.origin).toBe('https://api.bitbucket.org') - expect(url.pathname).toBe('/2.0/repositories/team/repo/pullrequests') - const requestInit = init! - expect(requestInit.method).toBe('POST') - expect((requestInit.headers as Record).Authorization).toBe('Bearer bb-token') - expect(JSON.parse(String(requestInit.body))).toEqual({ - title: 'Add login', - description: '', - source: { branch: { name: 'feature/login' } }, - destination: { branch: { name: 'main' } } - }) - return createdPullRequestResponse() - }) - globalThis.fetch = fetchMock as unknown as typeof fetch - - await expect(createBitbucketPullRequest('/repo', CREATE_INPUT, 'local')).resolves.toEqual({ - ok: true, - number: 42, - url: 'https://bitbucket.org/team/repo/pull-requests/42' - }) - expect(fetchMock).toHaveBeenCalledTimes(1) - }) - - it('ignores a draft request rather than dead-ending a hidden persisted default', async () => { - const fetchMock = vi.fn(async () => createdPullRequestResponse()) - globalThis.fetch = fetchMock as unknown as typeof fetch - - await expect( - createBitbucketPullRequest('/repo', { ...CREATE_INPUT, draft: true }, 'local') - ).resolves.toMatchObject({ ok: true, number: 42 }) - expect(JSON.parse(String((fetchMock.mock.calls[0] as never[])[1]['body']))).not.toHaveProperty( - 'draft' - ) - }) - - it('maps a duplicate-branch rejection to already_exists with the existing review', async () => { - let call = 0 - const fetchMock = vi.fn(async () => { - call += 1 - if (call === 1) { - return Response.json( - { error: { message: 'There is already a pull request for this branch.' } }, - { status: 400 } - ) - } - return Response.json({ - values: [ - { - id: 7, - title: 'Add login', - state: 'OPEN', - updated_on: '2026-08-11T00:00:00Z', - links: { html: { href: 'https://bitbucket.org/team/repo/pull-requests/7' } }, - source: { branch: { name: 'feature/login' } }, - destination: { branch: { name: 'main' } } - } - ] - }) - }) - globalThis.fetch = fetchMock as unknown as typeof fetch - - expect(await createBitbucketPullRequest('/repo', CREATE_INPUT, 'local')).toMatchObject({ - ok: false, - code: 'already_exists', - existingReview: { number: 7, url: 'https://bitbucket.org/team/repo/pull-requests/7' } - }) - }) - - it('maps a 401 to auth_required pointing at both credential paths', async () => { - globalThis.fetch = vi.fn(async () => - Response.json({ error: { message: 'Unauthorized' } }, { status: 401 }) - ) as unknown as typeof fetch - - const result = await createBitbucketPullRequest('/repo', CREATE_INPUT, 'local') - - expect(result).toMatchObject({ ok: false, code: 'auth_required', reason: 'auth_required' }) - expect(!result.ok && result.error).toContain('Settings') - }) - - it('refuses a non-Bitbucket remote', async () => { - gitExecFileAsyncMock.mockResolvedValue({ - stdout: 'https://github.com/team/repo.git\n', - stderr: '' - }) - globalThis.fetch = vi.fn() as unknown as typeof fetch - - await expect(createBitbucketPullRequest('/repo', CREATE_INPUT, 'local')).resolves.toMatchObject( - { - ok: false, - code: 'unsupported_provider' - } - ) - }) -}) diff --git a/src/main/browser/cdp/input/cdp-keyboard-us-layout.test.ts b/src/main/browser/cdp/input/cdp-keyboard-us-layout.test.ts deleted file mode 100644 index 4394cc8251d..00000000000 --- a/src/main/browser/cdp/input/cdp-keyboard-us-layout.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { imeFallbackKeyEvent, parseCdpKeyEvent, type CdpKeyEvent } from './cdp-keyboard-us-layout' - -describe('parseCdpKeyEvent', () => { - it('maps every printable ASCII character to a key event that types that character', () => { - const broken: string[] = [] - for (let charCode = 32; charCode <= 126; charCode++) { - const ch = String.fromCharCode(charCode) - const parsed = parseCdpKeyEvent(ch) - if (!parsed || parsed.text !== ch || parsed.keyCode === 0) { - broken.push(ch) - } - } - expect(broken).toEqual([]) - }) - - it.each([ - ['#', 51], - ['$', 52], - ['%', 53], - ['&', 55], - ["'", 222], - ['(', 57], - ['.', 190] - ])( - 'gives %s the US-layout key code %i instead of its own char code', - (ch: string, keyCode: number) => { - // '&' and '.' character codes invoke Up and Delete instead of typing. - expect(parseCdpKeyEvent(ch)).toMatchObject({ keyCode, text: ch }) - } - ) - - it.each([ - ['Ctrl+A', { keyCode: 65, key: 'a', modifiers: 2, text: null }], - ['Control+a', { keyCode: 65, key: 'a', modifiers: 2, text: null }], - ['Shift+Home', { keyCode: 36, key: 'Home', modifiers: 8, text: null }], - ['Alt+ArrowDown', { keyCode: 40, key: 'ArrowDown', modifiers: 1, text: null }], - ['Ctrl+Shift+K', { keyCode: 75, key: 'K', modifiers: 10, text: null }], - ['Meta+r', { keyCode: 82, key: 'r', modifiers: 4, text: null }], - ['Control+Shift+r', { keyCode: 82, key: 'R', modifiers: 10, text: null }] - ])('parses the shortcut %s', (raw: string, expected: Partial) => { - expect(parseCdpKeyEvent(raw)).toMatchObject(expected) - }) - - it('treats a capital letter in a shortcut as the key name, not a shift request', () => { - expect(parseCdpKeyEvent('Ctrl+A')).toMatchObject({ key: 'a', modifiers: 2 }) - expect(parseCdpKeyEvent('Ctrl+Shift+A')).toMatchObject({ key: 'A', modifiers: 10 }) - }) - - it('shifts a bare capital letter and reports the shifted character as text', () => { - expect(parseCdpKeyEvent('R')).toMatchObject({ keyCode: 82, key: 'R', modifiers: 8, text: 'R' }) - expect(parseCdpKeyEvent('Shift+a')).toMatchObject({ key: 'A', modifiers: 8, text: 'A' }) - }) - - it('maps shifted punctuation onto its base key with shift held', () => { - expect(parseCdpKeyEvent('Shift+1')).toMatchObject({ keyCode: 49, key: '!', text: '!' }) - expect(parseCdpKeyEvent('+')).toMatchObject({ keyCode: 187, modifiers: 8, text: '+' }) - }) - - it.each([ - ['Enter', { keyCode: 13, text: '\r' }], - ['Space', { keyCode: 32, key: ' ', text: ' ' }], - ['Esc', { keyCode: 27, key: 'Escape', text: null }], - ['PgDn', { keyCode: 34, key: 'PageDown', text: null }], - ['ContextMenu', { keyCode: 93, text: null }], - ['F5', { keyCode: 116, key: 'F5', code: 'F5', text: null }], - ['F12', { keyCode: 123, text: null }] - ])('parses the named key %s', (raw: string, expected: Partial) => { - expect(parseCdpKeyEvent(raw)).toMatchObject(expected) - }) - - it.each([ - ['Shift', { keyCode: 16, key: 'Shift', code: 'ShiftLeft', modifiers: 8, selfModifier: 8 }], - ['Ctrl', { keyCode: 17, key: 'Control', code: 'ControlLeft', modifiers: 2, selfModifier: 2 }], - ['Alt', { keyCode: 18, key: 'Alt', code: 'AltLeft', modifiers: 1, selfModifier: 1 }], - ['Meta', { keyCode: 91, key: 'Meta', code: 'MetaLeft', modifiers: 4, selfModifier: 4 }] - ])( - 'reports the own modifier bit and left-side location for a bare %s press', - (raw: string, expected: Partial) => { - expect(parseCdpKeyEvent(raw)).toMatchObject({ ...expected, location: 1, text: null }) - } - ) - - it('adds the self bit on top of held modifiers for a modifier-only chord', () => { - expect(parseCdpKeyEvent('Ctrl+Shift')).toMatchObject({ - keyCode: 16, - modifiers: 10, - selfModifier: 8 - }) - }) - - it('reports no self bit or location for non-modifier keys', () => { - expect(parseCdpKeyEvent('Enter')).toMatchObject({ location: 0, selfModifier: 0 }) - expect(parseCdpKeyEvent('a')).toMatchObject({ location: 0, selfModifier: 0 }) - expect(parseCdpKeyEvent('Ctrl+A')).toMatchObject({ location: 0, selfModifier: 0 }) - }) - - it.each([['MediaPlayPause'], ['F25'], [''], ['NoSuchKey']])( - 'returns null for %s so the caller can fall back', - (raw: string) => { - expect(parseCdpKeyEvent(raw)).toBeNull() - } - ) -}) - -describe('imeFallbackKeyEvent', () => { - it.each([['é'], ['ß'], ['ñ'], ['ü'], ['漢'], ['한']])( - 'gives %s the IME key event form with keyCode 229 and its text', - (ch: string) => { - expect(imeFallbackKeyEvent(ch)).toEqual({ - keyCode: 229, - key: ch, - code: '', - modifiers: 0, - location: 0, - selfModifier: 0, - text: ch - }) - } - ) - - it.each([ - ['a table-covered ASCII character', 'a'], - ['a surrogate-pair emoji', '👍'], - ['a combining sequence', 'e\u0301'], - ['a multi-character name', 'MediaPlayPause'], - ['a chord with a non-US character', 'Ctrl+é'], - ['an empty string', ''] - ])('returns null for %s so the helper keeps its behavior', (_name: string, raw: string) => { - expect(imeFallbackKeyEvent(raw)).toBeNull() - }) -}) diff --git a/src/main/browser/guest-lifecycle/manager/browser-manager-viewport-partial-failure.test.ts b/src/main/browser/guest-lifecycle/manager/browser-manager-viewport-partial-failure.test.ts deleted file mode 100644 index 1268ee5c628..00000000000 --- a/src/main/browser/guest-lifecycle/manager/browser-manager-viewport-partial-failure.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => ({ - appGetPathMock: vi.fn(() => '/downloads'), - shellOpenExternalMock: vi.fn(), - browserWindowFromWebContentsMock: vi.fn(), - menuBuildFromTemplateMock: vi.fn(), - guestOffMock: vi.fn(), - guestOnMock: vi.fn(), - guestSetBackgroundThrottlingMock: vi.fn(), - guestSetWindowOpenHandlerMock: vi.fn(), - guestOpenDevToolsMock: vi.fn(), - webContentsFromIdMock: vi.fn(), - screenGetCursorScreenPointMock: vi.fn(() => ({ x: 0, y: 0 })), - openPopupWithOriginBarMock: vi.fn() -})) - -vi.mock('electron', () => ({ - app: { getPath: mocks.appGetPathMock }, - BrowserWindow: { fromWebContents: mocks.browserWindowFromWebContentsMock }, - clipboard: { writeText: vi.fn() }, - shell: { openExternal: mocks.shellOpenExternalMock }, - Menu: { buildFromTemplate: mocks.menuBuildFromTemplateMock }, - screen: { getCursorScreenPoint: mocks.screenGetCursorScreenPointMock }, - webContents: { fromId: mocks.webContentsFromIdMock } -})) -vi.mock('../popups/popup-origin-bar-window', () => ({ - openPopupWithOriginBar: mocks.openPopupWithOriginBarMock -})) - -import { browserManager } from '../../browser-manager' -import { - rendererWebContentsId, - resetBrowserManagerMocks, - resetBrowserManagerState -} from './browser-manager-test-harness' -import { createViewportGuestFactory } from './browser-manager-viewport-test-fixtures' - -const makeGuest = createViewportGuestFactory(mocks) -const OVERRIDE = { width: 375, height: 667, deviceScaleFactor: 2, mobile: true } as const - -describe('browserManager viewport partial failure', () => { - beforeEach(() => { - resetBrowserManagerMocks(mocks) - resetBrowserManagerState() - }) - - it('keeps wheel routing active when follow-up setup fails after metrics apply', async () => { - const { guest, debuggerSendCommand } = makeGuest(42421) - debuggerSendCommand.mockImplementation((method: string) => - method === 'Emulation.setTouchEmulationEnabled' - ? Promise.reject(new Error('touch setup failed')) - : Promise.resolve(undefined) - ) - mocks.webContentsFromIdMock.mockReturnValue(guest) - browserManager.attachGuestPolicies(guest as never) - browserManager.registerGuest({ - browserPageId: 'tab-partial-apply', - webContentsId: guest.id as number, - rendererWebContentsId - }) - const renderer = { isDestroyed: vi.fn(() => false), send: vi.fn() } - mocks.webContentsFromIdMock.mockImplementation((id: number) => - id === rendererWebContentsId ? renderer : guest - ) - await expect(browserManager.setViewportOverride('tab-partial-apply', OVERRIDE)).resolves.toBe( - false - ) - browserManager.setViewportScrollState('tab-partial-apply', rendererWebContentsId, { - scrollLeft: 0, - scrollTop: 0, - maxScrollLeft: 400, - maxScrollTop: 300 - }) - - const beforeMouseEvent = mocks.guestOnMock.mock.calls.findLast( - ([event]) => event === 'before-mouse-event' - )?.[1] as ((event: Electron.Event, mouse: Electron.MouseInputEvent) => void) | undefined - expect(beforeMouseEvent).toBeDefined() - const preventDefault = vi.fn() - beforeMouseEvent?.( - { preventDefault } as unknown as Electron.Event, - { - type: 'mouseWheel', - x: 0, - y: 0, - modifiers: [], - deltaX: 0, - deltaY: 120 - } as Electron.MouseWheelInputEvent - ) - expect(preventDefault).toHaveBeenCalledTimes(1) - expect(renderer.send).toHaveBeenCalledWith('ui:scrollBrowserPage', { - browserPageId: 'tab-partial-apply', - deltaX: 0, - deltaY: 120 - }) - }) - - it('keeps host panning available when metrics setup fails', async () => { - const { guest, debuggerSendCommand } = makeGuest(42422) - debuggerSendCommand.mockImplementation((method: string) => - method === 'Emulation.setDeviceMetricsOverride' - ? Promise.reject(new Error('metrics setup failed')) - : Promise.resolve(undefined) - ) - mocks.webContentsFromIdMock.mockReturnValue(guest) - browserManager.attachGuestPolicies(guest as never) - browserManager.registerGuest({ - browserPageId: 'tab-metrics-failed', - webContentsId: guest.id as number, - rendererWebContentsId - }) - const renderer = { isDestroyed: vi.fn(() => false), send: vi.fn() } - mocks.webContentsFromIdMock.mockImplementation((id: number) => - id === rendererWebContentsId ? renderer : guest - ) - - await expect(browserManager.setViewportOverride('tab-metrics-failed', OVERRIDE)).resolves.toBe( - false - ) - browserManager.setViewportScrollState('tab-metrics-failed', rendererWebContentsId, { - scrollLeft: 0, - scrollTop: 0, - maxScrollLeft: 400, - maxScrollTop: 300 - }) - - const beforeMouseEvent = mocks.guestOnMock.mock.calls.findLast( - ([event]) => event === 'before-mouse-event' - )?.[1] as ((event: Electron.Event, mouse: Electron.MouseInputEvent) => void) | undefined - expect(beforeMouseEvent).toBeDefined() - const preventDefault = vi.fn() - beforeMouseEvent?.( - { preventDefault } as unknown as Electron.Event, - { - type: 'mouseWheel', - x: 0, - y: 0, - modifiers: [], - deltaX: 0, - deltaY: 120 - } as Electron.MouseWheelInputEvent - ) - - expect(preventDefault).toHaveBeenCalledTimes(1) - expect(renderer.send).toHaveBeenCalledWith('ui:scrollBrowserPage', { - browserPageId: 'tab-metrics-failed', - deltaX: 0, - deltaY: 120 - }) - }) -}) diff --git a/src/main/browser/guest-lifecycle/manager/browser-manager-worker-request-user-agent.test.ts b/src/main/browser/guest-lifecycle/manager/browser-manager-worker-request-user-agent.test.ts deleted file mode 100644 index 292d56f761e..00000000000 --- a/src/main/browser/guest-lifecycle/manager/browser-manager-worker-request-user-agent.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const browserMocks = vi.hoisted(() => ({ - appGetPathMock: vi.fn(() => '/downloads'), - shellOpenExternalMock: vi.fn(), - browserWindowFromWebContentsMock: vi.fn(), - menuBuildFromTemplateMock: vi.fn(), - guestOffMock: vi.fn(), - guestOnMock: vi.fn(), - guestSetBackgroundThrottlingMock: vi.fn(), - guestSetWindowOpenHandlerMock: vi.fn(), - guestOpenDevToolsMock: vi.fn(), - webContentsFromIdMock: vi.fn(), - screenGetCursorScreenPointMock: vi.fn(() => ({ x: 0, y: 0 })), - openPopupWithOriginBarMock: vi.fn(), - processUserAgentMode: 'clean', - processUserAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36' -})) - -vi.mock('electron', () => ({ - app: { getPath: browserMocks.appGetPathMock }, - BrowserWindow: { fromWebContents: browserMocks.browserWindowFromWebContentsMock }, - clipboard: { writeText: vi.fn() }, - shell: { openExternal: browserMocks.shellOpenExternalMock }, - Menu: { buildFromTemplate: browserMocks.menuBuildFromTemplateMock }, - screen: { getCursorScreenPoint: browserMocks.screenGetCursorScreenPointMock }, - webContents: { fromId: browserMocks.webContentsFromIdMock } -})) - -vi.mock('../popups/popup-origin-bar-window', () => ({ - openPopupWithOriginBar: browserMocks.openPopupWithOriginBarMock -})) - -vi.mock('../../identity/browser-process-user-agent', () => ({ - getBrowserProcessUserAgentIdentity: () => ({ - mode: browserMocks.processUserAgentMode, - userAgent: browserMocks.processUserAgent, - electronUserAgent: - browserMocks.processUserAgentMode === 'native' - ? browserMocks.processUserAgent - : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) orca/1.0.0 Chrome/134.0.0.0 Electron/30.0.0 Safari/537.36' - }) -})) - -import { browserManager } from '../../browser-manager' -import { - rendererWebContentsId, - resetBrowserManagerMocks, - resetBrowserManagerState -} from './browser-manager-test-harness' -import { - createViewportGuestFactory, - GUEST_CLEAN_UA -} from './browser-manager-viewport-test-fixtures' - -const { webContentsFromIdMock } = browserMocks -const makeGuest = createViewportGuestFactory(browserMocks) -const MOBILE_VIEWPORT_OVERRIDE = { width: 375, height: 667, deviceScaleFactor: 2, mobile: true } -const MOBILE_UA_PATTERN = /CriOS\// - -function registerGuest(browserPageId: string, webContentsId: number): Electron.Session { - const { guest } = makeGuest(webContentsId) - webContentsFromIdMock.mockReturnValue(guest) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the shared viewport fixture builds an untyped guest stub; the manager only reads members that stub defines. - browserManager.attachGuestPolicies(guest as never) - browserManager.registerGuest({ browserPageId, webContentsId, rendererWebContentsId }) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: resolveBrowserGuestRequestUserAgent reads no Session member, so the stub only has to be the object the guest carries. - return guest.session as Electron.Session -} - -function resolve(session: Electron.Session, webContentsId?: number): string { - return browserManager.resolveBrowserGuestRequestUserAgent({ - session, - url: 'https://example.com/asset.js', - webContentsId - }).userAgent -} - -// Shared/service worker requests lack a guest ID and must retain their process identity. -describe('worker request identity under viewport emulation', () => { - beforeEach(() => { - resetBrowserManagerMocks(browserMocks) - resetBrowserManagerState() - browserMocks.processUserAgentMode = 'clean' - browserMocks.processUserAgent = GUEST_CLEAN_UA - }) - - it('keeps a worker request desktop-clean while a tab in the same session is emulated mobile', async () => { - const session = registerGuest('tab-mobile', 4242) - expect(await browserManager.setViewportOverride('tab-mobile', MOBILE_VIEWPORT_OVERRIDE)).toBe( - true - ) - - expect(resolve(session)).toBe(GUEST_CLEAN_UA) - }) - - it('still resolves the mobile identity for the emulated tab itself', async () => { - const session = registerGuest('tab-mobile', 4242) - expect(await browserManager.setViewportOverride('tab-mobile', MOBILE_VIEWPORT_OVERRIDE)).toBe( - true - ) - - expect(resolve(session, 4242)).toMatch(MOBILE_UA_PATTERN) - }) - - it('leaves a desktop tab desktop-clean while a peer tab in its session is emulated mobile', async () => { - const session = registerGuest('tab-mobile', 4242) - registerGuest('tab-desktop', 4243) - expect(await browserManager.setViewportOverride('tab-mobile', MOBILE_VIEWPORT_OVERRIDE)).toBe( - true - ) - - expect(resolve(session, 4243)).toBe(GUEST_CLEAN_UA) - }) - - it('keeps worker requests desktop-clean when no tab is emulated at all', () => { - const session = registerGuest('tab-plain', 4244) - - expect(resolve(session)).toBe(GUEST_CLEAN_UA) - }) - - // Unmapped guests must not inherit another tab's mobile override. - it('keeps an unmapped webContents desktop-clean beside an emulated tab', async () => { - const session = registerGuest('tab-mobile', 4242) - expect(await browserManager.setViewportOverride('tab-mobile', MOBILE_VIEWPORT_OVERRIDE)).toBe( - true - ) - - expect(resolve(session, 9999)).toBe(GUEST_CLEAN_UA) - }) -}) diff --git a/src/main/browser/guest-lifecycle/prompts/browser-external-app-launch.test.ts b/src/main/browser/guest-lifecycle/prompts/browser-external-app-launch.test.ts deleted file mode 100644 index e094bfb0ca5..00000000000 --- a/src/main/browser/guest-lifecycle/prompts/browser-external-app-launch.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { EventEmitter } from 'node:events' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => ({ - getApplicationNameForProtocol: vi.fn((_url: string) => 'Okta Verify'), - openExternal: vi.fn((_url: string) => Promise.resolve()) -})) - -vi.mock('electron', () => ({ - app: { getApplicationNameForProtocol: mocks.getApplicationNameForProtocol }, - shell: { openExternal: mocks.openExternal } -})) - -import type { BrowserPagePromptRequest } from '../../../../shared/browser-page-prompt' -import { requestBrowserExternalAppLaunch } from './browser-external-app-launch' -import { - BROWSER_PAGE_PROMPT_TIMEOUT_MS, - respondToBrowserPagePrompt, - type BrowserPagePromptTarget -} from './browser-page-prompts' - -const OKTA_URL = 'com-okta-authenticator:/deviceChallenge?challengeRequest=abc' - -function mockRenderer(id: number): Electron.WebContents { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: prompts only read id, isDestroyed, send and subscribe to the window's lifecycle events. - return { - id, - isDestroyed: () => false, - send: vi.fn(), - on: vi.fn(), - off: vi.fn() - } as unknown as Electron.WebContents -} - -const requesters: (Electron.WebContents & EventEmitter)[] = [] - -function mockRequester(): Electron.WebContents & EventEmitter { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: prompts only read id, isDestroyed and the input, navigation and destroyed events. - const contents = new EventEmitter() as Electron.WebContents & EventEmitter - Object.assign(contents, { id: 1000 + requesters.length, isDestroyed: () => false }) - requesters.push(contents) - return contents -} - -function ask(rawUrl = OKTA_URL, requestingUrl: string | null = 'https://acme.okta.com/signin') { - return requestBrowserExternalAppLaunch({ - requester: mockRequester(), - target: resolveTarget(), - rawUrl, - requestingUrl: () => requestingUrl - }) -} - -function sentRequests(renderer: Electron.WebContents): BrowserPagePromptRequest[] { - return vi - .mocked(renderer.send) - .mock.calls.filter(([channel]) => channel === 'browser:page-prompt-requested') - .map(([, request]) => request) -} - -let resolveTarget: () => BrowserPagePromptTarget | null = () => null - -describe('browser external app launch', () => { - let renderer: Electron.WebContents - - beforeEach(() => { - vi.useFakeTimers() - mocks.openExternal.mockClear() - renderer = mockRenderer(10) - // Give each request its own page to avoid prompt sharing. - let page = 0 - resolveTarget = () => ({ browserPageId: `page-${++page}`, renderer }) - }) - - afterEach(() => { - vi.advanceTimersByTime(BROWSER_PAGE_PROMPT_TIMEOUT_MS) - for (const requester of requesters.splice(0)) { - requester.emit('destroyed') - } - vi.useRealTimers() - }) - - it('names the app and the asking site, and opens the app only after a yes', async () => { - expect(ask()).toBe(true) - const [request] = sentRequests(renderer) - expect(request).toEqual({ - requestId: expect.any(String), - kind: 'external-app', - details: { - appName: process.platform === 'linux' ? null : 'Okta Verify', - scheme: 'com-okta-authenticator', - origin: 'https://acme.okta.com' - } - }) - expect(mocks.openExternal).not.toHaveBeenCalled() - - await expect( - respondToBrowserPagePrompt(renderer, { requestId: request.requestId, answer: true }) - ).resolves.toBe('answered') - expect(mocks.openExternal).toHaveBeenCalledWith(OKTA_URL) - - ask('zoommtg://zoom.us/join?confno=1') - await respondToBrowserPagePrompt(renderer, { - requestId: sentRequests(renderer)[1].requestId, - answer: null - }) - expect(mocks.openExternal).toHaveBeenCalledTimes(1) - }) - - it.each([ - 'https://example.com/', - 'http://localhost:3000/', - 'file:///etc/passwd', - 'javascript:alert(1)', - 'data:text/html,x', - 'blob:https://example.com/id', - 'about:blank', - 'orca-preview://doc/index.html', - 'chrome-extension://nngceckbapebfimnlniiiahkandclblb/popup/index.html', - 'orca-extension-api://api/call', - 'shell:startup', - 'vbscript:msgbox(1)', - 'hcp://system/sysinfo', - 'ms-help://topic', - 'afp://share.example/volume', - 'not a url' - ])('refuses %s without asking', (rawUrl) => { - expect(ask(rawUrl)).toBe(false) - expect(renderer.send).not.toHaveBeenCalled() - }) - - it('refuses a page with no window on this computer', () => { - resolveTarget = () => null - expect(ask()).toBe(false) - }) - - it('keeps blocking the link while its page already has a prompt open', () => { - resolveTarget = () => ({ browserPageId: 'page-1', renderer }) - ask() - expect(ask()).toBe(true) - expect(sentRequests(renderer)).toHaveLength(1) - }) - - it('reports a launch the OS could not start, so the window can say so', async () => { - mocks.openExternal.mockRejectedValueOnce(new Error('no application for scheme')) - ask('zoommtg://zoom.us/join?confno=1') - - await expect( - respondToBrowserPagePrompt(renderer, { - requestId: sentRequests(renderer)[0].requestId, - answer: true - }) - ).resolves.toBe('failed') - }) -}) diff --git a/src/main/claude/claude-api-retry-idle-sweep.test.ts b/src/main/claude/claude-api-retry-idle-sweep.test.ts deleted file mode 100644 index 41c1099d942..00000000000 --- a/src/main/claude/claude-api-retry-idle-sweep.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -// Each retry frame must publish activity even when all attempts revise one row. -import { mkdtemp, rm } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest' -import { openTestAgentSessionRecordStore } from '../runtime/agent-session/records/agent-session-record-store-test-harness' -import type { StructuredAgentSessionAdapter } from '../native-chat/agent-session-wire/structured-agent-session-adapter' -import type { StructuredAgentSessionEventSink } from '../native-chat/agent-session-wire/structured-agent-session-event-sink' -import { StructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-host' -import { STRUCTURED_AGENT_SESSION_IDLE_MS } from '../native-chat/agent-session-wire/structured-agent-session-idle-sweep' -import { - HOST_TEST_NOW as NOW, - HOST_TEST_SESSION as SESSION, - HOST_TEST_THREAD as THREAD, - hostTestAttachParams, - resetHostTestOperationIds -} from '../native-chat/agent-session-wire/structured-agent-session-host-test-data' -import { createClaudeJournalTranslator } from './structured/journal/claude-structured-journal-translation' -import { - closeTestJournalHostDatabases, - openTestJournalHostDatabase -} from '../native-chat/agent-session-journal/journal-host-database-test-support' -import { createStructuredAgentSessionLogger } from '../native-chat/agent-session-wire/structured-agent-session-logger' -import { codexProviderHandle } from '../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' -import { NO_STRUCTURED_AGENTS } from '../native-chat/agent-session-wire/structured-agent-session-adapter-router-test-support' - -const SWEEP_MS = 5 -const RETRY_GAP_MS = 10 * 60_000 - -let root: string -let host: StructuredAgentSessionHost -let sink: StructuredAgentSessionEventSink | null -let closeSession: Mock> -let clock: number - -function apiRetry(attempt: number): Record { - return { - type: 'system', - subtype: 'api_retry', - attempt, - max_retries: 10, - retry_delay_ms: 600_000, - error_status: 429, - error: 'rate_limit', - session_id: 'provider-1', - uuid: `9b2f6a1e-0c4d-4e7a-8f3b-00000000000${attempt}` - } -} - -function waitOutSeveralSweeps(): Promise { - return new Promise((resolve) => setTimeout(resolve, SWEEP_MS * 20)) -} - -beforeEach(async () => { - root = await mkdtemp(join(tmpdir(), 'orca-claude-retry-sweep-')) - resetHostTestOperationIds() - sink = null - clock = NOW - closeSession = vi.fn(async () => true) - const store = await openTestAgentSessionRecordStore(root) - const adapter: StructuredAgentSessionAdapter = { - open: async () => ({ - reportedOptions: { model: '' } - }), - acquire: async ({ fence, spawnToken, events }) => { - sink = events ?? null - return { - process: { hostId: 'local', pid: 4242, processStartTimeMs: NOW - 1_000, spawnToken }, - acquisitionGeneration: 'generation-1', - link: { - linkId: `link-${fence}`, - handle: codexProviderHandle(THREAD), - origin: 'created', - mintedAtFence: fence, - observedAt: NOW - } - } - }, - closeSession, - releaseAcquisition: async () => true, - dispatch: async () => ({ state: 'admitted' }), - cancelTurn: async () => ({ cancelled: false }), - answerPrompt: async () => undefined, - setOption: async () => undefined - } - host = new StructuredAgentSessionHost({ - agents: NO_STRUCTURED_AGENTS, - logger: createStructuredAgentSessionLogger(), - store, - adapter, - journalDatabase: openTestJournalHostDatabase(root), - claimKeyId: 'key-1', - mintSpawnToken: () => 'spawn-1', - idleSweep: { intervalMs: SWEEP_MS, idleMs: STRUCTURED_AGENT_SESSION_IDLE_MS }, - now: () => clock - }) -}) - -afterEach(async () => { - await host.flushAllStreamedEvents() - await closeTestJournalHostDatabases() - await rm(root, { recursive: true, force: true }) -}) - -describe('a Claude retrying a refused request', () => { - it('keeps the conversation open past the idle window while retry frames arrive', async () => { - expect(await host.attach({ callerKey: 'client-1' }, hostTestAttachParams(null))).toMatchObject({ - ok: true - }) - if (!sink) { - throw new Error('the host never handed the provider its event sink') - } - const translator = createClaudeJournalTranslator({ sink, fallbackIdPrefix: '1' }) - - for (let attempt = 1; attempt <= 5; attempt += 1) { - clock += RETRY_GAP_MS - translator.handle({ type: 'message', sessionId: SESSION, message: apiRetry(attempt) }) - await waitOutSeveralSweeps() - expect(closeSession).not.toHaveBeenCalled() - expect(host.hasSession(SESSION)).toBe(true) - } - - const items = host['sessions'].get(SESSION)?.journal.snapshot().items ?? [] - const retryRows = items.filter( - (item) => item.body.kind === 'status' && item.body.failure?.kind === 'providerRetrying' - ) - expect(retryRows).toHaveLength(1) - expect(retryRows[0]?.body).toMatchObject({ - failure: { detail: { text: expect.stringContaining('"attempt":5') } } - }) - - clock += STRUCTURED_AGENT_SESSION_IDLE_MS - await vi.waitFor(() => { - expect(closeSession).toHaveBeenCalledWith(SESSION, undefined) - expect(host.hasSession(SESSION)).toBe(false) - }) - }) -}) diff --git a/src/main/claude/claude-folder-trust-file-wsl-guest.test.ts b/src/main/claude/claude-folder-trust-file-wsl-guest.test.ts deleted file mode 100644 index d09b419e381..00000000000 --- a/src/main/claude/claude-folder-trust-file-wsl-guest.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type * as NodeFs from 'node:fs' -import { - chmodSync, - mkdirSync, - mkdtempSync, - readdirSync, - readFileSync, - realpathSync, - rmSync, - statSync, - writeFileSync -} from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { WslResult, WslSpec } from '../wsl/execution/wsl-runner' -import { grantClaudeFolderTrust } from './claude-folder-trust-file' - -const { DISTRO, GUEST_PREFIX, guest } = vi.hoisted(() => ({ - DISTRO: 'Ubuntu-24.04', - GUEST_PREFIX: '//wsl.localhost/Ubuntu-24.04', - guest: { root: '' } -})) - -function toGuestDisk(path: string): string { - return path.startsWith(GUEST_PREFIX) ? `${guest.root}${path.slice(GUEST_PREFIX.length)}` : path -} - -// Model UNC's synthetic 0666 metadata, ignored chmod and guest-default 0644 creation. -vi.mock('node:fs', async (importOriginal) => { - const actual = await importOriginal() - const onGuest = (path: unknown): path is string => - typeof path === 'string' && path.startsWith(GUEST_PREFIX) - const disk = (path: string): string => `${guest.root}${path.slice(GUEST_PREFIX.length)}` - const windowsView = (stats: NodeFs.Stats): NodeFs.Stats => - Object.assign(stats, { mode: (stats.mode & ~0o777) | 0o666 }) - return { - ...actual, - existsSync: (path: string) => actual.existsSync(onGuest(path) ? disk(path) : path), - lstatSync: (path: string) => - onGuest(path) ? windowsView(actual.lstatSync(disk(path))) : actual.lstatSync(path), - statSync: (path: string) => - onGuest(path) ? windowsView(actual.statSync(disk(path))) : actual.statSync(path), - realpathSync: (path: string) => - onGuest(path) - ? `${GUEST_PREFIX}${actual.realpathSync(disk(path)).slice(guest.root.length)}` - : actual.realpathSync(path), - readFileSync: (path: string, options: BufferEncoding) => - actual.readFileSync(onGuest(path) ? disk(path) : path, options), - writeFileSync: (path: string, data: string, options?: NodeFs.WriteFileOptions) => { - if (!onGuest(path)) { - return actual.writeFileSync(path, data, options) - } - const created = !actual.existsSync(disk(path)) - const flag = typeof options === 'object' && options ? options.flag : undefined - actual.writeFileSync(disk(path), data, { flag }) - if (created) { - actual.chmodSync(disk(path), 0o644) - } - }, - chmodSync: (path: string, mode: number) => - onGuest(path) ? undefined : actual.chmodSync(path, mode), - renameSync: (from: string, to: string) => - actual.renameSync(onGuest(from) ? disk(from) : from, onGuest(to) ? disk(to) : to), - rmSync: (path: string, options?: NodeFs.RmOptions) => - actual.rmSync(onGuest(path) ? disk(path) : path, options) - } -}) - -vi.mock('proper-lockfile', () => ({ - lock: vi.fn(async () => async () => {}) -})) - -const runWslProcess = vi.hoisted(() => vi.fn<(spec: WslSpec) => Promise>()) -vi.mock('../wsl/execution/wsl-runner', () => ({ runWslProcess })) - -async function guestChmod(spec: WslSpec): Promise { - const [reference, separator, target] = spec.args ?? [] - expect(spec).toMatchObject({ distro: DISTRO, loginPath: 'none', program: 'chmod' }) - expect(separator).toBe('--') - const from = `${guest.root}${reference.replace(/^--reference=/, '')}` - chmodSync(`${guest.root}${target}`, statSync(from).mode & 0o7777) - return { environmentResolved: true, code: 0, stdout: '', stderr: '', timedOut: false } -} - -const originalPlatform = process.platform - -describe.skipIf(originalPlatform === 'win32')('grantClaudeFolderTrust on a WSL guest file', () => { - const guestFile = `${GUEST_PREFIX}/home/u/.claude.json` - - beforeEach(() => { - guest.root = realpathSync(mkdtempSync(join(tmpdir(), 'orca-claude-trust-guest-'))) - mkdirSync(join(guest.root, 'home/u'), { recursive: true }) - writeFileSync(toGuestDisk(guestFile), JSON.stringify({ theme: 'dark' })) - chmodSync(toGuestDisk(guestFile), 0o600) - runWslProcess.mockReset().mockImplementation(guestChmod) - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - }) - - afterEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform }) - rmSync(guest.root, { recursive: true, force: true }) - }) - - it("keeps the guest file's owner-only mode through the rewrite", async () => { - await expect( - grantClaudeFolderTrust({ configFile: guestFile, folderKeys: ['/home/u/wt'] }) - ).resolves.toBe('granted') - expect(statSync(toGuestDisk(guestFile)).mode & 0o777).toBe(0o600) - expect(JSON.parse(readFileSync(toGuestDisk(guestFile), 'utf-8'))).toEqual({ - theme: 'dark', - projects: { '/home/u/wt': { hasTrustDialogAccepted: true } } - }) - }) - - it('leaves the file untouched when the guest cannot copy its mode', async () => { - runWslProcess.mockResolvedValue({ - environmentResolved: true, - code: 1, - stdout: '', - stderr: "chmod: unrecognized option '--reference'", - timedOut: false - }) - await expect( - grantClaudeFolderTrust({ configFile: guestFile, folderKeys: ['/home/u/wt'] }) - ).rejects.toThrow(/guest mode/) - expect(readFileSync(toGuestDisk(guestFile), 'utf-8')).toBe(JSON.stringify({ theme: 'dark' })) - expect(statSync(toGuestDisk(guestFile)).mode & 0o777).toBe(0o600) - expect(readdirSync(join(guest.root, 'home/u'))).toEqual(['.claude.json']) - }) -}) diff --git a/src/main/claude/structured/claude-structured-effort-reporting.test.ts b/src/main/claude/structured/claude-structured-effort-reporting.test.ts index 7bd7b39983c..f66fd117bf0 100644 --- a/src/main/claude/structured/claude-structured-effort-reporting.test.ts +++ b/src/main/claude/structured/claude-structured-effort-reporting.test.ts @@ -1,13 +1,8 @@ -import { describe, expect, it, vi } from 'vitest' -import { setClaudeStructuredOption } from './launch/claude-structured-options' -import { readClaudeSettingsEffort } from './launch/claude-structured-session-options' +import { describe, expect, it } from 'vitest' import { ClaudeControlRequestTimeoutError } from '../claude-agent-sdk-control-requests' import recordedUltracodeSettings from '../__fixtures__/ultracode-settings.json' -import type { ClaudeSession, ClaudeStructuredSessionEvent } from './claude-structured-session-state' - import { USER_MESSAGE, - acquired, adapterFor, claudeFrame, fakeClaude, @@ -15,98 +10,7 @@ import { recordingJournalSink } from './claude-structured-session-test-support' -/** Verbatim from Claude Code 2.1.258's get_settings response. */ -const REAL_SETTINGS = { - applied: { model: 'claude-opus-5[1m]', effort: 'high', advisor: null, ultracode: false }, - effective: { model: 'claude-opus-5[1m]', effortLevel: 'high', env: {} }, - sources: {} -} - -function sessionWith( - reported: string | null, - calls: string[] = [], - listed?: { model: string; catalog: readonly Record[] } -) { - return { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the fixture supplies every session member the option paths under test read. - session: { - options: new Map(listed ? [['model', listed.model]] : []), - reportedOptions: {} as { model?: string; effort?: string }, - optionMutationSequence: 0, - reportedModelMutation: 0, - confirmedOptions: new Set(), - appliedInputOptions: new Map(), - connection: { - supportedModels: async () => { - calls.push('list_models') - return [...(listed?.catalog ?? [])] - }, - setModel: async (model: string) => { - calls.push(`set_model:${model}`) - }, - applyFlagSettings: async (settings: { effortLevel?: string }) => { - // The CLI accepts and ignores unknown effort values. - calls.push(`apply:${settings.effortLevel}`) - }, - getSettings: async () => { - calls.push('get_settings') - return reported === null - ? { applied: {}, effective: {}, sources: {} } - : { applied: { effort: reported }, effective: { effortLevel: reported }, sources: {} } - } - }, - startup: { state: 'proven' } - } as unknown as ClaudeSession, - calls - } -} - describe('Claude effort reporting', () => { - it('reads the effort get_settings reports', () => { - expect(readClaudeSettingsEffort(REAL_SETTINGS)).toBe('high') - }) - - it.each([ - [ - 'the provider stops reporting it', - { applied: { effort: 'high' }, effective: {}, sources: {} } - ], - ['the payload carries no effective block', { applied: { effort: 'high' } }], - ['the request failed outright', null] - ])('reports no effort when %s', (_case, settings) => { - expect(readClaudeSettingsEffort(settings)).toBeNull() - }) - - it('publishes the effort from get_settings, which system/init never carries', async () => { - const claude = fakeClaude({ settings: REAL_SETTINGS }) - const adapter = await acquired(claude) - - await expect(adapter.readOptions({ sessionId: 'session-1', fence: 7 })).resolves.toMatchObject({ - current: { effort: 'high' } - }) - }) - - it('keeps ordinary effort visible when a configured workflow uses another effort', async () => { - const settings = { - ...REAL_SETTINGS, - applied: { ...REAL_SETTINGS.applied, ultracode: true } - } - const adapter = await acquired(fakeClaude({ settings })) - expect((await adapter.readOptions({ sessionId: 'session-1', fence: 7 })).current.effort).toBe( - 'high' - ) - await expect( - adapter.dispatch({ - sessionId: 'session-1', - clientMessageId: 'configured-workflow', - body: USER_MESSAGE, - executionOptions: {}, - fence: 7 - }) - ).resolves.toMatchObject({ state: 'admitted', ranOptions: { effort: 'high' } }) - await adapter.closeAll() - }) - it('keeps a configured workflow at launch without confirming saved ordinary XHigh', async () => { const claude = fakeClaude({ settings: { applied: { effort: 'xhigh', ultracode: true }, effective: {} } @@ -199,66 +103,4 @@ describe('Claude effort reporting', () => { expect(connection.sent).toHaveLength(1) await adapter.closeAll() }) - - it('leaves the effort unreported when the session never learns one', async () => { - const model = 'claude-opus-5[1m]' - const claude = fakeClaude({ - settings: { applied: { model }, effective: { model }, sources: {} } - }) - const adapter = await acquired(claude) - - const options = await adapter.readOptions({ sessionId: 'session-1', fence: 7 }) - expect(options.current.effort).toBeUndefined() - expect(options.current.model).toBe(model) - }) - - it('keeps the init fixture free of an effort the real frame never sends', async () => { - const events: ClaudeStructuredSessionEvent[] = [] - const adapter = await acquired(fakeClaude(), {}, events) - // Live: init arrives when the first command starts a cycle, not at startup. - await adapter.dispatch({ - sessionId: 'session-1', - clientMessageId: 'seed-cycle', - body: USER_MESSAGE, - fence: 7 - }) - const init = events.flatMap((event) => - event.type === 'message' && event.message.subtype === 'init' ? [event.message] : [] - ) - - expect(init).toHaveLength(1) - expect(init[0]).toHaveProperty('model') - expect(Object.keys(init[0])).not.toContain('effortLevel') - }) -}) - -describe('Claude effort readback', () => { - it('records an effort the child did not adopt without vouching for it', async () => { - const { session, calls } = sessionWith('high') - - // The native enum is valid; a different readback cannot veto a successful control. - await expect( - setClaudeStructuredOption(session, { key: 'effort', value: 'low' }, undefined) - ).resolves.toEqual({ effort: 'low' }) - expect(session.confirmedOptions.has('effort')).toBe(false) - // The child's own answer is kept rather than discarded with the refusal. - await vi.waitFor(() => expect(session.reportedOptions.effort).toBe('high')) - expect(calls).toEqual(['apply:low', 'get_settings']) - }) - - it('records an effort the child confirms', async () => { - const { session } = sessionWith('low') - - await expect( - setClaudeStructuredOption(session, { key: 'effort', value: 'low' }, undefined) - ).resolves.toEqual({ effort: 'low' }) - }) - - it('records the request when the readback is unavailable', async () => { - const { session } = sessionWith(null) - - await expect( - setClaudeStructuredOption(session, { key: 'effort', value: 'low' }, undefined) - ).resolves.toEqual({ effort: 'low' }) - }) }) diff --git a/src/main/claude/structured/launch/claude-structured-spawn-options.test.ts b/src/main/claude/structured/launch/claude-structured-spawn-options.test.ts deleted file mode 100644 index 69916822fae..00000000000 --- a/src/main/claude/structured/launch/claude-structured-spawn-options.test.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - CLAUDE_STRUCTURED_BASE_OPTIONS, - claudeStructuredPermissionOptions, - type ClaudeStructuredSdkOptions -} from './claude-structured-launch-resolution' -import { claudeStructuredSpawnOptions } from './claude-structured-spawn-options' -import type { ClaudeCliFlagFact } from '../../claude-cli-flag-support' - -function launched( - saved: Record, - options: { - resumesTranscript?: boolean - base?: ClaudeStructuredSdkOptions - effortFlag?: ClaudeCliFlagFact - flagSettingsControl?: ClaudeCliFlagFact - } = {} -) { - return claudeStructuredSpawnOptions({ - launch: { - options: options.base ?? CLAUDE_STRUCTURED_BASE_OPTIONS, - resumesTranscript: options.resumesTranscript ?? true - }, - saved, - effortFlag: options.effortFlag ?? 'supported', - flagSettingsControl: options.flagSettingsControl ?? 'supported' - }) -} - -const FULL_ACCESS_LAUNCH: ClaudeStructuredSdkOptions = { - ...CLAUDE_STRUCTURED_BASE_OPTIONS, - extraArgs: { - ...CLAUDE_STRUCTURED_BASE_OPTIONS.extraArgs, - ...claudeStructuredPermissionOptions('full-access').extraArgs - } -} - -describe('a Claude chat launched with its saved options', () => { - it('passes the saved model, effort and Fast as launch options', () => { - const spawn = launched({ model: 'opus', effort: 'high', fastMode: 'true' }) - - expect(spawn.sdkOptions).toMatchObject({ - model: 'opus', - effort: 'high', - settings: { fastMode: true }, - includePartialMessages: true, - extraArgs: { 'replay-user-messages': null } - }) - expect(Object.fromEntries(spawn.options)).toEqual({ - model: 'opus', - effort: 'high', - fastMode: 'true' - }) - }) - - it.each(['claude-sonnet-5', 'claude-next-9', 'claude-retired-1'])( - 'passes the saved model %s as it was picked', - (model) => { - const spawn = launched({ model, effort: 'xhigh' }) - - expect(spawn.sdkOptions).toMatchObject({ model, effort: 'xhigh' }) - } - ) - - it.each(['unknown', 'unsupported'] as const)( - 'keeps saved effort off the command line when --effort is %s', - (flag) => { - const spawn = launched({ model: 'opus', effort: 'high' }, { effortFlag: flag }) - - expect(spawn.sdkOptions).toMatchObject({ model: 'opus' }) - expect(spawn.sdkOptions).not.toHaveProperty('effort') - expect(Object.fromEntries(spawn.options)).toEqual({ model: 'opus' }) - } - ) - - it('leaves out an effort no Claude can parse, and Fast it cannot decode', () => { - const spawn = launched({ effort: 'ludicrous', fastMode: 'yes' }) - - expect(spawn.sdkOptions).not.toHaveProperty('effort') - expect(spawn.sdkOptions).not.toHaveProperty('settings') - }) - - it('keeps a saved Fast on for a new conversation, for its start to apply', () => { - const spawn = launched({ fastMode: 'true' }, { resumesTranscript: false }) - - expect(spawn.sdkOptions).not.toHaveProperty('settings') - expect(spawn.options.get('fastMode')).toBe('true') - expect(spawn.fastModeAtStart).toBe(true) - // A CLI that never answers the control cannot apply it at start; never claim it is on. - const old = launched( - { fastMode: 'true' }, - { resumesTranscript: false, flagSettingsControl: 'unsupported' } - ) - expect(old.options.has('fastMode')).toBe(false) - expect(old.fastModeAtStart).toBe(false) - }) - - it('passes a saved Fast off to a new conversation', () => { - const spawn = launched({ fastMode: 'false' }, { resumesTranscript: false }) - - expect(spawn.sdkOptions.settings).toEqual({ fastMode: false }) - expect(spawn.fastModeAtStart).toBe(false) - }) - - // SDK extraArgs follow its own flags, so configured flags would override saved choices. - it("replaces the agent Arguments' model and effort with the chat's saved ones", () => { - const base = { - ...CLAUDE_STRUCTURED_BASE_OPTIONS, - extraArgs: { ...CLAUDE_STRUCTURED_BASE_OPTIONS.extraArgs, model: 'opus', effort: 'max' } - } - - expect(launched({ model: 'sonnet', effort: 'low' }, { base }).sdkOptions).toMatchObject({ - model: 'sonnet', - effort: 'low', - extraArgs: { 'replay-user-messages': null } - }) - expect(launched({ model: 'sonnet', effort: 'low' }, { base }).sdkOptions.extraArgs).toEqual({ - 'replay-user-messages': null - }) - expect(launched({}, { base }).sdkOptions.extraArgs).toEqual(base.extraArgs) - }) - - it.each(['true', 'false'])( - "keeps the agent Arguments' settings and leaves a saved Fast %s to the start", - (fastMode) => { - const base = { - ...CLAUDE_STRUCTURED_BASE_OPTIONS, - extraArgs: { ...CLAUDE_STRUCTURED_BASE_OPTIONS.extraArgs, settings: '/repo/claude.json' } - } - const spawn = launched({ fastMode }, { base }) - - expect(spawn.sdkOptions).not.toHaveProperty('settings') - expect(spawn.sdkOptions.extraArgs).toEqual(base.extraArgs) - expect(spawn.options.get('fastMode')).toBe(fastMode) - expect(spawn.fastModeAtStart).toBe(true) - } - ) - - it.each(['full-access', 'plan', 'bypassPermissions'])( - 'leaves the launch permission alone with a saved permission mode %s', - (permissionMode) => { - const spawn = launched({ permissionMode }, { base: FULL_ACCESS_LAUNCH }) - - expect(spawn.sdkOptions).toEqual(FULL_ACCESS_LAUNCH) - expect(spawn.options.size).toBe(0) - } - ) -}) - -it.each(['/repo/claude.json', '{"customInstructions":"keep","fastMode":true}'])( - 'compiles saved Ultracode without replacing configured settings %s', - (settings) => { - const spawn = launched( - { effort: 'ultracode', fastMode: 'true' }, - { - base: { ...CLAUDE_STRUCTURED_BASE_OPTIONS, extraArgs: { settings } } - } - ) - expect(spawn.sdkOptions.effort).toBe('xhigh') - expect(spawn.sdkOptions.extraArgs?.settings).toBe(settings) - expect(spawn.sdkOptions.settings).toBeUndefined() - expect(spawn.options.get('effort')).toBe('ultracode') - expect(spawn.fastModeAtStart).toBe(true) - } -) diff --git a/src/main/codex-usage/scanner-attribution-memo.test.ts b/src/main/codex-usage/scanner-attribution-memo.test.ts deleted file mode 100644 index c6d0001766b..00000000000 --- a/src/main/codex-usage/scanner-attribution-memo.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import type * as NodeOs from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import type * as WorktreePathComparison from '../git/worktrees/catalog/worktree-path-comparison' - -const { getPathMock, homedirMock, worktreePathComparisons } = vi.hoisted(() => ({ - getPathMock: vi.fn<(name: string) => string>(), - homedirMock: vi.fn<() => string>(), - worktreePathComparisons: { count: 0 } -})) - -vi.mock('electron', () => ({ - app: { - getPath: getPathMock - } -})) - -vi.mock('node:os', async () => { - const actual = await vi.importActual('node:os') - return { - ...actual, - homedir: homedirMock - } -}) - -vi.mock('../git/worktrees/catalog/worktree-path-comparison', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - areWorktreePathsEqual: (left: string, right: string) => { - worktreePathComparisons.count += 1 - return actual.areWorktreePathsEqual(left, right) - } - } -}) - -import { scanCodexUsageFiles } from './scanner' - -const WORKTREE_COUNT = 4 -const EVENTS_PER_FILE = 4 - -let fakeHomeDir: string -let userDataDir: string -let previousUserDataPath: string | undefined -const originalCodexHome = process.env.CODEX_HOME - -function usageRecord(timestamp: string, totalInputTokens: number): string { - return `${JSON.stringify({ - timestamp, - type: 'event_msg', - payload: { - type: 'token_count', - info: { - model: 'gpt-5-codex', - last_token_usage: { - input_tokens: 1, - cached_input_tokens: 0, - output_tokens: 0, - reasoning_output_tokens: 0, - total_tokens: 1 - }, - total_token_usage: { - input_tokens: totalInputTokens, - cached_input_tokens: 0, - output_tokens: 0, - reasoning_output_tokens: 0, - total_tokens: totalInputTokens - } - } - } - })}\n` -} - -function writeSessionFile( - sessionsDir: string, - sessionId: string, - cwd: string, - tokenOffset: number -): void { - // Vary usage records so cross-file deduplication preserves all three fixture sessions. - const records = [ - `${JSON.stringify({ type: 'session_meta', payload: { id: sessionId, cwd } })}\n`, - ...Array.from({ length: EVENTS_PER_FILE }, (_, index) => - usageRecord( - `2026-07-21T12:${String(index).padStart(2, '0')}:00.000Z`, - tokenOffset + index + 1 - ) - ) - ] - writeFileSync(join(sessionsDir, `${sessionId}.jsonl`), records.join(''), 'utf-8') -} - -beforeEach(() => { - delete process.env.CODEX_HOME - worktreePathComparisons.count = 0 - // Match canonical worktree paths on macOS, where /var resolves to /private/var. - fakeHomeDir = realpathSync(mkdtempSync(join(tmpdir(), 'orca-codex-memo-home-'))) - userDataDir = mkdtempSync(join(tmpdir(), 'orca-codex-memo-user-data-')) - previousUserDataPath = process.env.ORCA_USER_DATA_PATH - process.env.ORCA_USER_DATA_PATH = userDataDir - homedirMock.mockReturnValue(fakeHomeDir) - getPathMock.mockImplementation((name: string) => { - if (name === 'userData') { - return userDataDir - } - throw new Error(`unexpected app.getPath(${name})`) - }) -}) - -afterEach(() => { - rmSync(fakeHomeDir, { recursive: true, force: true }) - rmSync(userDataDir, { recursive: true, force: true }) - if (originalCodexHome === undefined) { - delete process.env.CODEX_HOME - } else { - process.env.CODEX_HOME = originalCodexHome - } - if (previousUserDataPath === undefined) { - delete process.env.ORCA_USER_DATA_PATH - } else { - process.env.ORCA_USER_DATA_PATH = previousUserDataPath - } - vi.clearAllMocks() -}) - -it('resolves each distinct cwd once per scan, not once per event', async () => { - const sessionsDir = join(fakeHomeDir, '.codex', 'sessions') - mkdirSync(sessionsDir, { recursive: true }) - const matchedCwd = join(fakeHomeDir, 'worktrees', 'repo-003', 'packages', 'app') - const unmatchedCwd = join(fakeHomeDir, 'elsewhere', 'project') - // Shared cwd exercises memoization across files. - writeSessionFile(sessionsDir, 'session-a', matchedCwd, 0) - writeSessionFile(sessionsDir, 'session-b', matchedCwd, 1_000) - writeSessionFile(sessionsDir, 'session-c', unmatchedCwd, 2_000) - const worktrees = Array.from({ length: WORKTREE_COUNT }, (_, index) => { - const worktreePath = join(fakeHomeDir, 'worktrees', `repo-${String(index).padStart(3, '0')}`) - mkdirSync(worktreePath, { recursive: true }) - return { - repoId: `repo-${index}`, - worktreeId: `repo-${index}::${worktreePath}`, - path: worktreePath, - displayName: `Repo ${index}` - } - }) - - const result = await scanCodexUsageFiles(worktrees, []) - - expect(result.sessions).toHaveLength(3) - const attributedWorktreeIds = new Set( - result.sessions.flatMap((session) => - session.locationBreakdown.map((location) => location.worktreeId) - ) - ) - expect(attributedWorktreeIds).toEqual( - new Set([`repo-3::${join(fakeHomeDir, 'worktrees', 'repo-003')}`, null]) - ) - expect(worktreePathComparisons.count).toBeLessThanOrEqual(2 * WORKTREE_COUNT) - expect(worktreePathComparisons.count).toBeLessThan(EVENTS_PER_FILE * WORKTREE_COUNT) -}) diff --git a/src/main/codex/app-server/codex-app-server-session-stop.test.ts b/src/main/codex/app-server/codex-app-server-session-stop.test.ts deleted file mode 100644 index a8cb2a71223..00000000000 --- a/src/main/codex/app-server/codex-app-server-session-stop.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { EventEmitter } from 'node:events' -import { PassThrough } from 'node:stream' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { PROVIDER_SUPERVISOR_MAX_STOP_MS } from '../../provider-process/provider-process-supervisor' -import { terminateProviderProcessTree } from '../../provider-process/provider-process-teardown' -import type * as ProcessTreeKill from './codex-app-server-process-tree-kill' -import { - killCodexAppServerProcessTree, - type CodexAppServerSpawn -} from './codex-app-server-process-tree-kill' -import { CodexAppServerTimeoutError, runCodexAppServerSession } from './codex-app-server-session' - -vi.mock('../../provider-process/provider-process-teardown', () => ({ - terminateProviderProcessTree: vi.fn(async () => 'exited' as const) -})) -vi.mock('./codex-app-server-process-tree-kill', async (importOriginal) => ({ - ...(await importOriginal()), - killCodexAppServerProcessTree: vi.fn() -})) - -type FakeChild = EventEmitter & { - pid: number - stdin: PassThrough - stdout: PassThrough - stderr: PassThrough - kill: ReturnType boolean>> -} - -/** A child that answers initialize and then nothing; it exits only on `exitsOn`. */ -function fakeChild(exitsOn: NodeJS.Signals | null): FakeChild { - const child: FakeChild = Object.assign(new EventEmitter(), { - pid: 4242, - stdin: new PassThrough(), - stdout: new PassThrough(), - stderr: new PassThrough(), - kill: vi.fn((signal?: NodeJS.Signals) => { - if (signal === exitsOn) { - child.emit('exit', null, signal) - child.emit('close', null, signal) - } - return true - }) - }) - child.stdin.on('data', (chunk: Buffer) => { - const message = JSON.parse(chunk.toString()) - if (message.method === 'initialize') { - child.stdout.write(`${JSON.stringify({ id: message.id, result: {} })}\n`) - } - }) - return child -} - -const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') - -afterEach(() => { - vi.useRealTimers() - vi.clearAllMocks() - if (originalPlatform) { - Object.defineProperty(process, 'platform', originalPlatform) - } -}) - -function startWedgedSession(child: FakeChild, platform: NodeJS.Platform): Promise { - Object.defineProperty(process, 'platform', { configurable: true, value: platform }) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the session reads only pid, stdio, kill and the exit/close/error events, which the fake implements. - const spawnImpl: CodexAppServerSpawn = () => child as never - return runCodexAppServerSession( - { command: '/bin/codex', cliPath: null, args: ['app-server'], timeoutMs: 1_000 }, - () => new Promise(() => {}), - spawnImpl - ).catch((error: unknown) => error) -} - -describe('runCodexAppServerSession stop', () => { - it('SIGTERMs a supervised session at its deadline instead of waiting out its stdin grace', async () => { - vi.useFakeTimers() - const child = fakeChild('SIGTERM') - let stdinEnded = false - child.stdin.on('finish', () => (stdinEnded = true)) - - const outcome = startWedgedSession(child, 'darwin') - await vi.advanceTimersByTimeAsync(1_000) - - expect(child.kill.mock.calls).toEqual([['SIGTERM']]) - expect(stdinEnded).toBe(true) - expect(await outcome).toBeInstanceOf(CodexAppServerTimeoutError) - expect(terminateProviderProcessTree).not.toHaveBeenCalled() - expect(killCodexAppServerProcessTree).not.toHaveBeenCalled() - }) - - it('tears a supervised session down only once the supervisor has had its full stop time', async () => { - vi.useFakeTimers() - const child = fakeChild(null) - - const outcome = startWedgedSession(child, 'linux') - await vi.advanceTimersByTimeAsync(1_000 + PROVIDER_SUPERVISOR_MAX_STOP_MS - 1) - expect(terminateProviderProcessTree).not.toHaveBeenCalled() - await vi.advanceTimersByTimeAsync(1) - - expect(terminateProviderProcessTree).toHaveBeenCalledWith(child, { - site: 'codex-app-server-session', - platform: 'linux', - supervised: true - }) - await vi.advanceTimersByTimeAsync(1_000) - expect(await outcome).toBeInstanceOf(CodexAppServerTimeoutError) - expect(child.kill.mock.calls).toEqual([['SIGTERM']]) - expect(killCodexAppServerProcessTree).not.toHaveBeenCalled() - }) - - it('keeps the Windows deadline kill and its 1.5 s close wait', async () => { - vi.useFakeTimers() - const child = fakeChild(null) - - const outcome = startWedgedSession(child, 'win32') - await vi.advanceTimersByTimeAsync(1_000) - expect(killCodexAppServerProcessTree).toHaveBeenCalledTimes(1) - await vi.advanceTimersByTimeAsync(1_499) - expect(terminateProviderProcessTree).not.toHaveBeenCalled() - await vi.advanceTimersByTimeAsync(1) - expect(terminateProviderProcessTree).toHaveBeenCalledWith(child, { - site: 'codex-app-server-session', - platform: 'win32', - supervised: false - }) - - await vi.advanceTimersByTimeAsync(1_000) - expect(await outcome).toBeInstanceOf(CodexAppServerTimeoutError) - // Windows SIGTERM is TerminateProcess; the mocked teardowns own termination. - expect(child.kill).not.toHaveBeenCalled() - expect(killCodexAppServerProcessTree).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/main/daemon/daemon-pty-adapter-wsl-quit-cwd.test.ts b/src/main/daemon/daemon-pty-adapter-wsl-quit-cwd.test.ts deleted file mode 100644 index ad5e45663a6..00000000000 --- a/src/main/daemon/daemon-pty-adapter-wsl-quit-cwd.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Bridge bash reports no folder; a distro stopped while Orca was quit left the pane at its spawn folder. -import './mock-descendant-sweep' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { join } from 'node:path' -import { readFileSync, rmSync } from 'node:fs' -import { DaemonPtyAdapter } from './daemon-pty-adapter' -import type { DaemonServer } from './daemon-server' -import { getHistorySessionDirName } from './history-paths' -import { createMockSubprocess, startDaemonAdapterHarness } from './daemon-pty-adapter-test-harness' -import type { WslGuestProcessRow } from '../wsl/execution/wsl-guest-pane-processes' -import type * as GuestProcessesModule from '../wsl/execution/wsl-guest-pane-processes' -import * as startupCwdProbe from '../ipc/pty/host-env/startup-cwd-directory-exists' - -const readRows = vi.hoisted(() => - vi.fn( - async ( - _distro: string, - _timeoutMs: number, - _opts?: { running?: boolean } - ): Promise => null - ) -) -vi.mock('../wsl/execution/wsl-guest-pane-processes', async (importOriginal) => ({ - ...(await importOriginal()), - readWslGuestPaneProcesses: readRows -})) - -const PANE_KEY = '11111111-1111-4111-8111-111111111111:22222222-2222-4222-8222-222222222222' -const WSL_CWD = '\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo' -const DEEP = `${WSL_CWD}\\deep` - -/** Shortens quit's folder budget so an unanswering distro is not a real-time wait. */ -class QuickQuitDaemonPtyAdapter extends DaemonPtyAdapter { - protected quitWslCwdBudgetMs = 5 -} - -describe('a bridge WSL pane keeps its shell folder across quit', () => { - let dir: string - let socketPath: string - let tokenPath: string - let server: DaemonServer - let harnessAdapter: DaemonPtyAdapter - let adapters: DaemonPtyAdapter[] = [] - const platform = Object.getOwnPropertyDescriptor(process, 'platform') - const spawnCwds: (string | undefined)[] = [] - let lastSubprocess: ReturnType - - beforeEach(async () => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - readRows.mockReset() - readRows.mockResolvedValue([ - { pid: 10, ppid: 1, comm: 'bash', paneKey: PANE_KEY, cwd: '/home/jin/repo/deep' } - ]) - spawnCwds.length = 0 - const harness = await startDaemonAdapterHarness((opts) => { - spawnCwds.push(opts.cwd) - lastSubprocess = createMockSubprocess() - return lastSubprocess - }) - ;({ dir, socketPath, tokenPath, server } = harness) - harnessAdapter = harness.adapter - }) - - afterEach(async () => { - vi.restoreAllMocks() - if (platform) { - Object.defineProperty(process, 'platform', platform) - } - for (const adapter of adapters) { - adapter.dispose() - } - adapters = [] - harnessAdapter.dispose() - await server.shutdown() - rmSync(dir, { recursive: true, force: true }) - }) - - const historyDir = () => join(dir, 'history') - const launch = (Adapter: typeof DaemonPtyAdapter = DaemonPtyAdapter) => { - const adapter = new Adapter({ socketPath, tokenPath, historyPath: historyDir() }) - adapters.push(adapter) - return adapter - } - const checkpointCwd = (id: string) => - JSON.parse( - readFileSync(join(historyDir(), getHistorySessionDirName(id), 'checkpoint.json'), 'utf-8') - ).cwd - const openBridgePane = (adapter: DaemonPtyAdapter, sessionId: string, cwd = WSL_CWD) => - adapter.spawn({ cols: 80, rows: 24, cwd, env: { ORCA_PANE_KEY: PANE_KEY }, sessionId }) - - it("saves the shell's folder in quit's final checkpoint, never booting the distro", async () => { - const app = launch() - const { id } = await openBridgePane(app, 'repo::/repo@@quit') - lastSubprocess._simulateData('$ cd deep\r\n') - - await app.disconnectOnly() - - // `running` lets the read skip its check only when the running-distro cache confirms it. - expect(readRows).toHaveBeenCalledWith('Ubuntu', expect.any(Number), { running: true }) - expect(checkpointCwd(id)).toBe(DEEP) - }) - - it('reopens in that folder when the distro stopped while Orca was quit', async () => { - vi.spyOn(startupCwdProbe, 'probeRecoveredStartupCwdDirectory').mockResolvedValue(true) - const app = launch() - const { id } = await openBridgePane(app, 'repo::/repo@@terminated') - lastSubprocess._simulateData('$ cd deep\r\n') - await app.disconnectOnly() - // `wsl --terminate` ends the bridge's wsl.exe; the daemon reaps the session. - lastSubprocess._simulateExit(1) - - const relaunched = launch() - await relaunched.reconcileOnStartup(new Set(['repo::/repo'])) - const restored = await openBridgePane(relaunched, id) - - expect(restored.coldRestore).toBeTruthy() - expect(spawnCwds.at(-1)).toBe(DEEP) - }) - - it('keeps the spawn folder when the distro cannot be read', async () => { - readRows.mockResolvedValue(null) - const app = launch() - const { id } = await openBridgePane(app, 'repo::/repo@@unreadable') - - await app.disconnectOnly() - - expect(checkpointCwd(id)).toBe(WSL_CWD) - }) - - it('quits on its budget when the distro does not answer', async () => { - readRows.mockReturnValue(new Promise(() => {})) - const app = launch(QuickQuitDaemonPtyAdapter) - const { id } = await openBridgePane(app, 'repo::/repo@@silent') - - await app.disconnectOnly() - - expect(readRows.mock.calls[0]?.[1]).toBe(5) - expect(checkpointCwd(id)).toBe(WSL_CWD) - }) -}) diff --git a/src/main/daemon/daemon-pty-startup-delivery.test.ts b/src/main/daemon/daemon-pty-startup-delivery.test.ts deleted file mode 100644 index 46b7c792ef2..00000000000 --- a/src/main/daemon/daemon-pty-startup-delivery.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { mkdirSync, readdirSync, rmSync } from 'node:fs' -import { basename, join } from 'node:path' -import * as localPtyUtils from '../providers/local-pty/local-pty-executable-spawn' -import { - createMockSubprocess, - startDaemonAdapterHarness, - waitFor, - type DaemonAdapterHarness, - type SpawnSubprocess -} from './daemon-pty-adapter-test-harness' - -const itOnPosix = process.platform === 'win32' ? it.skip : it - -describe('DaemonPtyAdapter startup delivery', () => { - let harness: DaemonAdapterHarness - let adapter: DaemonAdapterHarness['adapter'] - let dir: string - let lastSubprocess: ReturnType - let lastSpawnOpts: Parameters[0] | null - let nextShellPath: string | undefined - - beforeEach(async () => { - lastSpawnOpts = null - nextShellPath = undefined - harness = await startDaemonAdapterHarness((opts) => { - lastSpawnOpts = opts - lastSubprocess = Object.assign( - createMockSubprocess(), - nextShellPath ? { shellPath: nextShellPath } : {} - ) - return lastSubprocess - }) - adapter = harness.adapter - dir = harness.dir - }) - - afterEach(async () => { - adapter.dispose() - await harness.server.shutdown() - rmSync(dir, { recursive: true, force: true }) - }) - - itOnPosix('uses the normal shell-ready timeout for fish', async () => { - // The mock subprocess represents installed fish even on hosts without it. - const resolveShell = vi - .spyOn(localPtyUtils, 'resolveUnixShellPath') - .mockReturnValue('/usr/bin/fish') - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) - try { - await adapter.spawn({ - cols: 80, - rows: 24, - command: 'codex', - env: { SHELL: '/usr/bin/fish' } - }) - await vi.advanceTimersByTimeAsync(14_999) - expect(lastSubprocess.write).not.toHaveBeenCalled() - await vi.advanceTimersByTimeAsync(1) - expect(lastSubprocess.write).toHaveBeenCalledExactlyOnceWith('codex\r') - expect(lastSpawnOpts).not.toEqual( - expect.objectContaining({ startupCommandDelivery: 'shell-ready' }) - ) - } finally { - vi.useRealTimers() - resolveShell.mockRestore() - } - }) - - itOnPosix.for(['environment', 'override'] as const)( - 'waits for the fallback shell when the %s shell is missing', - async (source, context) => { - const missingShell = join(dir, 'missing-fish') - const fallbackName = basename(localPtyUtils.resolveUnixShellPath(missingShell)) - context.skip(!['bash', 'zsh'].includes(fallbackName), 'Requires a Bash/zsh fallback') - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) - try { - await adapter.spawn({ - cols: 80, - rows: 24, - command: 'codex', - env: { SHELL: source === 'environment' ? missingShell : '/bin/sh' }, - ...(source === 'override' ? { shellOverride: missingShell } : {}) - }) - await vi.advanceTimersByTimeAsync(350) - } finally { - vi.useRealTimers() - } - expect(lastSubprocess.write).not.toHaveBeenCalled() - expect(lastSpawnOpts).toEqual( - expect.objectContaining({ startupCommandDelivery: 'shell-ready' }) - ) - lastSubprocess._simulateData('\x1b]777;orca-shell-ready\x07\r\nuser@host $ ') - await waitFor(() => vi.mocked(lastSubprocess.write).mock.calls.length > 0) - expect(lastSubprocess.write).toHaveBeenCalledExactlyOnceWith('codex\r') - } - ) - - itOnPosix.each([ - { command: 'codex' }, - { command: 'codex', startupCommandDelivery: 'fast' as const }, - { command: "codex 'linked issue context'", startupCommandDelivery: 'shell-ready' as const } - ])('waits past 300ms and submits once after readiness: %j', async (startup) => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) - try { - await adapter.spawn({ cols: 80, rows: 24, ...startup, env: { SHELL: '/bin/zsh' } }) - await vi.advanceTimersByTimeAsync(350) - } finally { - vi.useRealTimers() - } - expect(lastSubprocess.write).not.toHaveBeenCalled() - expect(lastSpawnOpts).toEqual( - expect.objectContaining({ startupCommandDelivery: 'shell-ready' }) - ) - lastSubprocess._simulateData('\x1b]777;orca-shell-ready\x07') - lastSubprocess._simulateData('\r\nuser@host $ ') - - await waitFor(() => vi.mocked(lastSubprocess.write).mock.calls.length > 0) - expect(lastSubprocess.write).toHaveBeenCalledExactlyOnceWith(`${startup.command}\r`) - }) - - itOnPosix('types only the short line that sources a staged launch line', async () => { - const stagingDir = join(dir, 'tmp') - mkdirSync(stagingDir) - vi.stubEnv('TMPDIR', stagingDir) - nextShellPath = '/bin/zsh' - try { - const command = `claude '${'x'.repeat(600)}'` - await adapter.spawn({ - cols: 80, - rows: 24, - command, - env: { SHELL: '/bin/zsh' } - }) - lastSubprocess._simulateData('\x1b]777;orca-shell-ready\x07\r\nuser@host $ ') - await waitFor(() => vi.mocked(lastSubprocess.write).mock.calls.length > 0) - const [script] = readdirSync(stagingDir) - expect(lastSubprocess.write).toHaveBeenCalledExactlyOnceWith( - `. '${join(stagingDir, script)}'\r` - ) - } finally { - vi.unstubAllEnvs() - } - }) -}) diff --git a/src/main/daemon/daemon-stream-backpressure-socket.test.ts b/src/main/daemon/daemon-stream-backpressure-socket.test.ts deleted file mode 100644 index eb0c19824de..00000000000 --- a/src/main/daemon/daemon-stream-backpressure-socket.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { once } from 'node:events' -import { mkdtempSync, rmSync } from 'node:fs' -import { createServer, Socket } from 'node:net' -import { tmpdir } from 'node:os' -import { basename, join } from 'node:path' -import { setTimeout as delay } from 'node:timers/promises' -import { describe, expect, it } from 'vitest' -import { DaemonStreamDataBatcher } from './daemon-stream-data-batcher' -import { createNdjsonParser } from './ndjson' -import { SessionProducerPause } from './session-producer-pause' - -const MiB = 1024 * 1024 - -describe('daemon stream backpressure with a stalled Socket reader', () => { - it.each([ - { chunkSize: 64 * 1024, hidden: false }, - { chunkSize: 1024, hidden: false }, - { chunkSize: 64 * 1024, hidden: true }, - { chunkSize: 1024, hidden: true } - ])( - 'bounds $chunkSize-character writes (hidden=$hidden) and resumes', - async ({ chunkSize, hidden }) => { - // A fixed POSIX root keeps the full Unix socket path below the kernel's byte limit. - const dir = mkdtempSync(join(process.platform === 'win32' ? tmpdir() : '/tmp', 'orca-dsbp-')) - const socketPath = - process.platform === 'win32' - ? `\\\\.\\pipe\\${basename(dir)}.sock` - : join(dir, 'stream.sock') - const server = createServer() - const writer = new Socket() - let reader: Socket | undefined - let paused = false - let produced = 0 - let dropped = 0 - const received = new Map() - const producer = new SessionProducerPause({ - pause: () => { - paused = true - }, - resume: () => { - paused = false - } - }) - const batcher = new DaemonStreamDataBatcher(() => ({ streamSocket: writer }), { - isSessionDroppable: (sessionId) => hidden && sessionId === 'flood', - onProducerBackpressureChanged: (sessionId, value) => { - expect(sessionId).toBe('flood') - producer.setStreamBackpressured(value) - } - }) - writer.on('drain', () => batcher.flush('client')) - try { - const accepted = once(server, 'connection') - server.listen(socketPath) - await once(server, 'listening') - writer.connect(socketPath) - await once(writer, 'connect') - const [acceptedSocket] = await accepted - if (!(acceptedSocket instanceof Socket)) { - throw new Error('Expected accepted Socket') - } - reader = acceptedSocket - const parser = createNdjsonParser( - (event) => { - if ( - !event || - typeof event !== 'object' || - !('sessionId' in event) || - typeof event.sessionId !== 'string' || - !('payload' in event) - ) { - return - } - const payload = event.payload - if ( - payload && - typeof payload === 'object' && - 'droppedChars' in payload && - typeof payload.droppedChars === 'number' - ) { - dropped += payload.droppedChars - } - if ( - !payload || - typeof payload !== 'object' || - !('data' in payload) || - typeof payload.data !== 'string' - ) { - return - } - received.set( - event.sessionId, - (received.get(event.sessionId) ?? 0) + payload.data.length - ) - }, - () => { - throw new Error('Invalid stream frame') - } - ) - reader.on('data', (data) => parser.feed(data.toString('utf8'))) - reader.pause() - - const chunk = 'x'.repeat(chunkSize) - // Keep write callbacks pending until the pause assertion, independent of kernel buffer capacity. - while (!paused && produced < 8 * MiB) { - batcher.enqueue('client', 'flood', chunk, { - flushImmediately: chunkSize <= 1024, - flushMaxChars: 1024 - }) - batcher.flush('client') - produced += chunkSize - } - expect(paused).toBe(!hidden) - expect(writer.writableLength + 2 * batcher.queuedCharsForClient('client')).toBeLessThan( - 5 * MiB - ) - - if (!hidden) { - producer.pause() - producer.resumeClient() - expect(paused).toBe(true) - } - batcher.enqueue('client', 'typing', 'echo', { flushImmediately: true, flushMaxChars: 1024 }) - reader.resume() - const startedAt = performance.now() - while (paused || writer.writableLength || batcher.queuedCharsForClient('client')) { - if (performance.now() - startedAt > 5_000) { - throw new Error('Stream failed to drain') - } - await delay(5) - } - const ended = once(reader, 'end') - writer.end() - await ended - expect((received.get('flood') ?? 0) + dropped).toBe(produced) - expect(dropped > 0).toBe(hidden) - expect(received.get('typing')).toBe(4) - expect(paused).toBe(false) - } finally { - batcher.clear() - producer.release({ resume: false }) - writer.destroy() - reader?.destroy() - await new Promise((resolve) => server.close(() => resolve())) - rmSync(dir, { recursive: true, force: true }) - } - } - ) -}) diff --git a/src/main/dsh/dsh-home-patch.test.ts b/src/main/dsh/dsh-home-patch.test.ts index db032284f6a..b54f9affb5c 100644 --- a/src/main/dsh/dsh-home-patch.test.ts +++ b/src/main/dsh/dsh-home-patch.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest' import { applyManagedDshPatch, findManagedDshPatchRegion, - readManagedDshHooksConfigPath, removeManagedDshPatch } from './dsh-home-patch' @@ -16,65 +15,9 @@ function applyOrFail(text: string, hooksPath = HOOKS_PATH): string { return next } -const PRISTINE = [ - '# Your patch layer for this dsh profile, applied after every bundle layer:', - '# a top-level YAML array of loader patch entries (id-targeted config', - '# overrides, disables, and insert lists; `!!js` expressions allowed).', - '[]', - '' -].join('\n') - const USER_ROWS = ['- id: llm-deepseek', ' config:', " apiKeyEnv: 'MY_KEY'", ''].join('\n') describe('applyManagedDshPatch', () => { - it('creates the managed block in an empty file', () => { - const text = applyOrFail('') - expect(readManagedDshHooksConfigPath(text)).toBe(HOOKS_PATH) - expect(text).toContain("name: '@deepseek-ai/dsh-hooks-claude-code'") - expect(text.endsWith('\n')).toBe(true) - }) - - it('replaces the empty flow sequence DSH ships, keeping its comments', () => { - const text = applyOrFail(PRISTINE) - expect(text).not.toMatch(/^\[]$/m) - expect(text).toContain('# Your patch layer for this dsh profile') - expect(readManagedDshHooksConfigPath(text)).toBe(HOOKS_PATH) - }) - - it('appends after user rows without touching them', () => { - const text = applyOrFail(USER_ROWS) - expect(text.startsWith(USER_ROWS.trimEnd())).toBe(true) - expect(readManagedDshHooksConfigPath(text)).toBe(HOOKS_PATH) - }) - - it('rewrites its own block in place rather than stacking copies', () => { - const once = applyOrFail(USER_ROWS, '/old/path.json') - const twice = applyOrFail(once) - expect(twice.match(/orca-managed-dsh-hooks \(managed by Orca/g)).toHaveLength(1) - expect(readManagedDshHooksConfigPath(twice)).toBe(HOOKS_PATH) - expect(twice).not.toContain('/old/path.json') - }) - - it('is idempotent', () => { - const once = applyOrFail(PRISTINE) - expect(applyOrFail(once)).toBe(once) - }) - - it('treats `[] # comment` as the empty document it is, keeping the comment', () => { - const text = '[] # keep empty\n' - const next = applyOrFail(text) - expect(next).not.toMatch(/^\s*\[]/m) - expect(next).toContain('# keep empty') - expect(readManagedDshHooksConfigPath(next)).toBe(HOOKS_PATH) - }) - - it.each(['- id: llm-deepseek\n config: {}\n', '\n', '# only a comment\n'])( - 'still edits %j', - (text) => { - expect(applyManagedDshPatch(text, HOOKS_PATH)).not.toBeNull() - } - ) - it.each(['[{ id: llm-deepseek }]\n', '[\n { id: a },\n { id: b }\n]\n'])( 'refuses to append after the non-empty flow sequence %j', (text) => { @@ -82,31 +25,14 @@ describe('applyManagedDshPatch', () => { expect(applyManagedDshPatch(text, HOOKS_PATH)).toBeNull() } ) - - it('quotes a path containing a single quote', () => { - const awkward = "/home/o'brien/.orca/agent-hooks/dsh-hooks.json" - expect(readManagedDshHooksConfigPath(applyOrFail('', awkward))).toBe(awkward) - }) }) describe('removeManagedDshPatch', () => { - it('restores the empty flow sequence when nothing else remains', () => { - const installed = applyOrFail(PRISTINE) - const { text, changed } = removeManagedDshPatch(installed) - expect(changed).toBe(true) - expect(text.trimEnd().endsWith('[]')).toBe(true) - expect(text).toContain('# Your patch layer for this dsh profile') - }) - it('leaves user rows alone and adds no [] when they remain', () => { const installed = applyOrFail(USER_ROWS) const { text } = removeManagedDshPatch(installed) expect(text.trimEnd()).toBe(USER_ROWS.trimEnd()) }) - - it('reports no change when the file carries no managed block', () => { - expect(removeManagedDshPatch(USER_ROWS)).toEqual({ text: USER_ROWS, changed: false }) - }) }) describe('findManagedDshPatchRegion', () => { diff --git a/src/main/editor/format-editor-text.test.ts b/src/main/editor/format-editor-text.test.ts index 372346825e2..1e7d17dbd70 100644 --- a/src/main/editor/format-editor-text.test.ts +++ b/src/main/editor/format-editor-text.test.ts @@ -59,32 +59,6 @@ describe('project formatter execution', () => { ) }) - it('does not spawn for unconfigured projects, other hosts, or excluded files', async () => { - expect( - await formatEditorText(store, { ...target(), path: join(root, 'a.png') }, 'text') - ).toEqual({ kind: 'skipped' }) - expect( - await formatEditorText(store, { ...target(), executionHostId: 'ssh:other' }, 'text') - ).toEqual({ kind: 'skipped' }) - delete repo.editorFormatter - expect(await formatEditorText(store, target(), 'text')).toEqual({ kind: 'skipped' }) - expect(runProcess).not.toHaveBeenCalled() - }) - - it('checks host support only after a configured formatter applies', async () => { - const unsupported = vi.fn(() => { - throw new Error('unsupported host') - }) - expect( - await formatEditorText(store, { ...target(), path: join(root, 'a.png') }, 'text', unsupported) - ).toEqual({ kind: 'skipped' }) - expect(unsupported).not.toHaveBeenCalled() - await expect(formatEditorText(store, target(), 'text', unsupported)).rejects.toThrow( - 'unsupported host' - ) - expect(runProcess).not.toHaveBeenCalled() - }) - it('reports the formatter failure and never changes the saved file', async () => { repo.editorFormatter!.command = command('process.stderr.write("syntax error");process.exit(2)') await expect(formatEditorText(store, target(), 'input')).rejects.toThrow('syntax error') @@ -124,19 +98,4 @@ describe('project formatter execution', () => { } } ) - - it.each([ - [{ timedOut: true }, 'timed out'], - [{ outputTruncated: true }, 'output exceeded'] - ])('refuses incomplete formatter output (%j)', async (result, message) => { - vi.mocked(runProcess).mockResolvedValueOnce({ - code: 0, - signal: null, - timedOut: false, - stdout: 'partial', - stderr: '', - ...result - }) - await expect(formatEditorText(store, target(), 'input')).rejects.toThrow(message) - }) }) diff --git a/src/main/git/worktrees/creation/worktree-add-timeout-override.test.ts b/src/main/git/worktrees/creation/worktree-add-timeout-override.test.ts deleted file mode 100644 index a2529f67eb5..00000000000 --- a/src/main/git/worktrees/creation/worktree-add-timeout-override.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { gitExecFileAsyncMock, gitExecFileSyncMock, translateWslOutputPathsMock } = vi.hoisted( - () => ({ - gitExecFileAsyncMock: vi.fn(), - gitExecFileSyncMock: vi.fn(), - translateWslOutputPathsMock: vi.fn((output: string) => output) - }) -) - -vi.mock('../../command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal>()), - gitExecFileAsync: gitExecFileAsyncMock -})) - -vi.mock('../../command-runner/git-exec-file-sync', async (importOriginal) => ({ - ...(await importOriginal>()), - gitExecFileSync: gitExecFileSyncMock -})) - -vi.mock('../../command-runner/wsl-path-translation', async (importOriginal) => ({ - ...(await importOriginal>()), - translateWslOutputPaths: translateWslOutputPathsMock -})) - -import { GitCommandTimeoutError } from '../../../../shared/git/commands/git-command-timeout' -import { addWorktree } from './worktree-add' -import { - resolveWorktreeAddTimeoutMs, - WORKTREE_ADD_TIMEOUT_MAX_MS, - WORKTREE_ADD_TIMEOUT_MS, - worktreeAddTimeoutMessage -} from '../worktree-operation-options' -import { registerWorktreeSuiteHooks } from '../testing/worktree-test-harness' - -registerWorktreeSuiteHooks() - -describe('resolveWorktreeAddTimeoutMs', () => { - let warnSpy: ReturnType - - beforeEach(() => { - warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - }) - - afterEach(() => { - warnSpy.mockRestore() - }) - - it('falls back to the default when the override is unset, blank, or unparseable', () => { - expect(resolveWorktreeAddTimeoutMs({})).toBe(WORKTREE_ADD_TIMEOUT_MS) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: ' ' })).toBe( - WORKTREE_ADD_TIMEOUT_MS - ) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: 'nope' })).toBe( - WORKTREE_ADD_TIMEOUT_MS - ) - }) - - it('raises the timeout up to the closed maximum', () => { - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '300000' })).toBe(300_000) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '300000.9' })).toBe(300_000) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '999999999' })).toBe( - 1_800_000 - ) - }) - - it('treats an infinite override as the maximum, not the default', () => { - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: 'Infinity' })).toBe( - 1_800_000 - ) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '1e400' })).toBe(1_800_000) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '-Infinity' })).toBe( - WORKTREE_ADD_TIMEOUT_MS - ) - expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining('"Infinity" is outside [180000, 1800000]ms; using 1800000ms') - ) - }) - - it('stays quiet when the value is unset, blank, or lands in range', () => { - resolveWorktreeAddTimeoutMs({}) - resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: ' ' }) - resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '600000' }) - resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '300000.9' }) - - expect(warnSpy).not.toHaveBeenCalled() - }) - - // A seconds-versus-milliseconds mistake should warn when clamped. - it('warns with the range when it clamps an out-of-range value', () => { - resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '300' }) - - expect(warnSpy).toHaveBeenCalledTimes(1) - expect(warnSpy).toHaveBeenCalledWith( - '[git/worktree] ORCA_WORKTREE_ADD_TIMEOUT_MS="300" is outside [180000, 1800000]ms; using 180000ms' - ) - }) - - it('warns that an unparseable value is not a number rather than out of range', () => { - resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '600_000' }) - - expect(warnSpy).toHaveBeenCalledTimes(1) - expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining('"600_000" is not a number; using 180000ms') - ) - }) - - it('never lowers the timeout below the stall-guard default', () => { - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '300' })).toBe( - WORKTREE_ADD_TIMEOUT_MS - ) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '0' })).toBe( - WORKTREE_ADD_TIMEOUT_MS - ) - expect(resolveWorktreeAddTimeoutMs({ ORCA_WORKTREE_ADD_TIMEOUT_MS: '-1' })).toBe( - WORKTREE_ADD_TIMEOUT_MS - ) - }) -}) - -describe('addWorktree timeout', () => { - it('names the limit and the override when git worktree add times out', async () => { - gitExecFileAsyncMock.mockImplementation(async (args: string[]) => { - if (args.includes('worktree')) { - throw new GitCommandTimeoutError(WORKTREE_ADD_TIMEOUT_MS) - } - return { stdout: '', stderr: '' } - }) - - const error = await addWorktree('/repo', '/repo-feature', 'feature').catch((e: unknown) => e) - - expect(error).toBeInstanceOf(GitCommandTimeoutError) - expect(String(error)).toContain('180 seconds') - expect(String(error)).toContain('ORCA_WORKTREE_ADD_TIMEOUT_MS') - }) - - it('drops the override advice once the limit is already at the maximum', () => { - expect(worktreeAddTimeoutMessage(WORKTREE_ADD_TIMEOUT_MAX_MS)).not.toContain( - 'ORCA_WORKTREE_ADD_TIMEOUT_MS' - ) - }) -}) diff --git a/src/main/git/worktrees/creation/worktree-base-divergence.test.ts b/src/main/git/worktrees/creation/worktree-base-divergence.test.ts deleted file mode 100644 index 2965504b74c..00000000000 --- a/src/main/git/worktrees/creation/worktree-base-divergence.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => ({ gitExecFileAsync: vi.fn() })) - -vi.mock('../../command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal>()), - gitExecFileAsync: mocks.gitExecFileAsync -})) - -import { GitAdmissionScheduler } from '../../../../shared/git/admission/git-admission-scheduler' -import type { GitAdmissionTier } from '../../../../shared/rpc-contract/git-admission-tier-params' - -import { measureRetargetDivergence } from './worktree-base-divergence' - -type ExecOptions = { - cwd: string - timeout?: number - wslDistro?: string - signal?: AbortSignal - admissionTier?: GitAdmissionTier -} - -function subcommands(): string[] { - return mocks.gitExecFileAsync.mock.calls.map((call) => (call[0] as string[])[0]!) -} - -function answerProbes(count: string, mergeBase = 'abc123\n') { - mocks.gitExecFileAsync.mockImplementation(async (args: string[]) => - args[0] === 'merge-base' ? { stdout: mergeBase } : { stdout: count } - ) -} - -function exitCodeError(code: number): Error & { code: number } { - return Object.assign(new Error('git exited'), { code }) -} - -beforeEach(() => { - mocks.gitExecFileAsync.mockReset() -}) - -describe('measureRetargetDivergence deadlines', () => { - it('finishes through interactive headroom while general capacity is occupied', async () => { - const scheduler = new GitAdmissionScheduler({ generalCap: 1, generalHeadroom: 1 }) - const blocker = await scheduler.acquire({ args: ['status'], cwd: '/repo' }) - mocks.gitExecFileAsync.mockImplementation(async (args: string[], options: ExecOptions) => { - const grant = await scheduler.acquire({ - args, - cwd: options.cwd, - signal: options.signal, - tier: options.admissionTier - }) - try { - return { stdout: args[0] === 'merge-base' ? 'abc123\n' : '1\n' } - } finally { - grant.release() - } - }) - try { - await expect( - measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main', { - admissionTier: 'interactive', - budgetMsForTest: 200 - }) - ).resolves.toBe('within') - expect(subcommands()).toEqual(['rev-list', 'rev-list', 'merge-base']) - } finally { - blocker.release() - } - }) - - it('really aborts the in-flight probes when the shared budget expires', async () => { - mocks.gitExecFileAsync.mockImplementation( - (_args: string[], options: ExecOptions) => - new Promise((_resolve, reject) => { - options.signal?.addEventListener( - 'abort', - () => - reject( - Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' }) - ), - { once: true } - ) - }) - ) - - await expect( - measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main', { - budgetMsForTest: 25 - }) - ).resolves.toBe('unknown') - }) - - it('stops the probes when the create itself is cancelled, without waiting for the budget', async () => { - mocks.gitExecFileAsync.mockImplementation( - (_args: string[], options: ExecOptions) => - new Promise((_resolve, reject) => { - options.signal?.addEventListener( - 'abort', - () => - reject( - Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' }) - ), - { once: true } - ) - }) - ) - const controller = new AbortController() - const pending = measureRetargetDivergence( - '/repo', - 'refs/heads/main', - 'refs/remotes/origin/main', - // A budget long enough that only the caller's cancellation can end this in time. - { signal: controller.signal, budgetMsForTest: 60_000 } - ) - controller.abort() - - await expect(pending).resolves.toBe('unknown') - }) - - it('reports a blown deadline as unverifiable rather than as excess drift', async () => { - mocks.gitExecFileAsync.mockRejectedValue(new Error('git timed out.')) - - await expect( - measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main') - ).resolves.toBe('unknown') - expect(subcommands()).not.toContain('merge-base') - }) - - it('separates merge-base saying no from merge-base failing', async () => { - mocks.gitExecFileAsync.mockImplementation(async (args: string[]) => { - if (args[0] === 'merge-base') { - throw exitCodeError(1) - } - return { stdout: '2\n' } - }) - await expect( - measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main') - ).resolves.toBe('exceeded') - - mocks.gitExecFileAsync.mockReset() - mocks.gitExecFileAsync.mockImplementation(async (args: string[]) => { - if (args[0] === 'merge-base') { - throw new Error('git timed out.') - } - return { stdout: '2\n' } - }) - await expect( - measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main') - ).resolves.toBe('unknown') - }) - - it('reports drift past the cap without spending a merge-base walk', async () => { - answerProbes('101\n') - - await expect( - measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main') - ).resolves.toBe('exceeded') - expect(subcommands()).not.toContain('merge-base') - }) -}) diff --git a/src/main/git/worktrees/removal/worktree-posix-path-case-sensitivity.test.ts b/src/main/git/worktrees/removal/worktree-posix-path-case-sensitivity.test.ts deleted file mode 100644 index 4b0cd818d91..00000000000 --- a/src/main/git/worktrees/removal/worktree-posix-path-case-sensitivity.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -// Exercise the real Linux-to-UNC translator: an identity mock would hide ext4-tail case folding. -import type * as FsPromises from 'node:fs/promises' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { gitExecFileAsyncMock, gitExecFileSyncMock, statMock, readFileMock, resolveGitDirMock } = - vi.hoisted(() => ({ - gitExecFileAsyncMock: vi.fn(), - gitExecFileSyncMock: vi.fn(), - statMock: vi.fn(), - readFileMock: vi.fn(), - resolveGitDirMock: vi.fn() - })) - -vi.mock('../../command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal>()), - gitExecFileAsync: gitExecFileAsyncMock -})) -vi.mock('../../command-runner/git-exec-file-sync', async (importOriginal) => ({ - ...(await importOriginal>()), - gitExecFileSync: gitExecFileSyncMock -})) - -vi.mock('../../source-control/resolve-git-dir', () => ({ - resolveGitDir: resolveGitDirMock -})) - -vi.mock('../../source-control/git-read-cache-invalidation', () => ({ - runWithGitReadCacheInvalidation: (run: () => Promise) => run() -})) - -vi.mock('fs/promises', async () => { - const actual = await vi.importActual('fs/promises') - return { ...actual, stat: statMock, readFile: readFileMock } -}) - -import { - createGitCallReader, - createGitCommandMocker, - resetWorktreeRemovalState -} from './remove-worktree-test-harness' -import { areWorktreePathsEqual } from '../catalog/worktree-path-comparison' -import { removeWorktree } from './worktree-removal' - -const mockGitCommands = createGitCommandMocker(gitExecFileAsyncMock) -const getGitCalls = createGitCallReader(gitExecFileAsyncMock) - -const UNC = '\\\\wsl.localhost\\Ubuntu\\home\\alice\\ws' - -describe('worktree path comparison across path syntaxes', () => { - it('keeps two POSIX worktrees that differ only in case distinct on a Windows desktop', () => { - expect(areWorktreePathsEqual('/home/alice/ws/Feature', '/home/alice/ws/feature', 'win32')).toBe( - false - ) - }) - - it('still folds the share alias, the distro name and the slash style, which Windows folds', () => { - expect( - areWorktreePathsEqual( - '\\\\wsl.localhost\\Ubuntu\\home\\alice\\wt', - '//WSL$/ubuntu/home/alice/wt', - 'win32' - ) - ).toBe(true) - }) - - it('still folds a drvfs tail, which really is a Windows volume', () => { - expect( - areWorktreePathsEqual( - '\\\\wsl$\\Ubuntu\\mnt\\C\\Users\\Jin', - '\\\\wsl.localhost\\Ubuntu\\mnt\\c\\users\\jin', - 'win32' - ) - ).toBe(true) - }) - - it('does not fold a distro directory that merely looks like the drvfs mount', () => { - expect( - areWorktreePathsEqual( - '\\\\wsl$\\Ubuntu\\MNT\\c\\Repo', - '\\\\wsl$\\Ubuntu\\MNT\\c\\repo', - 'win32' - ) - ).toBe(false) - }) - - it('collapses dot segments in both case-sensitive syntaxes', () => { - expect(areWorktreePathsEqual(`${UNC}\\.\\feature`, `${UNC}\\x\\..\\feature`, 'win32')).toBe( - true - ) - expect( - areWorktreePathsEqual('/home/alice/ws/./feature', '/home/alice/ws/x/../feature', 'win32') - ).toBe(true) - }) - - it('still folds Windows drive paths by case and slash style', () => { - expect(areWorktreePathsEqual('C:/Users/Bob/wt', 'c:\\Users\\bob\\wt', 'win32')).toBe(true) - expect(areWorktreePathsEqual('C:/Users/Bob/wt', 'c:\\Users\\bob\\wt', 'darwin')).toBe(true) - }) - - it('never equates paths written in different syntaxes', () => { - expect(areWorktreePathsEqual('/home/alice/wt', `${UNC}\\..\\wt`, 'win32')).toBe(false) - expect(areWorktreePathsEqual('/Users/bob/wt', 'C:\\Users\\bob\\wt', 'win32')).toBe(false) - expect(areWorktreePathsEqual(`${UNC}\\wt`, 'C:\\ws\\wt', 'win32')).toBe(false) - }) -}) - -describe('removeWorktree branch selection on a Windows desktop', () => { - const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')! - - beforeEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - gitExecFileAsyncMock.mockReset() - gitExecFileSyncMock.mockReset() - statMock.mockReset() - statMock.mockRejectedValue(Object.assign(new Error('ENOENT'), { code: 'ENOENT' })) - readFileMock.mockReset() - readFileMock.mockRejectedValue(Object.assign(new Error('ENOENT'), { code: 'ENOENT' })) - resolveGitDirMock.mockReset() - resetWorktreeRemovalState() - }) - - afterEach(() => { - Object.defineProperty(process, 'platform', originalPlatform) - }) - - it('deletes the branch of the requested WSL worktree, not its case twin', async () => { - const listing = `worktree /home/alice/repo -HEAD aaa111 -branch refs/heads/main - -worktree /home/alice/ws/Feature -HEAD bbb222 -branch refs/heads/Feature - -worktree /home/alice/ws/feature -HEAD ccc333 -branch refs/heads/feature -` - mockGitCommands({ - 'git worktree list --porcelain -z': { stdout: listing }, - 'git worktree list --porcelain': { stdout: listing } - }) - - await removeWorktree('\\\\wsl.localhost\\Ubuntu\\home\\alice\\repo', `${UNC}\\feature`, true, { - wslDistro: 'Ubuntu' - }) - - const calls = getGitCalls() - expect(calls).toContain('git branch -d -- feature') - expect(calls).not.toContain('git branch -d -- Feature') - }) -}) diff --git a/src/main/git/wsl-process-group-termination.test.ts b/src/main/git/wsl-process-group-termination.test.ts deleted file mode 100644 index f50c9007eee..00000000000 --- a/src/main/git/wsl-process-group-termination.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { ChildProcess } from 'node:child_process' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as BackgroundDistroGate from '../wsl/discovery/wsl-background-distro-gate' - -const { runWslProcessMock, readWslDistroRunningStateMock } = vi.hoisted(() => ({ - runWslProcessMock: vi.fn(), - readWslDistroRunningStateMock: vi.fn() -})) - -vi.mock('../wsl/execution/wsl-runner', () => ({ runWslProcess: runWslProcessMock })) -vi.mock('../wsl/discovery/wsl-background-distro-gate', async (importOriginal) => ({ - ...(await importOriginal()), - readWslDistroRunningState: readWslDistroRunningStateMock -})) - -import { createWslProcessGroupTermination } from './wsl-process-group-termination' - -describe('WSL process-group termination', () => { - beforeEach(() => { - runWslProcessMock.mockReset() - runWslProcessMock.mockResolvedValue({ code: 0, timedOut: false }) - // Windows CI runs the real gate; these cases kill in a running distro. - readWslDistroRunningStateMock.mockReset().mockResolvedValue('running') - }) - - it('forces and verifies the reported guest process group', async () => { - const termination = createWslProcessGroupTermination('Ubuntu') - const wrapped = termination.wrapGuestArgs(['git', 'fetch']).join(' ') - const marker = wrapped.match(/(__ORCA_WSL_PROCESS_GROUP_[0-9a-f-]+__=)/)?.[1] - termination.observeStderr?.(Buffer.from(`${marker}43`)) - // The marker line is still truncated; committing 43 would target a stranger. - await expect(termination.signal({} as ChildProcess)).resolves.toBe(false) - termination.observeStderr?.(Buffer.from('21\n')) - - await expect(termination.force({} as ChildProcess)).resolves.toBe(true) - - const spec = runWslProcessMock.mock.calls[0]?.[0] - expect(spec.script).toContain('kill -KILL') - expect(spec.args).toEqual(['4321']) - }) - - it('does not claim termination before the guest reports its identity', async () => { - const termination = createWslProcessGroupTermination('Ubuntu') - - await expect(termination.signal({} as ChildProcess)).resolves.toBe(false) - expect(runWslProcessMock).not.toHaveBeenCalled() - }) - - it('reports failure when the guest kill times out', async () => { - runWslProcessMock.mockResolvedValue({ code: null, timedOut: true }) - const termination = createWslProcessGroupTermination('Ubuntu') - const wrapped = termination.wrapGuestArgs(['git', 'fetch']).join(' ') - const marker = wrapped.match(/(__ORCA_WSL_PROCESS_GROUP_[0-9a-f-]+__=)/)?.[1] - termination.observeStderr?.(Buffer.from(`${marker}4321\n`)) - - await expect(termination.force({} as ChildProcess)).resolves.toBe(false) - }) - - it('retains the guest identity from a large coalesced stderr chunk', async () => { - const termination = createWslProcessGroupTermination('Ubuntu') - const wrapped = termination.wrapGuestArgs(['git', 'fetch']).join(' ') - const marker = wrapped.match(/(__ORCA_WSL_PROCESS_GROUP_[0-9a-f-]+__=)/)?.[1] - termination.observeStderr?.(Buffer.from(`${marker}4321\n${'x'.repeat(1_024)}`)) - - await expect(termination.signal({} as ChildProcess)).resolves.toBe(true) - expect(runWslProcessMock.mock.calls[0]?.[0]?.args).toEqual(['4321']) - }) - - describe('on Windows (E9)', () => { - const originalPlatform = process.platform - - beforeEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - }) - - afterEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform }) - }) - - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the guest barrier never reads the host child. - const child = {} as ChildProcess - - function reportedGroup(): ReturnType { - const termination = createWslProcessGroupTermination('Ubuntu') - const wrapped = termination.wrapGuestArgs(['git', 'fetch']).join(' ') - const marker = wrapped.match(/(__ORCA_WSL_PROCESS_GROUP_[0-9a-f-]+__=)/)?.[1] - termination.observeStderr?.(Buffer.from(`${marker}4321\n`)) - return termination - } - - it('treats a group in a stopped distro as gone instead of booting it to kill', async () => { - readWslDistroRunningStateMock.mockResolvedValue('stopped') - await expect(reportedGroup().force(child)).resolves.toBe(true) - expect(runWslProcessMock).not.toHaveBeenCalled() - }) - - it('claims nothing when WSL cannot say whether the distro runs', async () => { - readWslDistroRunningStateMock.mockResolvedValue('unverifiable') - await expect(reportedGroup().signal(child)).resolves.toBe(false) - expect(runWslProcessMock).not.toHaveBeenCalled() - }) - - it('trusts a stop only from a probe started after the guest proved it ran', async () => { - readWslDistroRunningStateMock.mockResolvedValue('stopped') - const before = performance.now() - const termination = reportedGroup() - await termination.force(child) - const [distro, options] = readWslDistroRunningStateMock.mock.lastCall ?? [] - expect(distro).toBe('Ubuntu') - expect(options?.startedAfter).toBeGreaterThanOrEqual(before) - }) - }) -}) diff --git a/src/main/github/work-item-search-semantics.test.ts b/src/main/github/work-item-search-semantics.test.ts deleted file mode 100644 index f419bc0014f..00000000000 --- a/src/main/github/work-item-search-semantics.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { expect, it, vi } from 'vitest' -import { api } from './work-item-search-test-harness' -import { listWorkItems } from './client/list/list-work-items' -import { countWorkItems } from './client/list/count-work-items' -import { mapIssueWorkItem } from './client/map/work-item' -import metadata from './__fixtures__/work-item-search-metadata.json' - -it('matches the saved REST projection for users, bots, avatars, assignees and labels', async () => { - api.specialNodes = metadata.map((pair) => pair.graphql) - api.reportedCount = 2748 - const result = await listWorkItems('fixture/repo', 5, 'is:issue') - expect(result.items).toEqual(metadata.map((pair) => mapIssueWorkItem(pair.rest))) - expect(await countWorkItems('fixture/repo', 'is:issue')).toBe(2748) - expect(api.restSearches).toBe(0) -}) - -it('preserves deleted authors and hydrates associations beyond GraphQL connection limits', async () => { - const row = structuredClone(metadata[0].graphql) - api.specialNodes = [ - { ...row, author: null, labels: { ...row.labels, pageInfo: { hasNextPage: true } } } - ] - const result = await listWorkItems('fixture/repo', 24, 'is:issue') - expect(result.items).toHaveLength(1) - expect(result.items[0].author).toBeNull() - expect(result.items[0].labels).toEqual( - Array.from({ length: 125 }, (_, index) => `label-${index}`) - ) - expect(api.restDetails).toBe(1) - expect(api.restSearches).toBe(0) -}) - -it('passes every issue predicate to the server for both results and full counts', async () => { - api.expectedSearch = - 'repo:fixture/repo is:issue is:closed assignee:"some user" author:"some author" label:"needs review" label:bug in:"title,body" "needle phrase"' - api.specialNodes = [ - { ...metadata[0].graphql, number: 7, state: 'CLOSED', title: 'old needle phrase' } - ] - const query = - 'is:issue is:closed assignee:"some user" author:"some author" label:"needs review" label:bug in:"title,body" "needle phrase"' - expect((await listWorkItems('fixture/repo', 24, query)).items).toMatchObject([ - { number: 7, state: 'closed', title: 'old needle phrase' } - ]) - expect(await countWorkItems('fixture/repo', query)).toBe(1) - expect(api.restSearches).toBe(0) -}) - -it.each([ - ['is:issue state:all', 'repo:fixture/repo is:issue'], - ['is:issue is:open label:bug', 'repo:fixture/repo is:issue is:open label:bug'] -])('retains state/scope semantics for %s', async (query, expected) => { - api.expectedSearch = expected - expect((await listWorkItems('fixture/repo', 24, query)).items).toHaveLength(24) - expect(await countWorkItems('fixture/repo', query)).toBe(120) - expect(api.restSearches).toBe(0) -}) - -it.each([ - [ - 'is:draft', - 'is:open draft:true sort:created-desc', - 'repo:fixture/repo is:pull-request is:open draft:true' - ], - [ - 'is:pr is:closed', - 'is:closed -is:merged sort:created-desc', - 'repo:fixture/repo is:pull-request is:closed -is:merged' - ], - ['is:merged', 'is:merged sort:created-desc', 'repo:fixture/repo is:pull-request is:merged'], - [ - 'review-requested:"some user" reviewed-by:someone', - 'review-requested:"some user" reviewed-by:someone sort:created-desc', - 'repo:fixture/repo is:pull-request review-requested:"some user" reviewed-by:someone' - ], - [ - 'is:pr title:foo OR title:bar', - 'title:foo OR title:bar sort:created-desc', - 'repo:fixture/repo is:pull-request title:foo OR title:bar' - ] -])('keeps rich PR lists and full count predicates for %s', async (query, prSearch, countSearch) => { - api.expectedSearch = `( ${prSearch} ) repo:fixture/repo type:pr` - api.specialNodes = [] - api.reportedCount = 120 - expect((await listWorkItems('fixture/repo', 24, query)).items).toEqual([]) - expect(api.graphqlCalls).toBe(1) - expect(api.restSearches).toBe(0) - const request = api.calls.find(({ args }) => args.includes('graphql')) - expect(request?.args.join(' ')).toContain( - JSON.stringify(`( ${prSearch} ) repo:fixture/repo type:pr sort:created-desc`) - ) - expect(request?.args.join(' ')).toContain('reviewRequests(first: 100)') - expect(await countWorkItems('fixture/repo', query)).toBe(120) - expect(api.graphqlCalls).toBe(1) - vi.stubEnv('GH_TOKEN', 'fixture-rest-fallback') - api.graphqlAvailable = false - api.expectedSearch = countSearch - expect(await countWorkItems('fixture/repo', query)).toBe(120) - expect(api.restSearches).toBe(1) -}) - -it('falls back with the original numbered query when GraphQL is unavailable', async () => { - api.graphqlAvailable = false - api.expectedSearch = 'repo:fixture/repo is:issue is:closed label:"needs review" "exact phrase"' - const result = await listWorkItems( - 'fixture/repo', - 24, - 'is:issue is:closed label:"needs review" "exact phrase"', - 3, - undefined, - undefined, - true - ) - expect(result.items[0].number).toBe(9952) - const call = api.calls.find((call) => call.args.some((arg) => arg.startsWith('search/issues?'))) - expect(call?.args).toEqual([ - 'api', - '--hostname', - 'github.com', - `search/issues?q=${encodeURIComponent(api.expectedSearch)}&sort=created&order=desc&per_page=24&page=3`, - '--jq', - '.items' - ]) -}) - -it('falls back for malformed GraphQL rows instead of presenting a truncated result', async () => { - api.specialNodes = [{ ...metadata[0].graphql, __typename: 'PullRequest' }] - const result = await listWorkItems('fixture/repo', 24, 'is:issue') - expect(result.items).toHaveLength(1) - expect(api.restSearches).toBe(1) -}) diff --git a/src/main/ipc/notifications-macos-removal.test.ts b/src/main/ipc/notifications-macos-removal.test.ts deleted file mode 100644 index b85f0c63b9c..00000000000 --- a/src/main/ipc/notifications-macos-removal.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { StructuredAttentionOrigin } from '../../shared/agent-session/agent-session-attention' - -import { - getDismissHandler, - getDispatchHandler, - notificationCtorMock, - notificationRemoveGroupMock, - notificationRemoveMock, - resetNotificationDispatchMocks -} from './notifications-test-harness' - -vi.mock('electron', async () => - (await import('./notifications-test-harness')).createElectronModuleMock() -) - -vi.mock('./notification-authorization-status', async () => - (await import('./notifications-test-harness')).createNotificationAuthorizationModuleMock() -) - -vi.mock('./ui', async () => - (await import('./notifications-test-harness')).createTrustedUIRendererModuleMock() -) - -vi.mock('../tray/system-tray', async () => - (await import('./notifications-test-harness')).createSystemTrayModuleMock() -) - -import { registerNotificationHandlers } from './notifications' - -const PANE = 'tab-1:11111111-1111-4111-8111-111111111111' -const originalPlatform = process.platform - -const STRUCTURED_ORIGIN: StructuredAttentionOrigin = { - scope: { - executionHostId: 'runtime:remote-host', - wslDistro: null, - workspaceId: 'remote-folder', - workspaceKind: 'folder' - }, - sessionId: 'remote-session', - cause: { kind: 'prompt', promptId: 'A' }, - journalCursor: { epoch: 'remote-journal', sequence: 1 } -} - -function setPlatform(platform: NodeJS.Platform): void { - Object.defineProperty(process, 'platform', { value: platform, configurable: true }) -} - -function register(): void { - registerNotificationHandlers( - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: these paths read only getSettings; Store is a class, so a structural double needs the cast. - { - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - agentNeedsInput: true, - terminalBell: true, - suppressWhenFocused: false - } - }) - } as never - ) -} - -function dispatchStructured(): unknown { - return getDispatchHandler()( - {}, - { - source: 'agent-task-complete', - surface: 'agent-session', - worktreeId: 'repo::remote', - paneKey: PANE, - notificationId: 'agent-attention:A', - attentionKey: 'agent-attention:A', - structuredOrigin: STRUCTURED_ORIGIN - } - ) -} - -function lastNotificationOptions(): unknown { - return notificationCtorMock.mock.lastCall?.[0] -} - -describe('macOS Notification Center removal', () => { - beforeEach(() => { - vi.useFakeTimers() - vi.setSystemTime(new Date('2026-03-28T16:00:00Z')) - resetNotificationDispatchMocks() - setPlatform('darwin') - }) - - afterEach(() => { - setPlatform(originalPlatform) - vi.useRealTimers() - }) - - it.each([ - { source: 'agent-task-complete', notificationId: 'agent:turn-1' }, - { source: 'terminal-bell' } - ])('posts a $source notification in its pane thread with no id', async (request) => { - register() - await getDispatchHandler()({}, { ...request, worktreeId: 'repo::wt1', paneKey: PANE }) - - expect(notificationCtorMock).toHaveBeenCalledOnce() - expect(lastNotificationOptions()).toMatchObject({ groupId: `pane:${PANE}` }) - expect(lastNotificationOptions()).not.toHaveProperty('id') - }) - - it('posts a notification with no pane outside any thread', async () => { - register() - await getDispatchHandler()({}, { source: 'test' }) - - expect(notificationCtorMock).toHaveBeenCalledOnce() - expect(lastNotificationOptions()).not.toHaveProperty('id') - expect(lastNotificationOptions()).not.toHaveProperty('groupId') - }) - - it('removes a read pane’s thread with no notification held, as after a restart', () => { - register() - - getDismissHandler()({}, ['agent:rebuilt'], [PANE]) - - expect(notificationRemoveGroupMock).toHaveBeenCalledExactlyOnceWith(`pane:${PANE}`) - expect(notificationRemoveMock).not.toHaveBeenCalled() - }) - - it('removes a structured alert by id only once a read covers it', async () => { - register() - await dispatchStructured() - expect(lastNotificationOptions()).toMatchObject({ id: 'agent-attention:A' }) - expect(lastNotificationOptions()).not.toHaveProperty('groupId') - - getDismissHandler()({}, [], [PANE]) - expect(notificationRemoveMock).not.toHaveBeenCalled() - - getDismissHandler()( - {}, - [], - [PANE], - [ - { - paneKey: PANE, - sessionId: 'remote-session', - observedCursor: { epoch: 'remote-journal', sequence: 1 } - } - ] - ) - expect(notificationRemoveMock).toHaveBeenCalledExactlyOnceWith(['agent-attention:A']) - }) - - it.each(['win32', 'linux'] as const)('leaves %s on the close() path', async (platform) => { - setPlatform(platform) - register() - await getDispatchHandler()( - {}, - { - source: 'agent-task-complete', - worktreeId: 'repo::wt1', - paneKey: PANE, - notificationId: 'agent:turn-1' - } - ) - - expect(notificationCtorMock).toHaveBeenCalledOnce() - expect(lastNotificationOptions()).not.toHaveProperty('id') - expect(lastNotificationOptions()).not.toHaveProperty('groupId') - - getDismissHandler()({}, ['agent:turn-1'], [PANE]) - expect(notificationRemoveMock).not.toHaveBeenCalled() - expect(notificationRemoveGroupMock).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/ipc/pty/delivery/pty-ipc-gap-escape-state.test.ts b/src/main/ipc/pty/delivery/pty-ipc-gap-escape-state.test.ts deleted file mode 100644 index 2206b517eae..00000000000 --- a/src/main/ipc/pty/delivery/pty-ipc-gap-escape-state.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { setupPtyIpcSuite } from '../pty-ipc-test-harness' -import { registerPtyHandlers } from '../register-handlers' - -vi.mock('electron', () => import('../pty-ipc-mock-registry').then((m) => m.electronModuleMock())) -vi.mock('fs', () => import('../pty-ipc-mock-registry').then((m) => m.fsModuleMock())) -vi.mock('node-pty', () => import('../pty-ipc-mock-registry').then((m) => m.nodePtyModuleMock())) -vi.mock('node:child_process', async (importOriginal) => - (await import('../pty-ipc-mock-registry')).childProcessModuleMock(await importOriginal()) -) -vi.mock('../../../opencode/hook-service', () => - import('../pty-ipc-mock-registry').then((m) => m.openCodeHookServiceModuleMock()) -) -vi.mock('../../../mimo/hook-service', () => - import('../pty-ipc-mock-registry').then((m) => m.mimoHookServiceModuleMock()) -) -vi.mock('../../../agent-hooks/server', () => - import('../pty-ipc-mock-registry').then((m) => m.agentHookServerModuleMock()) -) -vi.mock('../../../pi/titlebar-extension-service', () => - import('../pty-ipc-mock-registry').then((m) => m.piTitlebarExtensionModuleMock()) -) -vi.mock('../../../pty/shell-startup/pwsh', () => - import('../pty-ipc-mock-registry').then((m) => m.pwshModuleMock()) -) -vi.mock('../../../wsl/filesystem/unc-path-access', async (importOriginal) => - (await import('../pty-ipc-mock-registry')).wslModuleMock(await importOriginal()) -) -vi.mock('../../../telemetry/client', () => - import('../pty-ipc-mock-registry').then((m) => m.telemetryClientModuleMock()) -) -vi.mock('../../../telemetry/classify-error', () => - import('../pty-ipc-mock-registry').then((m) => m.classifyErrorModuleMock()) -) -vi.mock('../../../cli/linux-terminal-orca-cli-shim', () => - import('../pty-ipc-mock-registry').then((m) => m.linuxCliShimModuleMock()) -) -vi.mock('../../../memory/pty-registry', () => - import('../pty-ipc-mock-registry').then((m) => m.ptyRegistryModuleMock()) -) -vi.mock('../../../agent-hooks/migration-unsupported-pty-state', () => - import('../pty-ipc-mock-registry').then((m) => m.migrationUnsupportedPtyModuleMock()) -) -vi.mock('../../../codex/configuration/homes/codex-pane-account-registry', () => - import('../pty-ipc-mock-registry').then((m) => m.codexPaneAccountRegistryModuleMock()) -) -vi.mock('../../../codex/history/index/codex-state-db-backfill-recovery', () => - import('../pty-ipc-mock-registry').then((m) => m.codexBackfillRecoveryModuleMock()) -) -// The observable daemon provider adopts the spawn's sessionId as the PTY id. -const PTY_ID = 'daemon-session' - -describe('the first delivery after a hidden drop (#33766)', () => { - const { - handlers, - mainWindow, - installObservableDaemonTestProvider, - getPtySetHiddenRendererPtyListener - } = setupPtyIpcSuite() - - async function hideDropAndResume(dropped: string, resumed: string) { - vi.useFakeTimers() - let seq = 0 - const runtime = { - setPtyController: vi.fn(), - registerPty: vi.fn(), - noteTerminalSpawnCommand: vi.fn(), - onPtySpawned: vi.fn(), - onPtyExit: vi.fn(), - onPtyData: vi.fn((_id: string, _data: string, _at: number, chars: number) => (seq += chars)), - getPtyOutputSequence: vi.fn(() => seq), - hasRemoteTerminalViewSubscriber: vi.fn(() => false), - createPreAllocatedTerminalHandle: vi.fn(() => null), - registerPreAllocatedHandleForPty: vi.fn() - } - const daemon = installObservableDaemonTestProvider() - try { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the suite's window and runtime stubs cover every member registerPtyHandlers reads. - registerPtyHandlers(mainWindow as never, runtime as never) - await handlers.get('pty:spawn')!(null, { cols: 80, rows: 24, sessionId: PTY_ID }) - const id = PTY_ID - const setHidden = getPtySetHiddenRendererPtyListener() - setHidden(null, { id, hidden: true }) - daemon.emitData(id, dropped) - vi.advanceTimersByTime(50) - setHidden(null, { id, hidden: false }) - mainWindow.webContents.send.mockClear() - daemon.emitData(id, resumed) - vi.advanceTimersByTime(50) - const delivered = mainWindow.webContents.send.mock.calls - .filter(([channel]) => channel === 'pty:data') - .map(([, payload]): Record => payload) - return { delivered, seq } - } finally { - vi.useRealTimers() - } - } - - it.each([ - [ - 'an OSC title', - 'done\x1b]0;Sequent', - 'ial refactoring PRs | cleanup-dumb-exports\x07> prompt', - '\x1b]' - ], - ['an SGR sequence', 'diff \x1b[38;5', ';242m+ line', '\x1b['], - ['a CSI whose ESC alone was dropped', 'diff \x1b', '[38;5;242m+ line', '\x1b'] - ])( - 'labels the first delivery after a drop that split %s, without trimming it', - async (_name, dropped, resumed, gapEscapeState) => { - const { delivered, seq } = await hideDropAndResume(dropped, resumed) - // A restore whose snapshot carries the open escape still needs every continuation byte. - expect(delivered).toEqual([ - { id: expect.any(String), data: resumed, seq, rawLength: resumed.length, gapEscapeState } - ]) - } - ) - - it('leaves a delivery after a clean drop boundary unlabeled, including a bell after text', async () => { - const { delivered } = await hideDropAndResume('hidden\x1b[0m', 'build done\x07') - expect(delivered.map((payload) => payload.data).join('')).toBe('build done\x07') - expect(delivered.some((payload) => 'gapEscapeState' in payload)).toBe(false) - }) -}) diff --git a/src/main/ipc/pty/host-env/spawn-cwd-wsl-stopped-folder.test.ts b/src/main/ipc/pty/host-env/spawn-cwd-wsl-stopped-folder.test.ts deleted file mode 100644 index 1dd1619725e..00000000000 --- a/src/main/ipc/pty/host-env/spawn-cwd-wsl-stopped-folder.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type * as FsPromises from 'node:fs/promises' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as BackgroundDistroGateModule from '../../../wsl/discovery/wsl-background-distro-gate' -import type * as WslMoveScopeGuardModule from '../../../persistence/moving-wsl-projects/wsl-move-scope-guard' -import type { Store } from '../../../persistence/loading-store/store' -import type { FolderWorkspace } from '../../../../shared/folder-workspaces/folder-workspace-types' -import { folderWorkspaceKey } from '../../../../shared/workspace-scope' -import { wslDistroHoldMessage } from '../../../../shared/wsl-distro-stopped-hold' -import { isWslUserActFor } from '../../../wsl/discovery/wsl-user-act-scope' - -const mocks = vi.hoisted(() => ({ - stat: vi.fn<(path: string) => Promise<{ isDirectory(): boolean }>>(), - readWslDistroRunningState: vi.fn<() => Promise<'running' | 'stopped' | 'unverifiable'>>() -})) -vi.mock('node:fs/promises', async (importOriginal) => ({ - ...(await importOriginal()), - stat: mocks.stat -})) -vi.mock('../../../wsl/discovery/wsl-background-distro-gate', async (importOriginal) => ({ - ...(await importOriginal()), - readWslDistroRunningState: mocks.readWslDistroRunningState -})) -vi.mock( - '../../../persistence/moving-wsl-projects/wsl-move-scope-guard', - async (importOriginal) => ({ - ...(await importOriginal()), - isWslMoveCwdFenced: () => false, - isWslMoveWorkspaceBlocked: () => false - }) -) - -import { assertWorkspaceAcceptsNewPty } from './spawn-cwd' -import { getFolderWorkspacePathStatus } from '../../../project-groups/folder-workspace-path-status' - -const FOLDER = '\\\\wsl.localhost\\Ubuntu\\home\\me\\notes' -const WORKTREE_ID = folderWorkspaceKey('folder-1') -const originalPlatform = process.platform - -function makeStore(twinOrder?: 'remote-first' | 'local-first'): Store { - const workspace: FolderWorkspace = { - id: 'folder-1', - projectGroupId: 'group-1', - name: 'notes', - folderPath: FOLDER, - linkedTask: null, - comment: '', - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 1, - createdAt: 1, - updatedAt: 1 - } - const remote = { ...workspace, connectionId: 'remote', folderPath: '/remote/notes' } - const folders = twinOrder - ? twinOrder === 'remote-first' - ? [remote, workspace] - : [workspace, remote] - : [workspace] - const store = { - getRepos: () => [], - getProjectGroups: () => [], - getFolderWorkspaces: () => folders - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the folder check reads only these three store methods. - return store as unknown as Store -} - -beforeEach(() => { - Object.defineProperty(process, 'platform', { value: 'win32' }) - mocks.stat.mockReset() - mocks.stat.mockImplementation(async () => { - expect(isWslUserActFor('Ubuntu')).toBe(true) - return { isDirectory: () => true } - }) - mocks.readWslDistroRunningState.mockReset() - mocks.readWslDistroRunningState.mockResolvedValue('stopped') -}) - -afterEach(() => { - Object.defineProperty(process, 'platform', { value: originalPlatform }) -}) - -describe('a folder workspace on a stopped WSL distro', () => { - it.each(['remote-first', 'local-first'] as const)( - 'keeps the selected host through the passive WSL check (%s)', - async (order) => { - const store = makeStore(order) - await expect( - assertWorkspaceAcceptsNewPty(store, WORKTREE_ID, 'session-1', undefined, true, null) - ).rejects.toThrow(wslDistroHoldMessage('Ubuntu', 'stopped')) - expect(mocks.stat).not.toHaveBeenCalled() - mocks.readWslDistroRunningState.mockClear() - await expect( - assertWorkspaceAcceptsNewPty(store, WORKTREE_ID, 'session-1', undefined, true, 'remote') - ).rejects.toThrow('folder_workspace_path_unavailable:/remote/notes') - expect(mocks.stat).not.toHaveBeenCalled() - expect(mocks.readWslDistroRunningState).not.toHaveBeenCalled() - } - ) - - it('reports the folder unavailable to a background status read without touching it', async () => { - await expect( - getFolderWorkspacePathStatus(makeStore(), { - scope: 'folder-workspace', - folderWorkspaceId: 'folder-1' - }) - ).resolves.toEqual({ path: FOLDER, exists: false, reason: 'unavailable' }) - expect(mocks.stat).not.toHaveBeenCalled() - }) - - it('holds a passive restore with Start instead of checking the folder', async () => { - await expect( - assertWorkspaceAcceptsNewPty(makeStore(), WORKTREE_ID, 'session-1', undefined, true) - ).rejects.toThrow(wslDistroHoldMessage('Ubuntu', 'stopped')) - expect(mocks.stat).not.toHaveBeenCalled() - }) - - it('checks the folder as the user act for a spawn the user asked for', async () => { - await expect( - assertWorkspaceAcceptsNewPty(makeStore(), WORKTREE_ID, undefined, undefined, false) - ).resolves.toBeUndefined() - expect(mocks.stat).toHaveBeenCalledExactlyOnceWith(FOLDER) - }) - - it('lets a passive restore check the folder once the distro runs', async () => { - mocks.readWslDistroRunningState.mockResolvedValue('running') - mocks.stat.mockResolvedValue({ isDirectory: () => true }) - await expect( - assertWorkspaceAcceptsNewPty(makeStore(), WORKTREE_ID, 'session-1', undefined, true) - ).resolves.toBeUndefined() - expect(mocks.stat).toHaveBeenCalledExactlyOnceWith(FOLDER) - }) -}) diff --git a/src/main/ipc/pty/ipc/spawn-push-target-materialization-real-git.test.ts b/src/main/ipc/pty/ipc/spawn-push-target-materialization-real-git.test.ts index 3c4afc037f4..588cc0fafd4 100644 --- a/src/main/ipc/pty/ipc/spawn-push-target-materialization-real-git.test.ts +++ b/src/main/ipc/pty/ipc/spawn-push-target-materialization-real-git.test.ts @@ -116,19 +116,4 @@ describe('triggerPtySpawnPushTargetMaterialization (real git fixture)', () => { ).trim() expect(fetchedHead).toBe(forkHead) }) - - it('is a no-op once the remote was already created (repeat pty:spawn, e.g. reattach)', async () => { - const target = { ...forkTarget(), remoteCreated: true } - const { deps } = depsFor(target) - await git(['remote', 'add', FORK_REMOTE, forkPath], repoPath) - - triggerPtySpawnPushTargetMaterialization(deps, { cols: 80, rows: 24, worktreeId }) - - // Let the fire-and-forget hook settle before asserting the remoteCreated no-op. - await new Promise((resolve) => setImmediate(resolve)) - const upstream = await git(['rev-parse', '--abbrev-ref', `${mainBranch}@{u}`], repoPath).catch( - () => '' - ) - expect(upstream.trim()).toBe('') - }) }) diff --git a/src/main/ipc/pty/pty-spawn-wsl-passive-restore-hold.test.ts b/src/main/ipc/pty/pty-spawn-wsl-passive-restore-hold.test.ts deleted file mode 100644 index 4570a1defed..00000000000 --- a/src/main/ipc/pty/pty-spawn-wsl-passive-restore-hold.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { statSyncMock, spawnMock, wslUncDirectoryExistsAsyncMock } from './pty-ipc-mock-registry' -import { setupPtyIpcSuite } from './pty-ipc-test-harness' - -import { registerPtyHandlers } from './register-handlers' - -vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock())) -vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock())) -vi.mock('node-pty', () => import('./pty-ipc-mock-registry').then((m) => m.nodePtyModuleMock())) -vi.mock('node:child_process', async (importOriginal) => - (await import('./pty-ipc-mock-registry')).childProcessModuleMock(await importOriginal()) -) -vi.mock('../../opencode/hook-service', () => - import('./pty-ipc-mock-registry').then((m) => m.openCodeHookServiceModuleMock()) -) -vi.mock('../../mimo/hook-service', () => - import('./pty-ipc-mock-registry').then((m) => m.mimoHookServiceModuleMock()) -) -vi.mock('../../agent-hooks/server', () => - import('./pty-ipc-mock-registry').then((m) => m.agentHookServerModuleMock()) -) -vi.mock('../../pi/titlebar-extension-service', () => - import('./pty-ipc-mock-registry').then((m) => m.piTitlebarExtensionModuleMock()) -) -vi.mock('../../pty/shell-startup/pwsh', () => - import('./pty-ipc-mock-registry').then((m) => m.pwshModuleMock()) -) -vi.mock('../../wsl/filesystem/unc-path-access', async (importOriginal) => - (await import('./pty-ipc-mock-registry')).wslModuleMock(await importOriginal()) -) -vi.mock('node:fs/promises', async (importOriginal) => - (await import('./pty-ipc-mock-registry')).fsPromisesModuleMock(await importOriginal()) -) -vi.mock('../../telemetry/client', () => - import('./pty-ipc-mock-registry').then((m) => m.telemetryClientModuleMock()) -) -vi.mock('../../telemetry/classify-error', () => - import('./pty-ipc-mock-registry').then((m) => m.classifyErrorModuleMock()) -) -vi.mock('../../cli/linux-terminal-orca-cli-shim', () => - import('./pty-ipc-mock-registry').then((m) => m.linuxCliShimModuleMock()) -) -vi.mock('../../memory/pty-registry', () => - import('./pty-ipc-mock-registry').then((m) => m.ptyRegistryModuleMock()) -) -vi.mock('../../agent-hooks/migration-unsupported-pty-state', () => - import('./pty-ipc-mock-registry').then((m) => m.migrationUnsupportedPtyModuleMock()) -) -vi.mock('../../codex/configuration/homes/codex-pane-account-registry', () => - import('./pty-ipc-mock-registry').then((m) => m.codexPaneAccountRegistryModuleMock()) -) -vi.mock('../../codex/history/index/codex-state-db-backfill-recovery', () => - import('./pty-ipc-mock-registry').then((m) => m.codexBackfillRecoveryModuleMock()) -) -const runningStateMock = vi.hoisted(() => - vi.fn(async (_distro: string, _options?: { startedAfter?: number }) => 'stopped') -) -vi.mock('../../wsl/discovery/wsl-background-distro-gate', async (importOriginal) => ({ - ...(await importOriginal()), - readWslDistroRunningState: runningStateMock -})) - -const WSL_RUNTIME = { - status: 'resolved', - runtime: { - kind: 'wsl', - hostPlatform: 'wsl', - projectId: 'repo-1', - distro: 'Ubuntu', - reason: 'project-override', - cacheKey: 'repo-1:wsl' - } -} as const - -// E9: a restore the user did not ask for must not start a stopped distro, whatever provider runs. -describe('a passive WSL restore with a stopped distro', () => { - const { handlers, mainWindow } = setupPtyIpcSuite() - const originalPlatform = process.platform - - beforeEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - runningStateMock.mockReset().mockResolvedValue('stopped') - wslUncDirectoryExistsAsyncMock.mockReset().mockResolvedValue(true) - statSyncMock.mockImplementation(() => ({ isDirectory: () => true, mode: 0o755 })) - }) - afterEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform }) - }) - - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the suite's window fake implements what the handlers read. - const register = (): void => registerPtyHandlers(mainWindow as never) - - const restore = (overrides: Record = {}) => - handlers.get('pty:spawn')!(null, { - cols: 80, - rows: 24, - cwd: '/home/me/repo', - worktreeId: 'repo-1::C:\\Users\\me\\repo', - sessionId: 'repo-1::C:\\Users\\me\\repo@@restored', - projectRuntime: WSL_RUNTIME, - wslPassiveRestore: true, - ...overrides - }) - - it('holds with the local provider (daemon off), before the cwd probe or any spawn', async () => { - register() - await expect(restore()).rejects.toThrow() - expect(runningStateMock).toHaveBeenCalledWith('Ubuntu', expect.anything()) - expect(wslUncDirectoryExistsAsyncMock).not.toHaveBeenCalled() - expect(spawnMock).not.toHaveBeenCalled() - }) - - it('restores once the distro runs, and never holds a restore the user asked for', async () => { - register() - runningStateMock.mockResolvedValue('running') - await restore() - expect(spawnMock).toHaveBeenCalledTimes(1) - - runningStateMock.mockResolvedValue('stopped') - runningStateMock.mockClear() - await restore({ wslPassiveRestore: undefined, sessionId: 'repo-1::C:\\Users\\me\\repo@@other' }) - expect(runningStateMock).not.toHaveBeenCalled() - expect(spawnMock).toHaveBeenCalledTimes(2) - }) -}) diff --git a/src/main/ipc/pty/spawn-host-env-wsl-passive-restore.test.ts b/src/main/ipc/pty/spawn-host-env-wsl-passive-restore.test.ts deleted file mode 100644 index effba54507a..00000000000 --- a/src/main/ipc/pty/spawn-host-env-wsl-passive-restore.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => ({ - ensureBackfill: vi.fn(async () => {}), - blockedPath: vi.fn(async (): Promise<{ distro: string; state: string } | null> => null), - regQuery: vi.fn() -})) - -const LXSS = 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss' -const UBUNTU_ID = '0e3a7f2c-1b4d-4c5e-9f60-7a8b9c0d1e2f' -// The common single-distro setup: Lxss names Ubuntu as the default. -const LXSS_UBUNTU_DEFAULT = [ - LXSS, - ` DefaultDistribution REG_SZ {${UBUNTU_ID}}`, - '', - `${LXSS}\\{${UBUNTU_ID}}`, - ' DistributionName REG_SZ Ubuntu', - '' -].join('\r\n') - -vi.mock('@orca/process-host', async (importOriginal) => ({ - ...(await importOriginal>()), - runProcess: mocks.regQuery -})) -// wsl.exe's own list is unavailable, so the default comes from the registry alone. -vi.mock('../../wsl/discovery/distro-catalog', async (importOriginal) => ({ - ...(await importOriginal>()), - listWslDistrosAsync: async () => [] -})) -vi.mock('../../codex/history/index/codex-state-db-backfill-recovery', () => ({ - ensureCodexStateDbBackfillRecoveryStarted: mocks.ensureBackfill -})) -vi.mock('../../wsl/filesystem/wsl-unc-access-gate', async (importOriginal) => ({ - ...(await importOriginal>()), - blockedWslDistroForPath: mocks.blockedPath -})) - -import { resolvePtySpawnHostEnv } from './spawn-host-env' -import { noCodexResumeLaunch, stripSequencedStartupResumeArgv } from './host-env/codex-resume' -import { isWslUserActFor } from '../../wsl/discovery/wsl-user-act-scope' -import { resetWslDefaultDistroForTests } from '../../wsl/discovery/wsl-default-distro' -import type { IPtyProvider } from '../../providers/pty-provider-contract' -import type { CodexAccountSelectionTarget } from '../../codex-accounts/runtime-selection' - -const HOME = '\\\\wsl.localhost\\Ubuntu\\home\\alice\\.codex' -const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') - -function resolve( - wslPassiveRestore: boolean, - seen: boolean[], - launch: { target?: CodexAccountSelectionTarget; expectedWslDistro?: string | null } = {} -) { - return resolvePtySpawnHostEnv({ - launchAgent: 'codex', - command: 'codex', - resumeProviderSession: undefined, - env: {}, - cwd: '\\\\wsl.localhost\\Ubuntu\\home\\alice\\repo', - shellOverride: undefined, - expectedWslDistro: launch.expectedWslDistro === undefined ? 'Ubuntu' : launch.expectedWslDistro, - codexSelectionTarget: launch.target ?? { runtime: 'wsl', wslDistro: 'Ubuntu' }, - preAdopted: false, - isDaemonHostSpawn: false, - sessionId: 'session-1', - callerNamedSession: true, - mintedSessionId: false, - resumeOnSelectedAccount: false, - emptySessionIdSkipsHostEnv: false, - wslPassiveRestore, - requestedAgentTeamsPath: undefined, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a non-daemon spawn returns before any provider call. - provider: {} as IPtyProvider, - deps: { - prepareCodexResumeHome: () => null, - resolveCodexResumeLaunch: async () => noCodexResumeLaunch('codex'), - noCodexResumeLaunch, - reconcileSharedRuntimeResumeHome: async (home) => home.codexHomePath, - stripSequencedStartupResumeArgv, - getSelectedCodexHomePath: async () => { - seen.push(isWslUserActFor('Ubuntu')) - return HOME - } - } - }) -} - -beforeEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - mocks.ensureBackfill.mockClear() - mocks.blockedPath.mockReset().mockResolvedValue(null) - mocks.regQuery.mockReset().mockResolvedValue({ code: 0, stdout: LXSS_UBUNTU_DEFAULT }) - resetWslDefaultDistroForTests() -}) - -afterEach(() => { - if (originalPlatform) { - Object.defineProperty(process, 'platform', originalPlatform) - } -}) - -// Spawn env prep runs before #35813's hold: only a launch someone asked for may start the distro (E9). -describe('WSL spawn host env', () => { - it('never turns a passive restore with no named distro into a user act on the default one', async () => { - const seen: boolean[] = [] - await resolve(true, seen, { - target: { runtime: 'wsl', wslDistro: null }, - expectedWslDistro: null - }) - expect(seen).toEqual([false]) - }) - - it('a WSL launch someone asked for, with no named distro, acts on the default one', async () => { - const seen: boolean[] = [] - await resolve(false, seen, { - target: { runtime: 'wsl', wslDistro: null }, - expectedWslDistro: null - }) - expect(seen).toEqual([true]) - }) - - it('a launch whose default WSL cannot name never acts on a guessed distro', async () => { - mocks.regQuery.mockResolvedValue({ code: 1, stdout: '' }) - const seen: boolean[] = [] - await resolve(false, seen, { - target: { runtime: 'wsl', wslDistro: null }, - expectedWslDistro: null - }) - expect(seen).toEqual([false]) - }) - - it('skips the state-DB recovery (a UNC read) when the distro is stopped', async () => { - mocks.blockedPath.mockResolvedValue({ distro: 'Ubuntu', state: 'stopped' }) - await resolve(true, []) - expect(mocks.blockedPath).toHaveBeenCalledWith(HOME) - expect(mocks.ensureBackfill).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/ipc/repos/base-ref-default-wsl-gate.test.ts b/src/main/ipc/repos/base-ref-default-wsl-gate.test.ts deleted file mode 100644 index 5a18345fac4..00000000000 --- a/src/main/ipc/repos/base-ref-default-wsl-gate.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as DistroGateModule from '../../wsl/discovery/wsl-background-distro-gate' -import type { Repo } from '../../../shared/repo-types' - -const mocks = vi.hoisted(() => { - const state: { - distroState: 'running' | 'stopped' | 'unverifiable' - gitOptions: { wslDistro?: string } - } = { distroState: 'stopped', gitOptions: {} } - return { - state, - handlers: new Map Promise>(), - send: vi.fn(), - getBaseRefDefault: vi.fn(async (_path: string, _options?: unknown) => 'origin/main'), - getRemoteCount: vi.fn(async (_path: string, _options?: unknown) => 1) - } -}) - -vi.mock('electron', () => ({ - BrowserWindow: { - getAllWindows: () => [{ isDestroyed: () => false, webContents: { send: mocks.send } }] - }, - ipcMain: { - handle: (channel: string, handler: (event: unknown, args: unknown) => Promise) => - mocks.handlers.set(channel, handler) - } -})) -vi.mock('../../git/repo', () => ({ - getBaseRefDefault: mocks.getBaseRefDefault, - getRemoteCount: mocks.getRemoteCount, - searchBaseRefDetails: vi.fn(async () => []) -})) -vi.mock('../../workspaces/execution-host/project-runtime-git-options', () => ({ - getProjectRuntimeReadGitOptions: () => mocks.state.gitOptions -})) -vi.mock('../../wsl/discovery/wsl-background-distro-gate', async (importOriginal) => ({ - ...(await importOriginal()), - readWslDistroRunningState: vi.fn(async () => mocks.state.distroState) -})) - -import { registerBaseRefQueryHandlers } from './base-ref-query-handlers' -import { - _resetWslBackgroundDistroGateForTests, - noteWslDistrosRunning -} from '../../wsl/discovery/wsl-background-distro-gate' -import { RuntimeRepositoryRefQueries } from '../../runtime/runtime-repository-ref-queries' - -const WSL_REPO = '\\\\wsl.localhost\\Ubuntu\\home\\me\\repo' -const UNKNOWN = { defaultBaseRef: null, remoteCount: 0 } -const REFUSED = { ...UNKNOWN, refusedWslDistro: 'Ubuntu' } -const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') - -function makeRepo(overrides: Partial = {}): Repo { - return { - id: 'repo-1', - path: WSL_REPO, - displayName: 'repo', - badgeColor: '#000', - addedAt: 1, - kind: 'git', - ...overrides - } -} - -function registerWith(repo: Repo): (args: Record) => Promise { - const store = { getRepo: () => repo, getRepos: () => [repo] } - registerBaseRefQueryHandlers( - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the handler reads only getRepo/getRepos, both provided. - store as unknown as Parameters[0] - ) - const handler = mocks.handlers.get('repos:getBaseRefDefault') - if (!handler) { - throw new Error('repos:getBaseRefDefault was not registered') - } - return (args) => handler(null, { repoId: repo.id, ...args }) -} - -describe('repos:getBaseRefDefault for a stopped WSL distro (E9)', () => { - beforeEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - mocks.handlers.clear() - mocks.send.mockClear() - _resetWslBackgroundDistroGateForTests() - mocks.state.distroState = 'stopped' - mocks.state.gitOptions = {} - mocks.getBaseRefDefault.mockClear() - mocks.getRemoteCount.mockClear() - }) - - afterEach(() => { - if (originalPlatform) { - Object.defineProperty(process, 'platform', originalPlatform) - } - }) - - it('returns unknown without running Git when the Settings pane opens', async () => { - const getDefault = registerWith(makeRepo()) - - await expect(getDefault({})).resolves.toEqual(REFUSED) - expect(mocks.getBaseRefDefault).not.toHaveBeenCalled() - expect(mocks.getRemoteCount).not.toHaveBeenCalled() - }) - - it('tells renderers to re-ask once the refusing distro runs', async () => { - const getDefault = registerWith(makeRepo()) - await getDefault({}) - expect(mocks.send).not.toHaveBeenCalled() - - noteWslDistrosRunning(['Ubuntu']) - - expect(mocks.send).toHaveBeenCalledWith('preflight:wslDistroRunning', 'Ubuntu') - }) - - it('refuses a host path whose Git runs through wsl.exe in a stopped distro', async () => { - mocks.state.gitOptions = { wslDistro: 'Ubuntu' } - const getDefault = registerWith(makeRepo({ path: 'C:\\src\\repo' })) - - await expect(getDefault({})).resolves.toEqual(REFUSED) - expect(mocks.getBaseRefDefault).not.toHaveBeenCalled() - }) -}) - -describe('runtime repo.baseRefDefault for a stopped WSL distro (E9)', () => { - beforeEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - mocks.state.distroState = 'stopped' - mocks.getBaseRefDefault.mockClear() - }) - - afterEach(() => { - if (originalPlatform) { - Object.defineProperty(process, 'platform', originalPlatform) - } - }) - - it('returns unknown, naming the refusing distro, without touching its UNC path', async () => { - const queries = new RuntimeRepositoryRefQueries({ resolveRepo: async () => makeRepo() }) - - await expect(queries.getDefault('id:repo-1')).resolves.toEqual(REFUSED) - expect(mocks.getBaseRefDefault).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/ipc/runtime-environments/orcad-runtime-maintenance-handlers.test.ts b/src/main/ipc/runtime-environments/orcad-runtime-maintenance-handlers.test.ts deleted file mode 100644 index 1222910d6eb..00000000000 --- a/src/main/ipc/runtime-environments/orcad-runtime-maintenance-handlers.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => ({ - handle: vi.fn(), - update: vi.fn(), - rollback: vi.fn(), - recover: vi.fn(), - stop: vi.fn(), - cancel: vi.fn(), - retire: vi.fn() -})) - -vi.mock('electron', () => ({ ipcMain: { handle: mocks.handle } })) -vi.mock('../../ssh/orcad-runtime-maintenance', () => ({ - updateManagedOrcadEnvironment: mocks.update, - rollbackManagedOrcadEnvironment: mocks.rollback, - recoverManagedOrcadEnvironment: mocks.recover -})) -vi.mock('../../managed-server/lifecycle/orcad-runtime-decommission', () => ({ - stopManagedOrcadEnvironment: mocks.stop, - cancelManagedOrcadStop: mocks.cancel -})) -vi.mock('./runtime-environment-removal-cleanup', () => ({ - retireRemovedRuntimeEnvironment: mocks.retire -})) - -const { registerOrcadRuntimeMaintenanceHandlers } = - await import('./orcad-runtime-maintenance-handlers') - -const invalidateTransport = vi.fn() -const clearHostServerStatus = vi.fn() -const clearHostServerNotes = vi.fn() -const store = { - removeWorkspaceSessionHost: vi.fn(), - getRemoteLayoutSnapshot: vi.fn(), - setRemoteLayoutSnapshot: vi.fn(), - setHostRepoCatalogSnapshot: vi.fn() -} - -function handler(channel: string): (_event: unknown, args: unknown) => Promise { - const registration = mocks.handle.mock.calls.find(([name]) => name === channel) - if (!registration) { - throw new Error(`${channel} handler was not registered`) - } - return registration[1] -} - -describe('managed orcad maintenance IPC', () => { - beforeEach(() => { - vi.clearAllMocks() - registerOrcadRuntimeMaintenanceHandlers({ - getUserDataPath: () => '/profile', - getActiveEnvironmentId: () => 'active-environment', - invalidateTransport, - clearHostServerStatus, - clearHostServerNotes, - store - }) - }) - - it('reconnects after an update restarts orcad, but not after a deferral', async () => { - mocks.update.mockResolvedValueOnce({ outcome: 'deferred', code: 'busy' }) - await handler('runtimeEnvironments:updateOrcad')(null, { selector: 'Managed', force: 'yes' }) - expect(mocks.update).toHaveBeenCalledWith('/profile', { selector: 'Managed', force: false }) - expect(invalidateTransport).not.toHaveBeenCalled() - expect(clearHostServerNotes).not.toHaveBeenCalled() - mocks.update.mockResolvedValueOnce({ outcome: 'updated', environment: { id: 'e-1' } }) - await handler('runtimeEnvironments:updateOrcad')(null, { selector: 'Managed', force: true }) - expect(invalidateTransport).toHaveBeenCalledWith('e-1') - }) - - it.each(['updated', 'already-current'])( - 'clears the host notes after a verified %s update', - async (outcome) => { - mocks.update.mockResolvedValueOnce({ - outcome, - environment: { id: 'e-1', orcadDeployment: { sshTargetId: 'ssh-1' } }, - activeVersion: 'candidate' - }) - await handler('runtimeEnvironments:updateOrcad')(null, { selector: 'Managed', force: true }) - expect(clearHostServerNotes).toHaveBeenCalledExactlyOnceWith('ssh-1', 'e-1') - expect(invalidateTransport).toHaveBeenCalledTimes(outcome === 'updated' ? 1 : 0) - } - ) - - it('clears the notes only after the old transport has been retired', async () => { - let finishRetirement: (() => void) | undefined - invalidateTransport.mockReturnValueOnce( - new Promise((resolve) => { - finishRetirement = resolve - }) - ) - mocks.update.mockResolvedValueOnce({ - outcome: 'updated', - environment: { id: 'e-1', orcadDeployment: { sshTargetId: 'ssh-1' } } - }) - const updating = handler('runtimeEnvironments:updateOrcad')(null, { selector: 'Managed' }) - await vi.waitFor(() => expect(invalidateTransport).toHaveBeenCalledOnce()) - expect(clearHostServerNotes).not.toHaveBeenCalled() - finishRetirement?.() - await updating - expect(clearHostServerNotes).toHaveBeenCalledOnce() - }) - - it('keeps the previous diagnostic when an update rejects', async () => { - mocks.update.mockRejectedValueOnce(new Error('SSH contact lost')) - await expect( - handler('runtimeEnvironments:updateOrcad')(null, { selector: 'Managed' }) - ).rejects.toThrow('SSH contact lost') - expect(clearHostServerNotes).not.toHaveBeenCalled() - expect(invalidateTransport).not.toHaveBeenCalled() - }) - - it('stops with the Active Server guard and the shared removal cleanup', async () => { - mocks.stop.mockResolvedValueOnce({ outcome: 'unlinked', sshTargetId: 'ssh-1' }) - await handler('runtimeEnvironments:stopOrcad')(null, { selector: ' Managed ' }) - const [, args, policy] = mocks.stop.mock.calls[0] ?? [] - expect(args).toEqual({ selector: 'Managed' }) - expect(policy.isActiveEnvironment('active-environment')).toBe(true) - expect(policy.isActiveEnvironment('e-1')).toBe(false) - policy.retireLocalState('e-1') - expect(mocks.retire).toHaveBeenCalledWith('e-1', invalidateTransport, store, '/profile') - // The SSH host stops naming the unlinked server without waiting for a reconnect. - expect(clearHostServerStatus).toHaveBeenCalledWith('ssh-1') - }) - - it('keeps the SSH host’s managed state when the stop is refused', async () => { - mocks.stop.mockResolvedValueOnce({ - outcome: 'refused', - verdict: 'live', - code: 'c', - reason: 'r' - }) - await handler('runtimeEnvironments:stopOrcad')(null, { selector: 'Managed' }) - expect(clearHostServerStatus).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/ipc/shell-server-ssh-editor.test.ts b/src/main/ipc/shell-server-ssh-editor.test.ts deleted file mode 100644 index fa9be15a0ff..00000000000 --- a/src/main/ipc/shell-server-ssh-editor.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { SshTarget } from '../../shared/ssh-types' - -const { handleMock, resolveCliCommandMock, spawnMock } = vi.hoisted(() => ({ - handleMock: vi.fn(), - resolveCliCommandMock: vi.fn(), - spawnMock: vi.fn() -})) - -vi.mock('electron', () => ({ - ipcMain: { handle: handleMock }, - shell: { showItemInFolder: vi.fn(), openExternal: vi.fn(), openPath: vi.fn() }, - dialog: { showOpenDialog: vi.fn() } -})) - -vi.mock('node:child_process', () => ({ spawn: spawnMock })) - -vi.mock('../../shared/node-cli-command-resolution', () => ({ - resolveCliCommand: resolveCliCommandMock -})) - -import { registerShellHandlers } from './shell' -import { fingerprintRuntimeSshTarget } from '../ssh/runtime-ssh-target-fingerprint' -import type { ListRuntimeEnvironments } from './shell-editor-ssh-host' - -const link = { sshTargetId: 'ssh-1', sshTargetGeneration: 1, localPort: 41000, remotePort: 6768 } -const managedServer = { id: 'env-1', orcadDeployment: link } - -function sshTarget(id: string, configHost: string, overrides: Partial = {}): SshTarget { - return { - id, - label: configHost, - host: `${configHost}.example.com`, - port: 22, - username: 'ada', - source: 'ssh-config', - configHost, - generation: 1, - orcadFence: { environmentId: 'env-1' }, - ...overrides - } -} - -function spawnedChild(): { once: unknown; off: unknown; unref: unknown } { - const child = { - once: vi.fn((eventName: string, callback: () => void) => { - if (eventName === 'spawn') { - queueMicrotask(callback) - } - return child - }), - off: vi.fn(() => child), - unref: vi.fn() - } - return child -} - -describe('Open in VS Code for a workspace on a server Orca reaches through an SSH host', () => { - const sshAccessServer = { - id: 'env-1', - sshAccess: { - ...link, - endpointId: 'tunnel', - previousPreferredEndpointId: 'lan', - targetFingerprint: fingerprintRuntimeSshTarget(sshTarget('ssh-1', 'builder')) - } - } - const targets = new Map() - - beforeEach(() => { - targets.clear() - targets.set('ssh-1', sshTarget('ssh-1', 'builder')) - targets.set('ssh-2', sshTarget('ssh-2', 'other')) - handleMock.mockReset() - spawnMock.mockReset() - spawnMock.mockImplementation(spawnedChild) - resolveCliCommandMock.mockReset() - resolveCliCommandMock.mockReturnValue('/usr/local/bin/code') - }) - - function openInEditor( - listRuntimeEnvironments: ListRuntimeEnvironments, - request: { connectionId: string; ownerHostId: string } - ): Promise { - registerShellHandlers({ getSshTarget: (id) => targets.get(id) }, listRuntimeEnvironments) - const call = handleMock.mock.calls.find((entry) => entry[0] === 'shell:openInExternalEditor') - return call?.[1]({}, { path: '/home/ada/project', command: 'code', ...request }) - } - - it.each([ - ['managed server', managedServer], - ['paired server with SSH access', sshAccessServer] - ])('opens a %s workspace through the host alias', async (_label, environment) => { - await expect( - openInEditor(() => [environment], { connectionId: 'ssh-1', ownerHostId: 'runtime:env-1' }) - ).resolves.toEqual({ ok: true }) - expect(spawnMock).toHaveBeenCalledWith( - '/usr/local/bin/code', - ['--remote', 'ssh-remote+builder', '/home/ada/project'], - expect.objectContaining({ detached: true, stdio: 'ignore', windowsHide: true, shell: false }) - ) - }) - - it.each([ - ['a host the server does not run on', { connectionId: 'ssh-2', ownerHostId: 'runtime:env-1' }], - ['an unknown server', { connectionId: 'ssh-1', ownerHostId: 'runtime:env-unknown' }], - ['a local owner', { connectionId: 'ssh-1', ownerHostId: 'local' }] - ])('refuses %s', async (_label, request) => { - await expect(openInEditor(() => [managedServer], request)).resolves.toEqual({ - ok: false, - reason: 'remote-runtime-unsupported' - }) - expect(spawnMock).not.toHaveBeenCalled() - }) - - it.each([ - ['was re-created since the server linked it', { generation: 2 }], - ['is fenced to another managed server', { orcadFence: { environmentId: 'env-2' } }] - ])('refuses a host whose SSH registration %s', async (_label, overrides) => { - targets.set('ssh-1', sshTarget('ssh-1', 'builder', overrides)) - await expect( - openInEditor(() => [managedServer], { connectionId: 'ssh-1', ownerHostId: 'runtime:env-1' }) - ).resolves.toEqual({ ok: false, reason: 'remote-runtime-unsupported' }) - expect(spawnMock).not.toHaveBeenCalled() - }) - - it('refuses SSH access verified against connection settings the host no longer has', async () => { - targets.set('ssh-1', sshTarget('ssh-1', 'builder', { host: 'elsewhere.example.com' })) - await expect( - openInEditor(() => [sshAccessServer], { connectionId: 'ssh-1', ownerHostId: 'runtime:env-1' }) - ).resolves.toEqual({ ok: false, reason: 'remote-runtime-unsupported' }) - expect(spawnMock).not.toHaveBeenCalled() - }) - - it('refuses when the server list cannot be read', async () => { - const unreadable: ListRuntimeEnvironments = () => { - throw new Error('unreadable store') - } - await expect( - openInEditor(unreadable, { connectionId: 'ssh-1', ownerHostId: 'runtime:env-1' }) - ).resolves.toEqual({ ok: false, reason: 'remote-runtime-unsupported' }) - expect(spawnMock).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/ipc/shell-wsl-owner.test.ts b/src/main/ipc/shell-wsl-owner.test.ts deleted file mode 100644 index 3d9d20fcf20..00000000000 --- a/src/main/ipc/shell-wsl-owner.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as WindowsPathAccessHost from '../windows/windows-path-access-host' -import type * as ExternalEditorLaunch from '../external-editors/external-editor-launch' - -const { handleMock, launchMock, showItemInFolderMock, statMock, recordForEnvironmentMock } = - vi.hoisted(() => ({ - handleMock: vi.fn<(channel: string, handler: (...args: unknown[]) => unknown) => void>(), - launchMock: vi.fn(), - showItemInFolderMock: vi.fn(), - statMock: vi.fn(), - recordForEnvironmentMock: vi.fn() - })) - -vi.mock('electron', () => ({ - ipcMain: { handle: handleMock }, - shell: { showItemInFolder: showItemInFolderMock, openExternal: vi.fn(), openPath: vi.fn() }, - dialog: { showOpenDialog: vi.fn() } -})) - -vi.mock('node:fs/promises', () => ({ - constants: { COPYFILE_EXCL: 1 }, - copyFile: vi.fn(), - readFile: vi.fn(), - stat: statMock -})) - -vi.mock('../../shared/node-cli-command-resolution', () => ({ - resolveCliCommand: (command: string) => command -})) - -vi.mock('../wsl/host-lifecycle/wsl-host-service-instance', () => ({ - getWslHostService: () => ({ recordForEnvironment: recordForEnvironmentMock }) -})) - -// WSL paths require Windows resolution even when this test runs on another platform. -vi.mock('../windows/windows-path-access-host', async (importOriginal) => { - const actual = await importOriginal() - return { - resolveWindowsPathAccess: (owner: unknown) => actual.resolveWindowsPathAccess(owner, 'win32') - } -}) - -vi.mock('../external-editors/external-editor-launch', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - launchExternalEditor: launchMock, - resolveExternalEditorLaunchSpec: (command: string | undefined, path: string) => - actual.resolveExternalEditorLaunchSpec(command, path, { - platform: 'win32', - fileExists: () => false - }) - } -}) - -import { registerShellHandlers } from './shell' -import { toRuntimeExecutionHostId } from '../../shared/execution-host' - -const WSL_OWNER = toRuntimeExecutionHostId('wsl:Ubuntu') -const UNC = '\\\\wsl.localhost\\Ubuntu\\home\\me\\my repo' - -function getHandler(channel: string): (...args: unknown[]) => Promise { - registerShellHandlers({ getSshTarget: () => undefined }) - const handler = handleMock.mock.calls.find(([name]) => name === channel)?.[1] - if (!handler) { - throw new Error(`${channel} handler not registered`) - } - return async (...args) => handler(...args) -} - -describe('shell handlers for a WSL-owned path', () => { - beforeEach(() => { - handleMock.mockReset() - launchMock.mockReset().mockResolvedValue(undefined) - showItemInFolderMock.mockReset() - statMock.mockReset().mockResolvedValue({ isDirectory: () => true }) - recordForEnvironmentMock - .mockReset() - .mockImplementation((id: string) => (id === 'wsl:Ubuntu' ? { distro: 'Ubuntu' } : null)) - }) - - it('reveals the \\\\wsl.localhost spelling in Explorer', async () => { - await expect( - getHandler('shell:openInFileManager')({}, '/home/me/my repo', WSL_OWNER) - ).resolves.toEqual({ ok: true }) - expect(statMock).toHaveBeenCalledWith(UNC) - expect(showItemInFolderMock).toHaveBeenCalledWith(UNC) - }) - - it('opens VS Code and Cursor with --remote wsl+ and the Linux path', async () => { - const handler = getHandler('shell:openInExternalEditor') - await expect( - handler({}, { path: '/home/me/my repo', command: 'code', ownerHostId: WSL_OWNER }) - ).resolves.toEqual({ ok: true }) - expect(launchMock.mock.calls[0][0].spawnArgs).toEqual([ - '--remote', - 'wsl+Ubuntu', - '/home/me/my repo' - ]) - await handler({}, { path: '/home/me/my repo', command: 'cursor', ownerHostId: WSL_OWNER }) - expect(launchMock.mock.calls[1][0].spawnArgs).toEqual([ - '--new-window', - '--remote', - 'wsl+Ubuntu', - '/home/me/my repo' - ]) - }) - - it('hands other editors the \\\\wsl.localhost path', async () => { - await getHandler('shell:openInExternalEditor')( - {}, - { path: '/home/me/my repo', command: 'zed', ownerHostId: WSL_OWNER } - ) - expect(launchMock.mock.calls[0][0].spawnArgs).toEqual([UNC]) - }) - - it('refuses a relative path and a missing one', async () => { - const reveal = getHandler('shell:openInFileManager') - await expect(reveal({}, 'home/me', WSL_OWNER)).resolves.toEqual({ - ok: false, - reason: 'not-absolute' - }) - statMock.mockRejectedValueOnce(new Error('ENOENT')) - await expect(reveal({}, '/home/gone', WSL_OWNER)).resolves.toEqual({ - ok: false, - reason: 'not-found' - }) - expect(showItemInFolderMock).not.toHaveBeenCalled() - }) - - it('still refuses a paired server that is not a WSL host', async () => { - const owner = toRuntimeExecutionHostId('paired-server') - await expect(getHandler('shell:openInFileManager')({}, '/srv', owner)).resolves.toEqual({ - ok: false, - reason: 'remote-runtime-unsupported' - }) - await expect( - getHandler('shell:openInExternalEditor')({}, { path: '/srv', ownerHostId: owner }) - ).resolves.toEqual({ ok: false, reason: 'remote-runtime-unsupported' }) - expect(statMock).not.toHaveBeenCalled() - expect(launchMock).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts b/src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts index 2bda6523eb1..63cad4ba060 100644 --- a/src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts +++ b/src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts @@ -116,25 +116,4 @@ describe('readGitCommonHeadIdentities', () => { expect(await readIdentities(commonDir)).toEqual([]) } }) - - it('never emits resolved content that is not a hex object id', async () => { - const commonDir = await makeCommonDir() - await writeFile(join(commonDir, 'HEAD'), 'ref: refs/heads/main\n') - await writeLooseRef(commonDir, 'refs/heads/main', 'not-an-object-id') - expect(await readIdentities(commonDir)).toEqual([]) - - await writeFile(join(commonDir, 'HEAD'), 'this is not a detached oid\n') - expect(await readIdentities(commonDir)).toEqual([]) - }) - - it('omits the primary row for non-standard common dir layouts', async () => { - const root = await mkdtemp(join(tmpdir(), 'orca-head-reader-')) - roots.push(root) - const commonDir = join(root, 'bare-repo') - await mkdir(commonDir, { recursive: true }) - await writeFile(join(commonDir, 'HEAD'), 'ref: refs/heads/main\n') - await writeLooseRef(commonDir, 'refs/heads/main', OID_A) - - expect(await readIdentities(commonDir)).toEqual([]) - }) }) diff --git a/src/main/ipc/wsl/wsl-move-orchestrator-host-connect.test.ts b/src/main/ipc/wsl/wsl-move-orchestrator-host-connect.test.ts deleted file mode 100644 index f885e924b0f..00000000000 --- a/src/main/ipc/wsl/wsl-move-orchestrator-host-connect.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * The move orchestrator's automatic path end to end over a real WSL host service: its own host - * readiness (prepareWslMoveHost), then the driver's automatic connect (E9). A host that is - * already serving is never reconnected, and a bridge user's host is added exactly once. - */ -import { mkdtempSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { WslMoveResult } from '../../../shared/wsl-move/wsl-move-driver-types' -import { prepareWslMoveHost } from '../../persistence/moving-wsl-projects/wsl-move-host-readiness' -import { WslMoveOrchestrator } from '../../persistence/moving-wsl-projects/wsl-move-orchestrator' -import { WslHostService } from '../../wsl/host-lifecycle/wsl-host-service' -import { - UBUNTU, - makeConnector -} from '../../wsl/host-lifecycle/__tests__/wsl-host-service-test-connector' -import { connectWslMoveHostService, wslHostRemovalPending } from './wsl-move-host-connect' - -const moved: WslMoveResult = { - outcome: 'moved', - migrationId: 'm1', - phase: 'retained', - reloadRequired: true -} - -let userDataPath: string - -beforeEach(() => { - userDataPath = mkdtempSync(join(tmpdir(), 'orca-wsl-move-orchestrator-connect-')) - vi.spyOn(console, 'warn').mockImplementation(() => {}) -}) -afterEach(() => { - vi.restoreAllMocks() - rmSync(userDataPath, { recursive: true, force: true }) -}) - -/** The orchestrator over a real host service, readying hosts as the wiring does. */ -function orchestratorOverHost(connector: ReturnType) { - const service = new WslHostService({ - userDataPath: () => userDataPath, - isEnabled: () => true, - hasServerFiles: () => true, - publish: () => {}, - pairingChanged: () => {}, - ready: () => {}, - removed: () => {} - }) - service.attach(connector) - const isHostReady = (distro: string): boolean => - service - .getStatuses() - .some( - (status) => status.distro.toLowerCase() === distro.toLowerCase() && status.state === 'ready' - ) - const environmentId = 'wsl:ubuntu' - // The driver's own reach for the host, in the orchestrator's (automatic) mode. - const driverConnect = vi.fn((distro: string) => - connectWslMoveHostService(service, userDataPath, distro, 'automatic') - ) - const orchestrator = new WslMoveOrchestrator({ - now: () => 1_000, - appVersion: '1.0.0', - autoMoveEnabled: () => true, - autoMovesBridgeProjects: () => true, - prepareHost: (distro, stillWanted) => - prepareWslMoveHost( - { - readRunningDistros: async () => ({ status: 'confirmed', distros: ['Ubuntu'] }), - isHostReady, - removalPending: () => wslHostRemovalPending(userDataPath, distro), - hasHost: () => service.recordForEnvironment(environmentId) !== null, - connect: () => service.connect(environmentId), - add: () => service.add({ distro, startIfStopped: false }) - }, - distro, - stillWanted - ), - hostRemovalPending: (distro) => wslHostRemovalPending(userDataPath, distro), - hasHost: () => service.recordForEnvironment(environmentId) !== null, - isDistroInUse: () => true, - isHostReady, - preview: async (distro) => { - await driverConnect(distro) - return { - outcome: 'ready', - distro, - repoIds: ['r1'], - folderWorkspaceIds: [], - changed: [], - heldByHost: [], - refused: [], - blockers: [] - } - }, - start: async (distro) => { - await driverConnect(distro) - return moved - }, - finishedMove: () => null, - checkMovedDistro: async () => 'present', - releaseFinishedMove: async () => ({ released: false, reason: 'not-moved' }), - load: () => new Map(), - save: () => {}, - schedule: () => () => {}, - track: () => {}, - publish: () => {}, - runAsUserAct: (_distro, act) => act() - }) - return { orchestrator, service, driverConnect } -} - -describe('the orchestrator readying a host, then the driver reaching it (E9)', () => { - it('never reconnects a host that is already serving', async () => { - const connector = makeConnector([UBUNTU]) - const { orchestrator, service, driverConnect } = orchestratorOverHost(connector) - await service.add({ distro: 'Ubuntu' }) - connector.connect.mockClear() - await orchestrator.trigger('Ubuntu', 'project-opened') - expect(orchestrator.list()[0]?.state).toEqual({ kind: 'done', migrationId: 'm1' }) - // Preview and start each reached the host through the automatic path. - expect(driverConnect).toHaveBeenCalledTimes(2) - expect(connector.connect).not.toHaveBeenCalled() - expect(connector.disconnect).not.toHaveBeenCalled() - }) - - it("adds a bridge user's host once, and the driver's automatic reach adds no connect", async () => { - const connector = makeConnector([UBUNTU]) - const { orchestrator, service } = orchestratorOverHost(connector) - expect(service.recordForEnvironment('wsl:ubuntu')).toBeNull() - await orchestrator.trigger('Ubuntu', 'project-opened') - expect(service.recordForEnvironment('wsl:ubuntu')).not.toBeNull() - expect(connector.connect).toHaveBeenCalledTimes(1) - expect(orchestrator.list()[0]?.state).toEqual({ kind: 'done', migrationId: 'm1' }) - }) -}) diff --git a/src/main/managed-server/lifecycle/orcad-decommission-released-peer-recovery.test.ts b/src/main/managed-server/lifecycle/orcad-decommission-released-peer-recovery.test.ts deleted file mode 100644 index f64c1f23f1d..00000000000 --- a/src/main/managed-server/lifecycle/orcad-decommission-released-peer-recovery.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Frozen released-client logic verifies that older desktops can safely recover newer stop journals. -import { describe, expect, it } from 'vitest' -import { - coreOrcadActivationRecord, - serializeOrcadActivationRecord, - type OrcadActivationRecord -} from '../activation/orcad-activation-record' -import { - parseOrcadActivationTransaction, - serializeOrcadActivationTransaction -} from '../activation/orcad-activation-transaction' -import { - createOrcadDecommissionTransaction, - withOrcadDecommissionProcessExited, - withOrcadDecommissionStopDispatched, - type OrcadDecommissionTransaction -} from './orcad-decommission-transaction' -import { FakeOrcadHost, NEW, OLD } from '../activation/orcad-activation-host-test-harness' -import { compareAppVersions } from '../../../shared/app-version' -import type { OrcadManagedStopRequest } from '../../../shared/orcad-stop-request' - -const T = new Date('2026-02-02T00:00:00.000Z') -const ID = '7f1c2a7e-6c1b-4a8e-9f0e-0a1b2c3d4e5f' -const NEWER_APP = '1.6.0' -const RELEASED_APP = '1.5.0' -const before: OrcadActivationRecord & { active: string } = { - ...FakeOrcadHost.newRecord(), - active: NEW, - activeAppVersion: NEWER_APP -} -const request: OrcadManagedStopRequest = { - schemaVersion: 1, - transactionId: ID, - version: NEW, - runtimeId: 'runtime-1', - instance: { pid: 42, startedAtMs: 5, nonce: 'nonce', lockPath: '/home/u/.orca/orcad.lock' }, - retireIdleDaemon: true -} -const prepared = createOrcadDecommissionTransaction({ - transactionId: ID, - recordBefore: before, - now: T -}) -const dispatched = withOrcadDecommissionStopDispatched(prepared, request, T) -const exited = withOrcadDecommissionProcessExited(dispatched, T) - -type ReleasedJournal = { - phase: string - request: unknown - activeVersion: string - recordBefore: OrcadActivationRecord - recordAfter: OrcadActivationRecord -} - -const sameCore = (a: OrcadActivationRecord, b: OrcadActivationRecord): boolean => - serializeOrcadActivationRecord(coreOrcadActivationRecord(a)) === - serializeOrcadActivationRecord(coreOrcadActivationRecord(b)) - -/** Frozen released parser: unknown fields are dropped. */ -function releasedParse(raw: string): ReleasedJournal { - const journal: ReleasedJournal = JSON.parse(raw) - if ((journal.phase === 'prepared') !== (journal.request === null)) { - throw new Error('Stop request is inconsistent with phase') - } - const b = journal.recordBefore - const deactivated = { - schemaVersion: 1, - active: null, - previous: b.active, - activatedAt: null, - snapshot: null - } - if (JSON.stringify(journal.recordAfter) !== JSON.stringify(deactivated)) { - throw new Error('recordAfter is not the deactivated recordBefore') - } - return journal -} - -/** Frozen released recovery record. */ -function releasedRecover( - journal: ReleasedJournal, - current: OrcadActivationRecord -): OrcadActivationRecord { - if (sameCore(current, journal.recordAfter)) { - return current - } - if (!sameCore(current, journal.recordBefore)) { - throw new Error('record changed') - } - return journal.phase === 'prepared' ? current : journal.recordAfter -} - -/** Frozen released auto-update planner. */ -function releasedAutoUpdate(record: OrcadActivationRecord, candidate: string): 'update' | 'skip' { - if (record.active === candidate || record.rolledBackFrom === candidate) { - return 'skip' - } - return record.activeAppVersion && compareAppVersions(record.activeAppVersion, RELEASED_APP) > 0 - ? 'skip' - : 'update' -} - -function releasedPeerAfterRecovery(raw: string): 'update' | 'skip' { - return releasedAutoUpdate(releasedRecover(releasedParse(raw), before), OLD) -} - -describe('a released desktop recovering a newer desktop’s interrupted stop', () => { - it.each([ - ['prepared', prepared], - ['stop-dispatched', dispatched], - ['process-exited', exited] - ])('never downgrades the host from %s', (_phase, transaction: OrcadDecommissionTransaction) => { - expect(releasedPeerAfterRecovery(serializeOrcadActivationTransaction(transaction))).toBe('skip') - }) - - it('would downgrade it if the journal named the dispatched phase at the top level', () => { - expect(releasedPeerAfterRecovery(JSON.stringify(exited))).toBe('update') - }) - - it('still reads the real phase and request on a current desktop', () => { - for (const transaction of [prepared, dispatched, exited]) { - const raw = serializeOrcadActivationTransaction({ ...transaction, fenceToken: 'token-1' }) - expect(JSON.parse(raw)).toMatchObject({ - phase: 'prepared', - request: null, - fenceToken: 'token-1' - }) - expect(parseOrcadActivationTransaction(raw)).toEqual({ state: 'ok', transaction }) - } - }) - - it.each([ - [ - 'filed twice', - { - ...JSON.parse(serializeOrcadActivationTransaction(exited)), - phase: 'process-exited', - request - } - ], - [ - 'for another transaction', - { - ...JSON.parse(serializeOrcadActivationTransaction(exited)), - dispatched: { - phase: 'process-exited', - request: { ...request, transactionId: '5a7e1f0c-3b2d-4e6f-9a8b-7c6d5e4f3a2b' } - } - } - ] - ])('reads a dispatched stop %s as unreadable', (_name, journal) => { - expect(parseOrcadActivationTransaction(JSON.stringify(journal))).toMatchObject({ - state: 'unreadable' - }) - }) -}) diff --git a/src/main/memory/collector-darwin-footprint.test.ts b/src/main/memory/collector-darwin-footprint.test.ts deleted file mode 100644 index 14667a3b55b..00000000000 --- a/src/main/memory/collector-darwin-footprint.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as NodeFs from 'node:fs' -import os from 'node:os' -import type { MemorySnapshotStore } from './collector' - -const MB = 1024 * 1024 - -const { appMetricsMock, runProcessMock, execMock, listRegisteredPtysMock, helperPresent } = - vi.hoisted(() => ({ - helperPresent: { value: false }, - appMetricsMock: vi.fn(), - runProcessMock: vi.fn(), - execMock: vi.fn(), - listRegisteredPtysMock: vi.fn() - })) - -vi.mock('child_process', () => ({ - exec: (cmd: string, opts: unknown, cb: (err: Error | null, out: { stdout: string }) => void) => - execMock(cmd, opts, cb) -})) -vi.mock('@orca/process-host', () => ({ - runProcess: (spec: { program: string }) => runProcessMock(spec) -})) -vi.mock('node:fs', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - existsSync: (path: string) => - path.endsWith('orca-process-footprint') ? helperPresent.value : actual.existsSync(path) - } -}) -vi.mock('./pty-registry', () => ({ listRegisteredPtys: listRegisteredPtysMock })) - -const emptyStore = { - getFolderWorkspace: () => undefined, - getProjectGroups: () => [], - getWorktreeMeta: () => undefined, - getRepo: () => undefined -} satisfies MemorySnapshotStore - -async function loadCollector() { - vi.resetModules() - const { setAppEnvironment } = await import('../../shared/app-environment') - setAppEnvironment({ - getPath: () => process.cwd(), - getAppPath: () => process.cwd(), - getVersion: () => '0.0.0-test', - isPackaged: () => false, - onWillQuit: () => {}, - exit: () => {}, - getAppMetrics: appMetricsMock - }) - const { setLocalDaemonPidReader } = await import('./local-daemon-pid') - setLocalDaemonPidReader(() => 200) - return await import('./collector') -} - -const TOP_OUTPUT = [ - 'Processes: 4 total, 1 running, 3 sleeping, 900 threads', - 'PhysMem: 31G used (3G wired), 1G unused.', - '', - 'PID MEM ', - '300 2048K', - '200 30M ', - '101 427M+', - '100 140M ' -].join('\n') - -describe('darwin memory footprint', () => { - beforeEach(() => { - vi.restoreAllMocks() - helperPresent.value = false - runProcessMock.mockReset() - vi.spyOn(os, 'platform').mockReturnValue('darwin') - appMetricsMock.mockReturnValue([ - { pid: 100, type: 'Browser', cpu: { percentCPUUsage: 0 }, memory: { workingSetSize: 0 } }, - { pid: 101, type: 'GPU', cpu: { percentCPUUsage: 0 }, memory: { workingSetSize: 0 } } - ]) - // RSS overstates main (shared framework pages) and understates GPU (no graphics memory). - execMock.mockImplementation((_cmd, _opts, cb) => - cb(null, { - stdout: ['100 1 0 307200', '101 100 0 96256', '200 1 0 71680', '300 200 0 5120'].join('\n') - }) - ) - runProcessMock.mockImplementation((spec: { program: string }) => - Promise.resolve( - spec.program === '/usr/bin/top' - ? { code: 0, signal: null, stdout: TOP_OUTPUT, stderr: '', timedOut: false } - : { code: 1, signal: null, stdout: '', stderr: '', timedOut: false } - ) - ) - listRegisteredPtysMock.mockReturnValue([ - { ptyId: 'pty-1', worktreeId: 'repo::/repo', sessionId: 's-1', paneKey: null, pid: 300 } - ]) - }) - - it('reports footprint for app processes, counts the detached daemon, and keeps PTYs with worktrees', async () => { - const { collectMemorySnapshot } = await loadCollector() - - const snapshot = await collectMemorySnapshot(emptyStore) - - expect(snapshot.processMemoryMetric).toBe('footprint') - expect(snapshot.app.main.memory).toBe(140 * MB) - expect(snapshot.app.other.memory).toBe((427 + 30) * MB) - expect(snapshot.worktrees[0].sessions[0].memory).toBe(2 * MB) - expect(snapshot.totalMemory).toBe((140 + 427 + 30 + 2) * MB) - }) - - it('prefers the bundled helper over top when it is present', async () => { - helperPresent.value = true - runProcessMock.mockImplementation((spec: { program: string }) => - Promise.resolve( - spec.program.endsWith('orca-process-footprint') - ? { - code: 0, - signal: null, - stdout: `100 ${150 * MB}\n101 ${400 * MB}\n`, - stderr: '', - timedOut: false - } - : { code: 1, signal: null, stdout: '', stderr: '', timedOut: false } - ) - ) - const { collectMemorySnapshot } = await loadCollector() - - const snapshot = await collectMemorySnapshot(emptyStore) - - expect(snapshot.processMemoryMetric).toBe('footprint') - expect(snapshot.app.main.memory).toBe(150 * MB) - expect(runProcessMock.mock.calls.map(([spec]) => spec.program)).not.toContain('/usr/bin/top') - }) - - it('falls back to top when the bundled helper fails', async () => { - helperPresent.value = true - const { collectMemorySnapshot } = await loadCollector() - - const snapshot = await collectMemorySnapshot(emptyStore) - - expect(snapshot.app.main.memory).toBe(140 * MB) - }) - - it('falls back to RSS when top is unavailable', async () => { - runProcessMock.mockRejectedValue(new Error('spawn ENOENT')) - const { collectMemorySnapshot } = await loadCollector() - - const snapshot = await collectMemorySnapshot(emptyStore) - - expect(snapshot.processMemoryMetric).toBe('rss') - expect(snapshot.app.main.memory).toBe(300 * MB) - }) -}) diff --git a/src/main/native-chat/agent-session-journal/adopted-directory-probe.test.ts b/src/main/native-chat/agent-session-journal/adopted-directory-probe.test.ts index 8d7a2151a39..76e388a6dbb 100644 --- a/src/main/native-chat/agent-session-journal/adopted-directory-probe.test.ts +++ b/src/main/native-chat/agent-session-journal/adopted-directory-probe.test.ts @@ -4,7 +4,6 @@ import { Readable } from 'node:stream' import { setImmediate } from 'node:timers/promises' import { expect, it, vi } from 'vitest' import { ADOPTED_DIRECTORY_PROBE_SOURCE } from './adopted-directory-probe' -import { normalizeRuntimePathForComparison } from '../../../shared/cross-platform-path' async function helper( workspaceCwd: string, @@ -55,64 +54,6 @@ async function helper( return { reads, output, code: process.exitCode } } -it('visits only selected native prefixes, stopping at a match or exhaustion', async () => { - const result = await helper('/link/pkg', '/physical/pkg', { - '/link/pkg': '/elsewhere', - '/link': '/physical', - '/': '/' - }) - expect(result).toEqual({ - code: 0, - output: '{"version":1,"matched":true}', - reads: ['/link/pkg', '/link'] - }) - const noMatch = await helper('/link/pkg', '/foreign', { - '/link/pkg': '/elsewhere', - '/link': '/physical', - '/': '/' - }) - expect(noMatch.reads).toEqual(['/link/pkg', '/link', '/']) - expect(noMatch.output).toBe('{"version":1,"matched":false}') - const unavailable = await helper('/missing/pkg', '/foreign', { '/': '/' }) - expect(unavailable.code).toBe(1) - expect(unavailable.output).toBe('') - const posixBackslash = await helper('/link/pkg', '/physical\\/pkg', { - '/link/pkg': '/elsewhere', - '/link': '/physical\\' - }) - expect(posixBackslash.output).toBe('{"version":1,"matched":true}') - expect(posixBackslash.reads).toEqual(['/link/pkg', '/link']) -}) - -it('keeps native drive/UNC, NFC and POSIX comparison semantics, without source dot collapsing', async () => { - for (const [workspace, resolved, source, windows] of [ - ['C:\\link\\pkg', 'D:\\Physical\\pkg', 'd:/physical/pkg/', true], - ['\\\\host\\share\\link', '\\\\host\\share\\Physical', '//HOST/share/physical', true], - ['C:\\link', '\\\\wsl.localhost\\Ubuntu\\Repo', '//wsl$/ubuntu/Repo', true], - ['C:\\link', '\\\\wsl.localhost\\Ubuntu\\Repo', '//wsl$/ubuntu/repo', true], - ['/link', '/café', '/cafe\u0301', false], - ['/link', '/Physical', '/physical', false], - ['/link', '/physical', '/other/../physical', false] - ] as const) { - const path = windows ? win32 : posix - const paths: Record = { [workspace]: resolved } - for (let parent = path.dirname(workspace); !(parent in paths); parent = path.dirname(parent)) { - paths[parent] = parent - if (path.dirname(parent) === parent) { - break - } - } - const result = await helper(workspace, source, paths, windows) - expect(result.code).toBe(0) - expect(JSON.parse(result.output).matched).toBe( - normalizeRuntimePathForComparison(resolved) === normalizeRuntimePathForComparison(source) - ) - } - const wsl = await helper('\\\\wsl.localhost\\Ubuntu\\repo', '/repo', {}, true) - expect(wsl.code).toBe(1) - expect(wsl.reads).toEqual([]) -}) - it.each(['é', '界', '😀'])( 'preserves %s in W and C across byte delivery schedules', async (character) => { diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-queued-stop-row.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-queued-stop-row.test.ts deleted file mode 100644 index f9df44cf9bd..00000000000 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-queued-stop-row.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { agentSessionFailureFact } from '../../../shared/agent-session/failures/agent-session-failure' -import { agentSessionFailureWords } from '../../../shared/agent-session/failures/agent-session-failure-words' -import { agentJournalSubmissionKey } from '../../../shared/agent-session/journal/agent-session-journal-item-key' -import type { AgentJournalAnsweredTurnIdentity } from '../../../shared/agent-session/journal/agent-session-journal-types' -import { projectStructuredAgentSessionMessages } from '../../../shared/agent-session/view/structured-agent-session-message-projection' -import { HOST_TEST_SESSION, HOST_TEST_THREAD } from './structured-agent-session-host-test-data' -import { - createQueuedMessageTestRig, - eventually, - type QueuedMessageTestRig -} from './structured-agent-session-queued-message-rig.test-fixture' -import { openRigTurnFor } from './structured-agent-session-queued-rig-turn.test-fixture' -import { structuredQueuePauses } from './structured-agent-session-queued-pause' - -let rig: QueuedMessageTestRig - -beforeEach(async () => { - vi.useFakeTimers() - rig = await createQueuedMessageTestRig({ restartable: true }) -}) - -afterEach(async () => { - try { - await rig.dispose() - } finally { - vi.useRealTimers() - } -}) - -async function queuedDraft(text: string): Promise { - const queued = await rig.send(text, 'queue-if-active').result - if (!queued.ok || !('queued' in queued.value)) { - throw new Error('expected a queued receipt') - } - return queued.value.queued.messageId -} - -function journal(sessionId = HOST_TEST_SESSION) { - const open = rig.host.collaboratorsForTests().sessions.get(sessionId)?.journal - if (!open) { - throw new Error('expected the conversation open') - } - return open -} - -async function expectHeld(reason: string | null, ...draftIds: string[]): Promise { - await vi.advanceTimersByTimeAsync(250) - for (const draftId of draftIds) { - expect(await rig.handoff(draftId)).toBeUndefined() - } - expect(await rig.queuePause()).toEqual(reason === null ? null : { reason }) -} - -function withdraw(clientMessageId: string, answeredInTurn?: AgentJournalAnsweredTurnIdentity) { - return rig.host.settleLateDispatch({ - sessionId: HOST_TEST_SESSION, - clientMessageId, - state: 'rejected', - ...agentSessionFailureWords(agentSessionFailureFact('cancelled'), { surface: 'rejection' }), - ...(answeredInTurn ? { answeredInTurn } : {}) - }) -} - -async function send(clientMessageId: string) { - return (await rig.host.journalSnapshot(HOST_TEST_SESSION)).submissions.find( - (entry) => entry.clientMessageId === clientMessageId - ) -} - -async function drawn(clientMessageId: string) { - const { items, submissions } = await rig.host.journalSnapshot(HOST_TEST_SESSION) - const message = projectStructuredAgentSessionMessages(items, [], submissions).find( - (entry) => entry.id === agentJournalSubmissionKey(clientMessageId) - ) - return message ? { stoppedBeforeStart: message.stoppedBeforeStart } : null -} - -it('a card handed over and stopped before the agent confirms it reads as sent, never sent again; only the cards behind it send', async () => { - const working = await rig.workingSend() - await openRigTurnFor(rig, working) - const steered = await queuedDraft('sent now into the turn') - const opening = await queuedDraft('opens its own turn') - const last = await queuedDraft('waiting behind them') - await rig.sendNow(steered) - await eventually(async () => expect((await rig.handoff(steered))?.handedOverAt).toBeDefined()) - const steer = await rig.handoffId(steered) - - await rig.stop() - await withdraw(steer, { - turn: { - provider: 'codex', - threadId: HOST_TEST_THREAD, - turnId: `turn-of-${working}`, - ordinal: 0 - }, - via: 'steer' - }) - await rig.settleAccepted(working, 'stopped') - await openRigTurnFor(rig, working, 'interrupted') - await expectHeld('stopped', opening, last) - expect(await rig.drafts()).toEqual([ - { messageId: opening, state: 'waiting' }, - { messageId: last, state: 'waiting' } - ]) - - expect(await send(steer)).toMatchObject({ dispatchState: 'unknown', recovered: true }) - expect(await drawn(steer)).toEqual({ stoppedBeforeStart: undefined }) - - expect(await rig.resume()).toMatchObject({ ok: true, value: { resumed: true } }) - await eventually(async () => expect((await rig.handoff(opening))?.handedOverAt).toBeDefined()) - const opener = await rig.handoffId(opening) - await rig.stop() - await withdraw(opener) - await expectHeld('stopped', last) - expect(await rig.drafts()).toEqual([{ messageId: last, state: 'waiting' }]) - - expect(await send(opener)).toMatchObject({ dispatchState: 'rejected' }) - expect(await drawn(opener)).toEqual({ stoppedBeforeStart: true }) - - expect(await rig.resume()).toMatchObject({ ok: true, value: { resumed: true } }) - await eventually(async () => expect(await rig.handoff(last)).toBeDefined()) - await rig.settleAccepted(await rig.handoffId(last), 'last') - expect([await rig.handoffId(steered), (await send(opener))?.dispatchState]).toEqual([ - steer, - 'rejected' - ]) - - await rig.restartHostProcess() - await rig.queuePause() - expect(structuredQueuePauses(journal()).map((pause) => pause.reason)).not.toContain('restarted') -}) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-repeated-stop.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-repeated-stop.test.ts deleted file mode 100644 index ffaa8610295..00000000000 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-repeated-stop.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { beforeEach, describe, expect, it, type Mock } from 'vitest' -import { agentJournalItemKey } from '../../../shared/agent-session/journal/agent-session-journal-item-key' -import { AGENT_JOURNAL_THREAD_SCOPE } from '../../../shared/agent-session/journal/agent-session-journal-types' -import type { AgentJournalTurnLifecycleState } from '../../../shared/agent-session/journal/agent-session-journal-types' -import { openTestAgentSessionRecordStore } from '../../runtime/agent-session/records/agent-session-record-store-test-harness' -import { openTestJournalHostDatabase } from '../agent-session-journal/journal-host-database-test-support' -import type { StructuredAgentSessionAdapter } from './structured-agent-session-adapter' -import { structuredAgentSessionStopNoteIdentity } from './structured-agent-session-command-turn' -import { StructuredAgentSessionHost } from './structured-agent-session-host' -import { createStructuredAgentSessionLogger } from './structured-agent-session-logger' -import { - adapter, - attach, - CALLER, - envelope, - hostTestState, - replaceHostTestState -} from './structured-agent-session-host-test-harness' -import { - HOST_TEST_NOW as NOW, - HOST_TEST_SESSION as SESSION, - HOST_TEST_THREAD as THREAD -} from './structured-agent-session-host-test-data' -import { startAgent } from './structured-agent-session-restart-interruption-test-harness' -import { NO_STRUCTURED_AGENTS } from './structured-agent-session-adapter-router-test-support' - -const REQUESTED = 'Cancellation requested.' - -let host: StructuredAgentSessionHost -let acquire: Mock -let cancelTurn: Mock - -beforeEach(() => { - ;({ host, acquire, cancelTurn } = hostTestState()) -}) - -function stopTurn(turnId = 'turn-1') { - return host.cancel(CALLER, { envelope: envelope('agentSession.cancel', { turnId }), turnId }) -} - -async function turn(state: AgentJournalTurnLifecycleState): Promise { - const events = acquire.mock.calls.at(-1)?.[0].events - if (!events) { - throw new Error('turn requires an acquired session') - } - events.appendItem( - { provider: 'codex', threadId: THREAD, turnId: 'turn-1', ordinal: 900 }, - { kind: 'turn', turnId: 'turn-1', state }, - { turnScope: AGENT_JOURNAL_THREAD_SCOPE } - ) - await host.flushStreamedEvents(SESSION) -} - -async function statusRows(): Promise { - return (await host.journalSnapshot(SESSION)).items.flatMap((item) => - item.body.kind === 'status' ? [item.body.text] : [] - ) -} - -describe('a Stop pressed again', () => { - it('writes nothing once the first Stop has settled, however late it arrives', async () => { - await attach() - await turn('running') - expect(await stopTurn()).toMatchObject({ ok: true, value: { cancelled: true } }) - await turn('interrupted') - cancelTurn.mockResolvedValueOnce({ cancelled: false }) - - expect(await stopTurn()).toMatchObject({ ok: true, value: { cancelled: false } }) - expect(await statusRows()).toEqual([REQUESTED]) - }) - - it('naming no turn, keys its row by the turn running, as a Stop naming it does', async () => { - await attach() - await turn('running') - const unnamed = () => host.cancel(CALLER, { envelope: envelope('agentSession.cancel', {}) }) - const [first, second, named] = await Promise.all([unnamed(), unnamed(), stopTurn()]) - - expect(first).toMatchObject({ ok: true, value: { cancelled: true } }) - expect(second).toMatchObject({ ok: true }) - expect(named).toMatchObject({ ok: true }) - const { items } = await host.journalSnapshot(SESSION) - expect(items.flatMap((item) => (item.body.kind === 'status' ? [item.itemId] : []))).toEqual([ - agentJournalItemKey(structuredAgentSessionStopNoteIdentity('turn-1')) - ]) - expect(await statusRows()).toEqual([REQUESTED]) - }) - - it('writes nothing more on a restarted host', async () => { - const { root, store } = hostTestState() - await attach() - await turn('running') - await stopTurn() - await turn('interrupted') - await host.flushAllStreamedEvents() - await store.renewLeases([]) - const relaunchedStore = await openTestAgentSessionRecordStore(root) - const relaunched = new StructuredAgentSessionHost({ - agents: NO_STRUCTURED_AGENTS, - logger: createStructuredAgentSessionLogger(), - store: relaunchedStore, - adapter: adapter(), - journalDatabase: openTestJournalHostDatabase(root), - claimKeyId: 'key-1', - mintSpawnToken: () => 'spawn-next', - probeOwner: async () => ({ outcome: 'pid-absent' }), - now: () => NOW + 1 - }) - replaceHostTestState({ store: relaunchedStore, host: relaunched }) - host = relaunched - await startAgent({ host, store: relaunchedStore }) - cancelTurn.mockResolvedValueOnce({ cancelled: false }) - - expect(await stopTurn()).toMatchObject({ ok: true, value: { cancelled: false } }) - expect(await statusRows()).toEqual([REQUESTED]) - }) -}) - -describe('a Stop of a turn that ended by itself', () => { - it('writes no row', async () => { - await attach() - await turn('running') - await turn('completed') - cancelTurn.mockResolvedValueOnce({ cancelled: false }) - - expect(await stopTurn()).toMatchObject({ ok: true, value: { cancelled: false } }) - expect(await statusRows()).toEqual([]) - }) -}) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-resolved-append.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-resolved-append.test.ts index 1058de1def9..b8000107c27 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-resolved-append.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-resolved-append.test.ts @@ -97,23 +97,6 @@ describe('resolved revisions', () => { expect(rowText()).toBe('abc') }) - it('publishes a revision in the operation that writes it, within the same reservation', async () => { - const deferred = createDeferredStructuredAgentSessionEventSink(testEventSinkLogging()) - const bytes = estimateStructuredAgentSessionItemBytes(ROW, text('a')) - deferred.sink.tryReviseResolvedItemAndPublish?.(bytes, appendSuffix('a'), { - turnScope: AGENT_JOURNAL_THREAD_SCOPE - }) - deferred.sink.tryReviseResolvedItemAndPublish?.(bytes, () => null, { - turnScope: AGENT_JOURNAL_THREAD_SCOPE - }) - const target = journalTarget() - const published: string[] = [] - vi.mocked(target.publish).mockImplementation(() => published.push(rowText())) - deferred.bind(target) - await expect(deferred.drained()).resolves.toEqual({ ok: true }) - expect(published).toEqual(['a']) - }) - it('refuses a resolved write larger than the reservation it was admitted with', async () => { const deferred = createDeferredStructuredAgentSessionEventSink(testEventSinkLogging()) const bytes = estimateStructuredAgentSessionItemBytes(ROW, text('a')) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-startup-failure-exit.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-startup-failure-exit.test.ts deleted file mode 100644 index cc1ec993d01..00000000000 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-startup-failure-exit.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { AgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-record' -import { - agentSessionLeaseFixture, - agentSessionRecordFixture -} from '../../../shared/agent-session/lifecycle/agent-session-record.test-fixture' -import { structuredAgentSessionCompactBody } from './structured-agent-session-command-turn' -import { - settleStructuredAgentSessionChildExit, - type StructuredAgentSessionChildExitContext, - type StructuredAgentSessionChildExitSession -} from './structured-agent-session-child-exit' -import { recordingStructuredAgentSessionLogger } from './structured-agent-session-logger-test-support' -import type { AgentJournalSubmission } from '../../../shared/agent-session/journal/agent-session-journal-types' -import { exitJournalDouble } from './structured-agent-session-settlement-double.test-fixture' - -const SESSION = 'session-1' -const GENERATION = 'generation-1' -const REASON = 'claude stream-json exited (code 1): session limit reached' -const STARTUP_TEXT = 'Claude stopped before it finished starting. Send your message to try again.' - -function startedSession( - submissions?: () => readonly Partial[] -): StructuredAgentSessionChildExitSession & { - appendLifecycleBatch: ReturnType -} { - const appendLifecycleBatch = vi.fn(async () => ({ epoch: 'epoch-1', sequence: 1 })) - return { - child: { generation: GENERATION, fence: 7, phase: 'ready' }, - appendLifecycleBatch, - journal: exitJournalDouble({ - items: () => [], - appendLifecycleBatch, - ...(submissions ? { submissions } : {}) - }) - } -} - -function contextFor(session: StructuredAgentSessionChildExitSession) { - let record: AgentSessionRecord = agentSessionRecordFixture( - agentSessionLeaseFixture({ - sessionId: SESSION, - runtimeKind: 'native', - runtimeFence: 7, - handoffStage: null, - ownerProcess: { hostId: 'local', pid: 4242, processStartTimeMs: 1, spawnToken: 'spawn-1' }, - reservedSpawnToken: 'spawn-1', - claimStatus: 'live', - unreconciled: false - }) - ) - const context: StructuredAgentSessionChildExitContext = { - logger: recordingStructuredAgentSessionLogger().logger, - store: { - getRecord: () => record, - transitionHandoff: async ( - _sessionId: string, - transition: (current: AgentSessionRecord) => AgentSessionRecord - ) => (record = transition(record)) - }, - sessions: new Map([[SESSION, session]]), - flushLifecycle: async () => ({ ok: true }), - generationEnded: vi.fn(), - serialize: async (_sessionId: string, task: () => Promise) => task(), - now: () => 1 - } - return context -} - -const ended = { - type: 'ended' as const, - sessionId: SESSION, - reason: REASON, - cause: 'unexpected-exit' as const, - fence: 7, - acquisitionGeneration: GENERATION -} - -describe('a provider that ends before it finished starting', () => { - it('keeps an ordinary idle exit silent', async () => { - const session = startedSession() - - await settleStructuredAgentSessionChildExit(contextFor(session), ended) - - expect(session.child).toBeNull() - expect(session.appendLifecycleBatch).not.toHaveBeenCalled() - }) - - it("reads a start that failed off the host's own phase", async () => { - const session = { - ...startedSession(), - child: { generation: GENERATION, fence: 7, phase: 'starting' as const } - } - - await settleStructuredAgentSessionChildExit(contextFor(session), { - ...ended, - failure: { kind: 'providerExited', detail: { text: REASON, audience: 'log' } } - }) - - expect(session.appendLifecycleBatch).toHaveBeenCalledWith( - expect.objectContaining({ - mutations: [ - expect.objectContaining({ - identity: { provider: 'orca', clientMessageId: `start-failure:${GENERATION}` }, - // Keep stderr in logs, outside the failure sentence. - body: { - kind: 'status', - text: STARTUP_TEXT, - tone: 'error', - failure: { - kind: 'providerStartFailed', - detail: { text: REASON, audience: 'log' } - } - } - }) - ] - }) - ) - }) - - it('names /compact as the next step when the start that failed was carrying it', async () => { - const base = startedSession(() => [ - { clientMessageId: 'compact-1', dispatchState: 'pending' as const } - ]) - const session = { - ...base, - child: { generation: GENERATION, fence: 7, phase: 'starting' as const }, - journal: { ...base.journal, itemBody: () => structuredAgentSessionCompactBody() } - } - - await settleStructuredAgentSessionChildExit(contextFor(session), ended) - - const text = 'Claude stopped before it finished starting. Run /compact again.' - expect(session.appendLifecycleBatch).toHaveBeenCalledWith( - expect.objectContaining({ - dispatches: [ - expect.objectContaining({ - clientMessageId: 'compact-1', - state: 'rejected', - reason: text, - fence: 7, - recovered: true - }) - ], - mutations: [expect.objectContaining({ body: expect.objectContaining({ text }) })] - }) - ) - }) -}) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-stopped-history.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-stopped-history.test.ts deleted file mode 100644 index a917e610cc2..00000000000 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-stopped-history.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest' -import { agentSessionFailureFact } from '../../../shared/agent-session/failures/agent-session-failure' -import { agentSessionFailureWords } from '../../../shared/agent-session/failures/agent-session-failure-words' -import type { AgentJournalMessageItem } from '../../../shared/agent-session/journal/agent-session-journal-types' -import { STOPPED_MESSAGE_INTERRUPTED_MARKER } from '../../../shared/agent-session/lifecycle/agent-session-stopped-history' -import { AgentJournalSubmissionSchema } from '../../../shared/agent-session/journal/agent-session-journal-submission-schema' -import { projectStructuredAgentSessionMessages } from '../../../shared/agent-session/view/structured-agent-session-message-projection' -import type { AgentSessionJournal } from '../agent-session-journal/journal-store' -import { - createQueuedMessageTestRig, - eventually, - type QueuedMessageTestRig -} from './structured-agent-session-queued-message-rig.test-fixture' -import { HOST_TEST_SESSION as SESSION } from './structured-agent-session-host-test-data' - -let rig: QueuedMessageTestRig - -async function start(): Promise { - rig = await createQueuedMessageTestRig({ restartable: true, starting: true }) -} - -afterEach(async () => { - await rig.dispose() -}) - -function journal(): AgentSessionJournal { - const open = rig.host.collaboratorsForTests().sessions.get(SESSION)?.journal - if (!open) { - throw new Error('the conversation is not open') - } - return open -} - -function text(body: AgentJournalMessageItem): string { - return body.blocks.flatMap((block) => (block.type === 'text' ? [block.text] : [])).join('\n') -} - -function carrying(stopped: string[], own: string): string { - return ( - stopped.map((words) => `${words}\n\n${STOPPED_MESSAGE_INTERRUPTED_MARKER}\n\n`).join('') + own - ) -} - -function dispatched(): string[] { - return rig.dispatch.mock.calls.map(([input]) => text(input.body)) -} - -async function heldWhileStarting(...texts: string[]): Promise { - const ids: string[] = [] - for (const body of texts) { - const { id, result } = rig.send(body) - expect(await result).toMatchObject({ ok: true }) - ids.push(id) - } - await eventually(() => - expect(rig.host.collaboratorsForTests().sessions.get(SESSION)?.child?.phase).toBe('starting') - ) - return ids -} - -async function stopWhileStarting(...texts: string[]): Promise { - const ids = await heldWhileStarting(...texts) - expect(await rig.stop()).toMatchObject({ ok: true }) - for (const id of ids) { - expect(await rig.submission(id)).toMatchObject({ - dispatchState: 'rejected', - rejection: { kind: 'cancelled', neverReachedAgent: true } - }) - } - return ids -} - -async function nextSend(body: string): Promise { - const before = rig.dispatch.mock.calls.length - const { id, result } = rig.send(body) - expect(await result).toMatchObject({ ok: true }) - if (rig.host.collaboratorsForTests().sessions.get(SESSION)?.child?.phase !== 'ready') { - await rig.proveStart() - } - await eventually(() => expect(rig.dispatch.mock.calls.length).toBe(before + 1)) - return id -} - -async function transcript(): Promise { - const page = await rig.host.history({ sessionId: SESSION, direction: 'tail' }) - if (!page.ok) { - throw new Error('history refused') - } - expect(page.page.submissions.some((entry) => 'carries' in entry)).toBe(false) - page.page.submissions.forEach((entry) => AgentJournalSubmissionSchema.parse(entry)) - return projectStructuredAgentSessionMessages(page.page.items, [], page.page.submissions).map( - (message) => - `${message.blocks.map((block) => ('text' in block ? block.text : '')).join('')}${ - message.stoppedBeforeStart ? ' (stopped)' : '' - }` - ) -} - -describe('a Stop withdraws what the agent never got, and the next send delivers it once', () => { - it('carries them at the front of the next send, in order, and never again', async () => { - await start() - const [first, second] = await stopWhileStarting('first', 'second') - const carrier = await nextSend('what did I send?') - await rig.settleAccepted(carrier, 'carrier') - await nextSend('after') - - expect(dispatched()).toEqual([carrying(['first', 'second'], 'what did I send?'), 'after']) - expect(journal().stoppedHistoryCarried().get(carrier)).toEqual([first, second]) - - expect(await transcript()).toEqual([ - 'first (stopped)', - 'second (stopped)', - 'Stopped manually', - 'Cancellation requested.', - 'what did I send?', - 'after' - ]) - }) - - it('a carrier refused before any write owes them again; one in doubt never re-sends them', async () => { - await start() - await stopWhileStarting('first') - rig.dispatch.mockResolvedValueOnce({ - state: 'rejected', - ...agentSessionFailureWords(agentSessionFailureFact('writeFailed'), { surface: 'rejection' }) - }) - await nextSend('refused') - const doubtful = await nextSend('in doubt') - await rig.host.settleLateDispatch({ - sessionId: SESSION, - clientMessageId: doubtful, - state: 'unknown', - reason: 'reply lost' - }) - await nextSend('after') - - expect(dispatched()).toEqual([ - carrying(['first'], 'refused'), - carrying(['first'], 'in doubt'), - 'after' - ]) - }) -}) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-stopping-journal.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-stopping-journal.test.ts deleted file mode 100644 index fee5437fc5e..00000000000 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-stopping-journal.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { codexProviderHandle } from '../../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' -import { mkdtemp, rm } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import { agentSessionFailureFact } from '../../../shared/agent-session/failures/agent-session-failure' -import { agentSessionFailureWords } from '../../../shared/agent-session/failures/agent-session-failure-words' -import { - AGENT_JOURNAL_THREAD_SCOPE, - type AgentJournalRenderItem, - type AgentJournalStatusItem -} from '../../../shared/agent-session/journal/agent-session-journal-types' -import type { AgentJournalItemIdentity } from '../../../shared/agent-session/journal/agent-session-journal-identity' -import { createTrackedJournalOpener } from '../agent-session-journal/journal-host-database-test-support' -import type { AgentSessionJournal } from '../agent-session-journal/journal-store' -import { structuredAgentSessionStopNoteIdentity } from './structured-agent-session-command-turn' -import { structuredAgentSessionStopping } from './structured-agent-session-stopping' - -const FENCE = 1 -const journals = createTrackedJournalOpener() -let root: string -let journal: AgentSessionJournal - -beforeEach(async () => { - root = await mkdtemp(join(tmpdir(), 'orca-stopping-journal-')) - journal = await journals.open({ - identity: { - sessionId: 'session-1', - workspaceId: 'workspace-1', - hostId: 'local', - agent: 'codex', - providerHandle: codexProviderHandle('thread-1') - }, - stateDirectory: join(root, 'session-1') - }) -}) - -afterEach(async () => { - await journals.closeAll() - await rm(root, { recursive: true, force: true }) -}) - -function turnIdentity(turnId: string): AgentJournalItemIdentity { - return { provider: 'codex', threadId: 'thread-1', turnId, ordinal: 0 } -} - -function turn(turnId: string, state: 'running' | 'completed') { - return journal.appendItem( - turnIdentity(turnId), - { - kind: 'turn', - turnId, - state, - startedAt: 1, - ...(state === 'running' ? {} : { completedAt: 2 }) - }, - { fence: FENCE, turnScope: AGENT_JOURNAL_THREAD_SCOPE } - ) -} - -const unconfirmed: AgentJournalStatusItem = { - kind: 'status', - ...agentSessionFailureWords(agentSessionFailureFact('cancelUnconfirmed'), { surface: 'row' }) -} - -function stopping(): boolean { - return structuredAgentSessionStopping(journal, journal.snapshot().items) -} - -describe("a person's Stop's Stopping", () => { - it('holds through an answer that says the Stop stopped nothing, until the turn ends', async () => { - await turn('turn-1', 'running') - await journal.appendStopEvent({ reason: 'user-stop', turnId: 'turn-1' }, FENCE) - await journal.appendItem(structuredAgentSessionStopNoteIdentity('turn-1'), unconfirmed, { - fence: FENCE, - turnScope: AGENT_JOURNAL_THREAD_SCOPE - }) - expect(stopping()).toBe(true) - - await turn('turn-1', 'completed') - - expect(stopping()).toBe(false) - }) - - it('reads only the journal from the running turn on, however long the chat before it', async () => { - for (let index = 0; index < 40; index += 1) { - await journal.appendItem( - { provider: 'orca', clientMessageId: `earlier-${index}` }, - { kind: 'status', text: `earlier row ${index}` }, - { fence: FENCE, turnScope: AGENT_JOURNAL_THREAD_SCOPE } - ) - } - await turn('turn-1', 'running') - await journal.appendStopEvent({ reason: 'user-stop', turnId: 'turn-1' }, FENCE) - const items = journal.snapshot().items - const turnIndex = items.findIndex((item) => item.body.kind === 'turn') - let lowestRead = items.length - const counted: AgentJournalRenderItem[] = [] - items.forEach((item, index) => - Object.defineProperty(counted, index, { - enumerable: true, - get: () => { - lowestRead = Math.min(lowestRead, index) - return item - } - }) - ) - expect(structuredAgentSessionStopping(journal, counted)).toBe(true) - expect(lowestRead).toBe(turnIndex) - }) - - it('holds while a Stop that named no turn settles, and after only for the turn it bound', async () => { - await journal.appendStopEvent({ reason: 'user-stop' }, FENCE) - const settle = journal.stopMarks.beginSettle() - expect(stopping()).toBe(true) - - journal.stopMarks.settled(settle) - expect(stopping()).toBe(false) - await turn('turn-1', 'running') - expect(stopping()).toBe(false) - - journal.stopMarks.settled(journal.stopMarks.beginSettle(), 'turn-1') - expect(stopping()).toBe(true) - }) - - it('tells its settle readers of each edge, which writes no row and so is no commit', async () => { - await journal.appendStopEvent({ reason: 'user-stop' }, FENCE) - const before = journal.stopMarks.revision() - let commits = 0 - let edges = 0 - journal.observeCommits(() => { - commits += 1 - }) - journal.stopMarks.observeSettleEdges(() => { - edges += 1 - }) - const cursor = journal.cursor() - - journal.stopMarks.settled(journal.stopMarks.beginSettle()) - - expect(journal.stopMarks.revision()).toBe(before + 2) - expect(edges).toBe(2) - expect(commits).toBe(0) - expect(journal.cursor()).toEqual(cursor) - }) -}) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-turn-completion-child-work.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-turn-completion-child-work.test.ts deleted file mode 100644 index cddaa4e1845..00000000000 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-turn-completion-child-work.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { - AgentJournalRenderItem, - AgentJournalSubmission, - AgentJournalTurnLifecycle -} from '../../../shared/agent-session/journal/agent-session-journal-types' -import { agentJournalSubmissionKey } from '../../../shared/agent-session/journal/agent-session-journal-item-key' -import type { AgentChildWorkLivenessCandidate } from '../../../shared/agent-status/child-work/agent-status-child-work-liveness' -import { projectStructuredAgentSessionStatusState } from '../../../shared/agent-session/view/structured-agent-session-projection' -import { StructuredAgentSessionTurnCompletionFeed } from './structured-agent-session-turn-completion-feed' - -const LOCATION = { - executionHostId: 'local', - wslDistro: null, - workspaceId: 'workspace-1', - workspaceKind: 'git-worktree' -} as const -const SUBAGENT = { kind: 'agent', state: 'working' } as const -const DEV_SERVER = { kind: 'command', state: 'working' } as const - -function turnItem(lifecycle: AgentJournalTurnLifecycle, sequence = 2): AgentJournalRenderItem { - return { - itemId: `codex:turn:${lifecycle.turnId}`, - revision: 1, - sequence, - observedAt: sequence, - body: { kind: 'turn', ...lifecycle } - } -} - -// A turn the agent opened itself keys its lifecycle row to itself. -function selfStarted(lifecycle: AgentJournalTurnLifecycle): AgentJournalTurnLifecycle { - return { ...lifecycle, userItemId: `codex:turn:${lifecycle.turnId}` } -} - -function harness(childWork: AgentChildWorkLivenessCandidate[]) { - let items: AgentJournalRenderItem[] = [] - let submissions: AgentJournalSubmission[] = [] - let sequence = 0 - const journal = { cursor: () => ({ epoch: 'epoch-1', sequence }) } - const feed = new StructuredAgentSessionTurnCompletionFeed({ - sessions: new Map([['session-1', { journal, params: { location: LOCATION } }]]), - now: () => 1_700, - readStatusState: () => projectStructuredAgentSessionStatusState(items, submissions), - readChildWork: () => childWork - }) - const outcomes: [string, string][] = [] - feed.subscribe({ - id: 'sub', - emit: (event) => { - if (event.type === 'completion') { - outcomes.push([event.completion.turnId, event.completion.outcome]) - } - } - }) - const commit = (next: AgentJournalRenderItem[], nextSubmissions: AgentJournalSubmission[]) => { - items = next - submissions = nextSubmissions - sequence += 1 - feed.observe('session-1') - } - return { - outcomes, - feed, - commit, - settle: (lifecycle: AgentJournalTurnLifecycle) => { - commit([turnItem({ ...lifecycle, state: 'running', outcome: undefined })], []) - commit([turnItem(lifecycle)], []) - } - } -} - -describe('a turn that settles while its child work is still live', () => { - it('holds the finish until the last subagent drains, then announces that turn once', () => { - const childWork: AgentChildWorkLivenessCandidate[] = [SUBAGENT] - const h = harness(childWork) - h.settle({ turnId: 'turn-1', state: 'completed', outcome: 'success' }) - expect(h.outcomes).toEqual([]) - - childWork[0] = { ...SUBAGENT, state: 'done' } - h.feed.observe('session-1') - h.feed.observe('session-1') - expect(h.outcomes).toEqual([['turn-1', 'success']]) - }) - - it('holds a coordinator waiting on workers through a background orca orchestration check --wait', () => { - const h = harness([{ ...DEV_SERVER, operation: { input: 'orca orchestration check --wait' } }]) - h.settle({ turnId: 'turn-1', state: 'completed', outcome: 'success' }) - expect(h.outcomes).toEqual([]) - }) - - it('does not hold a failed turn', () => { - const h = harness([SUBAGENT]) - h.settle({ turnId: 'turn-1', state: 'completed', outcome: 'failure' }) - expect(h.outcomes).toEqual([['turn-1', 'failure']]) - }) - - it('lets a dev server hold only a turn the agent started itself, with nothing steered into it', () => { - const user = harness([DEV_SERVER]) - user.settle({ turnId: 'turn-1', state: 'completed', outcome: 'success' }) - expect(user.outcomes).toEqual([['turn-1', 'success']]) - - const woken = harness([DEV_SERVER]) - woken.settle(selfStarted({ turnId: 'wake-1', state: 'completed', outcome: 'success' })) - expect(woken.outcomes).toEqual([]) - - const steered = harness([DEV_SERVER]) - const steer: AgentJournalSubmission = { - clientMessageId: 'm1', - fence: 1, - payloadFingerprint: 'm1', - providerItemId: null, - reason: null, - submittedAt: 10, - resolvedAt: 20, - handoverRecorded: true, - dispatchState: 'accepted', - answeredInTurn: { turnItemId: 'codex:turn:wake-1', via: 'steer' } - } - const message: AgentJournalRenderItem = { - itemId: agentJournalSubmissionKey('m1'), - revision: 0, - sequence: 1, - observedAt: 1, - body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'm1' }] } - } - steered.commit( - [message, turnItem(selfStarted({ turnId: 'wake-1', state: 'running' }))], - [steer] - ) - steered.commit( - [ - message, - turnItem(selfStarted({ turnId: 'wake-1', state: 'completed', outcome: 'success' })) - ], - [steer] - ) - expect(steered.outcomes).toEqual([['wake-1', 'success']]) - }) -}) diff --git a/src/main/native-chat/agent-session-wire/structured-rewind-cut.test.ts b/src/main/native-chat/agent-session-wire/structured-rewind-cut.test.ts index dd511aa111a..8e2291671d7 100644 --- a/src/main/native-chat/agent-session-wire/structured-rewind-cut.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-rewind-cut.test.ts @@ -13,7 +13,7 @@ import type { } from '../../../shared/agent-session/journal/agent-session-journal-identity' import { codexProviderHandle } from '../../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding' import { createTrackedJournalOpener } from '../agent-session-journal/journal-host-database-test-support' -import { isStructuredRewindTarget, structuredRewindCut } from './structured-rewind-cut' +import { structuredRewindCut } from './structured-rewind-cut' const IDENTITY: AgentSessionJournalIdentity = { sessionId: 'session-1', @@ -88,42 +88,3 @@ it("keeps a subagent row with the main turn it arrived in, across the subagent's expect(cut!.kept.map(({ body }) => body)).not.toContainEqual(text('child in b')) expect(cut!.kept.map(({ body }) => body)).not.toContainEqual(mainB) }) - -it('rewinds only to a message that opened its turn, and still cuts at one already saved', async () => { - const journal = await journals.open({ identity: IDENTITY, stateDirectory: root }) - const claude = (uuid: string) => ({ provider: 'claude' as const, sessionId: 'claude-1', uuid }) - const key = (uuid: string) => agentJournalItemKey(claude(uuid)) - const user = (uuid: string): AgentJournalItemBody => ({ - kind: 'message', - role: 'user', - blocks: [{ type: 'text', text: uuid }] - }) - const turnRecord = async (id: string, opener: string) => { - const turn = { provider: 'orca' as const, clientMessageId: id } - await journal.appendItem( - turn, - { kind: 'turn', turnId: id, state: 'completed', userItemId: key(opener) }, - { fence: 1, turnScope: AGENT_JOURNAL_THREAD_SCOPE } - ) - return { kind: 'turn' as const, turnItemId: agentJournalItemKey(turn) } - } - const first = await turnRecord('turn-1', 'm1') - await journal.appendItem(claude('m1'), user('m1'), { fence: 1, turnScope: first }) - await journal.appendItem(claude('a1'), text('a1'), { fence: 1, turnScope: first }) - const second = await turnRecord('turn-2', 'm2') - await journal.appendItem(claude('m2'), user('m2'), { - fence: 1, - turnScope: AGENT_JOURNAL_THREAD_SCOPE - }) - await journal.appendItem(claude('a2'), text('a2'), { fence: 1, turnScope: second }) - await journal.appendItem(claude('m3'), user('m3'), { fence: 1, turnScope: second }) - - expect(['m1', 'm2', 'm3'].map((uuid) => isStructuredRewindTarget(journal, key(uuid)))).toEqual([ - true, - true, - false - ]) - expect( - structuredRewindCut(journal, { itemId: key('m3'), provider: 'claude', nativeId: 'claude-1' }) - ).toMatchObject({ beforeMessage: { uuid: 'm3' } }) -}) diff --git a/src/main/orcad/orcad-launch-contract.test.ts b/src/main/orcad/orcad-launch-contract.test.ts deleted file mode 100644 index 29f4b370ef3..00000000000 --- a/src/main/orcad/orcad-launch-contract.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { mkdtemp, rm } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { describe, expect, it, vi } from 'vitest' -import { parseArgs } from './orcad-command-arguments' - -import { startOrcadWithLifecycle } from './orcad-lifecycle' -import { - beginAgentSessionRuntimeRecord, - readAgentSessionRuntimeEnds -} from '../runtime/agent-session/records/agent-session-runtime-end-record' - -describe('parseArgs', () => { - it('keeps help-looking values as values', () => { - expect(parseArgs(['--bind', '--help'])).toEqual({ bind: '--help' }) - expect(parseArgs(['--pairing-address', '-h'])).toEqual({ pairingAddress: '-h' }) - expect(parseArgs(['--project-root', '--help'])).toEqual({ projectRoot: '--help' }) - expect(parseArgs(['--bind', '--help', '-h'])).toEqual({ bind: '--help', help: true }) - }) - - it('still rejects invalid arguments and incompatible flags with help', () => { - expect(() => parseArgs(['--help', '--unknown'])).toThrow('Unknown argument: --unknown') - expect(() => parseArgs(['--help', '--port', 'bad'])).toThrow('--port expects an integer') - expect(() => parseArgs(['--help', '--bind'])).toThrow('--bind expects a value') - expect(() => parseArgs(['--help', '--recipe-json'])).toThrow( - 'Recipe JSON output requires --project-root.' - ) - for (const flag of ['--no-pairing', '--mobile-pairing']) { - expect(() => parseArgs(['--help', '--grant-desktop-control', flag])).toThrow( - '--grant-desktop-control applies only to the default runtime pairing offer' - ) - } - }) - - it('rejects contradictory pairing flags with the desktop serve message', () => { - expect(() => parseArgs(['--no-pairing', '--mobile-pairing'])).toThrow( - 'Use either --mobile-pairing or --no-pairing, not both.' - ) - for (const flag of ['--no-pairing', '--mobile-pairing']) { - expect(() => parseArgs(['--recipe-json', '--project-root', '/work/app', flag])).toThrow( - `Recipe JSON output requires runtime pairing; remove ${flag}.` - ) - } - }) - - it('rejects --bind with no value rather than silently binding the default', () => { - expect(() => parseArgs(['--bind'])).toThrow('--bind expects a value') - expect(() => parseArgs(['--bind', '--json'])).not.toThrow() - }) -}) - -describe('orcad lifecycle cleanup', () => { - it('uninstalls registered runtime resources when startup fails', async () => { - const cleanupRuntime = vi.fn(async () => {}) - const cleanupHost = vi.fn(async () => {}) - - await expect( - startOrcadWithLifecycle(async (registerCleanup) => { - registerCleanup(cleanupRuntime) - await Promise.resolve() - throw new Error('startup failed') - }, cleanupHost) - ).rejects.toThrow('startup failed') - - expect(cleanupRuntime).toHaveBeenCalledOnce() - expect(cleanupHost).toHaveBeenCalledExactlyOnceWith(true) - }) - - it('preserves the startup error when rollback also fails', async () => { - const startupError = new Error('bind failed') - const cleanupError = new Error('daemon stop failed') - const cleanupRuntime = vi.fn(async () => {}) - const cleanupHost = vi.fn(async () => { - throw cleanupError - }) - const report = vi.spyOn(console, 'error').mockImplementation(() => {}) - - try { - await expect( - startOrcadWithLifecycle(async (registerCleanup) => { - registerCleanup(cleanupRuntime) - throw startupError - }, cleanupHost) - ).rejects.toBe(startupError) - expect(report).toHaveBeenCalledWith('[orcad] startup cleanup failed:', cleanupError) - } finally { - report.mockRestore() - } - }) - - it('coalesces concurrent and repeated normal stops', async () => { - const cleanupRuntime = vi.fn(async () => {}) - const cleanupHost = vi.fn(async () => {}) - const handle = await startOrcadWithLifecycle(async (registerCleanup) => { - registerCleanup(cleanupRuntime) - return { readiness: 'ready' } - }, cleanupHost) - - await Promise.all([handle.stop(), handle.stop()]) - await handle.stop() - - expect(cleanupRuntime).toHaveBeenCalledOnce() - expect(cleanupHost).toHaveBeenCalledOnce() - }) - - it("records the runtime's end before its stop waits on anything", async () => { - const directory = await mkdtemp(join(tmpdir(), 'orcad-runtime-end-')) - try { - beginAgentSessionRuntimeRecord(directory, 'orcad-runtime', 1) - let endSeenByCleanup: string | undefined - const handle = await startOrcadWithLifecycle( - async (registerCleanup) => { - registerCleanup(async () => { - endSeenByCleanup = readAgentSessionRuntimeEnds(directory)?.get('orcad-runtime') - }) - return {} - }, - vi.fn(async () => {}) - ) - - await handle.stop() - - expect(endSeenByCleanup).toBe('quit') - } finally { - await rm(directory, { recursive: true, force: true }) - } - }) - - it('keeps the host aware of failed runtime teardown so it cannot release profile admission', async () => { - const failure = new Error('profile writer still running') - const cleanupHost = vi.fn(async () => {}) - const handle = await startOrcadWithLifecycle(async (registerCleanup) => { - registerCleanup(async () => { - throw failure - }) - return {} - }, cleanupHost) - await expect(handle.stop()).rejects.toBe(failure) - expect(cleanupHost).toHaveBeenCalledExactlyOnceWith(false) - }) -}) diff --git a/src/main/persistence/migrating-orcad-catalog/orcad-migration-export-holds.test.ts b/src/main/persistence/migrating-orcad-catalog/orcad-migration-export-holds.test.ts index 538dffbc7a8..7ff2525de28 100644 --- a/src/main/persistence/migrating-orcad-catalog/orcad-migration-export-holds.test.ts +++ b/src/main/persistence/migrating-orcad-catalog/orcad-migration-export-holds.test.ts @@ -1,19 +1,15 @@ import { mkdtempSync, rmSync, statSync, readFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it } from 'vitest' import { getDefaultWorkspaceSession } from '../../../shared/constants' import type { WorkspaceSessionState } from '../../../shared/workspace-session/workspace-session-state-types' -import { writeFileDurableSync } from '../../filesystem/durable-file-write' import { getTerminalScrollbackSnapshotPath, - readTerminalScrollbackStoredBytesSync, writeTerminalScrollbackSnapshotSync } from '../../pty/scrollback/terminal-scrollback-snapshots' import { deleteRemovedTerminalScrollbackSnapshots } from '../loading-store/terminal-session-cleanup' import { deleteRemovedTerminalScrollbackSnapshotsAsync } from '../../pty/scrollback/terminal-scrollback-snapshot-async-migration' -import { extractSessionOwnersForTransfer } from '../../orca-profiles/profile-session-owner-transfer' -import type { SleepingAgentSessionRecord } from '../../../shared/agent-session/lifecycle/agent-session-resume' const roots: string[] = [] afterEach(() => { @@ -45,21 +41,6 @@ function sessionWithRef(ref: string | null): WorkspaceSessionState { } describe('scrollback store reads and retention for migration export', () => { - it('reads stored bytes for a ref and none for an unknown one', () => { - const { storage: store } = storage() - const ref = writeTerminalScrollbackSnapshotSync({ - tabId: 'tab-1', - leafId: 'leaf-1', - buffer: 'saved output', - storage: store - }) - expect(ref).not.toBeNull() - expect(readTerminalScrollbackStoredBytesSync(ref ?? '', store)?.toString('utf8')).toBe( - 'saved output' - ) - expect(readTerminalScrollbackStoredBytesSync(`v1-${'0'.repeat(32)}`, store)).toBeNull() - }) - it.each(['sync', 'async'] as const)( 'keeps a %s-removed snapshot a pending export still reads', async (mode) => { @@ -94,59 +75,3 @@ describe('scrollback store reads and retention for migration export', () => { } ) }) - -describe.skipIf(process.platform === 'win32')('durable writes with a creation mode', () => { - it('creates the file with the requested mode', () => { - const { root } = storage() - const finalPath = join(root, 'state.json') - writeFileDurableSync(`${finalPath}.tmp`, finalPath, '{}', 0o600) - expect(statSync(finalPath).mode & 0o777).toBe(0o600) - }) -}) - -describe('session owner projection hooks', () => { - function sleeping(paneKey: string): SleepingAgentSessionRecord { - return { - paneKey, - worktreeId: 'repo-1::/srv/app', - agent: 'claude', - providerSession: { key: 'session_id', id: paneKey }, - prompt: 'resume me', - state: 'done', - capturedAt: 1, - updatedAt: 1 - } - } - - it('lets a transfer keep resumable sleeping agents and project focus scalars', () => { - const source: WorkspaceSessionState = { - ...getDefaultWorkspaceSession(), - sleepingAgentSessionsByPaneKey: { - 'tab-1:leaf-1': sleeping('tab-1:leaf-1'), - 'tab-2:leaf-1': sleeping('tab-2:leaf-1') - } - } - const projectSessionFocus = vi.fn() - const transferred = extractSessionOwnersForTransfer(source, { - mapOwnerKey: (ownerKey) => ownerKey, - mapWorktreeId: (id) => id, - projectSleepingAgentSession: (record) => (record.paneKey === 'tab-1:leaf-1' ? record : null), - projectSessionFocus - }) - expect(Object.keys(transferred.sleepingAgentSessionsByPaneKey ?? {})).toEqual(['tab-1:leaf-1']) - expect(projectSessionFocus).toHaveBeenCalledWith( - expect.objectContaining({ source, transferred }) - ) - }) - - it('drops sleeping agents when the projection does not opt in', () => { - const transferred = extractSessionOwnersForTransfer( - { - ...getDefaultWorkspaceSession(), - sleepingAgentSessionsByPaneKey: { 'tab-1:leaf-1': sleeping('tab-1:leaf-1') } - }, - { mapOwnerKey: (ownerKey) => ownerKey, mapWorktreeId: (id) => id } - ) - expect(transferred.sleepingAgentSessionsByPaneKey).toBeUndefined() - }) -}) diff --git a/src/main/persistence/terminal-topology/persistence-host-partitioned-ssh-pty-bindings.test.ts b/src/main/persistence/terminal-topology/persistence-host-partitioned-ssh-pty-bindings.test.ts deleted file mode 100644 index 4623241375c..00000000000 --- a/src/main/persistence/terminal-topology/persistence-host-partitioned-ssh-pty-bindings.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { - closeTestStores, - testState, - createStore, - createStoreWithSshRemotePtyLeases -} from '../persistence-test-harness' -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' - -import { rmSync, mkdtempSync } from 'node:fs' -import { join } from 'node:path' -import { tmpdir } from 'node:os' -import { ProfileStateSqliteAuthority } from '../profile-state/profile-state-sqlite-authority' -import type { WorkspaceSessionState } from '../../../shared/workspace-session/workspace-session-state-types' -import { getDefaultWorkspaceSession } from '../../../shared/constants' - -import { TEST_LEAF_1 } from '../persistence-session-fixtures' -import { PtyExitProof } from '../../pty/process-retirement/pty-exit-proof' - -const { trackMock, getCohortAtEmitMock } = vi.hoisted(() => ({ - trackMock: vi.fn(), - getCohortAtEmitMock: vi.fn() -})) - -vi.mock('electron', () => ({ - app: { - getPath: () => testState.dir - }, - safeStorage: { - isEncryptionAvailable: () => true, - encryptString: (plaintext: string) => Buffer.from(`encrypted:${plaintext}`, 'utf-8'), - decryptString: (ciphertext: Buffer) => { - const decoded = ciphertext.toString('utf-8') - if (!decoded.startsWith('encrypted:')) { - throw new Error('invalid ciphertext') - } - return decoded.slice('encrypted:'.length) - } - } -})) - -vi.mock('../../telemetry/client', () => ({ - track: trackMock -})) - -vi.mock('../../telemetry/cohort-classifier', () => ({ - getCohortAtEmit: getCohortAtEmitMock -})) - -describe('Store SSH remote PTY bindings across host partitions', () => { - beforeEach(() => { - testState.dir = mkdtempSync(join(tmpdir(), 'orca-test-')) - }) - - afterEach(async () => { - await closeTestStores() - rmSync(testState.dir, { recursive: true, force: true }) - }) - - const makeBoundHostSession = (ptyId: string | null): WorkspaceSessionState => ({ - ...getDefaultWorkspaceSession(), - activeRepoId: 'repo-1', - activeWorktreeId: 'repo-1::/worktree', - activeTabId: 'tab-1', - tabsByWorktree: { - 'repo-1::/worktree': [ - { - id: 'tab-1', - worktreeId: 'repo-1::/worktree', - title: 'Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1, - ptyId - } - ] - }, - terminalLayoutsByTabId: { - 'tab-1': { - root: { type: 'leaf', leafId: TEST_LEAF_1 }, - activeLeafId: TEST_LEAF_1, - expandedLeafId: null, - ptyIdsByLeafId: ptyId ? { [TEST_LEAF_1]: ptyId } : {} - } - } - }) - - it('persists an SSH PTY binding only in the SSH host partition', async () => { - const store = await createStore() - store.setWorkspaceSession(makeBoundHostSession(null), 'local') - store.setWorkspaceSession(makeBoundHostSession(null), 'ssh:ssh-1') - - await store.persistPtyBinding( - { - worktreeId: 'repo-1::/worktree', - tabId: 'tab-1', - leafId: TEST_LEAF_1, - ptyId: 'ssh:ssh-1@@remote-pty' - }, - 'ssh:ssh-1' - ) - - expect( - store.getWorkspaceSession('ssh:ssh-1').tabsByWorktree['repo-1::/worktree'][0]?.ptyId - ).toBe('ssh:ssh-1@@remote-pty') - expect( - store.getWorkspaceSession('local').tabsByWorktree['repo-1::/worktree'][0]?.ptyId - ).toBeNull() - }) - - it('rolls back a failed SSH PTY binding flush in the SSH host partition', async () => { - const store = await createStore() - store.setWorkspaceSession(makeBoundHostSession(null), 'local') - store.setWorkspaceSession(makeBoundHostSession(null), 'ssh:ssh-1') - store.flushOrThrow() - const flush = vi - .spyOn(ProfileStateSqliteAuthority.prototype, 'writeSerializedDomains') - .mockImplementationOnce(() => { - throw new Error('disk unavailable') - }) - - await expect( - store.persistPtyBinding( - { - worktreeId: 'repo-1::/worktree', - tabId: 'tab-1', - leafId: TEST_LEAF_1, - ptyId: 'ssh:ssh-1@@remote-pty' - }, - 'ssh:ssh-1' - ) - ).rejects.toThrow('disk unavailable') - flush.mockRestore() - - expect( - store.getWorkspaceSession('ssh:ssh-1').tabsByWorktree['repo-1::/worktree'][0]?.ptyId - ).toBeNull() - expect( - store.getWorkspaceSession('local').tabsByWorktree['repo-1::/worktree'][0]?.ptyId - ).toBeNull() - }) - - it('clears terminated SSH PTY bindings from the SSH partition and legacy local copy', async () => { - const store = createStoreWithSshRemotePtyLeases([ - { - targetId: 'ssh-1', - ptyId: 'remote-pty', - worktreeId: 'repo-1::/worktree', - tabId: 'tab-1', - leafId: TEST_LEAF_1, - state: 'attached' - } - ]) - const ptyId = 'ssh:ssh-1@@remote-pty' - store.setWorkspaceSession(makeBoundHostSession(ptyId), 'local') - store.setWorkspaceSession(makeBoundHostSession(ptyId), 'ssh:ssh-1') - - store.markSshRemotePtyLease('ssh-1', ptyId, PtyExitProof.from('exit-observed', undefined)) - - for (const hostId of ['local', 'ssh:ssh-1']) { - const session = store.getWorkspaceSession(hostId) - expect(session.tabsByWorktree['repo-1::/worktree'][0]?.ptyId).toBeNull() - expect(session.terminalLayoutsByTabId['tab-1']?.ptyIdsByLeafId).toEqual({}) - } - }) -}) diff --git a/src/main/persistence/terminal-topology/persistence-ssh-lease-tombstone-retention.test.ts b/src/main/persistence/terminal-topology/persistence-ssh-lease-tombstone-retention.test.ts deleted file mode 100644 index e44b4c8eab1..00000000000 --- a/src/main/persistence/terminal-topology/persistence-ssh-lease-tombstone-retention.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { - closeTestStores, - createStoreWithSshRemotePtyLeases, - testState -} from '../persistence-test-harness' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { mkdtempSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' - -import { TEST_LEAF_1 } from '../persistence-session-fixtures' -import { PtyExitProof } from '../../pty/process-retirement/pty-exit-proof' - -vi.mock('electron', () => ({ - app: { getPath: () => testState.dir }, - safeStorage: { isEncryptionAvailable: () => false } -})) - -vi.mock('../../telemetry/client', () => ({ track: vi.fn() })) -vi.mock('../../telemetry/cohort-classifier', () => ({ getCohortAtEmit: () => ({}) })) - -describe('operator-closed SSH lease tombstones', () => { - beforeEach(() => { - testState.dir = mkdtempSync(join(tmpdir(), 'orca-test-')) - }) - - afterEach(async () => { - await closeTestStores() - rmSync(testState.dir, { recursive: true, force: true }) - }) - - // Detached panes outgrow the lease’s frozen tab ID, so tab-qualified scrubbing cannot reach their binding. - async function storeWithDetachedPaneBinding(): Promise< - Awaited> - > { - const store = createStoreWithSshRemotePtyLeases([ - { - targetId: 'ssh-1', - ptyId: 'remote-pty', - worktreeId: 'wt1', - tabId: 'tab-old', - leafId: TEST_LEAF_1, - state: 'attached' - } - ]) - store.setWorkspaceSession({ - activeRepoId: 'r1', - activeWorktreeId: 'wt1', - activeTabId: 'tab-new', - tabsByWorktree: { - wt1: [ - { - id: 'tab-new', - worktreeId: 'wt1', - title: 'Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1, - ptyId: null - } - ] - }, - terminalLayoutsByTabId: { - 'tab-new': { - root: { type: 'leaf', leafId: TEST_LEAF_1 }, - activeLeafId: TEST_LEAF_1, - expandedLeafId: null, - ptyIdsByLeafId: { [TEST_LEAF_1]: 'ssh:ssh-1@@remote-pty' } - } - } - }) - return store - } - - it('keeps the tombstone while a binding the scrub could not reach still names the pty', async () => { - const store = await storeWithDetachedPaneBinding() - - store.markSshRemotePtyLease( - 'ssh-1', - 'ssh:ssh-1@@remote-pty', - PtyExitProof.from('exit-observed', undefined) - ) - - // The retained terminated lease must continue to refuse stale binding replay. - expect(store.getSshRemotePtyLeases('ssh-1')).toEqual([ - expect.objectContaining({ ptyId: 'remote-pty', state: 'terminated' }) - ]) - expect(store.getWorkspaceSession().terminalLayoutsByTabId['tab-new'].ptyIdsByLeafId).toEqual({ - [TEST_LEAF_1]: 'ssh:ssh-1@@remote-pty' - }) - }) - - it('keeps an operator-closed lease that still owes an undelivered stop', async () => { - const pendingKill = { incarnationId: 'inc-1', requestedAt: 1, attempts: 0 } - const store = createStoreWithSshRemotePtyLeases([ - { targetId: 'ssh-1', ptyId: 'remote-pty', state: 'attached', pendingKill } - ]) - - store.markSshRemotePtyLease( - 'ssh-1', - 'ssh:ssh-1@@remote-pty', - PtyExitProof.from('exit-observed', undefined) - ) - - expect(store.getSshRemotePtyLeases('ssh-1')).toEqual([ - expect.objectContaining({ ptyId: 'remote-pty', state: 'terminated', pendingKill }) - ]) - }) - - // Expired leases protect intentionally running shells from orphan sweeping; expiry does not prove exit. - it('keeps a superseded expired lease when a sibling pane is closed', async () => { - const store = createStoreWithSshRemotePtyLeases([ - { - targetId: 'ssh-1', - ptyId: 'remote-pty-1', - worktreeId: 'wt1', - tabId: 'tab1', - leafId: TEST_LEAF_1, - state: 'expired', - supersededBy: 'remote-pty-2' - }, - { - targetId: 'ssh-1', - ptyId: 'remote-pty-2', - worktreeId: 'wt1', - tabId: 'tab1', - leafId: TEST_LEAF_1, - state: 'attached' - }, - { targetId: 'ssh-1', ptyId: 'remote-pty-3', state: 'attached' } - ]) - - store.markSshRemotePtyLease( - 'ssh-1', - 'ssh:ssh-1@@remote-pty-3', - PtyExitProof.from('exit-observed', undefined) - ) - - expect(store.getSshRemotePtyLeases('ssh-1')).toEqual([ - expect.objectContaining({ - ptyId: 'remote-pty-1', - state: 'expired', - supersededBy: 'remote-pty-2' - }), - expect.objectContaining({ ptyId: 'remote-pty-2', state: 'attached' }) - ]) - }) - - it('retires every unreachable tombstone for the target, not only the one just closed', async () => { - const store = createStoreWithSshRemotePtyLeases([ - ...['remote-pty-1', 'remote-pty-2', 'remote-pty-3'].map((ptyId) => ({ - targetId: 'ssh-1', - ptyId, - state: 'terminated' as const - })), - { targetId: 'ssh-2', ptyId: 'other-pty', state: 'terminated' } - ]) - expect(store.getSshRemotePtyLeases()).toHaveLength(4) - - store.markSshRemotePtyLease( - 'ssh-1', - 'ssh:ssh-1@@remote-pty-1', - PtyExitProof.from('exit-observed', undefined) - ) - - expect(store.getSshRemotePtyLeases()).toEqual([ - expect.objectContaining({ targetId: 'ssh-2', ptyId: 'other-pty' }) - ]) - }) -}) diff --git a/src/main/plugins/hooks/plugin-hook-sites.test.ts b/src/main/plugins/hooks/plugin-hook-sites.test.ts deleted file mode 100644 index 769f6830e79..00000000000 --- a/src/main/plugins/hooks/plugin-hook-sites.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { PluginHookName } from '../../../shared/plugins/contributions/plugin-hook-contribution' -import type { PluginHookChainOutcome } from '../../../shared/plugins/workers/plugin-hook-chain' -import { setPluginHookRunner } from './plugin-hook-dispatch' -import { - applyWorkspaceCreatePluginHook, - createFolderWorkspaceWithPluginHooks, - notifyWorkspaceCreatedPluginHook -} from './workspace-create-plugin-hook' - -type Decide = (event: PluginHookName, payload: unknown) => PluginHookChainOutcome - -function installRunner(decide: Decide, wanted: readonly PluginHookName[] = []) { - const run = vi.fn(async (event: PluginHookName, payload: unknown) => decide(event, payload)) - setPluginHookRunner({ wants: (event) => wanted.includes(event), run }) - return run -} - -const rewrite = - (changes: Record): Decide => - (_event, payload) => ({ - kind: 'continue', - payload: typeof payload === 'object' ? { ...payload, ...changes } : payload - }) - -afterEach(() => { - setPluginHookRunner(null) -}) - -describe('workspace.create hook sites', () => { - const worktree = { id: 'repo-1', kind: 'git' as const } - const folder = { id: 'repo-2', kind: 'folder' as const } - - it('applies a changed name, branch and base to a worktree request', async () => { - const run = installRunner(rewrite({ name: 'feat-x', branch: 'me/feat-x', base: undefined }), [ - 'workspace.create.before' - ]) - const request = { name: 'x', baseBranch: 'origin/main', linkedPR: 4 } - await expect(applyWorkspaceCreatePluginHook(request, worktree)).resolves.toEqual({ - name: 'feat-x', - branchNameOverride: 'me/feat-x', - baseBranch: undefined, - linkedPR: 4 - }) - expect(run).toHaveBeenCalledWith('workspace.create.before', { - kind: 'worktree', - projectId: 'repo-1', - name: 'x', - base: 'origin/main' - }) - }) - - it('gives folder workspaces only a name to change', async () => { - const run = installRunner(rewrite({ name: 'notes', branch: 'ignored' }), [ - 'workspace.create.before' - ]) - await expect(applyWorkspaceCreatePluginHook({ name: 'n' }, folder)).resolves.toEqual({ - name: 'notes' - }) - expect(run).toHaveBeenCalledWith('workspace.create.before', { - kind: 'folder', - projectId: 'repo-2', - name: 'n' - }) - }) - - it('refuses the create on a deny and passes the request through when unhooked', async () => { - installRunner( - () => ({ kind: 'deny', pluginKey: 'acme.naming', reason: 'bad name' }), - ['workspace.create.before'] - ) - await expect(applyWorkspaceCreatePluginHook({ name: 'x' }, worktree)).rejects.toThrow( - 'workspace.create.before denied by plugin acme.naming: bad name' - ) - setPluginHookRunner(null) - const request = { name: 'x' } - await expect(applyWorkspaceCreatePluginHook(request, worktree)).resolves.toBe(request) - }) - - it('tells observers about created worktrees and folders without waiting', () => { - const run = installRunner(rewrite({}), ['workspace.create.after']) - notifyWorkspaceCreatedPluginHook({ - worktreeId: 'repo-1::/w', - path: '/w', - branch: 'refs/heads/feat', - folder: false - }) - notifyWorkspaceCreatedPluginHook({ - worktreeId: 'folder:f', - path: '/f', - branch: '', - folder: true - }) - expect(run.mock.calls).toEqual([ - [ - 'workspace.create.after', - { workspaceId: 'repo-1::/w', kind: 'worktree', path: '/w', branch: 'feat' } - ], - [ - 'workspace.create.after', - { workspaceId: 'folder:f', kind: 'folder', path: '/f', branch: '' } - ] - ]) - }) -}) - -describe('project-group folder workspace creates', () => { - it('run the before hook on the name, create once, then tell observers', async () => { - const run = installRunner(rewrite({ name: 'Notes (team)' }), [ - 'workspace.create.before', - 'workspace.create.after' - ]) - const create = vi.fn((input: { projectGroupId: string; name?: string }) => ({ - id: 'fw-1', - folderPath: '/notes', - name: input.name - })) - await expect( - createFolderWorkspaceWithPluginHooks({ projectGroupId: 'g1', name: 'Notes' }, create) - ).resolves.toMatchObject({ name: 'Notes (team)' }) - expect(create).toHaveBeenCalledOnce() - expect(run.mock.calls).toEqual([ - ['workspace.create.before', { kind: 'folder', projectId: 'g1', name: 'Notes' }], - [ - 'workspace.create.after', - { workspaceId: 'folder:fw-1', kind: 'folder', path: '/notes', branch: '' } - ] - ]) - }) - - it('keep Orca’s default name when unnamed and unchanged, and create nothing on a deny', async () => { - installRunner(rewrite({}), ['workspace.create.before']) - const create = vi.fn((input: { projectGroupId: string; name?: string }) => ({ - id: 'fw-2', - folderPath: '/x', - name: input.name - })) - await createFolderWorkspaceWithPluginHooks({ projectGroupId: 'g1' }, create) - expect(create).toHaveBeenCalledWith({ projectGroupId: 'g1' }) - installRunner( - () => ({ kind: 'deny', pluginKey: 'acme.n', reason: 'no' }), - ['workspace.create.before'] - ) - await expect( - createFolderWorkspaceWithPluginHooks({ projectGroupId: 'g1' }, create) - ).rejects.toThrow('denied by plugin acme.n') - expect(create).toHaveBeenCalledOnce() - }) -}) diff --git a/src/main/plugins/installation/plugin-catalog-install-guard.test.ts b/src/main/plugins/installation/plugin-catalog-install-guard.test.ts index 78c4648de06..bc79d141290 100644 --- a/src/main/plugins/installation/plugin-catalog-install-guard.test.ts +++ b/src/main/plugins/installation/plugin-catalog-install-guard.test.ts @@ -4,7 +4,6 @@ import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import type { PluginInstallSource } from '../../../shared/plugins/installation/plugin-install-lockfile' import { splitQualifiedPluginKey } from '../../../shared/plugins/catalog/plugin-marketplace' -import { pluginCatalogTakeoverError } from './plugin-catalog-install-guard' import { installPluginFromLocalPath, readPluginLockfile } from './plugin-install' import { writePluginLockfile } from './plugin-install-lockfile-store' import { installStagedPluginTree } from './plugin-install-staging' @@ -71,33 +70,6 @@ afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))) }) -describe('pluginCatalogTakeoverError', () => { - it('allows sources that are not catalog installs and the same catalog', () => { - expect(pluginCatalogTakeoverError('p.demo', undefined, null)).toBeNull() - expect( - pluginCatalogTakeoverError('p.demo', { kind: 'local-path', path: '/tmp/demo' }, null) - ).toBeNull() - expect(pluginCatalogTakeoverError('p.demo', marketplaceSource, catalog)).toBeNull() - }) - - it('refuses another catalog, another ref, and direct sources', () => { - const otherCatalog = - 'p.demo is installed from https://example.com/catalog.git; remove it first to install it from another catalog' - expect( - pluginCatalogTakeoverError('p.demo', marketplaceSource, { - url: 'https://example.com/other.git', - ref: 'main' - }) - ).toBe(otherCatalog) - expect( - pluginCatalogTakeoverError('p.demo', marketplaceSource, { url: catalog.url, ref: 'next' }) - ).toBe(otherCatalog) - expect(pluginCatalogTakeoverError('p.demo', marketplaceSource, null)).toBe( - 'p.demo is installed from https://example.com/catalog.git; remove it first to install it from a Git URL or local folder' - ) - }) -}) - describe('direct installs over a catalog install', () => { it('refuses a local folder and keeps the catalog lock source', async () => { const { pluginsDir, sourcePath, key } = await catalogInstall() diff --git a/src/main/plugins/plugin-secrets-store.test.ts b/src/main/plugins/plugin-secrets-store.test.ts index f7c0bd1d21c..deca42a1a7c 100644 --- a/src/main/plugins/plugin-secrets-store.test.ts +++ b/src/main/plugins/plugin-secrets-store.test.ts @@ -1,4 +1,4 @@ -import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises' +import { mkdtemp, readFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -25,7 +25,7 @@ vi.mock('electron', () => ({ } })) -import { PluginSecretsStore, pluginSecretsProtection } from './plugin-secrets-store' +import { PluginSecretsStore } from './plugin-secrets-store' const roots: string[] = [] const originalPlatform = process.platform @@ -57,24 +57,6 @@ afterEach(async () => { }) describe('PluginSecretsStore', () => { - it('encrypts, persists, decrypts, deletes, and isolates plugin namespaces', async () => { - const root = await tempRoot() - const first = new PluginSecretsStore(root, 'acme.first') - const second = new PluginSecretsStore(root, 'acme.second') - - expect(first.set('token', 'top-secret')).toEqual({ ok: true, value: true }) - expect(first.get('token')).toEqual({ ok: true, value: 'top-secret' }) - expect(second.get('token')).toEqual({ ok: true, value: null }) - const persisted = await readFile(join(root, 'acme.first', 'secrets.json.enc'), 'utf8') - expect(persisted).not.toContain('top-secret') - if (process.platform !== 'win32') { - expect((await stat(join(root, 'acme.first', 'secrets.json.enc'))).mode & 0o077).toBe(0) - } - - first.delete('token') - expect(first.get('token')).toEqual({ ok: true, value: null }) - }) - it('fails closed without OS encryption and writes no plaintext file', async () => { const root = await tempRoot() storageMocks.available = false @@ -86,60 +68,4 @@ describe('PluginSecretsStore', () => { code: 'ENOENT' }) }) - - it('reports corrupt or wrong-key ciphertext without returning bytes', async () => { - const root = await tempRoot() - const pluginDir = join(root, 'acme.demo') - await mkdir(pluginDir, { recursive: true }) - await writeFile( - join(pluginDir, 'secrets.json.enc'), - JSON.stringify({ - version: 1, - format: 'electron-safe-storage-v1', - ciphertexts: { token: Buffer.from('not-encrypted').toString('base64') } - }) - ) - const store = new PluginSecretsStore(root, 'acme.demo') - - expect(store.get('token')).toEqual({ ok: false, error: 'failed to decrypt stored secret' }) - }) - - it('refuses ciphertext that would exceed the bounded vault', async () => { - const root = await tempRoot() - storageMocks.encryptString.mockReturnValue(Buffer.alloc(6 * 1024 * 1024)) - const store = new PluginSecretsStore(root, 'acme.demo') - - expect(store.set('token', 'small-input')).toMatchObject({ ok: false }) - await expect(readFile(join(root, 'acme.demo', 'secrets.json.enc'))).rejects.toMatchObject({ - code: 'ENOENT' - }) - }) - - it('rejects unsafe plugin namespaces', async () => { - const root = await tempRoot() - expect(() => new PluginSecretsStore(root, 'constructor.demo')).toThrow('unsafe plugin key') - }) - - it('refuses writes on the Linux basic_text backend but still reads existing secrets', async () => { - const root = await tempRoot() - const store = new PluginSecretsStore(root, 'acme.linux') - Object.defineProperty(process, 'platform', { value: 'linux' }) - expect(store.set('token', 'sealed-earlier')).toEqual({ ok: true, value: true }) - expect(pluginSecretsProtection()).toEqual({ level: 'os', gap: null }) - - storageMocks.backend = 'basic_text' - - expect(pluginSecretsProtection()).toMatchObject({ level: 'obfuscated' }) - expect(store.set('token', 'next')).toMatchObject({ - ok: false, - error: expect.stringContaining('basic_text') - }) - expect(store.get('token')).toEqual({ ok: true, value: 'sealed-earlier' }) - }) - - it('reports unavailable protection when OS encryption is off', () => { - storageMocks.available = false - - expect(pluginSecretsProtection()).toMatchObject({ level: 'unavailable' }) - }) }) diff --git a/src/main/providers/agent-foreground-process-ps-scan-volume.test.ts b/src/main/providers/agent-foreground-process-ps-scan-volume.test.ts deleted file mode 100644 index af3c1b45486..00000000000 --- a/src/main/providers/agent-foreground-process-ps-scan-volume.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -// #6288: at the 750ms completion cadence, concurrent panes must share a scan and retain their own agent identity. -import type * as FsPromises from 'node:fs/promises' -import type * as DarwinTerminalNames from '../../shared/darwin-terminal-names' -import type * as FullReader from '../../shared/process-table-snapshot-reader' -import { - createProcessTablePsFixture, - type PsFixtureRow -} from '../../shared/process-table-ps-test-fixture' -import type * as Foreground from './agent-foreground-process' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { execFileMock, psScanCount } = vi.hoisted(() => ({ - execFileMock: vi.fn(), - psScanCount: { value: 0 } -})) - -vi.mock('child_process', () => ({ execFile: execFileMock })) - -// Inject /dev data without bypassing the production translator. -vi.mock('../../shared/darwin-terminal-names', async (importOriginal) => { - const original = await importOriginal() - return { - nameDarwinTerminals: (stdout: string, _deps: unknown, signal?: AbortSignal) => - psFixture.nameTerminals(original.nameDarwinTerminals, stdout, signal) - } -}) - -vi.mock('node:fs/promises', async (importOriginal) => ({ - ...(await importOriginal()), - readFile: async (path: string) => readProcStatMock(path) -})) - -let fullReader: typeof FullReader -let foreground: typeof Foreground - -const ACTIVE_POLL_INTERVAL_MS = 750 // mirrors agent-completion-coordinator.ts -const PANE_COUNT = 6 -const WINDOW_SECONDS = 30 -const TICKS = Math.floor((WINDOW_SECONDS * 1000) / ACTIVE_POLL_INTERVAL_MS) - -const shellPid = (pane: number): number => 100 + pane * 1000 - -// The shared system snapshot must contain every pane and its agent. -function processRows(): PsFixtureRow[] { - return Array.from({ length: PANE_COUNT }, (_, pane) => { - const shell = shellPid(pane) - const agent = shell + 1 - const base = { terminalMinor: pane, startTime: 'Thu Sep 3 16:02:01 2026', startTicks: 1_000 } - return [ - { ...base, pid: shell, ppid: 99, pgid: shell, tpgid: agent, stat: 'Ss', command: 'bash -i' }, - { - ...base, - pid: agent, - ppid: shell, - pgid: agent, - tpgid: agent, - stat: 'S+', - command: 'node /Users/dev/.nvm/versions/node/bin/codex' - } - ] - }).flat() -} -const psFixture = createProcessTablePsFixture(processRows) -const readProcStatMock = vi.fn((path: string) => psFixture.readProcStat(path)) - -function installCountingPsMock(): void { - execFileMock.mockImplementation( - ( - cmd: string, - args: string[], - _opts: unknown, - callback: (err: unknown, result: { stdout: string; stderr: string }) => void - ) => { - expect(cmd).toBe('ps') - expect(args[0]).toBe('-axo') - expect(args[1]).toBe( - process.platform === 'darwin' - ? 'pid=,ppid=,pgid=,tpgid=,stat=,tdev=,lstart=,command=' - : 'pid=,ppid=,pgid=,tpgid=,stat=,tty=,etimes=,command=' - ) - psScanCount.value += 1 - callback(null, { stdout: psFixture.render(args[1]), stderr: '' }) - } - ) -} - -describe.each(['darwin', 'linux'])('#6288 foreground ps scan volume (%s)', (hostPlatform) => { - let platform: PropertyDescriptor | undefined - - beforeEach(async () => { - execFileMock.mockReset() - readProcStatMock.mockClear() - psScanCount.value = 0 - platform = Object.getOwnPropertyDescriptor(process, 'platform') - Object.defineProperty(process, 'platform', { configurable: true, value: hostPlatform }) - // Column constants are chosen at import time, so each host needs a fresh module graph. - vi.resetModules() - fullReader = await import('../../shared/process-table-snapshot-reader') - fullReader.resetProcessTableSnapshotForTests() - foreground = await import('./agent-foreground-process') - vi.useFakeTimers({ toFake: ['Date'] }) - vi.setSystemTime(0) - }) - - afterEach(() => { - vi.useRealTimers() - if (platform) { - Object.defineProperty(process, 'platform', platform) - } - if (hostPlatform === 'darwin') { - expect(readProcStatMock).not.toHaveBeenCalled() - } else { - expect(readProcStatMock).toHaveBeenCalled() - } - // Production catches read errors; assert each path resolved against the fixture at read time. - for (const [index, result] of readProcStatMock.mock.results.entries()) { - expect(result.type, readProcStatMock.mock.calls[index][0]).toBe('return') - } - }) - - it('bounds ps scans by poll ticks, not by pane count, while resolving every pane', async () => { - installCountingPsMock() - - for (let tick = 0; tick < TICKS; tick++) { - vi.setSystemTime(tick * ACTIVE_POLL_INTERVAL_MS) - // All panes inspect concurrently within the tick (worst case for a busy relay). - const resolved = await Promise.all( - Array.from({ length: PANE_COUNT }, (_, pane) => - foreground.resolveAgentForegroundProcess(shellPid(pane), 'node') - ) - ) - expect(resolved.every((name) => name === 'codex')).toBe(true) - } - - const totalInspections = PANE_COUNT * TICKS - // The 500ms TTL expires each 750ms tick; concurrent panes should still share one scan. - expect(psScanCount.value).toBe(TICKS) - expect(psScanCount.value).toBeLessThan(totalInspections / 2) - }) -}) diff --git a/src/main/providers/local-pty/local-pty-pending-native-spawn.test.ts b/src/main/providers/local-pty/local-pty-pending-native-spawn.test.ts deleted file mode 100644 index 9f85b8bf5e5..00000000000 --- a/src/main/providers/local-pty/local-pty-pending-native-spawn.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { spawnLocalPty } from './local-pty-spawn' -import { cancelPendingLocalPtySpawns } from './local-pty-spawn-state' -import { pendingLocalPtySpawns, ptyProcesses } from './local-pty-provider-state' - -const { spawn, activate, destroy } = vi.hoisted(() => ({ - spawn: vi.fn(), - activate: vi.fn(), - destroy: vi.fn() -})) -vi.mock('./local-pty-runtime-spawn', () => ({ loadLocalPtyRuntimeSpawn: async () => spawn })) -vi.mock('../macos-tcc-login-shell', () => ({ prepareMacosTccLoginShell: async () => {} })) -vi.mock('./local-pty-finalize-environment', () => ({ - finalizeLocalPtySpawnEnvironment: () => null -})) -vi.mock('./local-pty-spawn-environment', () => ({ - buildLocalPtySpawnEnvironment: () => ({}), - enforceLocalPtySpawnEnvironmentOverrides() {} -})) -vi.mock('./local-pty-launch-plan', () => ({ - DeferredLocalPtyLaunchPlan: class {}, - createLocalPtyLaunchPlan: () => ({ - shellPath: '/bin/sh', - shellArgs: [], - effectiveCwd: '/tmp', - cwd: '/tmp', - windowsFallbackAttempts: [] - }) -})) -vi.mock('./local-pty-session-activation', () => ({ activateLocalPtySession: activate })) -vi.mock('./local-pty-termination', () => ({ destroyPtyProcess: destroy })) - -function createProcess() { - return { - pid: 12345, - process: '/bin/sh', - cols: 80, - rows: 24, - handleFlowControl: false, - onData: () => ({ dispose() {} }), - onExit: () => ({ dispose() {} }), - write() {}, - clear() {}, - pause() {}, - resume() {}, - resize: vi.fn(), - kill: vi.fn() - } -} - -function start(id = 'pending-bun-shell') { - return spawnLocalPty({ sessionId: id, cols: 80, rows: 24 }, () => ({})) -} - -beforeEach(() => { - vi.clearAllMocks() - activate.mockImplementation(({ id, proc }) => { - ptyProcesses.set(id, proc) - return { id, pid: proc.pid } - }) -}) -afterEach(() => { - ptyProcesses.clear() - expect(pendingLocalPtySpawns.size).toBe(0) -}) - -describe('local PTY native spawn admission', () => { - it('aborts a pending receipt when the requesting client disconnects', async () => { - const controller = new AbortController() - spawn.mockImplementationOnce( - ({ signal }: { signal: AbortSignal }) => - new Promise((_resolve, reject) => { - signal.addEventListener('abort', () => reject(signal.reason), { once: true }) - }) - ) - const result = spawnLocalPty( - { sessionId: 'disconnected-shell', cols: 80, rows: 24, signal: controller.signal }, - () => ({}) - ) - const rejected = expect(result).rejects.toThrow('client disconnected') - await vi.waitFor(() => expect(spawn).toHaveBeenCalledOnce()) - controller.abort(new Error('client disconnected')) - await rejected - expect(activate).not.toHaveBeenCalled() - }) - - it('reserves the same session until a delayed shell receipt is activated', async () => { - const proc = createProcess() - let release!: () => void - spawn.mockImplementationOnce( - () => - new Promise((resolve) => { - release = () => resolve({ process: proc, shellPath: '/bin/sh' }) - }) - ) - const first = start() - await vi.waitFor(() => expect(spawn).toHaveBeenCalledOnce()) - const second = start() - await new Promise((resolve) => setImmediate(resolve)) - expect(spawn).toHaveBeenCalledOnce() - release() - expect(await first).toEqual({ id: 'pending-bun-shell', pid: proc.pid }) - expect(await second).toMatchObject({ id: 'pending-bun-shell', pid: proc.pid, isReattach: true }) - expect(activate).toHaveBeenCalledOnce() - expect(spawn).toHaveBeenCalledOnce() - }) - - it('aborts a pending receipt on shutdown and cancels queued same-session launches', async () => { - spawn.mockImplementationOnce( - ({ signal }: { signal: AbortSignal }) => - new Promise((_resolve, reject) => { - signal.addEventListener('abort', () => reject(signal.reason), { once: true }) - }) - ) - const first = start() - await vi.waitFor(() => expect(spawn).toHaveBeenCalledOnce()) - const second = start() - const results = Promise.allSettled([first, second]) - cancelPendingLocalPtySpawns('pending-bun-shell') - expect(await results).toEqual([ - { status: 'rejected', reason: new Error('PTY spawn canceled: pending-bun-shell') }, - { status: 'rejected', reason: new Error('PTY spawn canceled: pending-bun-shell') } - ]) - expect(spawn).toHaveBeenCalledOnce() - expect(activate).not.toHaveBeenCalled() - }) - - it('cleans a confirmed process when shutdown races the receipt continuation', async () => { - const proc = createProcess() - let release!: () => void - spawn.mockImplementationOnce( - () => - new Promise((resolve) => { - release = () => resolve({ process: proc, shellPath: '/bin/sh' }) - }) - ) - const first = start() - const rejected = expect(first).rejects.toThrow('PTY spawn canceled: pending-bun-shell') - await vi.waitFor(() => expect(spawn).toHaveBeenCalledOnce()) - release() - cancelPendingLocalPtySpawns('pending-bun-shell') - await rejected - expect(proc.kill).toHaveBeenCalledWith('SIGKILL') - expect(destroy).toHaveBeenCalledWith(proc) - expect(activate).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts b/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts deleted file mode 100644 index 34d8636c92f..00000000000 --- a/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { setMainHttpClient } from '../network/http-client' -import { - expiredCreds, - makeResponse, - quotaResponse, - validCreds -} from './gemini-usage-fetcher.test-fixtures' - -const { readFileMock, extractCredsMock } = vi.hoisted(() => ({ - readFileMock: vi.fn(), - extractCredsMock: vi.fn() -})) -const fetchMock = vi.fn() - -// Mock extraction at its module boundary so tests cover refresh/quota behavior independently of install layouts. -vi.mock('./gemini-cli-oauth-extractor', () => ({ - extractOAuthClientCredentials: extractCredsMock -})) - -vi.mock('node:fs/promises', () => ({ - readFile: readFileMock -})) - -// Refresh must never write the developer's real ~/.gemini. -vi.mock('../credentials/integration-credential-file', () => ({ - writeCredentialFileAtomic: vi.fn().mockReturnValue(true) -})) - -import { fetchGeminiRateLimits } from './gemini-usage-fetcher' - -setMainHttpClient({ fetch: fetchMock, proxySession: () => null }) - -describe('fetchGeminiRateLimits fallback oauth creds', () => { - beforeEach(() => { - vi.useFakeTimers() - vi.setSystemTime(new Date('2026-04-24T12:00:00.000Z')) - readFileMock.mockReset() - extractCredsMock.mockReset() - fetchMock.mockReset() - extractCredsMock.mockResolvedValue({ - clientId: 'client-id-123', - clientSecret: 'client-secret-456' - }) - }) - - it('falls back to oauth_creds.json and resolves project via loadCodeAssist', async () => { - readFileMock.mockImplementation(async (filePath: string) => { - if (filePath.includes('auth.json')) { - return JSON.stringify({}) - } - if (filePath.includes('oauth_creds.json')) { - return JSON.stringify(validCreds) - } - throw { code: 'ENOENT' } - }) - fetchMock - .mockResolvedValueOnce(makeResponse({ cloudaicompanionProject: 'cli-proj-456' })) - .mockResolvedValueOnce(makeResponse(quotaResponse)) - - const result = await fetchGeminiRateLimits(true) - - expect(result.status).toBe('ok') - expect(result.error).toBeNull() - expect(result.session).not.toBeNull() - - const quotaCall = fetchMock.mock.calls.find( - (call) => typeof call[0] === 'string' && call[0].includes('retrieveUserQuota') - ) - expect(quotaCall).toBeDefined() - const quotaBody = JSON.parse((quotaCall![1] as RequestInit).body as string) - expect(quotaBody.project).toBe('cli-proj-456') - }) - - it('refreshes via bundled client credentials when expiry passed', async () => { - readFileMock.mockImplementation(async (filePath: string) => { - if (filePath.includes('auth.json')) { - return JSON.stringify({}) - } - if (filePath.includes('oauth_creds.json')) { - return JSON.stringify(expiredCreds) - } - throw { code: 'ENOENT' } - }) - fetchMock - .mockResolvedValueOnce( - makeResponse({ access_token: 'bundle-refreshed-token', expires_in: 3600 }) - ) - .mockResolvedValueOnce(makeResponse({ cloudaicompanionProject: 'cli-proj-456' })) - .mockResolvedValueOnce(makeResponse(quotaResponse)) - - const result = await fetchGeminiRateLimits(true) - - expect(result.status).toBe('ok') - expect(result.error).toBeNull() - expect(result.session).not.toBeNull() - - const refreshCall = fetchMock.mock.calls.find( - (call) => typeof call[0] === 'string' && call[0].includes('oauth2.googleapis.com') - ) - expect(refreshCall).toBeDefined() - const refreshBody = new URLSearchParams((refreshCall![1] as RequestInit).body as string) - expect(refreshBody.get('client_id')).toBe('client-id-123') - expect(refreshBody.get('client_secret')).toBe('client-secret-456') - }) - - it('returns error when oauth_creds.json token expired and bundle refresh fails', async () => { - readFileMock.mockImplementation(async (filePath: string) => { - if (filePath.includes('auth.json')) { - return JSON.stringify({}) - } - if (filePath.includes('oauth_creds.json')) { - return JSON.stringify(expiredCreds) - } - throw { code: 'ENOENT' } - }) - extractCredsMock.mockResolvedValue(null) - - const result = await fetchGeminiRateLimits(true) - - expect(result.status).toBe('error') - expect(result.error).toContain('Token refresh failed') - expect(result.session).toBeNull() - expect(result.weekly).toBeNull() - }) - - it('returns error when loadCodeAssist cannot resolve a project for oauth_creds path', async () => { - // Missing project must fail before quota API calls; posting empty project produces a misleading auth-like 400. - readFileMock.mockImplementation(async (filePath: string) => { - if (filePath.includes('auth.json')) { - return JSON.stringify({}) - } - if (filePath.includes('oauth_creds.json')) { - return JSON.stringify(validCreds) - } - throw { code: 'ENOENT' } - }) - fetchMock.mockResolvedValueOnce(makeResponse('Internal Server Error', 500)) - - const result = await fetchGeminiRateLimits(true) - - expect(result.status).toBe('error') - expect(result.error).toContain('Gemini project ID not found') - }) - - it('returns unavailable without reading OAuth files when geminiCliOAuthEnabled=false', async () => { - const result = await fetchGeminiRateLimits(false) - - expect(result.status).toBe('unavailable') - expect(result.error).toContain('disabled') - expect(readFileMock).not.toHaveBeenCalled() - expect(fetchMock).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/rate-limits/initial-account-rate-limit-target.test.ts b/src/main/rate-limits/initial-account-rate-limit-target.test.ts deleted file mode 100644 index 485fa8bcef6..00000000000 --- a/src/main/rate-limits/initial-account-rate-limit-target.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { getDefaultSettings } from '../../shared/constants' -import type { GlobalSettings } from '../../shared/global-settings-types' -import { getInitialClaudeRateLimitTarget } from './claude-rate-limit-target' -import { getInitialCodexRateLimitTarget } from './codex-rate-limit-target' - -type RuntimeSelection = { - host: string | null - wsl: Record -} - -type Scenario = { - name: string - settings: Partial - expected: { runtime: 'host' } | { runtime: 'wsl'; wslDistro: string | null } - legacy?: boolean - platform?: NodeJS.Platform - selection?: RuntimeSelection -} - -const providers = [ - { - name: 'Codex', - getTarget: getInitialCodexRateLimitTarget, - selectionSettings: (selection: RuntimeSelection) => ({ - activeCodexManagedAccountIdsByRuntime: selection - }) - }, - { - name: 'Claude', - getTarget: getInitialClaudeRateLimitTarget, - selectionSettings: (selection: RuntimeSelection) => ({ - activeClaudeManagedAccountIdsByRuntime: selection - }) - } -] as const - -const scenarios: Scenario[] = [ - { - name: 'uses the trimmed configured WSL distro before the selected account', - settings: { - localAccountRuntime: 'wsl', - localAccountWslDistro: ' Fedora ', - localAgentRuntime: 'host', - terminalWindowsWslDistro: 'Debian' - }, - selection: { host: null, wsl: { Ubuntu: 'wsl-account-1' } }, - expected: { runtime: 'wsl', wslDistro: 'Fedora' } - }, - { - name: 'uses the single selected distro for an unpinned WSL runtime', - settings: { localAccountRuntime: 'wsl' }, - selection: { host: 'host-account-1', wsl: { Ubuntu: 'wsl-account-1' } }, - expected: { runtime: 'wsl', wslDistro: 'Ubuntu' } - }, - { - name: 'ignores a stale terminal distro for an unpinned WSL runtime', - settings: { - localAccountRuntime: 'wsl', - localAccountWslDistro: null, - terminalWindowsWslDistro: 'Debian' - }, - selection: { host: 'host-account-1', wsl: {} }, - expected: { runtime: 'wsl', wslDistro: null } - }, - { - name: 'keeps an explicit host runtime on host', - settings: { localAccountRuntime: 'host', terminalWindowsShell: 'wsl.exe' }, - selection: { host: null, wsl: { Ubuntu: 'wsl-account-1' } }, - expected: { runtime: 'host' } - }, - { - name: 'ignores an explicit WSL runtime on non-Windows hosts', - settings: { localAccountRuntime: 'wsl', localAccountWslDistro: 'Ubuntu' }, - platform: 'linux', - expected: { runtime: 'host' } - }, - { - name: 'auto follows the global WSL runtime before stale selections', - settings: { - localAccountRuntime: 'auto', - localWindowsRuntimeDefault: { kind: 'wsl', distro: 'Ubuntu' } - }, - selection: { host: null, wsl: { Debian: 'wsl-account-1' } }, - expected: { runtime: 'wsl', wslDistro: 'Ubuntu' } - }, - { - name: 'auto keeps the global host runtime before stale selections', - settings: { - localAccountRuntime: 'auto', - localWindowsRuntimeDefault: { kind: 'windows-host' } - }, - selection: { host: null, wsl: { Ubuntu: 'wsl-account-1' } }, - expected: { runtime: 'host' } - }, - { - name: 'legacy settings use the global WSL runtime before selections', - settings: { localWindowsRuntimeDefault: { kind: 'wsl', distro: 'Ubuntu' } }, - selection: { host: null, wsl: { Debian: 'wsl-account-1' } }, - legacy: true, - expected: { runtime: 'wsl', wslDistro: 'Ubuntu' } - }, - { - name: 'legacy settings ignore stale agent and terminal WSL values', - settings: { - localWindowsRuntimeDefault: { kind: 'windows-host' }, - localAgentRuntime: 'wsl', - localAgentWslDistro: 'Ubuntu', - terminalWindowsShell: 'wsl.exe', - terminalWindowsWslDistro: 'Debian' - }, - legacy: true, - expected: { runtime: 'host' } - }, - { - name: 'legacy settings preserve a single WSL selection on macOS', - settings: {}, - selection: { host: null, wsl: { Ubuntu: 'wsl-account-1' } }, - legacy: true, - platform: 'darwin', - expected: { runtime: 'wsl', wslDistro: 'Ubuntu' } - }, - { - name: 'legacy settings map the provider default WSL key to no distro', - settings: {}, - selection: { host: null, wsl: { __default__: 'wsl-account-1' } }, - legacy: true, - platform: 'linux', - expected: { runtime: 'wsl', wslDistro: null } - }, - { - name: 'legacy selection fallback requires no selected host account', - settings: {}, - selection: { host: 'host-account-1', wsl: { Ubuntu: 'wsl-account-1' } }, - legacy: true, - expected: { runtime: 'host' } - }, - { - name: 'legacy selection fallback requires exactly one selected WSL account', - settings: {}, - selection: { - host: null, - wsl: { Debian: 'wsl-account-1', Fedora: null, Ubuntu: 'wsl-account-2' } - }, - legacy: true, - expected: { runtime: 'host' } - } -] - -describe.each(providers)('$name initial rate-limit target', ({ getTarget, selectionSettings }) => { - it.each(scenarios)('$name', (scenario) => { - const settings = { - ...getDefaultSettings('/tmp'), - ...scenario.settings, - ...(scenario.selection ? selectionSettings(scenario.selection) : {}) - } - if (scenario.legacy) { - delete (settings as Partial).localAccountRuntime - } - - expect(getTarget(settings, scenario.platform ?? 'win32')).toEqual(scenario.expected) - }) -}) diff --git a/src/main/runtime/agent-session/agent-session-runtime-attribution.test.ts b/src/main/runtime/agent-session/agent-session-runtime-attribution.test.ts deleted file mode 100644 index 214258ac43b..00000000000 --- a/src/main/runtime/agent-session/agent-session-runtime-attribution.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -// Attribute death only to recorded prior runtimes of this build; unknown owners retain generic guidance. - -import { closeTestJournalHostDatabases } from '../../native-chat/agent-session-journal/journal-host-database-test-support' -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import type { AgentSessionLease } from '../../../shared/agent-session/lifecycle/agent-session-record' -import { - agentSessionLeaseFixture, - agentSessionRecordFixture -} from '../../../shared/agent-session/lifecycle/agent-session-record.test-fixture' -import { - openTestAgentSessionRecordStore, - seedTestAgentSessionRecordStore -} from './records/agent-session-record-store-test-harness' -import { - agentSessionRuntimeIncarnation, - beginAgentSessionRuntimeIncarnationForTest -} from './agent-session-runtime-attribution' -import { - beginAgentSessionRuntimeRecord, - recordAgentSessionRuntimeEnd -} from './records/agent-session-runtime-end-record' - -const SESSION = 'session-alpha-1' -const OWNER = { - hostId: 'local', - pid: 4242, - processStartTimeMs: 1_700_000_000_000, - spawnToken: 'spawn-owner' -} - -let directory: string - -beforeEach(async () => { - directory = await mkdtemp(join(tmpdir(), 'orca-runtime-attribution-')) -}) - -afterEach(async () => { - await closeTestJournalHostDatabases() - await rm(directory, { recursive: true, force: true }) -}) - -async function seedHeldBy(runtime: string | null, lease: Partial = {}) { - await seedTestAgentSessionRecordStore(directory, { - records: [ - agentSessionRecordFixture( - agentSessionLeaseFixture({ - ownerProcess: runtime ? { ...OWNER, runtime } : OWNER, - unreconciled: true, - ...lease - }) - ) - ] - }) -} - -function recorded(runtime: string, end?: 'quit' | 'update') { - beginAgentSessionRuntimeRecord(directory, runtime, 1_000) - if (end) { - recordAgentSessionRuntimeEnd(end, 50_000) - } -} - -async function restartEvidence() { - const store = await openTestAgentSessionRecordStore(directory) - await store.reconcileOnRestart({ probe: async () => ({ outcome: 'pid-absent' }), now: 90_000 }) - return store.getRecord(SESSION)?.lease.deathEvidence ?? null -} - -describe("an agent that died with Orca's previous runtime", () => { - it('died in a crash when that runtime started and never ended', async () => { - await seedHeldBy('runtime-a') - recorded('runtime-a') - expect(await restartEvidence()).toMatchObject({ kind: 'pid-absent', runtimeEnd: 'crash' }) - }) - - it.each(['update', 'quit'] as const)( - 'died with the %s that runtime began, when the quit did not finish', - async (trigger) => { - await seedHeldBy('runtime-a') - recorded('runtime-a', trigger) - expect(await restartEvidence()).toMatchObject({ runtimeEnd: trigger }) - } - ) - - it("is never told by another runtime's end", async () => { - await seedHeldBy('runtime-b') - recorded('runtime-b') - recorded('runtime-a', 'update') - expect(await restartEvidence()).toMatchObject({ runtimeEnd: 'crash' }) - }) - - it('names no cause when that runtime left no record', async () => { - await seedHeldBy('runtime-a') - recorded('runtime-b', 'quit') - expect(await restartEvidence()).not.toHaveProperty('runtimeEnd') - }) - - it("names no cause when that runtime's record cannot be read", async () => { - await seedHeldBy('runtime-a') - await mkdir(join(directory, 'agent-session-runtimes'), { recursive: true }) - await writeFile(join(directory, 'agent-session-runtimes', 'runtime-a.json'), '{not json') - expect(await restartEvidence()).not.toHaveProperty('runtimeEnd') - }) - - it('names no cause for an owner an older build recorded', async () => { - await seedHeldBy(null) - expect(await restartEvidence()).not.toHaveProperty('runtimeEnd') - }) - - it("names no cause for a terminal's agent", async () => { - await seedHeldBy('runtime-a', { claimStatus: 'conflicted' }) - expect(await restartEvidence()).not.toHaveProperty('runtimeEnd') - }) - - it('is told once a survivor that outlived the runtime is stopped and proven gone', async () => { - await seedHeldBy('runtime-a') - recorded('runtime-a') - const store = await openTestAgentSessionRecordStore(directory) - // Surviving owners stay in recovery until their stop is proven. - await store.reconcileOnRestart({ - probe: async () => ({ outcome: 'identity-matched', matchedOn: ['spawn-token'] }), - now: 90_000 - }) - expect(store.getRecord(SESSION)?.lease.handoffStage).toBe('recovering') - - await store.evictProvenDeadOwner({ - sessionId: SESSION, - expectedFence: 7, - probe: { outcome: 'pid-absent' }, - now: 95_000 - }) - - expect(store.getRecord(SESSION)?.lease.deathEvidence).toMatchObject({ runtimeEnd: 'crash' }) - }) -}) - -describe('an agent this runtime ran', () => { - it("becomes an earlier runtime's after a relaunch", async () => { - await seedHeldBy(agentSessionRuntimeIncarnation()) - await openTestAgentSessionRecordStore(directory) - beginAgentSessionRuntimeIncarnationForTest() - expect(await restartEvidence()).toMatchObject({ runtimeEnd: 'crash' }) - }) -}) diff --git a/src/main/runtime/agent-terminal-launch-host.test.ts b/src/main/runtime/agent-terminal-launch-host.test.ts deleted file mode 100644 index 3cdc2aa917a..00000000000 --- a/src/main/runtime/agent-terminal-launch-host.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { mergeWorktree } from '../ipc/worktree-metadata-merge' -import type { Repo } from '../../shared/repo-types' -// #11163: colliding repo ids on two hosts once selected the wrong launch settings. -import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('electron', () => ({ - BrowserWindow: { fromId: vi.fn(() => null) }, - webContents: { fromId: vi.fn(() => null) }, - ipcMain: { on: vi.fn(), removeListener: vi.fn() }, - app: { getPath: vi.fn(() => '/tmp'), isPackaged: false } -})) - -import { OrcaRuntimeService } from './orca-runtime' - -const REMOTE_PATH = '/srv/app-feature' - -type RuntimeInternals = { - resolveGitWorktreeSelector: (selector: string) => Promise - buildStartupForAgent: ( - repo: unknown, - agent: unknown, - prompt: string, - args?: unknown, - options?: unknown - ) => unknown - createTerminal: ( - selector: string, - opts: unknown, - created?: unknown, - resolved?: unknown - ) => Promise -} - -function makeRuntime( - repos: readonly Pick[], - hostId?: string -) { - const store = { - getSettings: () => ({ disabledTuiAgents: [], workspaceDir: '/tmp/workspaces' }), - getProjectHostSetups: () => [], - getRepos: () => repos, - getRepo: (id: string) => repos.find((repo) => repo.id === id) - } - const runtime = new OrcaRuntimeService(store as never) - const internals = runtime as unknown as RuntimeInternals - vi.spyOn(internals, 'resolveGitWorktreeSelector').mockResolvedValue({ - id: 'repo-shared::/srv/app-feature', - repoId: 'repo-shared', - path: REMOTE_PATH, - ...(hostId ? { hostId } : {}) - }) - const buildStartup = vi.spyOn(internals, 'buildStartupForAgent').mockReturnValue({ - agent: 'codex', - startup: { command: 'codex', env: {}, startupCommandDelivery: 'none', telemetry: {} } - }) - vi.spyOn(internals, 'createTerminal').mockResolvedValue({ id: 'pty-1' }) - return { runtime, buildStartup } -} - -describe('launchAgentTerminal execution host', () => { - it('carries the resolved same-path target through launch scope and terminal creation', async () => { - const local: Repo = { - id: 'repo-shared', - path: '/same', - displayName: 'Local', - addedAt: 1, - badgeColor: '#000' - } - const remote: Repo = { ...local, connectionId: 'builder' } - const { runtime, buildStartup } = makeRuntime([local, remote], 'ssh:builder') - const git = { path: '/same', head: 'abc', branch: 'main', isMainWorktree: true, isBare: false } - const worktree = { - ...mergeWorktree(remote.id, git, undefined), - hostId: 'ssh:builder' as const, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - git - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the private selector is replaced solely to prove the internal evidence prevents another lookup. - const internals = runtime as unknown as RuntimeInternals - vi.spyOn(internals, 'resolveGitWorktreeSelector').mockRejectedValue( - new Error('bare selector was repeated') - ) - await runtime.launchAgentTerminal( - `id:${worktree.id}`, - { agent: 'codex', prompt: 'Review' }, - { worktree, repo: remote } - ) - expect(buildStartup.mock.calls[0]?.[0]).toEqual(remote) - expect(buildStartup.mock.calls[0]?.[4]).toMatchObject({ - launchScope: { connectionId: 'builder', repo: remote, path: '/same' } - }) - expect(internals.createTerminal).toHaveBeenCalledWith( - `id:${worktree.id}`, - expect.any(Object), - undefined, - worktree, - undefined - ) - }) - - beforeEach(() => { - vi.restoreAllMocks() - }) - - it('builds the launch for the host the worktree names, not a rival row', async () => { - const { runtime, buildStartup } = makeRuntime( - [ - { id: 'repo-shared', path: '/home/me/app', connectionId: 'openclaw' }, - { id: 'repo-shared', path: '/srv/app', connectionId: 'm4air' } - ], - 'ssh:m4air' - ) - - await runtime.launchAgentTerminal('id:repo-shared::/srv/app-feature', { - agent: 'codex', - prompt: 'go' - }) - - expect(buildStartup.mock.calls[0]?.[0]).toMatchObject({ connectionId: 'm4air' }) - }) - - it('builds a local launch for a local worktree even when a remote row shares the id', async () => { - const { runtime, buildStartup } = makeRuntime( - [ - { id: 'repo-shared', path: '/srv/app', connectionId: 'm4air' }, - { id: 'repo-shared', path: '/home/me/app' } - ], - 'local' - ) - - await runtime.launchAgentTerminal('id:repo-shared::/srv/app-feature', { - agent: 'codex', - prompt: 'go' - }) - - expect(buildStartup.mock.calls[0]?.[0]).toMatchObject({ path: '/home/me/app' }) - expect(buildStartup.mock.calls[0]?.[0]).not.toHaveProperty('connectionId') - }) - - it('refuses rather than guessing when rival rows disagree and the worktree names no host', async () => { - const { runtime } = makeRuntime([ - { id: 'repo-shared', path: '/srv/app', connectionId: 'm4air' }, - { id: 'repo-shared', path: '/home/me/app' } - ]) - - await expect( - runtime.launchAgentTerminal('id:repo-shared::/srv/app-feature', { - agent: 'codex', - prompt: 'go' - }) - ).rejects.toThrow('worktree_execution_host_unresolved') - }) -}) diff --git a/src/main/runtime/browser/browser-tab-listing.test.ts b/src/main/runtime/browser/browser-tab-listing.test.ts deleted file mode 100644 index dd5d6be987c..00000000000 --- a/src/main/runtime/browser/browser-tab-listing.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { BrowserTabInfo } from '../../../shared/runtime-browser-contracts' -import type { - RuntimeMobileSessionBrowserTab, - RuntimeMobileSessionTabsSnapshot -} from '../../../shared/runtime-types' -import { - mergeBrowserTabListing, - readBrowserTabBar, - type BrowserTabBarEntry -} from './browser-tab-listing' - -function barEntry(browserPageId: string, worktreeId = 'wt-1'): BrowserTabBarEntry { - return { - browserPageId, - worktreeId, - title: `${browserPageId} title`, - url: `https://${browserPageId}.test/`, - loadError: null, - certificateFailure: null - } -} - -function live(browserPageId: string, active = false): BrowserTabInfo { - return { - browserPageId, - index: 9, - url: `https://${browserPageId}.test/live`, - title: `${browserPageId} live`, - active, - worktreeId: 'wt-1', - profileId: 'default', - profileLabel: 'Default' - } -} - -function snapshot(worktree: string, tabs: unknown[]): RuntimeMobileSessionTabsSnapshot { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the test builds only the fields readBrowserTabBar reads. - return { worktree, tabs } as RuntimeMobileSessionTabsSnapshot -} - -function browserRow( - browserPageId: string | null, - extra: Partial = {} -): RuntimeMobileSessionBrowserTab { - return { - type: 'browser', - id: `tab-${browserPageId}`, - title: `${browserPageId} title`, - browserWorkspaceId: `ws-${browserPageId}`, - browserPageId, - url: `https://${browserPageId}.test/`, - loading: false, - canGoBack: false, - canGoForward: false, - isActive: false, - ...extra - } -} - -describe('mergeBrowserTabListing', () => { - it('lists every tab-bar tab in order, marking those without a live page as not loaded', () => { - const tabs = mergeBrowserTabListing({ - tabBar: [barEntry('a'), barEntry('b'), barEntry('c')], - liveTabs: [live('c', true)], - clientTabs: [] - }) - expect(tabs.map((tab) => [tab.index, tab.browserPageId, tab.loaded, tab.active])).toEqual([ - [0, 'a', false, false], - [1, 'b', false, false], - [2, 'c', true, true] - ]) - expect(tabs[0]).toMatchObject({ title: 'a title', url: 'https://a.test/', profileId: null }) - expect(tabs[2]).toMatchObject({ title: 'c live', url: 'https://c.test/live' }) - }) - - it('keeps live pages the tab bar does not show yet, after the bar', () => { - const tabs = mergeBrowserTabListing({ - tabBar: [barEntry('a')], - liveTabs: [live('new', true), live('a')], - clientTabs: [] - }) - expect(tabs.map((tab) => [tab.browserPageId, tab.loaded])).toEqual([ - ['a', true], - ['new', true] - ]) - }) - - it('lists client-hosted pages once, from the registry, after everything else', () => { - const client = { ...live('remote'), active: true } - const tabs = mergeBrowserTabListing({ - tabBar: [barEntry('remote'), barEntry('a')], - liveTabs: [], - clientTabs: [client] - }) - expect(tabs.map((tab) => [tab.browserPageId, tab.loaded])).toEqual([ - ['a', false], - ['remote', true] - ]) - }) - - it('lists an id held by both a live page and a client page once, as the client page', () => { - const tabs = mergeBrowserTabListing({ - tabBar: [], - liveTabs: [live('shared')], - clientTabs: [{ ...live('shared'), title: 'client' }] - }) - expect(tabs.map((tab) => [tab.browserPageId, tab.title])).toEqual([['shared', 'client']]) - }) - - it('with nothing active, marks the first live page active, never an unloaded tab', () => { - const tabs = mergeBrowserTabListing({ - tabBar: [barEntry('a'), barEntry('b'), barEntry('c')], - liveTabs: [live('c'), live('b')], - clientTabs: [] - }) - expect(tabs.map((tab) => [tab.browserPageId, tab.active])).toEqual([ - ['a', false], - ['b', false], - ['c', true] - ]) - }) - - it('without a tab bar, lists live pages as before', () => { - const tabs = mergeBrowserTabListing({ - tabBar: [], - liveTabs: [live('x'), live('y')], - clientTabs: [] - }) - expect(tabs.map((tab) => [tab.browserPageId, tab.active, tab.loaded])).toEqual([ - ['x', true, true], - ['y', false, true] - ]) - }) -}) - -describe('readBrowserTabBar', () => { - const snapshots = new Map([ - [ - 'wt-1', - snapshot('wt-1', [ - { type: 'terminal', id: 'term' }, - browserRow('a'), - browserRow(null), - browserRow('client', { - placement: { - kind: 'client', - browserHostClientId: 'client-1', - browserHostGeneration: 1, - pageHostGeneration: 1 - } - }), - browserRow('b') - ]) - ], - ['wt-2', snapshot('wt-2', [browserRow('c')])], - ['server-wt', snapshot('server-wt', [browserRow('mirrored')])] - ]) - const ownsWorkspace = (worktreeId: string) => worktreeId !== 'server-wt' - - it("reads one worktree's local browser tabs in order", () => { - expect( - readBrowserTabBar(snapshots, 'wt-1', ownsWorkspace).map((entry) => entry.browserPageId) - ).toEqual(['a', 'b']) - }) - - it("reads every worktree this host owns when none is named, skipping a paired server's mirror", () => { - expect( - readBrowserTabBar(snapshots, undefined, ownsWorkspace).map((entry) => [ - entry.worktreeId, - entry.browserPageId - ]) - ).toEqual([ - ['wt-1', 'a'], - ['wt-1', 'b'], - ['wt-2', 'c'] - ]) - }) -}) diff --git a/src/main/runtime/dsb-terminal-runtime.test.ts b/src/main/runtime/dsb-terminal-runtime.test.ts deleted file mode 100644 index 3ce8e3127f0..00000000000 --- a/src/main/runtime/dsb-terminal-runtime.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type * as WorktreeScanCacheModule from '../git/worktrees/catalog/worktree-scan-cache' -import type * as WorktreeListingModule from '../git/worktrees/catalog/worktree-listing' -import { readFileSync } from 'node:fs' -import { join } from 'node:path' -import { afterEach, describe, expect, expectTypeOf, it, vi } from 'vitest' -import { buildAgentPromptPasteBytes } from '../../shared/agent-launch/startup/agent-prompt-injection' -import type { RuntimeTerminalSummary } from '../../shared/runtime-terminal-contracts' -import type { TerminalAgent } from '../../shared/agent-status/identity/terminal-agent' -import { OrcaRuntimeService } from './orca-runtime' -import { makeStore } from './runtime-rpc-worktree-store-fixtures' -import { partialRuntimeStore } from './runtime-store-partial-fixture' - -vi.mock('../git/worktrees/catalog/worktree-scan-cache', async (importOriginal) => { - const facade = getWorktreeListingMocks() - return { - ...(await importOriginal()), - listWorktrees: facade.listWorktrees - } -}) - -vi.mock('../git/worktrees/catalog/worktree-listing', async (importOriginal) => { - const facade = getWorktreeListingMocks() - return { - ...(await importOriginal()), - listWorktreesStrict: facade.listWorktreesStrict - } -}) - -const { getWorktreeListingMocks } = vi.hoisted(() => { - const createWorktreeListingMocks = () => ({ - listWorktrees: vi.fn().mockResolvedValue([ - { - path: '/tmp/worktree-a', - head: 'abc', - branch: 'feature/dsb', - isBare: false, - isMainWorktree: false - } - ]), - listWorktreesStrict: vi.fn().mockResolvedValue([ - { - path: '/tmp/worktree-a', - head: 'abc', - branch: 'feature/dsb', - isBare: false, - isMainWorktree: false - } - ]) - }) - let listings: ReturnType | undefined - return { getWorktreeListingMocks: () => (listings ??= createWorktreeListingMocks()) } -}) - -describe('manually started DeepSeek Build terminals', () => { - afterEach(() => vi.useRealTimers()) - - it('publishes title-only identity from captured release PTY bytes', async () => { - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - runtime.setPtyController({ - spawn: async () => ({ id: 'pty-dsb' }), - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - const terminal = await runtime.createTerminal('path:/tmp/worktree-a') - const transcript = readFileSync(join(__dirname, '__fixtures__', 'dsb-6-9-0-folder.txt'), 'utf8') - expect(transcript).toContain('\x1b]0;DeepSeek Build\x07') - for (let offset = 0; offset < transcript.length; offset += 101) { - runtime.onPtyData('pty-dsb', transcript.slice(offset, offset + 101), Date.now()) - } - const listing = await runtime.listTerminals() - expect(listing.terminals).toEqual([ - expect.objectContaining({ handle: terminal.handle, agentIdentity: 'dsb' }) - ]) - }) - - it.each(['✦', '⏲', '◇', '✋'])( - 'preserves Build title and identity through OSC normalization with task glyph %s', - async (glyph) => { - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - runtime.setPtyController({ - spawn: async () => ({ id: 'pty-dsb' }), - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - const terminal = await runtime.createTerminal('path:/tmp/worktree-a') - const title = `⠋ - Review ${glyph} rendering - DeepSeek Build` - runtime.onPtyData('pty-dsb', `\x1b]0;${title}\x07`, Date.now()) - const listing = await runtime.listTerminals() - expect(listing.terminals).toEqual([ - expect.objectContaining({ handle: terminal.handle, agentIdentity: 'dsb', title }) - ]) - } - ) - - it('publishes observed DSB identity and sends a prompt with generic input behavior', async () => { - vi.useFakeTimers() - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - const writes: string[] = [] - runtime.setPtyController({ - spawn: async () => ({ id: 'pty-dsb' }), - write: (_ptyId, data) => { - writes.push(data) - if (data === '\r') { - runtime.onPtyData('pty-dsb', '\x1b]0;⠋ - Review Codex - DeepSeek Build\x07', Date.now()) - } - return true - }, - kill: () => true, - getForegroundProcess: async () => 'dsb' - }) - const terminal = await runtime.createTerminal('path:/tmp/worktree-a') - runtime.onPtyData('pty-dsb', '\x1b]0;DeepSeek Build\x07', Date.now()) - await runtime.refreshPtyForegroundAgentFromController('pty-dsb') - const listing = await runtime.listTerminals() - expectTypeOf().toEqualTypeOf< - TerminalAgent | undefined - >() - expect(listing.terminals).toEqual([ - expect.objectContaining({ handle: terminal.handle, agentIdentity: 'dsb' }) - ]) - expect(JSON.parse(JSON.stringify(listing)).terminals[0].agentIdentity).toBe('dsb') - - const submission = runtime.sendTerminalAgentPrompt(terminal.handle, 'review this', { - inputKind: 'driving', - leadLine: 'Please review' - }) - await Promise.all([ - expect(submission).resolves.toMatchObject({ accepted: true }), - vi.runAllTimersAsync() - ]) - expect(writes).toEqual([buildAgentPromptPasteBytes('review this', 'Please review'), '\r']) - }) -}) diff --git a/src/main/runtime/opencode-run-lifetime-runtime.test.ts b/src/main/runtime/opencode-run-lifetime-runtime.test.ts deleted file mode 100644 index 6ce2cd16de9..00000000000 --- a/src/main/runtime/opencode-run-lifetime-runtime.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { readCommandLineMock } = vi.hoisted(() => ({ readCommandLineMock: vi.fn() })) - -vi.mock('./local-pty-foreground-command-line', () => ({ - readLocalPtyForegroundCommandLine: readCommandLineMock -})) - -import { OrcaRuntimeService } from './orca-runtime' -import { FOREGROUND_COMMAND_READS } from '../../shared/foreground-command-settle' -import type { RuntimeTerminalAgentStatusEvent } from './runtime-terminal-contracts' - -const WORKTREE_ID = 'repo::/worktree' -const LEAF_ID = '11111111-1111-4111-8111-111111111111' -const PANE_KEY = `tab-1:${LEAF_ID}` - -function createRuntime(): { - runtime: OrcaRuntimeService - statuses: RuntimeTerminalAgentStatusEvent[] - channelOrder: string[] -} { - const statuses: RuntimeTerminalAgentStatusEvent[] = [] - const channelOrder: string[] = [] - const runtime = new OrcaRuntimeService(undefined, undefined, { - onTerminalAgentStatus: (event) => { - statuses.push(event) - channelOrder.push(`status:${event.payload.state}`) - }, - onTerminalSideEffects: (batch) => - channelOrder.push(...batch.facts.map((fact) => `fact:${fact.kind}`)) - }) - runtime.setPtyController({ - spawn: vi.fn(), - write: () => true, - kill: () => true, - getForegroundProcess: async () => 'opencode' - }) - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { - tabs: [ - { - tabId: 'tab-1', - worktreeId: WORKTREE_ID, - title: 'Terminal', - activeLeafId: LEAF_ID, - layout: null - } - ], - leaves: [ - { tabId: 'tab-1', worktreeId: WORKTREE_ID, leafId: LEAF_ID, paneRuntimeId: 1, ptyId: 'pty-1' } - ] - }) - return { runtime, statuses, channelOrder } -} - -const summary = (statuses: RuntimeTerminalAgentStatusEvent[]): string[] => - statuses.map( - (event) => - `${event.paneKey}:${event.payload.state}${event.payload.interrupted ? ':interrupted' : ''}:${event.origin}` - ) - -describe('OpenCode run process lifetime in the runtime', () => { - beforeEach(() => { - vi.useFakeTimers() - readCommandLineMock.mockReset() - readCommandLineMock.mockResolvedValue('opencode run fix the bug') - }) - - afterEach(() => { - vi.useRealTimers() - }) - - it('posts Working after the command starts and Done when it finishes, on its own pane', async () => { - const { runtime, statuses } = createRuntime() - - runtime.onPtyData('pty-1', '\x1b]133;C\x07', 100) - await vi.advanceTimersByTimeAsync(FOREGROUND_COMMAND_READS.settleMs) - runtime.onPtyData('pty-1', 'done\x1b]133;D;0\x07', 101) - - expect(summary(statuses)).toEqual([`${PANE_KEY}:working:process`, `${PANE_KEY}:done:process`]) - expect(readCommandLineMock).toHaveBeenCalledWith('pty-1', 'opencode') - expect(statuses[0]?.yieldsToHookSince).toBe(statuses[1]?.yieldsToHookSince) - }) - - // Renderer command-finished handling drops rows unless a newer agent status follows. - it("publishes the run's Done after the command-finished fact of the same chunk", async () => { - const { runtime, channelOrder } = createRuntime() - - runtime.onPtyData('pty-1', '\x1b]133;C\x07', 100) - await vi.advanceTimersByTimeAsync(FOREGROUND_COMMAND_READS.settleMs) - runtime.onPtyData('pty-1', 'done\x1b]133;D;0\x07', 101) - - expect(channelOrder).toEqual(['status:working', 'fact:command-finished', 'status:done']) - }) - - it('posts Done from the daemon fact when the pane finished while hidden', async () => { - const { runtime, statuses, channelOrder } = createRuntime() - - runtime.onPtyData('pty-1', '\x1b]133;C\x07', 100) - await vi.advanceTimersByTimeAsync(FOREGROUND_COMMAND_READS.settleMs) - runtime.emitDaemonPtyTransientFact('pty-1', { kind: 'command-finished', exitCode: 130 }) - - expect(channelOrder.slice(-2)).toEqual(['fact:command-finished', 'status:done']) - expect(summary(statuses)).toEqual([ - `${PANE_KEY}:working:process`, - `${PANE_KEY}:done:interrupted:process` - ]) - }) - - it('stays silent for an SSH pane', async () => { - const { runtime, statuses } = createRuntime() - runtime.registerPty('pty-1', WORKTREE_ID, 'ssh-conn-1') - - runtime.onPtyData('pty-1', '\x1b]133;C\x07', 100) - await vi.advanceTimersByTimeAsync(FOREGROUND_COMMAND_READS.settleMs) - runtime.onPtyData('pty-1', '\x1b]133;D;0\x07', 101) - - expect(statuses).toEqual([]) - expect(readCommandLineMock).not.toHaveBeenCalled() - }) - - it('reports a local Windows pane from its own foreground', async () => { - const platform = Object.getOwnPropertyDescriptor(process, 'platform') - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - try { - const { runtime, statuses } = createRuntime() - readCommandLineMock.mockResolvedValue('"C:\\Tools\\opencode.exe" run fix it') - - runtime.onPtyData('pty-1', '\x1b]133;C\x07', 100) - await vi.advanceTimersByTimeAsync(FOREGROUND_COMMAND_READS.settleMs) - runtime.onPtyData('pty-1', '\x1b]133;D;0\x07', 101) - - expect(summary(statuses)).toEqual([`${PANE_KEY}:working:process`, `${PANE_KEY}:done:process`]) - } finally { - if (platform) { - Object.defineProperty(process, 'platform', platform) - } - } - }) -}) diff --git a/src/main/runtime/opencode-wsl-folder-launch-host.test.ts b/src/main/runtime/opencode-wsl-folder-launch-host.test.ts deleted file mode 100644 index f27c7671c44..00000000000 --- a/src/main/runtime/opencode-wsl-folder-launch-host.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import './rpc/unused-default-rpc-methods.test-fixture' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { OrcaRuntimeService } from './orca-runtime' -import { RpcDispatcher } from './rpc/dispatcher' -import { AGENT_SESSION_METHODS } from './rpc/methods/agent-session' -import { _resetWslCachesForTests, _setWslCachesForTests } from '../wsl/discovery/distro-catalog' -import { PROJECT_RUNTIME_REPAIR_REQUIRED_CODE } from '../../shared/project-runtime-repair-message' -import { - partialRuntimeStore, - storeFolderWorkspace, - storeProject, - storeProjectGroup, - storeRepo, - type PartialRuntimeStore -} from './runtime-store-partial-fixture' - -vi.mock('../opencode/opencode-launch-capabilities', () => ({ - probeOpenCodeLaunchCapabilities: async () => ({ - version: '1.18.30', - pluginApi: 'v1', - promptMode: 'submit' - }) -})) -vi.mock('../opencode/opencode-model-executable', () => ({ - resolveOpenCodeDirectModelExecutable: async () => 'opencode' -})) - -const repo = storeRepo({ id: 'r', path: 'C:\\repo', kind: 'git', projectGroupId: 'g' }) -const share = storeRepo({ id: 's', path: '\\\\wsl$\\Ubuntu\\s', kind: 'git', projectGroupId: 'g' }) -const folder = storeFolderWorkspace({ id: 'f', projectGroupId: 'g', folderPath: 'C:\\work' }) -const wsl = { kind: 'wsl', distro: 'Ubuntu' } as const -const store: PartialRuntimeStore = { - // A WSL terminal setting must not redirect the Windows model check. - getSettings: () => ({ - disabledTuiAgents: [], - terminalWindowsShell: 'wsl.exe', - terminalWindowsWslDistro: 'Ubuntu' - }), - getProjects: () => [ - storeProject({ id: 'p', sourceRepoIds: ['r'], localWindowsRuntimePreference: wsl }), - storeProject({ - id: 'q', - sourceRepoIds: ['s'], - localWindowsRuntimePreference: { kind: 'windows-host' } - }) - ], - getRepo: (id: string) => [repo, share].find((entry) => entry.id === id), - getRepos: () => [repo], - getFolderWorkspaces: () => [folder], - getProjectGroups: () => [storeProjectGroup({ id: 'g' })] -} -const scope = { id: 'f', path: 'C:\\work', connectionId: null, repo: null, folderWorkspace: folder } -const clientOperationId = `${Date.now()}-0123456789abcdef0123456789abcdef` -let runtime: OrcaRuntimeService -let spawned: () => (string | undefined)[] -const original = process.platform -const setPlatform = (value: string) => - Object.defineProperty(process, 'platform', { configurable: true, value }) -beforeEach(() => { - setPlatform('win32') - runtime = new OrcaRuntimeService(partialRuntimeStore(store)) - Object.assign(runtime, { - resolveTerminalWorkspaceLaunchScope: async () => scope, - executionOwnerSupportsAgentSessionOperation: async () => true - }) - const launches: (string | undefined)[] = [] - // Capture host-planned launch scope before spawning. - vi.spyOn(runtime, 'createTerminal').mockImplementation(async (selector = '', opts = {}) => { - const launchScope = await runtime.resolveTerminalWorkspaceLaunchScope(selector) - const planned = await runtime.resolveAgentTerminalCreateOptions(launchScope, opts) - launches.push(planned.launchRuntime?.platform) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: callers read only the handle. - return { handle: 't' } as never - }) - spawned = () => launches -}) -afterEach(() => { - setPlatform(original) - _resetWslCachesForTests() -}) - -// TEMPORARY until STA-10251: WSL-folder OpenCode create/resume/model checks all retain main's Windows runtime. -it('starts, resumes and checks models for OpenCode in a WSL folder on Windows', async () => { - await runtime.createAgentSession({ clientOperationId, worktree: 'id:f', agent: 'opencode' }) - const providerSession = { key: 'session_id', id: 'ses_1' } as const - const resume = { kind: 'explicit', worktree: 'id:f', agent: 'opencode', providerSession } as const - await runtime.ensureAgentSession(resume) - expect(spawned()).toEqual(['win32', 'win32']) - const target = { worktree: 'id:f', model: 'm' } - expect(await runtime.probeOrchestrationOpenCodeModelLaunchSupport(target)).toBe(true) -}) - -it('refuses an agent whose WSL distro is gone, under its own code', async () => { - _setWslCachesForTests({ available: true, distros: ['Debian'] }) - const params = { clientOperationId, worktree: 'id:f', agent: 'codex' } - const call = { id: '1', authToken: 't', method: 'terminal.createAgentSession', params } - const response = await new RpcDispatcher({ runtime, methods: AGENT_SESSION_METHODS }).dispatch( - call - ) - const message = - 'No agent was started. Ubuntu is not installed in WSL. Choose an installed distro or switch this project to Windows.' - expect(response).toMatchObject({ error: { code: PROJECT_RUNTIME_REPAIR_REQUIRED_CODE, message } }) -}) - -// WSL-share OpenCode runs inside WSL; refuse model selection until that host can verify it. -it('refuses an OpenCode model in a WSL-share repo set to Windows host', async () => { - const shareScope = { ...scope, id: 's', path: share.path, repo: share, folderWorkspace: null } - Object.assign(runtime, { resolveTerminalWorkspaceLaunchScope: async () => shareScope }) - const request = { clientOperationId, worktree: 'id:s', agent: 'opencode' } as const - const picked = runtime.createAgentSession({ ...request, launchPreferences: { model: 'm' } }) - await expect(picked).rejects.toThrow('OpenCode cannot apply this model choice') - expect(spawned()).toEqual([]) -}) diff --git a/src/main/runtime/orca-runtime-startup-draft-launch-parity.test.ts b/src/main/runtime/orca-runtime-startup-draft-launch-parity.test.ts deleted file mode 100644 index 3a975be19b6..00000000000 --- a/src/main/runtime/orca-runtime-startup-draft-launch-parity.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import './orca-runtime-test-lifecycle.spec' -import { describe, expect, it, vi } from 'vitest' -import { - OrcaRuntimeService, - computeWorktreePathMock, - detectInstalledAgentsWithShellPathHydrationMock, - ensurePathWithinWorkspaceMock, - listWorktrees -} from './orca-runtime-test-mocks.spec' -import type { WorktreeMeta } from './orca-runtime-test-mocks.spec' -import { makeWorktreeMeta, store } from './orca-runtime-test-fixtures.spec' -import { createWorktreeWithStartupAgent } from '../agent-launch/startup-agent-worktree-create' -import type { RuntimeManagedWorktreeCreateArgs } from './runtime-managed-worktree-create-types' -import type { HostWorktreeTarget } from '../../shared/worktree/host-qualified-identity' - -type DraftSettings = { - defaultTuiAgent: 'claude' | null - agentDefaultArgs: Record - detected: string[] -} - -async function runCreate( - route: 'create' | 'launch', - settings: DraftSettings, - request: Omit -) { - detectInstalledAgentsWithShellPathHydrationMock.mockReset() - detectInstalledAgentsWithShellPathHydrationMock.mockResolvedValue(settings.detected) - const metaById: Record = {} - const runtime = new OrcaRuntimeService({ - ...store, - getSettings: () => ({ - ...store.getSettings(), - defaultTuiAgent: settings.defaultTuiAgent, - agentCmdOverrides: {}, - agentDefaultArgs: settings.agentDefaultArgs - }), - getAllWorktreeMeta: () => metaById, - getWorktreeMeta: (worktreeId: string) => metaById[worktreeId], - setWorktreeMeta: ({ id }: HostWorktreeTarget, meta: Partial) => { - metaById[id] = { ...(metaById[id] ?? makeWorktreeMeta()), ...meta } - return metaById[id] - } - }) - const spawn = vi.fn().mockResolvedValue({ id: `pty-${route}` }) - runtime.setPtyController({ - spawn, - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - const path = `/tmp/workspaces/draft-parity-${route}` - computeWorktreePathMock.mockReturnValue(path) - ensurePathWithinWorkspaceMock.mockReturnValue(path) - vi.mocked(listWorktrees).mockResolvedValue([ - { path, head: 'abc', branch: 'draft-parity', isBare: false, isMainWorktree: false } - ]) - const args = { repoSelector: 'id:repo-1', name: 'draft-parity', ...request } - const result = - route === 'create' - ? await runtime.createManagedWorktree(args) - : await createWorktreeWithStartupAgent(runtime, args) - return { - createdWithAgent: metaById[result.worktree.id]?.createdWithAgent, - spawned: spawn.mock.calls.map(([options]) => options.command ?? null), - detections: detectInstalledAgentsWithShellPathHydrationMock.mock.calls.length - } -} - -const DRAFT = 'https://github.com/stablyai/orca/issues/123' - -// Linked-draft launch execution must preserve direct-create results. -describe('a linked-draft create through the launch executor', () => { - it.each([false, true])( - 'matches the create when the default agent cannot be launched (activate %s)', - async (activate) => { - const settings = { - defaultTuiAgent: 'claude' as const, - agentDefaultArgs: { claude: '--foo "unbalanced' }, - detected: [] - } - const request = { startupDraft: DRAFT, activate } - - const before = await runCreate('create', settings, request) - const after = await runCreate('launch', settings, request) - - expect(after).toEqual(before) - expect(before.createdWithAgent).toBeUndefined() - if (!activate) { - expect(before.spawned).toHaveLength(1) - } - } - ) - - it('detects once and starts the detected agent with the draft, as the create does', async () => { - const settings = { defaultTuiAgent: null, agentDefaultArgs: {}, detected: ['claude'] } - const request = { startupDraft: DRAFT, activate: false } - - const before = await runCreate('create', settings, request) - const after = await runCreate('launch', settings, request) - - expect(after).toEqual(before) - expect(before.detections).toBe(1) - expect(before.createdWithAgent).toBe('claude') - expect(before.spawned).toEqual([expect.stringContaining(DRAFT)]) - }) - - it('detects once when no agent is found, and records none', async () => { - const settings = { defaultTuiAgent: null, agentDefaultArgs: {}, detected: [] } - const request = { startupDraft: DRAFT, activate: false } - - const before = await runCreate('create', settings, request) - const after = await runCreate('launch', settings, request) - - expect(after).toEqual(before) - expect(before.detections).toBe(1) - expect(before.createdWithAgent).toBeUndefined() - }) - - it('keeps a requested agent the request named, without detecting', async () => { - const settings = { defaultTuiAgent: null, agentDefaultArgs: {}, detected: ['codex'] } - const request = { startupDraft: DRAFT, createdWithAgent: 'claude' as const, activate: false } - - const before = await runCreate('create', settings, request) - const after = await runCreate('launch', settings, request) - - expect(after).toEqual(before) - expect(before.detections).toBe(0) - expect(before.createdWithAgent).toBe('claude') - }) -}) diff --git a/src/main/runtime/orca-runtime-terminal-handle-leaf-removal.test.ts b/src/main/runtime/orca-runtime-terminal-handle-leaf-removal.test.ts deleted file mode 100644 index 86dfb450a15..00000000000 --- a/src/main/runtime/orca-runtime-terminal-handle-leaf-removal.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { makePaneKey } from '../../shared/workspace-layout/stable-pane-id' -import { DISCONNECTED_PTY_RECORD_MAX } from './orca-runtime-postlude' -import { - createHarness, - LEAF_ID, - PTY_ID, - TAB_ID, - WORKTREE_ID, - type CloseContinuityHarness -} from './__fixtures__/orca-runtime-terminal-close-continuity-fixtures' - -// A paired client's exit wait can reach the host after the close removed the pane it resolved, -// and a terminal list can run in between. The pane's handle keeps answering that wait; the -// terminal itself is listed and controlled through its own handle, as main does for unshown terminals. -function paneHandle(harness: CloseContinuityHarness): string { - return harness.runtime.resolveTerminalPane(makePaneKey(TAB_ID, LEAF_ID), WORKTREE_ID).handle -} -function waitForExit(harness: CloseContinuityHarness, handle: string) { - return harness.runtime.waitForTerminal(handle, { condition: 'exit', timeoutMs: 1_000 }) -} -async function listedHandle(harness: CloseContinuityHarness): Promise { - const { terminals } = await harness.runtime.listTerminals(`id:${WORKTREE_ID}`) - const listed = terminals.find((terminal) => terminal.ptyId === PTY_ID) - expect(listed).toBeDefined() - return listed!.handle -} -const EXITED = { satisfied: true, status: 'exited', exitCode: 0 } - -describe('a pane-issued handle names its PTY incarnation, not the pane', () => { - it('answers an exit wait registered after its leaf left while the PTY still ran', async () => { - const harness = createHarness({ registerPtyBacked: true }) - const handle = paneHandle(harness) - harness.syncEmptyGraph() - const waiting = waitForExit(harness, handle) - harness.runtime.onPtyExit(PTY_ID, 0) - await expect(waiting).resolves.toMatchObject(EXITED) - }) - - it('lists a live unshown terminal under its own working handle; after exit only the pane wait answers', async () => { - const harness = createHarness({ registerPtyBacked: true }) - const handle = paneHandle(harness) - harness.syncEmptyGraph() - const listed = await listedHandle(harness) - expect(listed).not.toBe(handle) - await expect(harness.runtime.readTerminal(listed)).resolves.toMatchObject({ - status: 'running' - }) - await expect(harness.runtime.showTerminal(listed)).resolves.toBeDefined() - await expect( - harness.runtime.sendTerminal(listed, { text: 'ls' }, { inputKind: 'driving' }) - ).resolves.toBeDefined() - // The process exits before the delayed wait arrives, after another graph sync. - harness.removeVictimFromInventory() - harness.runtime.onPtyExit(PTY_ID, 0) - harness.syncEmptyGraph() - await expect(waitForExit(harness, handle)).resolves.toMatchObject(EXITED) - await expect(harness.runtime.readTerminal(listed)).resolves.toMatchObject({ status: 'exited' }) - await expect(harness.runtime.showTerminal(listed)).resolves.toBeDefined() - for (const exited of [handle, listed]) { - await expect( - harness.runtime.sendTerminal(exited, { text: 'ls' }, { inputKind: 'driving' }) - ).rejects.toThrow() - await expect(harness.runtime.focusTerminal(exited)).rejects.toThrow() - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: reads the runtime's own mail-target lookup. - const mail = harness.runtime as unknown as { - getOrchestrationMailboxTerminalForHandle(handle: string): unknown - } - expect(mail.getOrchestrationMailboxTerminalForHandle(handle)).toBeNull() - expect(mail.getOrchestrationMailboxTerminalForHandle(listed)).toBeNull() - }) - - it('answers a late exit wait when the exit came before the pane left', async () => { - const harness = createHarness({ registerPtyBacked: true }) - const handle = paneHandle(harness) - harness.removeVictimFromInventory() - harness.runtime.onPtyExit(PTY_ID, 0) - harness.syncEmptyGraph() - await expect(waitForExit(harness, handle)).resolves.toMatchObject(EXITED) - }) - - it('refuses the old handle once the PTY id names a new incarnation', async () => { - const harness = createHarness({ registerPtyBacked: true }) - const handle = paneHandle(harness) - harness.syncEmptyGraph() - harness.runtime.registerPty(PTY_ID, WORKTREE_ID, null, { - tabId: TAB_ID, - leafId: LEAF_ID, - incarnationId: '44444444-4444-4444-8444-444444444444' - }) - await expect(waitForExit(harness, handle)).rejects.toThrow('terminal_handle_stale') - }) - - it('refuses the old handle once the exited-terminal archive prunes its record', async () => { - const harness = createHarness({ registerPtyBacked: true }) - const handle = paneHandle(harness) - harness.runtime.onPtyExit(PTY_ID, 0) - harness.syncEmptyGraph() - for (let index = 0; index < DISCONNECTED_PTY_RECORD_MAX; index += 1) { - harness.runtime.registerPty(`pty-archived-${index}`, WORKTREE_ID) - harness.runtime.onPtyExit(`pty-archived-${index}`, 0) - } - await expect(waitForExit(harness, handle)).rejects.toThrow('terminal_handle_stale') - }) - - it('keeps nothing for an unknown incarnation, so a returning pane gets a new handle', async () => { - const harness = createHarness() - const handle = paneHandle(harness) - harness.syncEmptyGraph() - await expect(waitForExit(harness, handle)).rejects.toThrow('terminal_handle_stale') - harness.syncFixtureGraph() - expect(paneHandle(harness)).not.toBe(handle) - }) -}) diff --git a/src/main/runtime/orca-runtime-tests/mobile-session-tabs-shell-selection.spec.ts b/src/main/runtime/orca-runtime-tests/mobile-session-tabs-shell-selection.spec.ts deleted file mode 100644 index ef95a0f3672..00000000000 --- a/src/main/runtime/orca-runtime-tests/mobile-session-tabs-shell-selection.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { committingSpawn } from '../bind-first-pane.test-fixture' -import { OrcaRuntimeService, electronMocks, ipcMain } from '../orca-runtime-test-mocks.spec' -import { TEST_REPO_ID, TEST_WORKTREE_ID, store } from '../orca-runtime-test-fixtures.spec' -import { createMobileCreateTestNotifier } from '../orca-runtime-test-scenario-builders.spec' - -function makeHeadlessRuntime(runtimeStore: typeof store = store) { - const spawn = vi.fn().mockResolvedValue({ id: 'pty-shell-pick' }) - const runtime = new OrcaRuntimeService(runtimeStore) - runtime.setPtyController({ - spawn: committingSpawn(runtime, spawn), - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - runtime.syncWindowGraph(0, { tabs: [], leaves: [] }) - return { runtime, spawn } -} - -async function onHostPlatform(platform: NodeJS.Platform, run: () => Promise): Promise { - const hostPlatform = Object.getOwnPropertyDescriptor(process, 'platform')! - Object.defineProperty(process, 'platform', { configurable: true, value: platform }) - try { - await run() - } finally { - Object.defineProperty(process, 'platform', hostPlatform) - } -} - -const onWindowsHost = (run: () => Promise) => onHostPlatform('win32', run) - -describe('OrcaRuntimeService session-tab shell selection', () => { - it('spawns the picked shell for a headless session terminal instead of typing it', async () => { - await onWindowsHost(async () => { - const { runtime, spawn } = makeHeadlessRuntime() - - await runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, { - shellOverride: 'git-bash' - }) - - // Passing the shell as command typed git-bash into PowerShell instead of selecting it. - expect(spawn).toHaveBeenCalledWith( - expect.objectContaining({ shellOverride: 'git-bash', command: undefined }) - ) - }) - }) - - it('keeps the pick when a renderer-created tab is materialized after its surface stalls', async () => { - await onWindowsHost(async () => { - vi.useFakeTimers() - try { - const leafId = '44444444-4444-4444-8444-444444444444' - const { runtime, spawn } = makeHeadlessRuntime() - runtime.setNotifier(createMobileCreateTestNotifier(vi.fn())) - const webContents = { send: vi.fn() } - webContents.send = vi.fn((_channel: string, payload: { requestId: string }) => { - ipcMain.emit( - 'terminal:tabCreateReply', - { sender: webContents }, - { requestId: payload.requestId, tabId: 'tab-pending', title: 'Terminal' } - ) - }) - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [] }) - electronMocks.BrowserWindow.fromId.mockReturnValue({ - isDestroyed: () => false, - webContents - }) - - const create = runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, { - shellOverride: 'cmd.exe' - }) - await vi.waitFor(() => expect(webContents.send).toHaveBeenCalledTimes(1)) - expect(webContents.send.mock.calls[0]?.[1]).toMatchObject({ shellOverride: 'cmd.exe' }) - runtime.syncWindowGraph(1, { - tabs: [], - leaves: [], - mobileSessionTabs: [ - { - worktree: TEST_WORKTREE_ID, - publicationEpoch: 'renderer-pending', - snapshotVersion: 1, - activeGroupId: 'group-1', - activeTabId: `tab-pending::${leafId}`, - activeTabType: 'terminal', - tabs: [ - { - type: 'terminal', - id: `tab-pending::${leafId}`, - parentTabId: 'tab-pending', - leafId, - title: 'Terminal', - isActive: true - } - ] - } - ] - }) - await vi.advanceTimersByTimeAsync(1_000) - await create - - expect(spawn).toHaveBeenCalledWith( - expect.objectContaining({ tabId: 'tab-pending', shellOverride: 'cmd.exe' }) - ) - } finally { - vi.useRealTimers() - } - }) - }) - - it('refuses a Windows shell on a POSIX host before any terminal exists', async () => { - await onHostPlatform('linux', async () => { - const { runtime, spawn } = makeHeadlessRuntime() - - await expect( - runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, { shellOverride: 'cmd.exe' }) - ).rejects.toThrow(/names a Windows shell/) - expect(spawn).not.toHaveBeenCalled() - }) - }) - - it('refuses a shell pick for a workspace that runs over SSH', async () => { - await onWindowsHost(async () => { - const remoteRepo = { ...store.getRepo(TEST_REPO_ID)!, connectionId: 'ssh-1' } - const { runtime, spawn } = makeHeadlessRuntime({ - ...store, - getRepos: () => [remoteRepo], - getRepo: (id: string) => (id === TEST_REPO_ID ? remoteRepo : undefined) - }) - - await expect( - runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, { shellOverride: 'cmd.exe' }) - ).rejects.toThrow(/over SSH/) - expect(spawn).not.toHaveBeenCalled() - }) - }) -}) diff --git a/src/main/runtime/orca-runtime-tests/terminal-sleep-concurrent-worktrees.spec.ts b/src/main/runtime/orca-runtime-tests/terminal-sleep-concurrent-worktrees.spec.ts deleted file mode 100644 index 186fcccb8c5..00000000000 --- a/src/main/runtime/orca-runtime-tests/terminal-sleep-concurrent-worktrees.spec.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - MOCK_GIT_WORKTREES, - OrcaRuntimeService, - listWorktrees -} from '../orca-runtime-test-mocks.spec' -import { - TEST_REPO_ID, - TEST_WORKTREE_ID, - store, - deferred, - makeRuntimeStoreWithWorkspaceSession, - makeWorkspaceSessionWithHeadlessTerminal -} from '../orca-runtime-test-fixtures.spec' -import type { PtyProcessInfo } from '../../providers/pty-process-info' -import { spawnWorktreeTarget } from '../../../shared/worktree/host-qualified-identity' - -describe('concurrent worktree terminal sleeps', () => { - it('verifies sleep without waiting for a pending background poll or reviving its old PTY', async () => { - vi.useFakeTimers() - const live: PtyProcessInfo[] = [ - { - id: 'persisted-pty', - cwd: MOCK_GIT_WORKTREES[0].path, - title: 'bash', - worktreeId: TEST_WORKTREE_ID - } - ] - const listings: { - reply: ReturnType> - snapshot: PtyProcessInfo[] - }[] = [] - let draining = false - const { runtimeStore, getSession } = makeRuntimeStoreWithWorkspaceSession( - makeWorkspaceSessionWithHeadlessTerminal({ activeWorktreeId: null, activeTabId: null }) - ) - const runtime = new OrcaRuntimeService(runtimeStore) - const stopAndWait = vi.fn(async (ptyId: string) => { - live.splice(0) - runtime.onPtyExit(ptyId, 0) - return true - }) - runtime.setPtyController({ - write: () => true, - kill: () => false, - stopAndWait, - getForegroundProcess: async () => null, - listProcesses: () => { - if (draining) { - return Promise.resolve([...live]) - } - const reply = deferred() - listings.push({ reply, snapshot: [...live] }) - return reply.promise - } - }) - const sleeping = runtime.sleepTerminalsForWorktree(`id:${TEST_WORKTREE_ID}`) - const polling: ReturnType[] = [] - try { - await vi.advanceTimersByTimeAsync(0) - expect(listings).toHaveLength(1) - polling.push(runtime.listTerminals()) - await vi.advanceTimersByTimeAsync(0) - expect(listings).toHaveLength(2) - listings[0].reply.resolve(listings[0].snapshot) - await vi.advanceTimersByTimeAsync(0) - expect(stopAndWait).toHaveBeenCalledExactlyOnceWith('persisted-pty', expect.any(Object)) - expect(listings).toHaveLength(3) - polling.push(runtime.listTerminals()) - await vi.advanceTimersByTimeAsync(0) - expect(listings).toHaveLength(4) - listings[2].reply.resolve(listings[2].snapshot) - await expect(sleeping).resolves.toMatchObject({ - postStopVerified: true, - stoppedPtyIds: ['persisted-pty'] - }) - } finally { - draining = true - for (const { reply, snapshot } of listings) { - reply.resolve(snapshot) - } - await Promise.allSettled([sleeping, ...polling]) - vi.useRealTimers() - } - expect((await runtime.listTerminals()).terminals).toEqual([]) - expect( - runtime.shutdownReconnectFactsFor(getSession(), 'local').activeWorktreeIdsOnShutdown - ).toEqual([]) - await expect( - runtime.acquireWorktreeTerminalSpawn(spawnWorktreeTarget(TEST_WORKTREE_ID, null), { - refuseSleptWorktree: true - }) - ).rejects.toThrow('worktree_terminals_sleeping') - const release = await runtime.acquireWorktreeTerminalSpawn( - spawnWorktreeTarget(TEST_WORKTREE_ID, null) - ) - release() - }) - - it.each([2, 3])('sleeps %i worktrees at once on one PTY controller', async (count) => { - const worktrees = Array.from({ length: count }, (_, index) => - index === 0 - ? { path: MOCK_GIT_WORKTREES[0].path, id: TEST_WORKTREE_ID } - : { - path: `/tmp/worktree-${index}`, - id: `${TEST_REPO_ID}::/tmp/worktree-${index}` - } - ) - vi.mocked(listWorktrees).mockResolvedValue([ - ...MOCK_GIT_WORKTREES, - ...worktrees.slice(1).map((worktree, index) => ({ - path: worktree.path, - head: `head-${index}`, - branch: `feature/${index}`, - isBare: false, - isMainWorktree: false - })) - ]) - const live = new Map( - worktrees.map((worktree, index) => [ - `pty-${index}`, - { id: `pty-${index}`, cwd: worktree.path, title: 'bash', worktreeId: worktree.id } - ]) - ) - const runtime = new OrcaRuntimeService(store) - runtime.setPtyController({ - write: () => true, - kill: () => false, - stopAndWait: vi.fn(async (ptyId: string) => { - live.delete(ptyId) - runtime.onPtyExit(ptyId, 0) - return true - }), - getForegroundProcess: async () => null, - // Why a delay: a real daemon answers on a later turn, so the sleeps' listings overlap. - listProcesses: async () => { - const snapshot = [...live.values()] - await new Promise((resolve) => setTimeout(resolve, 5)) - return snapshot - } - }) - - const results = await Promise.all( - worktrees.map((worktree) => runtime.sleepTerminalsForWorktree(`id:${worktree.id}`)) - ) - - expect(results.map((result) => result.postStopVerified)).toEqual(worktrees.map(() => true)) - expect(results.flatMap((result) => result.stoppedPtyIds).sort()).toEqual( - worktrees.map((_, index) => `pty-${index}`) - ) - expect(live.size).toBe(0) - }) -}) diff --git a/src/main/runtime/orca-runtime-tests/worktree-terminal-close-reattached-relay.spec.ts b/src/main/runtime/orca-runtime-tests/worktree-terminal-close-reattached-relay.spec.ts deleted file mode 100644 index 7d99bef2ac8..00000000000 --- a/src/main/runtime/orca-runtime-tests/worktree-terminal-close-reattached-relay.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { OrcaRuntimeService } from '../orca-runtime-test-mocks.spec' -import { toSshExecutionHostId } from '../../../shared/execution-host' -import { - HEADLESS_LEAF_ID, - TEST_REPO_ID, - TEST_WORKTREE_ID, - makeHeadlessTerminalLayout, - makeRuntimeStoreWithWorkspaceSession, - makeWorkspaceSessionWithHeadlessTerminal, - store -} from '../orca-runtime-test-fixtures.spec' - -const CONNECTION_ID = 'conn-relay' -const REATTACHED_PTY_ID = `ssh:${CONNECTION_ID}@@pty-1` -const INCARNATION_ID = 'relay-incarnation-1' - -// A relay lease from an earlier app launch, reattached by this one, then closed from the CLI. -function makeReattachedRelayRuntime(): OrcaRuntimeService { - const session = makeWorkspaceSessionWithHeadlessTerminal({ - tabsByWorktree: { - [TEST_WORKTREE_ID]: [ - { - id: 'host-tab', - ptyId: REATTACHED_PTY_ID, - worktreeId: TEST_WORKTREE_ID, - title: 'Relay Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1 - } - ] - }, - terminalLayoutsByTabId: { - 'host-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: REATTACHED_PTY_ID }) - } - }) - // The real Store keeps an SSH workspace's session in its host's partition. - const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession( - session, - toSshExecutionHostId(CONNECTION_ID) - ) - const repo = { ...store.getRepo(TEST_REPO_ID)!, connectionId: CONNECTION_ID } - runtimeStore.getRepos = () => [repo] - runtimeStore.getRepo = (id: string) => (id === TEST_REPO_ID ? repo : undefined) - const runtime = new OrcaRuntimeService(runtimeStore) - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [] }) - return runtime -} - -function reattach(runtime: OrcaRuntimeService): void { - runtime.registerPty(REATTACHED_PTY_ID, TEST_WORKTREE_ID, CONNECTION_ID, { - tabId: 'host-tab', - leafId: HEADLESS_LEAF_ID, - incarnationId: INCARNATION_ID - }) -} - -describe('closing a workspace whose relay terminal was reattached from an earlier launch', () => { - it('reports the host-confirmed exit instead of doubting the retained SSH record', async () => { - const runtime = makeReattachedRelayRuntime() - reattach(runtime) - runtime.setPtyController({ - write: () => true, - kill: vi.fn(() => false), - stopAndWait: vi.fn(async (ptyId: string) => { - runtime.onPtyExit(ptyId, 0, INCARNATION_ID) - return true - }), - getForegroundProcess: async () => null - }) - - await expect( - runtime.closeTerminalsForWorktree(`id:${TEST_WORKTREE_ID}`) - ).resolves.not.toHaveProperty('ptyStopVerdict') - expect(runtime.getPtyLivenessVerdict(REATTACHED_PTY_ID)).toEqual({ - status: 'exited', - incarnationId: INCARNATION_ID, - cause: { kind: 'unknown', reason: 'cause_unreported' } - }) - }) - - it('accepts the host exit even when the stop confirmation carries no incarnation', async () => { - const runtime = makeReattachedRelayRuntime() - reattach(runtime) - runtime.setPtyController({ - write: () => true, - kill: vi.fn(() => false), - stopAndWait: vi.fn(async (ptyId: string) => { - // Field shape: the reattached relay exit arrives with code 1 and no incarnation. - runtime.onPtyExit(ptyId, 1, undefined, { hostExitConfirmed: true }) - return true - }), - getForegroundProcess: async () => null - }) - - await expect(runtime.closeTerminalsForWorktree(`id:${TEST_WORKTREE_ID}`)).resolves.toEqual( - expect.not.objectContaining({ ptyStopVerdict: expect.anything() }) - ) - expect(runtime.getPtyLivenessVerdict(REATTACHED_PTY_ID)).toEqual({ - status: 'exited', - incarnationId: INCARNATION_ID, - cause: { kind: 'exited', exitCode: 1 } - }) - }) - - it('keeps the exit when the reattach registration lands after the stop', async () => { - const runtime = makeReattachedRelayRuntime() - reattach(runtime) - runtime.setPtyController({ - write: () => true, - kill: vi.fn(() => false), - stopAndWait: vi.fn(async (ptyId: string) => { - runtime.onPtyExit(ptyId, 0, INCARNATION_ID) - // The reattach that proved this incarnation alive finishes registering only now. - reattach(runtime) - return true - }), - getForegroundProcess: async () => null - }) - - await expect( - runtime.closeTerminalsForWorktree(`id:${TEST_WORKTREE_ID}`) - ).resolves.not.toHaveProperty('ptyStopVerdict') - expect(runtime.getPtyLivenessVerdict(REATTACHED_PTY_ID)).toEqual({ - status: 'exited', - incarnationId: INCARNATION_ID, - cause: { kind: 'unknown', reason: 'cause_unreported' } - }) - }) - - it('still doubts a retained SSH record whose exit was never confirmed', async () => { - const runtime = makeReattachedRelayRuntime() - reattach(runtime) - runtime.setPtyController({ - write: () => true, - kill: vi.fn(() => false), - stopAndWait: vi.fn(async () => false), - getForegroundProcess: async () => null - }) - - await expect( - runtime.closeTerminalsForWorktree(`id:${TEST_WORKTREE_ID}`) - ).resolves.toMatchObject({ - stopped: 0, - ptyStopVerdict: 'unverifiable', - ptyStopReason: 'the owning host did not confirm the PTY exit' - }) - }) -}) diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index a9d8b19a34e..88bb93a43cd 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -94,7 +94,6 @@ await import('./orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec') await import('./orca-runtime-tests/mobile-session-tabs-empty-headless-snapshot.spec') await import('./orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec') await import('./orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec') -await import('./orca-runtime-tests/mobile-session-tabs-shell-selection.spec') await import('./orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec') await import('./orca-runtime-tests/mobile-session-tabs-windowless-sleep-resume.spec') await import('./orca-runtime-tests/mobile-session-tabs-targeted-agent-resume.spec') @@ -114,8 +113,6 @@ await import('./orca-runtime-tests/worktree-ps-agent-row-dismissal.spec') await import('./orca-runtime-tests/terminal-sleep-and-teardown.spec') await import('./orca-runtime-tests/terminal-sleep-and-teardown-part-02.spec') await import('./orca-runtime-tests/terminal-sleep-and-teardown-part-03.spec') -await import('./orca-runtime-tests/terminal-sleep-concurrent-worktrees.spec') -await import('./orca-runtime-tests/worktree-terminal-close-reattached-relay.spec') await import('./orca-runtime-tests/lineage-and-scan-cache.spec') await import('./orca-runtime-tests/lineage-and-scan-cache-part-02.spec') await import('./orca-runtime-tests/lineage-and-scan-cache-part-05.spec') diff --git a/src/main/runtime/orchestration-mailbox-delivery.test.ts b/src/main/runtime/orchestration-mailbox-delivery.test.ts index 0d5bafc5767..ba0e65348a6 100644 --- a/src/main/runtime/orchestration-mailbox-delivery.test.ts +++ b/src/main/runtime/orchestration-mailbox-delivery.test.ts @@ -10,7 +10,6 @@ vi.mock('electron', () => ({ import './orchestration-dispatch-mailbox-delivery.spec' import './orchestration-mailbox-pointer-cli-command.spec' -import './orchestration-mailbox-transport-settlement.spec' import './orchestration-mailbox-cold-park-idle.spec' import './orchestration-mailbox-bound-run-scan.spec' import './orchestration-mailbox-typed-pointer-submit.spec' diff --git a/src/main/runtime/orchestration-mailbox-transport-settlement.spec.ts b/src/main/runtime/orchestration-mailbox-transport-settlement.spec.ts deleted file mode 100644 index 56852898800..00000000000 --- a/src/main/runtime/orchestration-mailbox-transport-settlement.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { rmSync } from 'node:fs' -import { - WRITE_ACCEPTED, - writeRefused, - writeUnverifiable, - type WriteSettlement -} from '../../shared/terminal/pty/lifecycle/pty-write-settlement' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { - createBoundRun, - createDatabase, - createRuntime, - driveToLiveIdle, - insertDirectRunMessage, - isMailboxPointer, - pointerCount, - temporaryDirectories -} from './orchestration-mailbox-notification-test-harness' -import { - MAILBOX_POINTER_ENTER_ATTEMPTED, - MAILBOX_POINTER_WRITE_ATTEMPTED -} from './orchestration/db/messages/mailbox-pointer-enter-state' - -describe('orchestration mailbox transport settlement', () => { - afterEach(() => { - vi.useRealTimers() - for (const directory of temporaryDirectories.splice(0)) { - rmSync(directory, { recursive: true, force: true }) - } - }) - - it('durably reserves before transport and redrives a rejected write', async () => { - vi.useFakeTimers() - const db = createDatabase('orca-mailbox-transport-settlement-') - const first = createRuntime(db) - const observedWrite = vi.fn((_ptyId: string, _data: string) => true) - let settleWrite: ((settlement: WriteSettlement) => void) | undefined - first.runtime.setPtyController({ - write: observedWrite, - writeWithSettlement: vi.fn( - () => - new Promise((resolve) => { - settleWrite = resolve - }) - ), - kill: vi.fn(), - getForegroundProcess: async () => null - }) - const run = createBoundRun(db, 'SSH settlement Run') - const message = insertDirectRunMessage(db, run.id, 'Rejected SSH pointer') - - await driveToLiveIdle(first.runtime) - expect(pointerCount(observedWrite)).toBe(0) - expect(db.getMessageById(message.id)).toMatchObject({ - delivered_at: null, - pointer_enter_pending: MAILBOX_POINTER_WRITE_ATTEMPTED - }) - - settleWrite?.(writeRefused('provider_refused_write')) - await Promise.resolve() - await Promise.resolve() - expect(pointerCount(observedWrite)).toBe(0) - // Proven refusal releases the reservation outright; ambiguity never may. - expect(db.getMessageById(message.id)).toMatchObject({ - delivered_at: null, - pointer_enter_pending: 0 - }) - - const restarted = createRuntime(db) - await driveToLiveIdle(restarted.runtime) - await Promise.resolve() - expect(pointerCount(restarted.write)).toBe(1) - expect(db.getMessageById(message.id)?.delivered_at).toBeNull() - db.close() - }) - - it('preserves the reservation when a settled write throws after handing off bytes', async () => { - vi.useFakeTimers() - const db = createDatabase('orca-mailbox-throwing-settlement-') - const first = createRuntime(db) - const observedWrite = vi.fn((_ptyId: string, _data: string) => true) - first.runtime.setPtyController({ - write: observedWrite, - writeWithSettlement: (ptyId: string, data: string) => { - observedWrite(ptyId, data) - if (isMailboxPointer(data)) { - throw new Error('relay socket destroyed mid-write') - } - return WRITE_ACCEPTED - }, - kill: vi.fn(), - getForegroundProcess: async () => null - }) - const run = createBoundRun(db, 'Throwing SSH pointer') - const message = insertDirectRunMessage(db, run.id, 'Keep one pointer through a throw') - - await driveToLiveIdle(first.runtime) - await Promise.resolve() - expect(pointerCount(observedWrite)).toBe(1) - // A throw after the bytes may have left is unverifiable, so the claim must survive. - expect(db.getMessageById(message.id)?.pointer_enter_pending).toBe( - MAILBOX_POINTER_WRITE_ATTEMPTED - ) - - const restarted = createRuntime(db) - await driveToLiveIdle(restarted.runtime) - expect(pointerCount(restarted.write)).toBe(0) - expect(db.getMessageById(message.id)?.read).toBe(0) - db.close() - }) - - it('does not replay Enter after its settlement is lost', async () => { - vi.useFakeTimers() - const db = createDatabase('orca-mailbox-ambiguous-enter-') - const first = createRuntime(db) - const observedWrite = vi.fn((_ptyId: string, _data: string) => true) - first.runtime.setPtyController({ - write: observedWrite, - writeWithSettlement: (ptyId: string, data: string) => { - observedWrite(ptyId, data) - return Promise.resolve( - data === '\r' ? writeUnverifiable('transport_settlement_lost', true) : WRITE_ACCEPTED - ) - }, - kill: vi.fn(), - getForegroundProcess: async () => null - }) - const run = createBoundRun(db, 'Ambiguous Enter Run') - const message = insertDirectRunMessage(db, run.id, 'Submit exactly once') - - await driveToLiveIdle(first.runtime) - await vi.advanceTimersByTimeAsync(500) - expect(enterCount(observedWrite)).toBe(1) - // Unproven submission: not settled as delivered, and not rolled back to a resendable state. - expect(db.getMessageById(message.id)).toMatchObject({ - delivered_at: null, - pointer_enter_pending: MAILBOX_POINTER_ENTER_ATTEMPTED - }) - - const restarted = createRuntime(db) - await driveToLiveIdle(restarted.runtime) - await vi.advanceTimersByTimeAsync(500) - expect(enterCount(restarted.write)).toBe(0) - expect(pointerCount(restarted.write)).toBe(0) - expect(db.getMessageById(message.id)?.read).toBe(0) - db.close() - }) -}) - -function enterCount(write: ReturnType): number { - return write.mock.calls.filter(([, payload]) => payload === '\r').length -} diff --git a/src/main/runtime/orchestration/crew/crew-pending-questions.test.ts b/src/main/runtime/orchestration/crew/crew-pending-questions.test.ts index e3ec7e99ee4..8e58454fbae 100644 --- a/src/main/runtime/orchestration/crew/crew-pending-questions.test.ts +++ b/src/main/runtime/orchestration/crew/crew-pending-questions.test.ts @@ -1,17 +1,6 @@ import { expect, it } from 'vitest' import { fixture } from './crew-read-model.test-fixture' import { buildCrewSnapshot } from './crew-read-model' -import { AGENT_STATUS_STALE_AFTER_MS } from '../../../../shared/agent-status/contracts/agent-status-freshness' -import { agentSessionRecordFixture } from '../../../native-chat/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' - -// Ages the clock from the writers' actual timestamps, so the cutoff lands just past them. -function agedWriterClock(previousMaximum: number | undefined, ...timestamps: string[]) { - const maximum = Math.max(previousMaximum ?? -Infinity, ...timestamps.map(Date.parse)) - const cutoff = maximum + 1 - return { maximum, now: cutoff + AGENT_STATUS_STALE_AFTER_MS, cutoff } -} it('decorates handed-back peer questions after row creation without flattening direct children', () => { const { db, deps, run, assign } = fixture() @@ -34,98 +23,3 @@ it('decorates handed-back peer questions after row creation without flattening d ) expect(db.getQuestion(question.message.id)?.status).toBe('pending') }) - -it('keeps true traffic cutoff independent of pending membership and excludes self', () => { - const f = fixture() - const self = f.assign('term_coord') - const helper = f.assign('term_helper', 'term_worker') - const pending = f.db.createQuestion({ - runId: f.run.id, - dispatchId: helper.id, - askerHandle: 'term_helper', - question: 'attention survives cutoff' - }) - f.db.createQuestion({ - runId: f.run.id, - dispatchId: self.id, - askerHandle: 'term_coord', - question: 'not my own peer' - }) - const at = Date.parse(pending.message.created_at) - const read = (now: number) => buildCrewSnapshot('term_coord', { ...f.deps, now }) - const boundary = read(at + AGENT_STATUS_STALE_AFTER_MS) - const rowAt = boundary.sections.talkingWith.find( - (row) => row.pendingQuestion?.preview === 'attention survives cutoff' - ) - expect(rowAt?.latest?.sequence).toBe(pending.message.sequence) - const aged = read(at + AGENT_STATUS_STALE_AFTER_MS + 1) - const rowAfter = aged.sections.talkingWith.find( - (row) => row.pendingQuestion?.preview === 'attention survives cutoff' - ) - expect(rowAfter).toHaveProperty('pendingQuestion') - expect(rowAfter).not.toHaveProperty('latest') - expect( - Object.values(aged.sections) - .flat() - .some((row) => row.partyKey.endsWith('term_coord"]')) - ).toBe(false) -}) - -it('folds pending representatives across committed clear aliases without fabricating Latest', () => { - const f = fixture() - const root = testOrcaSessionId('chat-pending-root') - const next = testOrcaSessionId('chat-pending-next') - const records = [ - agentSessionConversationView( - agentSessionRecordFixture({ - sessionId: root, - conversationCommand: { - command: 'clear', - state: 'completed', - callerKey: 'pending-clear', - phase: 'committed', - replacementSessionId: next, - operationId: 'pending-clear-one' - } - }) - ), - agentSessionConversationView(agentSessionRecordFixture({ sessionId: next })) - ] - f.deps.records = { - listRecords: () => records, - getRecord: (id) => records.find((record) => record.sessionId === id) ?? null, - getVisibleSessionTabIndex: () => ({ present: true, sessionIds: [next] }) - } - const questions = [root, next].map((id) => { - const dispatch = f.assign(`orca_session_id:${id}`, 'term_worker') - return f.db.createQuestion({ - runId: f.run.id, - dispatchId: dispatch.id, - askerHandle: `orca_session_id:${id}`, - question: `pending ${id}` - }) - }) - let aged = agedWriterClock(undefined, ...questions.map(({ message }) => message.created_at)) - let now = aged.now - const snapshot = () => buildCrewSnapshot('term_coord', { ...f.deps, now }) - let peers = snapshot().sections.talkingWith - expect(peers).toHaveLength(1) - expect(peers[0]).toMatchObject({ - partyKey: JSON.stringify(['test-host', `orca_session_id:${root}`]), - pendingQuestion: { preview: `pending ${next}` }, - openTarget: { kind: 'chat', sessionId: next } - }) - expect(peers[0]).not.toHaveProperty('latest') - const answer = f.db.answerQuestion({ - messageId: questions[1].message.id, - runId: f.run.id, - consumerGeneration: f.run.consumer_generation, - body: 'answered representative' - }) - aged = agedWriterClock(aged.maximum, answer.message.created_at) - now = aged.now - peers = snapshot().sections.talkingWith - expect(peers).toHaveLength(1) - expect(peers[0].pendingQuestion?.preview).toBe(`pending ${root}`) - expect(peers[0]).not.toHaveProperty('latest') -}) diff --git a/src/main/runtime/orchestration/db-task-create-readiness.test.ts b/src/main/runtime/orchestration/db-task-create-readiness.test.ts index 4b661829d02..fd49eed8cda 100644 --- a/src/main/runtime/orchestration/db-task-create-readiness.test.ts +++ b/src/main/runtime/orchestration/db-task-create-readiness.test.ts @@ -2,13 +2,8 @@ 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 type Database from '../../sqlite/sync-database' import { OrchestrationDb } from './db' -type OrchestrationDbAccess = { - db: Database.Database -} - describe('task creation dependency readiness', () => { const databases: OrchestrationDb[] = [] const directories: string[] = [] @@ -31,46 +26,6 @@ describe('task creation dependency readiness', () => { return database } - it('creates a late dependent as ready when every dependency is completed', () => { - const db = createDb() - const first = db.createTask({ runId: 'run_legacy_local', spec: 'first' }) - const second = db.createTask({ runId: 'run_legacy_local', spec: 'second' }) - db.updateTaskStatus(first.id, 'completed') - db.updateTaskStatus(second.id, 'completed') - - const child = db.createTask({ - runId: 'run_legacy_local', - spec: 'child', - deps: [first.id, second.id] - }) - - expect(child.status).toBe('ready') - }) - - it('observes a dependency completion forced between readiness evaluation and insertion', () => { - const directory = mkdtempSync(join(tmpdir(), 'orca-task-readiness-race-')) - directories.push(directory) - const path = join(directory, 'orchestration.db') - const db = createDb(path) - const concurrent = createDb(path) - const dependency = db.createTask({ runId: 'run_legacy_local', spec: 'dependency' }) - const sqlite = (db as unknown as OrchestrationDbAccess).db - const prepare = sqlite.prepare.bind(sqlite) - let injected = false - vi.spyOn(sqlite, 'prepare').mockImplementation((sql) => { - if (!injected && sql.includes('INSERT INTO tasks')) { - injected = true - concurrent.updateTaskStatus(dependency.id, 'completed') - } - return prepare(sql) - }) - - const child = db.createTask({ runId: 'run_legacy_local', spec: 'child', deps: [dependency.id] }) - - expect(injected).toBe(true) - expect(child.status).toBe('ready') - }) - it('promotes only after every dependency completes', () => { const db = createDb() const first = db.createTask({ runId: 'run_legacy_local', spec: 'first' }) @@ -116,17 +71,6 @@ describe('task creation dependency readiness', () => { expect(db.listTasks()).toEqual([]) }) - it('preserves a caller-owned transaction', () => { - const db = createDb() - const sqlite = (db as unknown as OrchestrationDbAccess).db - sqlite.exec('BEGIN IMMEDIATE') - - const task = db.createTask({ runId: 'run_legacy_local', spec: 'transactional child' }) - sqlite.exec('ROLLBACK') - - expect(db.getTask(task.id)).toBeUndefined() - }) - it('preserves readiness and later promotion across reload', () => { const directory = mkdtempSync(join(tmpdir(), 'orca-task-readiness-')) directories.push(directory) diff --git a/src/main/runtime/rpc/methods/mobile-web-bundle-window-refusals.test.ts b/src/main/runtime/rpc/methods/mobile-web-bundle-window-refusals.test.ts index ef62b859d9d..8f66911b48c 100644 --- a/src/main/runtime/rpc/methods/mobile-web-bundle-window-refusals.test.ts +++ b/src/main/runtime/rpc/methods/mobile-web-bundle-window-refusals.test.ts @@ -12,11 +12,7 @@ import { resetBundledMobileWebBundleCacheForTests } from '../../bundled-mobile-w import type { RpcResponse } from '../core' import type { RpcDispatcher } from '../dispatcher' import { resetMobileWebBundleAssetVerdictsForTests } from './mobile-web-bundle-asset-reader' -import { - acquireMobileWebBundleReadSlot, - MAX_CONCURRENT_MOBILE_WEB_BUNDLE_READS, - resetMobileWebBundleReadAdmissionForTests -} from './mobile-web-bundle-read-admission' +import { resetMobileWebBundleReadAdmissionForTests } from './mobile-web-bundle-read-admission' import { installMobileWebBundleAppPath, mobileWebBundleDispatcher, @@ -52,7 +48,7 @@ afterEach(() => { describe.each([ { method: MOBILE_WEB_BUNDLE_CHUNK_METHOD, grid: MOBILE_WEB_BUNDLE_CHUNK_BYTES }, { method: MOBILE_WEB_BUNDLE_RANGE_METHOD, grid: MOBILE_WEB_BUNDLE_CHUNK_BYTES } -])('$method refusals', ({ method, grid }) => { +])('$method refusals', ({ method }) => { let bundle: SyntheticMobileWebBundle let script: SyntheticMobileWebBundle['assets'][number] @@ -64,11 +60,6 @@ describe.each([ script = bundle.assets.find((asset) => asset.path.endsWith('.js'))! }) - it('refuses a stale build before looking the path up', async () => { - const response = await read({ buildId: '0'.repeat(64), path: 'assets/nope.js', offset: 0 }) - expect(errorMessage(response)).toBe('mobile_web_bundle_build_changed') - }) - it('refuses a path that is not a manifest member', async () => { for (const path of ['assets/does-not-exist.js', 'manifest.json', 'INDEX.HTML']) { const response = await read({ buildId: bundle.buildId, path, offset: 0 }) @@ -76,50 +67,9 @@ describe.each([ } }) - it('refuses an offset off its grid, or an aligned one past the end', async () => { - for (const offset of [1, grid - 1, grid + 1, grid * 3]) { - const response = await read({ buildId: bundle.buildId, path: script.path, offset }) - expect(errorMessage(response)).toBe('mobile_web_bundle_offset_invalid') - } - }) - it('refuses an asset whose bytes no longer hash to the manifest', async () => { writeFileSync(join(bundle.root, script.path), mobileWebBundleFiller(script.byteLength, 99)) const response = await read({ buildId: bundle.buildId, path: script.path, offset: 0 }) expect(errorMessage(response)).toBe('mobile_web_bundle_asset_changed') }) - - // Both methods share one per-connection budget; mixing them must not double the cap. - it('charges the shared read slots and refuses one past the cap', async () => { - const held = Array.from({ length: MAX_CONCURRENT_MOBILE_WEB_BUNDLE_READS }, () => - acquireMobileWebBundleReadSlot('conn-a') - ) - expect(held.every((release) => release !== null)).toBe(true) - const params = { buildId: bundle.buildId, path: 'index.html', offset: 0 } - - expect(errorMessage(await read(params, { connectionId: 'conn-a' }))).toBe( - 'mobile_web_bundle_read_limited' - ) - expect((await read(params, { connectionId: 'conn-b' })).ok).toBe(true) - held[0]!() - expect((await read(params, { connectionId: 'conn-a' })).ok).toBe(true) - }) - - it('falls back to the device token when the connection has no id', async () => { - Array.from({ length: MAX_CONCURRENT_MOBILE_WEB_BUNDLE_READS }, () => - acquireMobileWebBundleReadSlot('device-token-1') - ) - const response = await read( - { buildId: bundle.buildId, path: 'index.html', offset: 0 }, - { clientId: 'device-token-1' } - ) - expect(errorMessage(response)).toBe('mobile_web_bundle_read_limited') - }) - - it('answers unavailable on an install with no bundle', async () => { - rmSync(join(scratch, 'out'), { recursive: true, force: true }) - resetBundledMobileWebBundleCacheForTests() - const response = await read({ buildId: '0'.repeat(64), path: 'index.html', offset: 0 }) - expect(errorMessage(response)).toBe('mobile_web_bundle_unavailable') - }) }) diff --git a/src/main/runtime/rpc/methods/structured-agent-session-policy.test.ts b/src/main/runtime/rpc/methods/structured-agent-session-policy.test.ts deleted file mode 100644 index 0c2dcb0ae35..00000000000 --- a/src/main/runtime/rpc/methods/structured-agent-session-policy.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - PI_STRUCTURED_DIALOGS_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY -} from '../../../../shared/agent-session/structured-agent-session-surface-capabilities' -import { - STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY, - STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY -} from '../../../../shared/agent-session/agent-session-runtime-capabilities' -import { - createSupportFollowsHostSetting, - isStructuredNativeChatDefaultFor, - structuredAgentsReadBy, - clientRendersStructuredAgent, - supportsStructuredAgentSessions -} from './structured-agent-session-policy' -import { parseRuntimeClientCapabilities } from '../runtime-client-capabilities' -import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/electron-remote-runtime-client-capabilities' -import { remoteRuntimeClientCapabilities } from '../../../../shared/remote-runtime/remote-runtime-client-capabilities' -import { WEB_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/web-runtime-client-capabilities' - -const CAPABLE = [STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY] - -describe('Pi audience in a registered-agents client', () => { - const registered = [...CAPABLE, STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY] - - it('keeps Pi out of tab and restart audiences until the dialog shape is advertised', () => { - expect(clientRendersStructuredAgent(registered, 'pi')).toBe(false) - expect(clientRendersStructuredAgent(registered, 'grok')).toBe(true) - const audience = structuredAgentsReadBy( - { clientKind: 'runtime', clientCapabilities: registered }, - ['codex', 'grok', 'pi'] - ) - expect(audience?.('pi')).toBe(false) - expect(audience?.('grok')).toBe(true) - expect( - structuredAgentsReadBy( - { - clientKind: 'runtime', - clientCapabilities: [...registered, PI_STRUCTURED_DIALOGS_RUNTIME_CAPABILITY] - }, - ['codex', 'grok', 'pi'] - ) - ).toBeUndefined() - }) -}) - -describe('supportsStructuredAgentSessions', () => { - it.each(['runtime', 'mobile'] as const)( - 'admits a %s client that advertises the capability', - (clientKind) => { - expect(supportsStructuredAgentSessions({ clientKind, clientCapabilities: CAPABLE })).toBe( - true - ) - } - ) - - it('admits a capability-less in-process caller, which negotiates nothing', () => { - expect( - supportsStructuredAgentSessions({ clientKind: undefined, clientCapabilities: undefined }) - ).toBe(true) - }) - - it.each(['runtime', 'mobile'] as const)( - 'refuses a %s client that did not advertise the capability', - (clientKind) => { - expect(supportsStructuredAgentSessions({ clientKind, clientCapabilities: [] })).toBe(false) - expect(supportsStructuredAgentSessions({ clientKind, clientCapabilities: undefined })).toBe( - false - ) - } - ) -}) - -describe('createSupportFollowsHostSetting', () => { - it.each(['runtime', 'mobile'] as const)( - 'leaves a %s client that picks its own launch mode to capability alone', - (clientKind) => { - expect( - createSupportFollowsHostSetting({ - clientKind, - clientCapabilities: [...CAPABLE, STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY] - }) - ).toBe(false) - } - ) - - it.each(['runtime', 'mobile'] as const)( - 'answers a %s client that leaves the mode to the host with the host setting', - (clientKind) => { - expect(createSupportFollowsHostSetting({ clientKind, clientCapabilities: CAPABLE })).toBe( - true - ) - } - ) - - it('leaves an in-process caller, the same build as the host, to capability alone', () => { - expect( - createSupportFollowsHostSetting({ clientKind: undefined, clientCapabilities: undefined }) - ).toBe(false) - }) -}) - -// Both run the same chat; the browser client once omitted the structured capability and every -// chat was refused (#32145). -describe.each([ - ['browser client', WEB_RUNTIME_CLIENT_CAPABILITIES], - ['desktop', remoteRuntimeClientCapabilities(ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES)] -])("this build's %s handshake, as a paired host parses it", (_client, advertised) => { - const clientCapabilities = parseRuntimeClientCapabilities([...advertised]) - const context = { clientKind: 'runtime' as const, clientCapabilities } - - it('is kept whole and admits structured sessions', () => { - expect(clientCapabilities).toEqual(advertised) - expect(supportsStructuredAgentSessions(context)).toBe(true) - expect(createSupportFollowsHostSetting(context)).toBe(false) - }) - - it.each(['claude', 'codex', 'pi', 'opencode'])('renders %s chats', (agent) => { - expect(clientRendersStructuredAgent(clientCapabilities, agent)).toBe(true) - }) -}) - -it.each(['chat', 'terminal'] as const)( - 'legacy phone admission reads host default %s and its override', - (agentDefaultSurface) => { - const runtime = { - requireStore: () => ({ - getSettings: () => ({ - agentDefaultSurface, - nativeChatProviders: { codex: agentDefaultSurface !== 'chat' } - }) - }) - } - expect( - createSupportFollowsHostSetting({ clientKind: 'mobile', clientCapabilities: CAPABLE }) - ).toBe(true) - expect(isStructuredNativeChatDefaultFor(runtime, 'claude')).toBe(agentDefaultSurface === 'chat') - expect(isStructuredNativeChatDefaultFor(runtime, 'codex')).toBe(agentDefaultSurface !== 'chat') - } -) diff --git a/src/main/runtime/rpc/methods/worktree-create-navigation.test.ts b/src/main/runtime/rpc/methods/worktree-create-navigation.test.ts deleted file mode 100644 index bd3490ef453..00000000000 --- a/src/main/runtime/rpc/methods/worktree-create-navigation.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import '../unused-default-rpc-methods.test-fixture' -import { describe, expect, it, vi } from 'vitest' -import { RpcDispatcher } from '../dispatcher' -import type { RpcRequest } from '../core' -import type { OrcaRuntimeService } from '../../orca-runtime' -import { WORKTREE_METHODS } from './worktree' - -const repo = { - id: 'repo-1', - path: '/workspace/repo', - displayName: 'repo', - badgeColor: '#000', - addedAt: 1, - kind: 'git' as const, - executionHostId: 'ssh:ssh-target-1' as const -} - -function makeRequest(method: string, params?: unknown): RpcRequest { - return { id: 'req-1', authToken: 'tok', method, params } -} - -const passthroughDedupe = (_repo: string, _id: string | undefined, run: () => Promise) => - run() - -describe('worktree.create navigation authority', () => { - it.each([ - ['runtime', 'caller'], - // Mobile still needs the host renderer to provision setup/default tabs. - ['mobile', 'host'] - ] as const)( - 'resolves create activation from the paired %s client kind', - async (clientKind, expected) => { - const runtime = { - getRuntimeId: () => 'test-runtime', - dedupeWorktreeCreate: passthroughDedupe, - showRepo: vi.fn().mockResolvedValue(repo), - createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) - - await dispatcher.dispatchStreaming( - makeRequest('worktree.create', { repo: 'repo-1', name: 'feature', activate: true }), - () => {}, - { clientKind, pairedDeviceId: 'device-1', connectionId: 'conn-1' } - ) - - expect(runtime.createManagedWorktree).toHaveBeenCalledWith( - expect.objectContaining({ activate: true, navigation: expected }) - ) - } - ) - - it.each([undefined, 'all'] as const)( - 'keeps an older CLI reveal on the host (navigation=%s)', - async (navigation) => { - const runtime = { - getRuntimeId: () => 'test-runtime', - dedupeWorktreeCreate: passthroughDedupe, - showRepo: vi.fn().mockResolvedValue(repo), - createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) - - await dispatcher.dispatchStreaming( - makeRequest('worktree.create', { - repo: 'repo-1', - name: 'feature', - activate: true, - cliProvenanceRequest: {}, - ...(navigation ? { navigation } : {}) - }), - () => {}, - { clientKind: 'runtime', pairedDeviceId: 'device-1', connectionId: 'conn-1' } - ) - - expect(runtime.createManagedWorktree).toHaveBeenCalledWith( - expect.objectContaining({ navigation: 'host' }) - ) - } - ) - - it('still scopes a desktop create that carries no CLI marker', async () => { - const runtime = { - getRuntimeId: () => 'test-runtime', - dedupeWorktreeCreate: passthroughDedupe, - showRepo: vi.fn().mockResolvedValue(repo), - createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) - - await dispatcher.dispatchStreaming( - makeRequest('worktree.create', { repo: 'repo-1', name: 'feature', activate: true }), - () => {}, - { clientKind: 'runtime', pairedDeviceId: 'device-1', connectionId: 'conn-1' } - ) - - expect(runtime.createManagedWorktree).toHaveBeenCalledWith( - expect.objectContaining({ navigation: 'caller' }) - ) - }) - - it('honors an explicit follow navigation on create', async () => { - const runtime = { - getRuntimeId: () => 'test-runtime', - dedupeWorktreeCreate: passthroughDedupe, - showRepo: vi.fn().mockResolvedValue(repo), - createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) - - await dispatcher.dispatchStreaming( - makeRequest('worktree.create', { - repo: 'repo-1', - name: 'feature', - activate: true, - navigation: 'clients' - }), - () => {}, - { clientKind: 'runtime', pairedDeviceId: 'device-1', connectionId: 'conn-1' } - ) - - expect(runtime.createManagedWorktree).toHaveBeenCalledWith( - expect.objectContaining({ navigation: 'clients' }) - ) - }) - - it('keeps an explicit all-surface reveal from a paired caller', async () => { - const runtime = { - getRuntimeId: () => 'test-runtime', - dedupeWorktreeCreate: passthroughDedupe, - showRepo: vi.fn().mockResolvedValue(repo), - createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-1' } }) - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }) - - await dispatcher.dispatchStreaming( - makeRequest('worktree.create', { - repo: 'repo-1', - name: 'feature', - activate: true, - navigation: 'all' - }), - () => {}, - { clientKind: 'runtime', pairedDeviceId: 'device-1', connectionId: 'conn-1' } - ) - - expect(runtime.createManagedWorktree).toHaveBeenCalledWith( - expect.objectContaining({ navigation: 'all' }) - ) - }) -}) diff --git a/src/main/runtime/runtime-legacy-worker-recovery-lifetime.test.ts b/src/main/runtime/runtime-legacy-worker-recovery-lifetime.test.ts deleted file mode 100644 index ab17d878085..00000000000 --- a/src/main/runtime/runtime-legacy-worker-recovery-lifetime.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { afterEach, expect, it, vi } from 'vitest' -import { RuntimeLegacyWorkerTerminalRecoveryController } from './runtime-legacy-worker-terminal-recovery-controller' -import type { LegacyWorkerRecoveryPorts } from './runtime-legacy-worker-terminal-recovery-types' -import type { - LegacyWorkerTerminalRecoveryCandidate, - LegacyWorkerTerminalRecoveryPlan -} from './orchestration/orchestration-legacy-worker-terminal-recovery' - -function candidate(ptyId: string, dispatchId: string): LegacyWorkerTerminalRecoveryCandidate { - return { - dispatchId, - dispatchStatus: 'dispatched', - contractVersion: 1, - taskId: 'task', - worktreeId: 'worktree', - terminalHandle: 'handle', - paneKey: 'tab:leaf', - tabId: 'tab', - leafId: 'leaf', - processIncarnation: `${ptyId}:incarnation`, - ptyId, - incarnationId: 'incarnation' - } -} - -// A local worker the pass must resolve, so the pass is still running when shutdown begins. -const ACTIVE_CANDIDATE: LegacyWorkerTerminalRecoveryPlan['candidates'][number] = { - dispatchId: 'dispatch-1', - dispatchStatus: 'dispatched', - contractVersion: 1, - taskId: 'task-1', - worktreeId: 'repo-1::/tmp/worktree-a', - terminalHandle: 'handle-1', - paneKey: 'tab-1:pane-1', - tabId: 'tab-1', - leafId: 'pane-1', - processIncarnation: 'pty-1:inc-1', - ptyId: 'pty-1', - incarnationId: 'inc-1' -} - -function setup() { - const ports = { - preparePlan: vi.fn((): LegacyWorkerTerminalRecoveryPlan => ({ - candidates: [], - ambiguousDispatchIds: [] - })), - resolveWorkspace: vi.fn(async () => { - throw new Error('unused') - }), - refreshInventory: vi.fn(async () => null), - runMutation: (_worktreeId: string, operation: () => Promise) => operation(), - getActivation: () => ({}), - hasExactPersistedSurface: () => false, - hasExactSurface: () => false, - adopt: vi.fn(async () => {}), - getRendererEpoch: () => 0, - reveal: vi.fn(async () => null), - onPtyExit: vi.fn(), - persist: vi.fn(async (): Promise> => new Set()), - rollback: vi.fn(), - reconcileMissing: () => false, - notifyResolution: vi.fn(), - canRecoverPersistentLocalPtys: () => true, - reconcileRequestedReleases: vi.fn(async (): Promise => undefined), - hasRequestedReleases: () => false, - reconcile: vi.fn(async () => ({ - adoptedDispatchIds: [], - exitedDispatchIds: [], - deferredDispatchIds: [] - })), - updateRetry: vi.fn() - } satisfies LegacyWorkerRecoveryPorts - const controller = new RuntimeLegacyWorkerTerminalRecoveryController(ports) - return { controller, ports } -} -afterEach(() => vi.useRealTimers()) - -it('cancels both local and SSH retry timers and refuses later recovery work', async () => { - vi.useFakeTimers() - const { controller, ports } = setup() - const plan: LegacyWorkerTerminalRecoveryPlan = { - ambiguousDispatchIds: [], - candidates: [ - candidate('local-pty', 'local-dispatch'), - candidate('ssh:host@@remote-pty', 'remote-dispatch') - ] - } - const deferred = new Set(['local-dispatch', 'remote-dispatch']) - controller.updateRetry(plan, deferred, {}) - controller.updateRetry(plan, deferred, { connectionId: 'host' }) - expect(vi.getTimerCount()).toBe(2) - await controller.stop() - controller.updateRetry(plan, deferred, {}) - await vi.advanceTimersByTimeAsync(60_000) - expect(vi.getTimerCount()).toBe(0) - expect(ports.reconcile).not.toHaveBeenCalled() - await expect(controller.reconcile()).rejects.toThrow('recovery_stopped') -}) - -it('drains an active recovery pass but refuses a queued pass after shutdown', async () => { - const { controller, ports } = setup() - ports.preparePlan.mockReturnValueOnce({ - candidates: [ACTIVE_CANDIDATE], - ambiguousDispatchIds: [] - }) - let finish!: () => void - ports.resolveWorkspace.mockImplementationOnce( - () => - new Promise((_resolve, reject) => { - finish = () => reject(new Error('workspace gone')) - }) - ) - const first = controller.reconcile() - await vi.waitFor(() => expect(ports.resolveWorkspace).toHaveBeenCalledOnce()) - const queued = expect(controller.reconcile()).rejects.toThrow('recovery_stopped') - const settled = vi.fn() - const stopping = controller.stop().then(settled) - await Promise.resolve() - expect(settled).not.toHaveBeenCalled() - finish() - await Promise.all([first, queued, stopping]) - expect(ports.preparePlan).toHaveBeenCalledOnce() - expect(ports.resolveWorkspace).toHaveBeenCalledOnce() -}) - -it('holds shutdown until requested-release reconciliation finishes', async () => { - const { controller, ports } = setup() - let finish!: () => void - ports.reconcileRequestedReleases.mockImplementationOnce( - () => - new Promise((resolve) => { - finish = () => resolve(undefined) - }) - ) - const recovery = controller.reconcile() - await vi.waitFor(() => expect(ports.reconcileRequestedReleases).toHaveBeenCalledOnce()) - const settled = vi.fn() - const stopping = controller.stop().then(settled) - await Promise.resolve() - expect(settled).not.toHaveBeenCalled() - finish() - await Promise.all([recovery, stopping]) - expect(settled).toHaveBeenCalledOnce() -}) diff --git a/src/main/runtime/runtime-managed-worktree-metadata.test.ts b/src/main/runtime/runtime-managed-worktree-metadata.test.ts deleted file mode 100644 index 23c3660e195..00000000000 --- a/src/main/runtime/runtime-managed-worktree-metadata.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { WorktreeMeta } from '../../shared/worktree/meta-types' -import { hostWorktreeTarget } from '../../shared/worktree/host-qualified-identity' -import type { Worktree } from '../../shared/worktree/types' -import { mergeWorktreeMetaForWrite } from '../persistence/loading-store/worktree-meta-write-normalization' -import { updateRuntimeManagedWorktreeMetadata } from './runtime-managed-worktree-metadata' -import type { ResolvedWorktree } from './runtime-worktree-path-identity' -import type { RuntimeStore } from './runtime-store-contract' - -describe('updateRuntimeManagedWorktreeMetadata', () => { - it('writes metadata through the resolved worktree execution host', async () => { - const worktree = { - id: 'repo-1::/workspace/app', - repoId: 'repo-1', - hostId: 'ssh:build-box', - path: '/workspace/app', - instanceId: 'instance-1' - } as unknown as ResolvedWorktree - const setWorktreeMeta = vi.fn() - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the update only reads these store members. - const store = { - getRepos: () => [], - setWorktreeMeta - } as unknown as RuntimeStore - const ports = { - resolveWorktree: vi.fn(async () => worktree), - validateParent: vi.fn(), - invalidateResolved: vi.fn(), - invalidateScan: vi.fn(), - notifyChanged: vi.fn(), - showWorktree: vi.fn(async () => worktree as unknown as Worktree) - } - - await updateRuntimeManagedWorktreeMetadata({ - selector: `id:${worktree.id}`, - updates: { comment: 'remote row only' }, - store, - ports - }) - - expect(setWorktreeMeta).toHaveBeenCalledExactlyOnceWith( - hostWorktreeTarget({ id: worktree.id, hostId: 'ssh:build-box' }), - { comment: 'remote row only' } - ) - }) - - it('persists an empty comment so a set comment can be cleared (#37464)', async () => { - const worktree = { - id: 'repo-1::/workspace/app', - repoId: 'repo-1', - hostId: 'local', - path: '/workspace/app', - instanceId: 'instance-1' - } as unknown as ResolvedWorktree - let row: WorktreeMeta | undefined - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the update only reads these store members. - const store = { - getRepos: () => [], - setWorktreeMeta: (_target: unknown, meta: Partial) => { - row = mergeWorktreeMetaForWrite(row, meta) - } - } as unknown as RuntimeStore - const ports = { - resolveWorktree: vi.fn(async () => worktree), - validateParent: vi.fn(), - invalidateResolved: vi.fn(), - invalidateScan: vi.fn(), - notifyChanged: vi.fn(), - showWorktree: vi.fn(async () => worktree as unknown as Worktree) - } - const update = (comment: string) => - updateRuntimeManagedWorktreeMetadata({ - selector: `id:${worktree.id}`, - updates: { comment }, - store, - ports - }) - - await update('waiting on review') - expect(row?.comment).toBe('waiting on review') - - await update('') - expect(row?.comment).toBe('') - }) - - describe('a folder workspace removed while its resolution was in flight (#22712)', () => { - const folderRepo = { id: 'repo-1', path: '/workspace/app', displayName: 'app', kind: 'folder' } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the update only reads id, repoId, hostId and instanceId. - const worktree = { - id: 'repo-1::/workspace/app::workspace:0b6f6d2e-1c1d-4c55-9a51-3f1c0f4f2a10', - repoId: 'repo-1', - hostId: 'local', - path: '/workspace/app', - instanceId: 'instance-1' - } as unknown as ResolvedWorktree - - function run(meta: Partial | undefined) { - const setWorktreeMeta = vi.fn() - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the update only reads these store members. - const store = { - getRepos: () => [folderRepo], - getWorktreeMeta: () => meta, - getWorktreeMetaForHost: () => meta, - setWorktreeMeta - } as unknown as RuntimeStore - const result = updateRuntimeManagedWorktreeMetadata({ - selector: `id:${worktree.id}`, - updates: { lastActivityAt: 2 }, - store, - ports: { - resolveWorktree: vi.fn(async () => worktree), - validateParent: vi.fn(), - invalidateResolved: vi.fn(), - invalidateScan: vi.fn(), - notifyChanged: vi.fn(), - showWorktree: vi.fn(async () => worktree as unknown as Worktree) - } - }) - return { result, setWorktreeMeta } - } - - it('refuses to recreate the removed workspace as a blank row', async () => { - const { result, setWorktreeMeta } = run(undefined) - - await expect(result).rejects.toThrow('selector_not_found') - expect(setWorktreeMeta).not.toHaveBeenCalled() - }) - - it('still writes to a folder workspace that exists', async () => { - const { result, setWorktreeMeta } = run({ instanceId: 'instance-1' }) - - await result - expect(setWorktreeMeta).toHaveBeenCalledWith( - hostWorktreeTarget({ id: worktree.id, hostId: 'local' }), - { lastActivityAt: 2 } - ) - }) - }) -}) diff --git a/src/main/runtime/runtime-mobile-structured-tab-status.test.ts b/src/main/runtime/runtime-mobile-structured-tab-status.test.ts deleted file mode 100644 index 5f5451663ef..00000000000 --- a/src/main/runtime/runtime-mobile-structured-tab-status.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { makeStructuredAgentStatusSubject } from '../../shared/agent-status/contracts/agent-status-subject' -import type { AgentSessionStatusSummary } from '../../shared/agent-session/agent-session-wire' -import type { RuntimeMobileSessionTabsSnapshot } from '../../shared/runtime-types' -import { AgentHookServer } from '../agent-hooks/server' -import { agentHookRuntimeDeps } from '../agent-hooks/agent-hook-runtime-deps' -import { AgentStatusObservedPaneIdentities } from './agent-status-observed-pane-identity' -import { projectRuntimeMobileSessionTabs } from './runtime-mobile-session-projection' -import type { RuntimeMobileSessionProjectionHost } from './runtime-mobile-session-projection-contract' -import { agentSessionRecordFixture } from '../../shared/agent-session/lifecycle/agent-session-record.test-fixture' -import { agentSessionConversationView } from '../../shared/agent-session/lifecycle/agent-session-conversation-view' -import type { AgentSessionLastTurn } from '../../shared/agent-session/lifecycle/agent-session-last-turn' -import { StructuredAgentSessionStatusFeed } from '../native-chat/agent-session-wire/structured-agent-session-status-feed' -import { createStructuredAgentSessionLogger } from '../native-chat/agent-session-wire/structured-agent-session-logger' - -vi.mock('../telemetry/client', () => ({ track: vi.fn() })) -vi.mock('../telemetry/cohort-classifier', () => ({ - getCohortAtEmit: vi.fn(() => ({ nth_repo_added: 2 })) -})) - -const WORKTREE_ID = 'repo-1::/workspace/app' -const SESSION = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d' - -function projectChatTab(store: AgentHookServer, worktree: string) { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a chat tab reads only the live browser tabs and the pane's status rows; the rest finalizes the result. - const host = { - getLiveBrowserTabs: () => new Map(), - getProviderSessionRows: (paneKey: string) => store.getStatusSnapshotForPane(paneKey), - sanitizeGroups: () => [], - pruneGroupLayout: () => null, - collectTabIds: () => new Set() - } as unknown as RuntimeMobileSessionProjectionHost - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: one chat tab is the only snapshot field this projection path reads. - const snapshot = { - worktree, - publicationEpoch: 'epoch', - tabs: [ - { - type: 'agent-session', - id: `agent-session:${SESSION}`, - title: 'Claude', - sessionId: SESSION, - agent: 'claude', - isActive: true - } - ] - } as RuntimeMobileSessionTabsSnapshot - return projectRuntimeMobileSessionTabs(snapshot, host).tabs[0] -} - -describe('a chat tab on the phone', () => { - it.each([ - [{ state: 'settled', outcome: 'success', at: 22_000 }, 'success'], - [{ state: 'open', at: 22_000 }, 'cancellation'], - [{ state: 'settled', outcome: 'cancellation', at: 22_000 }, 'cancellation'], - [{ state: 'settled', outcome: 'failure', at: 22_000 }, 'failure'], - [{ state: 'settled', outcome: 'interruption', at: 22_000 }, 'interruption'] - ] satisfies [AgentSessionLastTurn, string][])( - 'carries saved %j through the trusted status sink to the phone', - (lastTurn, outcome) => { - const store = new AgentHookServer() - const record = agentSessionConversationView({ - ...agentSessionRecordFixture(), - sessionId: SESSION, - location: { - executionHostId: 'local', - wslDistro: null, - workspaceId: WORKTREE_ID, - workspaceKind: 'folder' - }, - lastTurn - }) - const feed = new StructuredAgentSessionStatusFeed({ - sessions: new Map(), - getConversation: () => record, - now: () => 99_000, - logger: createStructuredAgentSessionLogger(), - statusSink: () => - agentHookRuntimeDeps(store, new AgentStatusObservedPaneIdentities(), () => ({})) - .structuredAgentStatusSink - }) - feed.publishSavedGroup((publish) => publish(record)) - expect(projectChatTab(store, WORKTREE_ID)).toMatchObject({ - type: 'agent-session', - chatStatus: { state: 'done', mainAgent: { state: 'done', outcome, stateStartedAt: 22_000 } } - }) - expect(projectChatTab(store, 'other-folder')).not.toHaveProperty('chatStatus') - } - ) - - it("carries the host's structured row for its session, and only in that workspace", () => { - const store = new AgentHookServer() - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the fields a structured status summary needs for ingest. - const summary = { - sessionId: SESSION, - workspaceId: WORKTREE_ID, - agent: 'claude', - status: 'working', - latestPrompt: 'ship the thing', - updatedAt: 1_757_030_400_000, - hostExecutionOwned: true - } as AgentSessionStatusSummary - store.ingestStructuredStatus( - summary, - makeStructuredAgentStatusSubject( - { - executionHostId: 'local', - wslDistro: null, - workspaceId: WORKTREE_ID, - workspaceKind: 'git-worktree' - }, - SESSION - ) - ) - - expect(projectChatTab(store, WORKTREE_ID)).toMatchObject({ - type: 'agent-session', - chatStatus: { state: 'working', structuredHostOwned: true } - }) - expect(projectChatTab(store, 'repo-1::/workspace/other')).not.toHaveProperty('chatStatus') - }) -}) diff --git a/src/main/runtime/runtime-pairing-desktop-control-grant.test.ts b/src/main/runtime/runtime-pairing-desktop-control-grant.test.ts index 3b047705da6..67b9e85fea3 100644 --- a/src/main/runtime/runtime-pairing-desktop-control-grant.test.ts +++ b/src/main/runtime/runtime-pairing-desktop-control-grant.test.ts @@ -5,8 +5,6 @@ import { describe, expect, it, vi } from 'vitest' import { DeviceRegistry } from './device-registry' import { OrcaRuntimeService } from './orca-runtime' import { OrcaRuntimeRpcServer } from './runtime-rpc' -import { getServeOptions } from '../startup/serve-options' -import { parseArgs as parseOrcadArgs } from '../orcad/orcad-command-arguments' function tempDir(): string { return mkdtempSync(join(tmpdir(), 'orca-desktop-control-grant-')) @@ -45,21 +43,6 @@ describe('desktop control for paired runtime clients', () => { const reply = await dispatchOverWebSocket(server, device.token, 'computer.click') expect(errorCode(reply)).toBe('forbidden') }) - - it('passes the permission gate for a pairing created with the grant', async () => { - const userDataPath = tempDir() - const server = new OrcaRuntimeRpcServer({ - runtime: new OrcaRuntimeService(), - userDataPath, - enableWebSocket: false - }) - server['deviceRegistry'] = new DeviceRegistry(userDataPath) - const device = server['deviceRegistry']!.addDevice('laptop', 'runtime', 'network', [ - 'desktop-control' - ]) - const reply = await dispatchOverWebSocket(server, device.token, 'computer.click') - expect(errorCode(reply)).not.toBe('forbidden') - }) }) describe('pairing and push administration for paired runtime clients', () => { @@ -102,22 +85,3 @@ describe('pairing-time grants on the device registry', () => { expect(registry.getOrCreatePendingDevice('cli', 'runtime').token).toBe(plain.token) }) }) - -describe('the --grant-desktop-control serve flag', () => { - it('reaches the desktop serve options in both argv shapes', () => { - expect(getServeOptions(['/AppRun', '--serve']).grantDesktopControl).toBe(false) - expect( - getServeOptions(['/AppRun', '--serve', '--serve-grant-desktop-control']).grantDesktopControl - ).toBe(true) - expect(() => - getServeOptions(['/AppRun', '--serve', '--grant-desktop-control', '--mobile-pairing']) - ).toThrow('--grant-desktop-control applies only to the default runtime pairing offer.') - }) - - it('reaches orcad', () => { - expect(parseOrcadArgs(['--grant-desktop-control'])).toEqual({ grantDesktopControl: true }) - expect(() => parseOrcadArgs(['--grant-desktop-control', '--no-pairing'])).toThrow( - '--grant-desktop-control applies only to the default runtime pairing offer' - ) - }) -}) diff --git a/src/main/runtime/runtime-reference-catalog.test.ts b/src/main/runtime/runtime-reference-catalog.test.ts deleted file mode 100644 index 6f75c2964bf..00000000000 --- a/src/main/runtime/runtime-reference-catalog.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - listReferenceWorkspaces, - listWorkspaceReferences, - selectReferenceWorkspace -} from './runtime-reference-catalog' -import { WORKTREE_META_PERSISTED_DEFAULTS } from '../../shared/worktree/meta-persisted-defaults' -import type { Repo } from '../../shared/repo-types' -import type { WorktreeMeta } from '../../shared/worktree/meta-types' - -const repo: Repo = { id: 'repo', path: '/repo', displayName: 'api', badgeColor: '', addedAt: 0 } -const meta: WorktreeMeta = { - ...WORKTREE_META_PERSISTED_DEFAULTS, - displayName: 'api', - linkedPR: 5123, - comment: '', - isUnread: false, - sortOrder: 0, - lastActivityAt: 0 -} -const id = 'repo::/repo' - -function catalog(metadata: Record = { [id]: meta }) { - return listReferenceWorkspaces({ getRepos: () => [repo], getAllWorktreeMeta: () => metadata }) -} - -describe('reference metadata catalog', () => { - it('exposes old scalar links without a scan', () => { - const rows = catalog() - expect(rows[0].linkedItems).toEqual([{ provider: 'github', type: 'pr', number: 5123 }]) - expect(listWorkspaceReferences(rows[0]).references[0]).toMatchObject({ - selected: true, - number: 5123 - }) - }) - it('finds the closest local workspace only for explicit current', () => { - const rows = catalog({ [id]: meta, 'repo::/repo/child': { ...meta, displayName: 'child' } }) - expect(selectReferenceWorkspace(rows, 'current', '/repo/child/src').name).toBe('child') - expect(() => selectReferenceWorkspace(rows, 'current')).toThrow('working directory') - expect(() => selectReferenceWorkspace(rows, 'current', '/repository')).toThrow( - 'selector_not_found' - ) - }) - it('includes discovered workspaces before their first metadata write', () => { - const [worktree] = catalog() - const rows = listReferenceWorkspaces( - { getRepos: () => [repo], getAllWorktreeMeta: () => ({}) }, - [worktree] - ) - expect(rows).toHaveLength(1) - expect(selectReferenceWorkspace(rows, `id:${id}`).id).toBe(id) - }) - it('resolves explicit WSL paths only with the caller distro evidence', () => { - const [row] = catalog() - const ubuntu = { ...row, path: '//wsl$/Ubuntu/home/me/api' } - const debian = { ...row, id: 'repo::debian', path: '//wsl$/Debian/home/me/api' } - expect( - selectReferenceWorkspace([ubuntu, debian], 'path:/home/me/api', '//wsl$/Ubuntu/home/me').id - ).toBe(row.id) - expect(() => selectReferenceWorkspace([ubuntu, debian], 'path:/home/me/api')).toThrow( - 'selector_not_found' - ) - expect(() => - selectReferenceWorkspace([ubuntu], 'path:/home/me/api', '//wsl$/Debian/home/me') - ).toThrow('selector_not_found') - }) - it('retains same-id rows on separate hosts and refuses an ambiguous id', () => { - const rows = listReferenceWorkspaces({ - getRepos: () => [repo, { ...repo, connectionId: 'server' }], - getAllWorktreeMeta: () => ({}), - getAllWorktreeMetaForHost: (hostId) => ({ [id]: { ...meta, hostId } }) - }) - expect(rows.map((row) => row.hostId)).toEqual(['local', 'ssh:server']) - expect(() => selectReferenceWorkspace(rows, `id:${id}`)).toThrow('selector_ambiguous') - expect(selectReferenceWorkspace(rows, 'current', '/repo').hostId).toBe('local') - }) - it('collapses duplicate same-host path registrations but not cross-host ones', () => { - const [row] = catalog() - const duplicate = { ...row, id: 'repo-copy::/repo' } - expect(selectReferenceWorkspace([row, duplicate], `path:${row.path}`).id).toBe(row.id) - expect(selectReferenceWorkspace([row, duplicate], 'current', '/repo/src').id).toBe(row.id) - expect(() => - selectReferenceWorkspace([row, { ...duplicate, hostId: 'ssh:build' }], `path:${row.path}`) - ).toThrow('selector_ambiguous') - }) - it('does not match issue:null against unlinked workspaces', () => { - const [row] = catalog() - expect(() => selectReferenceWorkspace([row], 'issue:null')).toThrow('selector_not_found') - expect(selectReferenceWorkspace([{ ...row, linkedIssue: 42 }], 'issue:42').id).toBe(row.id) - }) - it('does not attribute unqualified legacy metadata to colliding hosts', () => { - const rows = listReferenceWorkspaces({ - getRepos: () => [repo, { ...repo, connectionId: 'server' }], - getAllWorktreeMeta: () => ({ [id]: meta }) - }) - expect(rows).toEqual([]) - }) - it('uses folder storage and exposes selected legacy tasks', () => { - const rows = listReferenceWorkspaces({ - getRepos: () => [], - getAllWorktreeMeta: () => ({}), - getFolderWorkspaces: () => [ - { - id: 'notes', - projectGroupId: 'group', - name: 'notes', - folderPath: 'C:\\notes', - linkedTask: { - provider: 'linear', - type: 'issue', - number: 0, - title: 'Task', - url: 'https://linear.app/acme/issue/STA-1234', - linearIdentifier: 'STA-1234' - }, - comment: '', - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - createdAt: 0, - updatedAt: 0 - } - ] - }) - expect(selectReferenceWorkspace(rows, 'path:C:/notes')).toMatchObject({ - id: 'folder:notes', - kind: 'folder' - }) - expect(listWorkspaceReferences(rows[0]).references).toEqual([ - expect.objectContaining({ selected: true, identifier: 'STA-1234' }) - ]) - }) -}) diff --git a/src/main/runtime/runtime-repository-registration-host-dedupe.test.ts b/src/main/runtime/runtime-repository-registration-host-dedupe.test.ts deleted file mode 100644 index 63b347a9de8..00000000000 --- a/src/main/runtime/runtime-repository-registration-host-dedupe.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { mkdir, mkdtemp, realpath, rm, stat } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { Repo } from '../../shared/repo-types' -import { RuntimeRepositoryRegistrationController } from './runtime-repository-registration-controller' -import type { RuntimeStore } from './runtime-store-contract' - -vi.mock('../repository-icons/repo-icon-autodetect', () => ({ - detectRepoIconAndUpstream: vi.fn().mockResolvedValue({}) -})) -vi.mock('../workspaces/creation/worktree-root-preparation', () => ({ - prepareLocalWorktreeRootForRepo: vi.fn().mockResolvedValue(undefined) -})) - -function createController(seed: Repo[]): { - controller: RuntimeRepositoryRegistrationController - repos: Repo[] -} { - const repos = [...seed] - const store = { - getRepos: () => repos, - getRepo: (id: string) => repos.find((repo) => repo.id === id), - addRepo: (repo: Repo) => { - repos.push(repo) - return repo - }, - updateRepo: () => null - } - const controller = new RuntimeRepositoryRegistrationController({ - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: add()/create() only use getRepos/getRepo/addRepo/updateRepo. - getStore: () => store as unknown as RuntimeStore, - invalidateResolvedWorktrees: () => {}, - invalidateWorktreeScan: () => {}, - notifyReposChanged: () => {} - }) - return { controller, repos } -} - -function remoteRow(path: string, fields: Pick): Repo { - return { - id: 'ssh-row', - path, - displayName: 'remote', - badgeColor: 'blue', - addedAt: 1, - kind: 'folder', - ...fields - } -} - -// Why the third: a runtime host's nested SSH target appears only in `connectionId`. -const SSH_SPELLINGS = [ - ['executionHostId', { executionHostId: 'ssh:gpu' as const }], - ['legacy connectionId', { connectionId: 'gpu' }], - ['nested SSH on a runtime host', { executionHostId: 'runtime:env' as const, connectionId: 'gpu' }] -] as const - -describe('project registration dedupes only against rows on the same host', () => { - let root: string - - beforeEach(async () => { - root = await realpath(await mkdtemp(join(tmpdir(), 'orca-runtime-host-dedupe-'))) - }) - - afterEach(async () => { - await rm(root, { recursive: true, force: true }) - }) - - it.each(SSH_SPELLINGS)( - 'create makes a local project where an SSH project uses the same path (%s)', - async (_label, fields) => { - const targetPath = join(root, 'notes') - const { controller, repos } = createController([remoteRow(targetPath, fields)]) - - const result = await controller.create(root, 'notes', 'folder') - - if (!('repo' in result)) { - throw new Error(result.error) - } - expect(result.repo.id).not.toBe('ssh-row') - expect(result.repo.path).toBe(targetPath) - expect(repos).toHaveLength(2) - expect((await stat(targetPath)).isDirectory()).toBe(true) - } - ) - - it('create still returns the local project already at that path', async () => { - const targetPath = join(root, 'notes') - await mkdir(targetPath) - const local: Repo = { ...remoteRow(targetPath, {}), id: 'local-row' } - const { controller, repos } = createController([local]) - - const result = await controller.create(root, 'notes', 'folder') - - if (!('repo' in result)) { - throw new Error(result.error) - } - expect(result.repo.id).toBe('local-row') - expect(repos).toHaveLength(1) - }) - - it.each(SSH_SPELLINGS)( - 'add registers a local folder where an SSH project uses the same path (%s)', - async (_label, fields) => { - const folderPath = join(root, 'notes') - await mkdir(folderPath) - const { controller, repos } = createController([remoteRow(folderPath, fields)]) - - const added = await controller.add(folderPath, 'folder') - - expect(added.id).not.toBe('ssh-row') - expect(repos).toHaveLength(2) - } - ) - - it("add for a runtime host skips that host's nested SSH project at the same path", async () => { - const folderPath = join(root, 'notes') - await mkdir(folderPath) - const nested = remoteRow(folderPath, { executionHostId: 'runtime:env', connectionId: 'gpu' }) - const { controller, repos } = createController([nested]) - - const added = await controller.add(folderPath, 'folder', 'runtime:env') - - expect(added.id).not.toBe('ssh-row') - expect(repos).toHaveLength(2) - }) - - it('add for a runtime host still returns its own project at the same path', async () => { - const folderPath = join(root, 'notes') - await mkdir(folderPath) - const own: Repo = { ...remoteRow(folderPath, { executionHostId: 'runtime:env' }), id: 'own' } - const { controller, repos } = createController([own]) - - const added = await controller.add(folderPath, 'folder', 'runtime:env') - - expect(added.id).toBe('own') - expect(repos).toHaveLength(1) - }) - - it('two adds of the same new folder at once register it once', async () => { - const folderPath = join(root, 'notes') - await mkdir(folderPath) - const { controller, repos } = createController([]) - - const [first, second] = await Promise.all([ - controller.add(folderPath, 'folder', 'runtime:env'), - controller.add(folderPath, 'folder', 'runtime:env') - ]) - - expect(second.id).toBe(first.id) - expect(repos).toHaveLength(1) - }) -}) diff --git a/src/main/runtime/selected-review-branch.test.ts b/src/main/runtime/selected-review-branch.test.ts deleted file mode 100644 index a2f421a76bb..00000000000 --- a/src/main/runtime/selected-review-branch.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - getSelectedReviewBranch, - getSelectedReviewLookupHints, - isAllowedPushTargetRemoteConflict, - isMatchingSelectedGitHubPr, - type SelectedReviewBranchInput -} from './selected-review-branch' - -const PROVIDER_CASES: { - label: string - input: SelectedReviewBranchInput - expected: { provider: string; number: number } | null -}[] = [ - { label: 'no linked review', input: {}, expected: null }, - { label: 'github', input: { linkedPR: 7 }, expected: { provider: 'github', number: 7 } }, - { - label: 'gitlab', - input: { linkedGitLabMR: 12 }, - expected: { provider: 'gitlab', number: 12 } - }, - { - label: 'bitbucket', - input: { linkedBitbucketPR: 3 }, - expected: { provider: 'bitbucket', number: 3 } - }, - { - label: 'azure devops', - input: { linkedAzureDevOpsPR: 44 }, - expected: { provider: 'azure-devops', number: 44 } - }, - { label: 'gitea', input: { linkedGiteaPR: 9 }, expected: { provider: 'gitea', number: 9 } }, - { - label: 'zero is a real number', - input: { linkedPR: 0 }, - expected: { provider: 'github', number: 0 } - }, - { - label: 'null linked ids are skipped', - input: { linkedPR: null, linkedGitLabMR: null }, - expected: null - }, - { - label: 'github wins over gitlab when both are set', - input: { linkedPR: 1, linkedGitLabMR: 2 }, - expected: { provider: 'github', number: 1 } - }, - { - label: 'gitlab wins over bitbucket when both are set', - input: { linkedGitLabMR: 2, linkedBitbucketPR: 3 }, - expected: { provider: 'gitlab', number: 2 } - } -] - -describe('getSelectedReviewBranch', () => { - it.each(PROVIDER_CASES)('resolves $label', ({ input, expected }) => { - expect(getSelectedReviewBranch(input)).toEqual(expected) - }) -}) - -type ExistingPr = Parameters[0] - -const pr = (number: number): ExistingPr => ({ number }) as ExistingPr - -describe('isMatchingSelectedGitHubPr', () => { - it('matches when the PR number and the branch override both line up', () => { - expect( - isMatchingSelectedGitHubPr(pr(7), { linkedPR: 7, branchNameOverride: 'feat' }, 'feat') - ).toBe(true) - }) - - it('rejects a different PR number', () => { - expect( - isMatchingSelectedGitHubPr(pr(8), { linkedPR: 7, branchNameOverride: 'feat' }, 'feat') - ).toBe(false) - }) - - it('rejects when the branch override does not equal the branch', () => { - expect( - isMatchingSelectedGitHubPr(pr(7), { linkedPR: 7, branchNameOverride: 'other' }, 'feat') - ).toBe(false) - }) - - it('rejects when there is no branch override at all', () => { - expect(isMatchingSelectedGitHubPr(pr(7), { linkedPR: 7 }, 'feat')).toBe(false) - }) - - it('rejects a non-GitHub selection even when the branch override matches', () => { - expect( - isMatchingSelectedGitHubPr(pr(7), { linkedGitLabMR: 7, branchNameOverride: 'feat' }, 'feat') - ).toBe(false) - }) - - it('returns false for a null existing PR', () => { - expect( - isMatchingSelectedGitHubPr(null, { linkedPR: 7, branchNameOverride: 'feat' }, 'feat') - ).toBe(false) - }) -}) - -describe('isAllowedPushTargetRemoteConflict', () => { - const args: SelectedReviewBranchInput = { - linkedGitLabMR: 12, - branchNameOverride: 'feat', - pushTarget: { remoteName: 'origin', branchName: 'feat' } - } - - it('allows a remote conflict on the selected review branch', () => { - expect(isAllowedPushTargetRemoteConflict('remote', 'feat', args)).toBe(true) - }) - - it('does not allow a local conflict', () => { - expect(isAllowedPushTargetRemoteConflict('local', 'feat', args)).toBe(false) - }) - - it('does not allow a null conflict kind', () => { - expect(isAllowedPushTargetRemoteConflict(null, 'feat', args)).toBe(false) - }) - - it('requires a linked review', () => { - expect( - isAllowedPushTargetRemoteConflict('remote', 'feat', { - branchNameOverride: 'feat', - pushTarget: args.pushTarget - }) - ).toBe(false) - }) - - it('requires the push target to name the same branch', () => { - expect( - isAllowedPushTargetRemoteConflict('remote', 'feat', { - ...args, - pushTarget: { remoteName: 'origin', branchName: 'elsewhere' } - }) - ).toBe(false) - }) - - it('requires a push target', () => { - expect( - isAllowedPushTargetRemoteConflict('remote', 'feat', { - linkedGitLabMR: 12, - branchNameOverride: 'feat' - }) - ).toBe(false) - }) -}) - -describe('getSelectedReviewLookupHints', () => { - it('normalizes every absent hint to null', () => { - expect(getSelectedReviewLookupHints({})).toEqual({ - linkedGitHubPR: null, - linkedGitLabMR: null, - linkedBitbucketPR: null, - linkedAzureDevOpsPR: null, - linkedGiteaPR: null - }) - }) - - it('renames linkedPR to linkedGitHubPR and passes the rest through', () => { - expect( - getSelectedReviewLookupHints({ - linkedPR: 7, - linkedGitLabMR: 12, - linkedBitbucketPR: 3, - linkedAzureDevOpsPR: 44, - linkedGiteaPR: 9 - }) - ).toEqual({ - linkedGitHubPR: 7, - linkedGitLabMR: 12, - linkedBitbucketPR: 3, - linkedAzureDevOpsPR: 44, - linkedGiteaPR: 9 - }) - }) - - it('keeps 0 rather than coercing it to null', () => { - expect(getSelectedReviewLookupHints({ linkedPR: 0 }).linkedGitHubPR).toBe(0) - }) -}) diff --git a/src/main/runtime/terminal-orphan-topology.test.ts b/src/main/runtime/terminal-orphan-topology.test.ts index aea55932462..321ea5be7be 100644 --- a/src/main/runtime/terminal-orphan-topology.test.ts +++ b/src/main/runtime/terminal-orphan-topology.test.ts @@ -1,24 +1,7 @@ import { describe, expect, it } from 'vitest' -import { - hasExactTerminalOrphanGroupLayout, - mergeTerminalOrphanGroupLayout -} from './terminal-orphan-topology' +import { mergeTerminalOrphanGroupLayout } from './terminal-orphan-topology' describe('terminal orphan topology', () => { - it('rejects duplicate and missing group leaves', () => { - expect( - hasExactTerminalOrphanGroupLayout( - { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'group-a' }, - second: { type: 'leaf', groupId: 'group-a' } - }, - new Set(['group-a', 'group-b']) - ) - ).toBe(false) - }) - it('keeps current host layout while preserving an unrelated proposed subtree', () => { expect( mergeTerminalOrphanGroupLayout({ @@ -83,49 +66,6 @@ describe('terminal orphan topology', () => { }) }) - it('uses the proposed layout when the host has no groups', () => { - expect( - mergeTerminalOrphanGroupLayout({ - existingLayout: null, - existingGroupIds: [], - proposedLayout: { type: 'leaf', groupId: 'group-recovered' }, - proposedGroupIds: ['group-recovered'], - mergedGroupIds: ['group-recovered'] - }) - ).toEqual({ type: 'leaf', groupId: 'group-recovered' }) - }) - - it('keeps the host layout when the proposal has no groups', () => { - expect( - mergeTerminalOrphanGroupLayout({ - existingLayout: { type: 'leaf', groupId: 'group-live' }, - existingGroupIds: ['group-live'], - proposedLayout: null, - proposedGroupIds: [], - mergedGroupIds: ['group-live'] - }) - ).toEqual({ type: 'leaf', groupId: 'group-live' }) - }) - - it('does not rewrite host layout when the proposal adds no groups', () => { - const existingLayout = { - type: 'split' as const, - direction: 'horizontal' as const, - ratio: 0.4, - first: { type: 'leaf' as const, groupId: 'group-a' }, - second: { type: 'leaf' as const, groupId: 'group-b' } - } - expect( - mergeTerminalOrphanGroupLayout({ - existingLayout, - existingGroupIds: ['group-a', 'group-b'], - proposedLayout: existingLayout, - proposedGroupIds: ['group-a', 'group-b'], - mergedGroupIds: ['group-a', 'group-b'] - }) - ).toEqual(existingLayout) - }) - it('appends new groups when multiple shared anchors make placement ambiguous', () => { expect( mergeTerminalOrphanGroupLayout({ diff --git a/src/main/runtime/terminal-tail-buffer.test.ts b/src/main/runtime/terminal-tail-buffer.test.ts deleted file mode 100644 index bb0957b0550..00000000000 --- a/src/main/runtime/terminal-tail-buffer.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { deepStrictEqual } from 'node:assert' -import { describe, expect, it, vi } from 'vitest' -import { appendNormalizedToTailBuffer } from './terminal-tail-buffer' -import { MAX_TAIL_LINES } from './terminal-tail-limits' -import type { RetainedTailRedrawCursor } from './terminal-tail-redraw-buffer' - -// Guard per-chunk retained-tail work; compare warm cached appends with fresh-array cold appends that recompute every total. - -type TailSim = { - lines: string[] - partialLine: string - redrawCursor: RetainedTailRedrawCursor | null -} - -function newSim(): TailSim { - return { lines: [], partialLine: '', redrawCursor: null } -} - -type Step = ReturnType - -function feed(sim: TailSim, chunk: string, cold: boolean): Step { - const next = appendNormalizedToTailBuffer( - cold ? [...sim.lines] : sim.lines, - sim.partialLine, - chunk, - sim.redrawCursor - ) - sim.lines = next.lines - sim.partialLine = next.partialLine - sim.redrawCursor = next.redrawCursor - return next -} - -function mulberry32(seed: number): () => number { - let state = seed >>> 0 - return () => { - state = (state + 0x6d2b79f5) >>> 0 - let t = Math.imul(state ^ (state >>> 15), 1 | state) - t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t - return ((t ^ (t >>> 14)) >>> 0) / 4294967296 - } -} - -const ESC = String.fromCharCode(27) - -/** Short streams hit the 2,000-line cap with redraws; wide newline streams hit the 256KiB cap, covering both carried-total eviction paths. */ -function randomChunk(random: () => number, profile: 'short' | 'long'): string { - const roll = random() - if (profile === 'long') { - if (roll < 0.7) { - return `${'w'.repeat(1000 + Math.floor(random() * 3000))}\n` - } - if (roll < 0.8) { - return `\rspinner ${Math.floor(random() * 100)}%` - } - if (roll < 0.9) { - return 'trailing spaces here \n' - } - return roll < 0.95 ? '' : `no newline ${Math.floor(random() * 1000)}` - } - if (roll < 0.22) { - const lines: string[] = [] - for (let index = 0; index < 30; index += 1) { - lines.push(`burst ${Math.floor(random() * 1e6)}${random() < 0.3 ? ' ' : ''}`) - } - return `${lines.join('\n')}\n` - } - if (roll < 0.42) { - return `plain output ${Math.floor(random() * 1e6)}\n` - } - if (roll < 0.56) { - return `${' '.repeat(Math.floor(random() * 3))}\n` - } - if (roll < 0.68) { - const rows = 1 + Math.floor(random() * 12) - return `${ESC}[${rows}A${ESC}[2Kredrawn ${Math.floor(random() * 1000)}\n` - } - if (roll < 0.8) { - return `\rspinner ${Math.floor(random() * 100)}%` - } - if (roll < 0.86) { - return 'trailing spaces here \n' - } - if (roll < 0.92) { - return `multi\nline\nchunk ${Math.floor(random() * 1000)}\n` - } - if (roll < 0.96) { - return '' - } - return `no newline ${Math.floor(random() * 1000)}` -} - -describe('retained tail buffer prefix reuse', () => { - for (const profile of ['short', 'long'] as const) { - for (const seed of [3, 11, 91, 2024]) { - it(`carries the retained char total exactly (${profile}, seed ${seed})`, () => { - const random = mulberry32(seed) - const warm = newSim() - const cold = newSim() - let sawCap = false - for (let step = 0; step < 1400; step += 1) { - const chunk = randomChunk(random, profile) - const lineCountBefore = warm.lines.length - const warmStep = feed(warm, chunk, false) - const coldStep = feed(cold, chunk, true) - deepStrictEqual(warmStep.lines, coldStep.lines, `step ${step} lines`) - expect(warmStep.partialLine, `step ${step} partial`).toBe(coldStep.partialLine) - expect(warmStep.truncated, `step ${step} truncated`).toBe(coldStep.truncated) - expect(warmStep.redrawCursor, `step ${step} cursor`).toEqual(coldStep.redrawCursor) - expect(warmStep.newCompleteLines, `step ${step} newCompleteLines`).toBe( - coldStep.newCompleteLines - ) - expect(warmStep.newlyCompletedLines, `step ${step} newlyCompletedLines`).toEqual( - coldStep.newlyCompletedLines - ) - sawCap = - sawCap || - (profile === 'short' - ? warm.lines.length >= MAX_TAIL_LINES - : // Lines dropped below the line cap on an append-only chunk == character-cap eviction. - !chunk.includes(ESC) && - warm.lines.length < MAX_TAIL_LINES && - lineCountBefore + warmStep.newlyCompletedLines.length > warm.lines.length) - } - // Guard against a vacuous pass: the profile's eviction path must have run. - expect(sawCap).toBe(true) - }) - } - } - - it('does not walk the untouched redraw prefix on every chunk', () => { - const sim = newSim() - for (let index = 0; index < MAX_TAIL_LINES + 200; index += 1) { - feed(sim, `streaming build output line ${index}\n`, false) - } - expect(sim.lines.length).toBe(MAX_TAIL_LINES) - - const redrawChunk = `${ESC}[3A${ESC}[2Krewritten row${ESC}[2B\n` - const spy = vi.spyOn(String.prototype, 'charCodeAt') - let prefixTouches = 0 - try { - feed(sim, redrawChunk, false) - prefixTouches = spy.mock.calls.length - } finally { - spy.mockRestore() - } - // A full-prefix trim cost about 1,990 charCodeAt calls; the chunk itself needs under 100. - expect(prefixTouches).toBeLessThan(300) - }) -}) diff --git a/src/main/runtime/terminal/lifecycle/bind-first-registration.test.ts b/src/main/runtime/terminal/lifecycle/bind-first-registration.test.ts deleted file mode 100644 index 31af23539f3..00000000000 --- a/src/main/runtime/terminal/lifecycle/bind-first-registration.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -// Layout-owner builds bind a terminal's pane before registering it (L80), so main's layout answers -// which pane holds the terminal from its first registration. -import { describe, expect, it, vi } from 'vitest' -import { HEADLESS_RUNTIME_WINDOW_ID } from '../../../../shared/runtime-types' -import type { RuntimeStore } from '../../runtime-store-contract' - -vi.mock('../../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: true })) - -// Fragments stay side-effect ordered: mocks, then lifecycle, then fixtures. -await import('../../orca-runtime-test-mocks.spec') -await import('../../orca-runtime-test-lifecycle.spec') -const { TEST_WORKTREE_ID, TEST_WORKTREE_PATH } = - await import('../../orca-runtime-test-fixtures.spec') -const { surfaceRuntime } = await import('../surface/terminal-surface-replies.test-fixture') -const { spawnInAdmittedPane } = await import('../../runtime-pane-admission') -const { leaveSpawnsUncommitted } = await import('../../bind-first-pane.test-fixture') -const { PaneStartStatuses } = await import('../../pane-start-statuses') -const { bindingOf } = await import('../../pane-live-facts') - -type SpawnArgs = { worktreeId?: string; tabId?: string; leafId?: string } - -function createOn(bindsFirst: boolean) { - const { runtime, bindPane, store } = surfaceRuntime() - runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] }) - // The terminal main's layout binds the registering pane to, read as registration starts. - const answeredAtRegistration: (string | null)[] = [] - const register = runtime.registerPty.bind(runtime) - runtime.registerPty = (ptyId, worktreeId, connectionId, binding) => { - if (binding) { - const layout = store.getSession().terminalLayoutsByTabId[binding.tabId] - answeredAtRegistration.push(layout?.ptyIdsByLeafId?.[binding.leafId] ?? null) - } - return register(ptyId, worktreeId, connectionId, binding) - } - const controller = { - spawn: vi.fn(async (args: SpawnArgs) => { - if (bindsFirst) { - // As the PTY host's spawn commit: it binds the pane before the spawn answers. - await bindPane({ - worktreeId: args.worktreeId!, - tabId: args.tabId!, - leafId: args.leafId!, - ptyId: 'pty-new' - }) - } - return { id: 'pty-new' } - }), - write: () => true, - kill: () => true, - stopAndWait: async () => true, - getForegroundProcess: async () => null, - listProcesses: async () => - ['pty-a', 'pty-b', 'pty-new'].map((id) => ({ id, cwd: TEST_WORKTREE_PATH, title: 'sh' })) - } - // The suites' stand-in spawn commit binds first; this test drives a spawn that does not. - runtime.setPtyController(bindsFirst ? controller : leaveSpawnsUncommitted(controller)) - return { runtime, answeredAtRegistration } -} - -function createFromPhone(runtime: ReturnType['runtime']) { - return runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, { - activate: false, - select: false, - navigation: 'caller', - clientNavigationId: 'phone' - }) -} - -describe('bind-first registration (switch on)', () => { - it('registers a created terminal only once main’s layout binds its pane', async () => { - const { runtime, answeredAtRegistration } = createOn(true) - - const created = await createFromPhone(runtime) - - expect(answeredAtRegistration.length).toBeGreaterThan(0) - expect(answeredAtRegistration).toEqual(answeredAtRegistration.map(() => 'pty-new')) - expect(created.tab).toMatchObject({ status: 'ready' }) - }) - - it('refuses, in tests, a path that registers before the binding write', async () => { - const { runtime } = createOn(false) - - await expect(createFromPhone(runtime)).rejects.toThrow('pane_unbound_at_registration') - }) - - it('shows the failure on a pane whose start died between the pane write and the binding write', async () => { - const paneKey = 'tab-1:11111111-1111-4111-8111-111111111111' - const store = { - admitTerminalPane: vi.fn(async () => 'admitted' as const), - withdrawTerminalPane: vi.fn(async () => null), - getWorkspaceSession: vi.fn(() => ({ - terminalLayoutsByTabId: { - 'tab-1': { root: { type: 'leaf', leafId: '11111111-1111-4111-8111-111111111111' } } - } - })) - } - const statuses = new PaneStartStatuses(vi.fn()) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the admission path calls only these three store methods. - const runtimeStore = store as unknown as RuntimeStore - - await expect( - spawnInAdmittedPane( - runtimeStore, - { - // The process started, then its commit could not write the binding and took it back. - spawn: vi.fn(async () => { - throw new Error('terminal session state could not be saved') - }) - }, - { - cols: 80, - rows: 24, - worktreeId: 'wt-1', - tabId: 'tab-1', - leafId: '11111111-1111-4111-8111-111111111111', - startStatus: { sink: statuses, keepOnFailure: true } - } - ) - ).rejects.toThrow('could not be saved') - - // The pane stays, bound to nothing, and says why it has no terminal. - expect(store.withdrawTerminalPane).not.toHaveBeenCalled() - expect(statuses.get(paneKey, bindingOf({}))).toEqual({ - status: 'failed', - reason: 'terminal session state could not be saved' - }) - }) -}) diff --git a/src/main/runtime/terminal/recovery/restored-pane-start.test.ts b/src/main/runtime/terminal/recovery/restored-pane-start.test.ts deleted file mode 100644 index a45624fdba1..00000000000 --- a/src/main/runtime/terminal/recovery/restored-pane-start.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - RestoredPaneStarter, - restoredPaneStartPlan, - type RestoredPane, - type RestoredPaneStartPorts, - type StartedPty -} from './restored-pane-start' - -const PANE: RestoredPane = { - worktreeId: 'repo-1::/repo', - tabId: 'tab-1', - leafId: '11111111-1111-4111-8111-111111111111' -} -const OWN = 'repo-1::/repo@@abc' - -function ports(over: Partial = {}) { - let bound: string | null | undefined = OWN - const live = new Set() - const start = vi.fn(async () => { - bound = 'pty-new' - live.add('pty-new') - return { id: 'pty-new' } - }) - return { - live, - setBound: (id: string | null | undefined) => { - bound = id - }, - ports: { - boundPtyId: () => bound, - isLive: (ptyId: string) => live.has(ptyId), - start, - ...over - } satisfies RestoredPaneStartPorts, - start - } -} - -describe('restoredPaneStartPlan', () => { - it("restores from its own stored terminal, never another workspace's", () => { - expect(restoredPaneStartPlan(PANE, OWN)).toEqual({ sessionId: OWN }) - expect(restoredPaneStartPlan(PANE, 'repo-2::/other@@x')).toEqual({}) - expect(restoredPaneStartPlan(PANE, undefined)).toEqual({}) - // An SSH id names its connection, not a workspace, so the pane reattaches over the relay. - expect(restoredPaneStartPlan(PANE, 'ssh:ssh-1@@relay-pty')).toEqual({ - sessionId: 'ssh:ssh-1@@relay-pty' - }) - }) -}) - -describe('RestoredPaneStarter', () => { - it('shows the start before any await, and a failure with no winner', async () => { - const starting = vi.fn() - const failed = vi.fn() - const error = new Error('boom') - const starter = new RestoredPaneStarter({ - boundPtyId: () => OWN, - isLive: () => false, - starting, - failed, - start: vi.fn(async (): Promise => { - throw error - }) - }) - - const started = starter.ensureStarted(PANE) - expect(starting).toHaveBeenCalledWith(PANE) - await expect(started).rejects.toBe(error) - expect(failed).toHaveBeenCalledWith(PANE, error) - }) - - it('starts once for a trigger that arrives after the start but before its binding is written', async () => { - const live = new Set() - const start = vi.fn(async () => { - live.add('pty-new') - return { id: 'pty-new' } - }) - // The stored layout still names the old terminal: the bind write has not landed. - const starter = new RestoredPaneStarter({ - boundPtyId: () => OWN, - isLive: (ptyId) => live.has(ptyId), - start - }) - - await expect(starter.ensureStarted(PANE)).resolves.toEqual({ id: 'pty-new' }) - await expect(starter.ensureStarted(PANE)).resolves.toEqual({ id: 'pty-new' }) - expect(start).toHaveBeenCalledOnce() - - // Once that terminal is gone, the next trigger starts the pane again. - live.delete('pty-new') - await starter.ensureStarted(PANE) - expect(start).toHaveBeenCalledTimes(2) - }) - - it('starts a pane with no live terminal once for concurrent triggers, all bound to it', async () => { - const { ports: p, start } = ports() - const starter = new RestoredPaneStarter(p) - - const results = await Promise.all([ - starter.ensureStarted(PANE), - starter.ensureStarted(PANE), - starter.ensureStarted(PANE) - ]) - - expect(start).toHaveBeenCalledOnce() - expect(start).toHaveBeenCalledWith(PANE, { sessionId: OWN }, {}) - expect(results).toEqual(Array.from({ length: 3 }, () => ({ id: 'pty-new' }))) - }) - - it('starts nothing for a pane already live, or one main no longer holds', async () => { - const live = ports() - live.live.add(OWN) - expect(await new RestoredPaneStarter(live.ports).ensureStarted(PANE)).toEqual({ id: OWN }) - expect(live.start).not.toHaveBeenCalled() - - const gone = ports() - gone.setBound(null) - expect(await new RestoredPaneStarter(gone.ports).ensureStarted(PANE)).toBeNull() - expect(gone.start).not.toHaveBeenCalled() - }) - - it("starts fresh a pane still bound to another workspace's live terminal, never resolving to it", async () => { - const foreign = 'repo-2::/other@@x' - const harness = ports() - harness.setBound(foreign) - harness.live.add(foreign) - harness.start.mockImplementationOnce(async () => { - throw new Error('shell not found') - }) - - await expect(new RestoredPaneStarter(harness.ports).ensureStarted(PANE)).rejects.toThrow( - 'shell not found' - ) - expect(harness.start).toHaveBeenCalledWith(PANE, {}, {}) - }) - - it('resolves to the terminal another starter bound when its own start lost the race', async () => { - const harness = ports() - harness.start.mockImplementationOnce(async () => { - harness.setBound('pty-window') - harness.live.add('pty-window') - throw new Error('terminal_pane_owner_unknown') - }) - - expect(await new RestoredPaneStarter(harness.ports).ensureStarted(PANE)).toEqual({ - id: 'pty-window' - }) - }) - - it('reports a failed start that left the pane without a live terminal', async () => { - const harness = ports() - harness.start.mockRejectedValueOnce(new Error('shell not found')) - - await expect(new RestoredPaneStarter(harness.ports).ensureStarted(PANE)).rejects.toThrow( - 'shell not found' - ) - }) -}) diff --git a/src/main/runtime/windows-firewall-remote-scope.test.ts b/src/main/runtime/windows-firewall-remote-scope.test.ts index 6e2462c0334..53e1fbc8286 100644 --- a/src/main/runtime/windows-firewall-remote-scope.test.ts +++ b/src/main/runtime/windows-firewall-remote-scope.test.ts @@ -8,31 +8,6 @@ function rule(remoteAddresses: unknown): RuleScope { } describe('Windows firewall remote-address scope', () => { - it.each([['Any'], ['any'], ['LocalSubnet']])('accepts the documented %s scope', (scope) => { - expect(hasSufficientWindowsFirewallRemoteScope([rule(scope)], undefined, undefined)).toBe(true) - }) - - it.each([['Any4'], ['LocalSubnet4']])( - 'accepts address-family-specific %s on the selected IPv4 interface', - (scope) => { - expect(hasSufficientWindowsFirewallRemoteScope([rule([scope])], '192.168.0.108', 24)).toBe( - true - ) - } - ) - - it('keeps address-family-specific keywords on the selected interface family', () => { - expect(hasSufficientWindowsFirewallRemoteScope([rule(['Any6'])], 'fd7a:115c:a1e0::5', 64)).toBe( - true - ) - expect( - hasSufficientWindowsFirewallRemoteScope([rule(['LocalSubnet6'])], 'fd7a:115c:a1e0::5', 64) - ).toBe(true) - expect( - hasSufficientWindowsFirewallRemoteScope([rule(['LocalSubnet6'])], '192.168.0.108', 24) - ).toBe(false) - }) - it.each([ ['192.168.0.0/24', '192.168.0.108', 24], ['192.168.0.0-192.168.0.255', '192.168.0.108', 24], @@ -52,32 +27,6 @@ describe('Windows firewall remote-address scope', () => { expect(hasSufficientWindowsFirewallRemoteScope([rule([scope])], address, prefix)).toBe(false) }) - it('does not infer explicit scope coverage without selected interface subnet data', () => { - expect( - hasSufficientWindowsFirewallRemoteScope([rule(['192.168.0.0/24'])], undefined, undefined) - ).toBe(false) - expect( - hasSufficientWindowsFirewallRemoteScope([rule(['192.168.0.0/24'])], '192.168.0.108', 40) - ).toBe(false) - expect( - hasSufficientWindowsFirewallRemoteScope([rule(['100.64.0.0/10'])], '100.64.1.20', 32) - ).toBe(false) - }) - - it.each([ - undefined, - null, - [], - {}, - [rule(undefined)], - [rule([])], - [rule([''])], - [rule(['not-an-address'])], - [{ remoteAddresses: [42] }] - ])('rejects malformed or empty structured output %#', (rules) => { - expect(hasSufficientWindowsFirewallRemoteScope(rules, '192.168.0.108', 24)).toBe(false) - }) - it('evaluates each rule independently instead of merging partial ranges', () => { expect( hasSufficientWindowsFirewallRemoteScope( @@ -95,16 +44,6 @@ describe('Windows firewall remote-address scope', () => { ).toBe(true) }) - it('accepts PowerShell single-object and single-string JSON shapes', () => { - expect( - hasSufficientWindowsFirewallRemoteScope( - { remoteAddresses: '192.168.0.0/24' }, - '192.168.0.108', - 24 - ) - ).toBe(true) - }) - it('accepts dotted-netmask CIDR with a contiguous mask and rejects a holey one', () => { expect( hasSufficientWindowsFirewallRemoteScope( @@ -133,25 +72,4 @@ describe('Windows firewall remote-address scope', () => { hasSufficientWindowsFirewallRemoteScope([rule(['100.64.0.0/10'])], '100.64.1.20', 32) ).toBe(false) }) - - it('fails address-family keywords closed when the interface family is unknown', () => { - expect(hasSufficientWindowsFirewallRemoteScope([rule(['Any'])], undefined, undefined)).toBe( - true - ) - expect(hasSufficientWindowsFirewallRemoteScope([rule(['Any4'])], undefined, undefined)).toBe( - false - ) - expect(hasSufficientWindowsFirewallRemoteScope([rule(['Any6'])], undefined, undefined)).toBe( - false - ) - }) - - it.each([['Intranet'], ['DNS'], ['DHCP'], ['DefaultGateway'], ['PlayToDevice']])( - 'fails the policy-defined %s keyword closed rather than assuming subnet coverage', - (scope) => { - expect(hasSufficientWindowsFirewallRemoteScope([rule([scope])], '192.168.0.108', 24)).toBe( - false - ) - } - ) }) diff --git a/src/main/runtime/workspaces/workspace-lifecycle-commands.test.ts b/src/main/runtime/workspaces/workspace-lifecycle-commands.test.ts deleted file mode 100644 index 5c547403715..00000000000 --- a/src/main/runtime/workspaces/workspace-lifecycle-commands.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { WorkspaceLifecycleQueue } from './workspace-lifecycle-queue' -import { - closeOutcome, - deleteLostContact, - observeTerminals, - queueTerminalWorkspaceCommand, - queueWorkspaceDelete, - sleepOutcome, - terminalLostContact, - workspaceLifecycleKey -} from './workspace-lifecycle-commands' - -const flush = () => new Promise((resolve) => setTimeout(resolve, 0)) - -const repos = [ - { id: 'repo-local', connectionId: null, executionHostId: null }, - { id: 'repo-ssh', connectionId: 'box', executionHostId: null } -] - -const BY_NAME: Record = { feature: 'repo-local::/a' } - -function runtime(queue: WorkspaceLifecycleQueue | null) { - return { - workspaceLifecycle: queue, - listRepos: () => repos, - showManagedWorktree: async (selector: string) => { - const id = BY_NAME[selector] ?? selector.replace(/^id:/, '') - if (!id.includes('::')) { - throw new Error('selector_not_found') - } - return { id, repoId: id.split('::')[0] } - }, - listTerminals: async () => ({ totalCount: 0 }) - } -} - -const removal = { - force: true, - runHooks: false, - allowUnverifiedPtyStop: false, - allowFailedArchiveHook: false -} - -describe('workspace lifecycle commands', () => { - it('runs directly in a build with no queue', async () => { - let ran = false - const reply = await queueTerminalWorkspaceCommand(runtime(null), 'id:repo-local::/a', { - kind: 'sleep', - run: async () => { - ran = true - return 'slept' - } - }) - expect([ran, reply]).toEqual([true, 'slept']) - }) - - it('keys a command by the host its repo routes to, the same key a delete uses', async () => { - const queue = new WorkspaceLifecycleQueue() - let release!: () => void - const gate = new Promise((resolve) => (release = resolve)) - const sleeping = queueTerminalWorkspaceCommand(runtime(queue), 'id:repo-ssh::/a', { - kind: 'sleep', - run: () => gate - }) - await flush() - expect(queue.hints()).toEqual({ - [workspaceLifecycleKey('ssh:box', 'repo-ssh::/a')]: { kind: 'sleep', queuedBehind: 0 } - }) - release() - await sleeping - }) - - it('makes a delete by name wait for the running sleep of the workspace it names', async () => { - const queue = new WorkspaceLifecycleQueue() - const log: string[] = [] - let release!: () => void - const gate = new Promise((resolve) => (release = resolve)) - const sleeping = queueTerminalWorkspaceCommand(runtime(queue), 'id:repo-local::/a', { - kind: 'sleep', - run: async () => { - log.push('sleep') - await gate - log.push('slept') - } - }) - const deleting = queueWorkspaceDelete( - runtime(queue), - 'feature', - undefined, - removal, - async () => { - log.push('delete') - } - ) - await flush() - expect(log).toEqual(['sleep']) - expect(queue.hints()).toEqual({ - [workspaceLifecycleKey('local', 'repo-local::/a')]: { kind: 'delete', queuedBehind: 0 } - }) - release() - await Promise.all([sleeping, deleting]) - expect(log).toEqual(['sleep', 'slept', 'delete']) - }) - - it('runs a delete of a workspace no lookup finds without queueing it', async () => { - const queue = new WorkspaceLifecycleQueue() - let ran = false - await queueWorkspaceDelete(runtime(queue), 'gone', undefined, removal, async () => { - ran = true - }) - expect(ran).toBe(true) - }) - - it('reads lost contact the same way for sleep, close and delete', async () => { - expect(terminalLostContact(new Error('terminal_liveness_unavailable'))).toBe(true) - expect(terminalLostContact(new Error('terminal_worktree_sleep_failed'))).toBe(false) - expect( - deleteLostContact(new Error('Timed out waiting for physical PTY teardown: repo::/a.')) - ).toBe(true) - expect(deleteLostContact(new Error('Repo not found: repo'))).toBe(false) - const silent = { - listTerminals: async () => { - throw new Error('terminal_liveness_unavailable') - } - } - await expect(observeTerminals(silent, 'id:a')()).rejects.toThrow() - const gone = { - listTerminals: async () => { - throw new Error('selector_not_found') - } - } - await expect(observeTerminals(gone, 'id:a')()).resolves.toBeUndefined() - }) - - it('reads an unconfirmed sleep or close as unverifiable, and an observed result as settled', () => { - const sleep = { stopped: 1, stoppedPtyIds: [], livePtyIds: [] } - expect( - sleepOutcome({ - ...sleep, - postStopVerified: false, - postStopFailure: 'terminal_liveness_unavailable' - }) - ).toBe('unverifiable') - expect( - sleepOutcome({ - ...sleep, - postStopVerified: false, - postStopFailure: 'terminal_worktree_sleep_still_live', - remainingLivePtyIds: ['pty-1'] - }) - ).toBe('settled') - expect( - sleepOutcome({ stopped: 1, stoppedPtyIds: [], livePtyIds: [], postStopVerified: true }) - ).toBe('settled') - const closed = { closed: 1, stopped: 1, retiredSurfaces: true as const } - expect(closeOutcome({ ...closed, ptyStopVerdict: 'unverifiable' })).toBe('unverifiable') - expect(closeOutcome({ ...closed, ptyStopVerdict: 'live' })).toBe('settled') - }) -}) diff --git a/src/main/skills/computer-use-rename.integration.test.ts b/src/main/skills/computer-use-rename.integration.test.ts index 317c27ae851..8fa7778873c 100644 --- a/src/main/skills/computer-use-rename.integration.test.ts +++ b/src/main/skills/computer-use-rename.integration.test.ts @@ -12,8 +12,6 @@ import { writeCopy } from './bundled-skill-install/skill-home.test-support' import { clearSkillRootScanCache, discoverSkills } from './discovery' -import { parseWslSkillDiscoveryOutput } from './skill-discovery-wsl-observation' -import { buildSkillDiscoverySources } from './skill-discovery-sources' const NAME = 'orca-computer-use' const OLD = 'computer-use' @@ -81,22 +79,6 @@ describe('released computer-use installation migration', () => { } ) - it('never recognizes another provider’s generic name as Orca Computer Use', async () => { - const home = await makeSkillHome() - await writeCopy( - join(dirname(home.shared), OLD), - '---\nname: computer-use\ndescription: Another provider\n---\nDifferent tool\n' - ) - const result = await discoverSkills({ - homeDir: home.home, - includeCwd: false, - names: [NAME], - sourceKinds: ['home'], - refresh: true - }) - expect(result.skills.some((skill) => skill.name === NAME)).toBe(false) - }) - it('keeps a former package with user-added files visible alongside the new skill', async () => { const home = await makeSkillHome() const old = join(dirname(home.shared), OLD) @@ -115,23 +97,4 @@ describe('released computer-use installation migration', () => { expect(result.skills.map((skill) => skill.name).sort()).toEqual([OLD, NAME]) expect(await readFile(join(old, 'notes.md'), 'utf8')).toBe('My workflow') }) - - it('recognizes the shipped former skill through WSL discovery without claiming unrelated names', async () => { - const home = await makeSkillHome() - const roots = buildSkillDiscoverySources({ homeDir: home.home, repos: [], includeCwd: false }) - const root = roots[0] - const markdown = await readFile(FIXTURE, 'utf8') - const row = (name: string, body: string) => - [ - 'S', - '0', - `${root.path}/${name}/SKILL.md`, - `${root.path}/${name}/SKILL.md`, - '1', - Buffer.from(body).toString('base64') - ].join('\0') - const output = `R\x000\x001\0${row(OLD, markdown)}\0${row('other', '---\nname: computer-use\n---\nOther tool\n')}\0` - const result = parseWslSkillDiscoveryOutput(output, roots, 42, ['home'], [NAME]) - expect(result.skills.filter((skill) => skill.bundledName === NAME)).toHaveLength(1) - }) }) diff --git a/src/main/skills/skill-discovery-wsl-plugins.test.ts b/src/main/skills/skill-discovery-wsl-plugins.test.ts deleted file mode 100644 index 3a02d283d35..00000000000 --- a/src/main/skills/skill-discovery-wsl-plugins.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { WslResult } from '../wsl/execution/wsl-runner' -import { posix as pathPosix } from 'node:path' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const runWslProcessMock = vi.hoisted(() => vi.fn()) - -vi.mock('../wsl/execution/wsl-runner', () => ({ runWslProcess: runWslProcessMock })) - -import { buildSkillDiscoverySources } from './skill-discovery-sources' -import { rootMayContainSourceKind } from './skill-discovery-source-filter' -import { discoverSkillsInWsl } from './skill-discovery-wsl' - -function record(...fields: string[]): string { - return `${fields.join('\0')}\0` -} - -function wslResult(stdout: string): WslResult { - return { environmentResolved: true, code: 0, stdout, stderr: '', timedOut: false } -} - -function recordedScript(index: number): string { - const script: unknown = runWslProcessMock.mock.calls[index]?.[0].script - if (typeof script !== 'string') { - throw new Error('Expected a generated WSL script') - } - return script -} - -describe('WSL Claude plugin skill discovery', () => { - beforeEach(() => runWslProcessMock.mockReset()) - afterEach(() => vi.unstubAllEnvs()) - - it('skips workspace roots and plugin metadata for home-only discovery without cwd', async () => { - runWslProcessMock.mockResolvedValueOnce(wslResult('')) - - const result = await discoverSkillsInWsl({ - distro: 'Ubuntu', - homeDir: '/home/alice', - sourceKinds: ['home'] - }) - - expect(runWslProcessMock).toHaveBeenCalledTimes(1) - const scanScript = recordedScript(0) - expect(scanScript.match(/'\/home\/alice\/\.agents\/skills'/g)).toHaveLength(1) - expect(scanScript.match(/'\/home\/alice\/\.claude\/skills'/g)).toHaveLength(1) - const expectedRoots = buildSkillDiscoverySources({ - homeDir: '/home/alice', - cwd: undefined, - repos: [], - includeCwd: false, - pathApi: pathPosix - }) - expect(result.sources).toHaveLength( - expectedRoots.filter((root) => rootMayContainSourceKind(root, ['home'])).length - ) - }) - - it('skips plugin metadata and unrelated roots for filtered home discovery', async () => { - runWslProcessMock.mockResolvedValueOnce(wslResult('')) - - const result = await discoverSkillsInWsl({ - distro: 'Ubuntu', - homeDir: '/home/alice', - cwd: '/work/orca', - names: ['orchestration'], - sourceKinds: ['home'] - }) - - expect(runWslProcessMock).toHaveBeenCalledTimes(1) - const scanScript = recordedScript(0) - expect(scanScript).not.toContain('/work/orca') - expect(scanScript).not.toContain("'/home/alice/.codex/plugins/cache'") - const expectedRoots = buildSkillDiscoverySources({ - homeDir: '/home/alice', - cwd: '/work/orca', - repos: [], - includeCwd: true, - pathApi: pathPosix - }).filter((root) => rootMayContainSourceKind(root, ['home'])) - expect(result.sources).toHaveLength(expectedRoots.length) - }) - - it.each([true, false])( - 'preserves enabled plugins with explicit workspace=%s', - async (explicitWorkspace) => { - const homeDir = '/home/alice' - const cwd = explicitWorkspace ? '/work/orca' : homeDir - // Windows host Hermes paths must not leak into distro discovery. - vi.stubEnv('HERMES_HOME', 'C:\\Users\\alice\\hermes') - vi.stubEnv('LOCALAPPDATA', 'C:\\Users\\alice\\AppData\\Local') - const pluginId = 'compound-engineering@compound-engineering-plugin' - const installPath = '/home/alice/.claude/plugins/cache/compound/3.14.3' - const installed = JSON.stringify({ - plugins: { - [pluginId]: [{ scope: 'project', projectPath: cwd, installPath }] - } - }) - const settings = JSON.stringify({ enabledPlugins: { [pluginId]: true } }) - const metadataOutput = [ - record('F', '0', '1', Buffer.from(installed).toString('base64')), - record('F', '1', '1', Buffer.from(settings).toString('base64')), - record('F', '2', '0', ''), - record('F', '3', '0', '') - ].join('') - const baseRootCount = buildSkillDiscoverySources({ - homeDir, - cwd, - repos: [], - pathApi: pathPosix - }).length - const skillPath = `${installPath}/skills/ce-plan/SKILL.md` - const markdown = Buffer.from('---\nname: ce-plan\ndescription: Plan work.\n---\n').toString( - 'base64' - ) - const scanOutput = [ - record('R', String(baseRootCount), '1'), - record('S', String(baseRootCount), skillPath, skillPath, '1700000000', markdown) - ].join('') - runWslProcessMock.mockResolvedValueOnce(wslResult(metadataOutput)) - runWslProcessMock.mockResolvedValueOnce(wslResult(scanOutput)) - - const result = await discoverSkillsInWsl({ - distro: 'Ubuntu', - homeDir, - ...(explicitWorkspace ? { cwd } : {}) - }) - - expect(runWslProcessMock).toHaveBeenCalledTimes(2) - const scanScript = recordedScript(1) - expect(scanScript).toContain('/home/alice/.hermes/skills') - expect(scanScript).not.toContain('AppData') - expect(scanScript).toContain(`${installPath}/skills`) - expect(result.skills).toEqual([ - expect.objectContaining({ - name: 'ce-plan', - sourceKind: 'plugin', - rootPath: `${installPath}/skills` - }) - ]) - expect(result.sources).toEqual( - expect.arrayContaining([ - expect.objectContaining({ path: `${installPath}/skills`, owner: 'claude', exists: true }) - ]) - ) - } - ) -}) diff --git a/src/main/ssh/orcad-migration-snapshot-resume.test.ts b/src/main/ssh/orcad-migration-snapshot-resume.test.ts index 3b5e9d0ef60..0736977737f 100644 --- a/src/main/ssh/orcad-migration-snapshot-resume.test.ts +++ b/src/main/ssh/orcad-migration-snapshot-resume.test.ts @@ -129,19 +129,6 @@ async function transfer( } describe('resuming a scrollback upload the journal still holds', () => { - it('retries after the tab closed and the upload was interrupted', async () => { - const { store } = relayProfile() - const manifest = createOrcadMigrationManifest(store, TARGET) - store.syncOrcadMigrationScrollbackRetention([manifest]) - store.setWorkspaceSession(getDefaultWorkspaceSession(), toSshExecutionHostId(TARGET.id)) - const remote = destination(manifest, true) - - await expect(transfer(store, manifest, remote)).rejects.toThrow('transport lost') - await transfer(store, manifest, remote) - - expect(remote.received()).toBe(Buffer.byteLength(OUTPUT)) - }) - it('recovers after a restart, from the journal alone', async () => { const { store, dataFile } = relayProfile() const manifest = createOrcadMigrationManifest(store, TARGET) diff --git a/src/main/ssh/runtime-ssh-access-verification.test.ts b/src/main/ssh/runtime-ssh-access-verification.test.ts deleted file mode 100644 index 3b63b97a167..00000000000 --- a/src/main/ssh/runtime-ssh-access-verification.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { pairedRuntimeConnectionFromOffer } from '../../shared/remote-runtime/pairing/paired-runtime-connection' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createEnvironmentFromPairingOffer } from '../../shared/remote-runtime/host-catalog/runtime-environments' -import { RUNTIME_PROTOCOL_VERSION } from '../../shared/protocol-version' -import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../shared/electron-remote-runtime-client-capabilities' -import { RemoteRuntimeClientError } from '../../shared/remote-runtime/remote-runtime-client-error' -import { verifyRuntimeEnvironmentSshTunnel } from './runtime-ssh-access-verification' - -const send = vi.hoisted(() => vi.fn()) -vi.mock('../../shared/remote-runtime/remote-runtime-client', () => ({ - sendRemoteRuntimeRequest: send -})) - -const pairing = { - v: 2 as const, - endpoint: 'wss://public.example/reverse-proxy/runtime', - deviceToken: 'existing-device-token', - publicKeyB64: Buffer.alloc(32, 1).toString('base64'), - pairedDeviceId: 'existing-paired-client' -} -const tunneledConnection = pairedRuntimeConnectionFromOffer({ - ...pairing, - endpoint: 'ws://127.0.0.1:41000' -}) -function environment() { - return createEnvironmentFromPairingOffer({ - id: 'environment', - name: 'Existing host', - now: 1, - offer: pairing, - runtimeId: 'host-runtime' - }) -} -function success() { - return { - id: 'status', - ok: true, - result: { - runtimeId: 'host-runtime', - rendererGraphEpoch: 1, - graphStatus: 'ready', - authoritativeWindowId: null, - liveTabCount: 0, - liveLeafCount: 0, - runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION, - deviceScope: 'runtime', - pairedDeviceId: 'existing-paired-client' - }, - _meta: { runtimeId: 'host-runtime' } - } -} - -describe('existing paired host verification over SSH', () => { - beforeEach(() => { - send.mockReset().mockResolvedValue(success()) - }) - - it('uses the native tunnel listener with the existing E2EE key and grant', async () => { - const original = environment() - const signal = new AbortController().signal - const result = await verifyRuntimeEnvironmentSshTunnel(original, 41000, signal) - expect(send).toHaveBeenCalledExactlyOnceWith( - tunneledConnection, - 'status.get', - undefined, - 15_000, - undefined, - signal, - ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES - ) - expect(result.verifiedRuntimeId).toBe('host-runtime') - expect(result.verifiedConnection).toEqual(tunneledConnection) - expect(original).toEqual(environment()) - }) - - it('can learn an unrecorded runtime id only after authenticated status verification', async () => { - const original = { ...environment(), runtimeId: null } - expect((await verifyRuntimeEnvironmentSshTunnel(original, 41000)).verifiedRuntimeId).toBe( - 'host-runtime' - ) - expect(original.runtimeId).toBeNull() - }) - - it.each([0, -1, 65536, 1.5, Number.NaN, Infinity])( - 'rejects invalid bound port %s before contact', - async (port) => { - await expect(verifyRuntimeEnvironmentSshTunnel(environment(), port)).rejects.toThrow( - 'valid local port' - ) - expect(send).not.toHaveBeenCalled() - } - ) - - it.each(['host-identity', 'access-grant', 'connect'] as const)( - 'does not fallback when %s fails', - async (pairingStage) => { - const error = new RemoteRuntimeClientError('unauthorized', 'verification failed', { - pairingStage - }) - send.mockRejectedValue(error) - await expect(verifyRuntimeEnvironmentSshTunnel(environment(), 41000)).rejects.toBe(error) - expect(send).toHaveBeenCalledTimes(1) - } - ) - - it('refuses an RPC failure even when it carries the expected runtime metadata', async () => { - send.mockResolvedValue({ - id: 'status', - ok: false, - error: { code: 'unauthorized', message: 'Access revoked' }, - _meta: { runtimeId: 'host-runtime' } - }) - await expect(verifyRuntimeEnvironmentSshTunnel(environment(), 41000)).rejects.toThrow( - 'Access revoked' - ) - }) - - it.each([ - { runtimeId: 'other-host' }, - { pairedDeviceId: 'other-client' }, - { deviceScope: 'mobile' }, - { minCompatibleRuntimeClientVersion: RUNTIME_PROTOCOL_VERSION + 1 }, - { rendererGraphEpoch: -1 } - ])( - 'refuses wrong identity, grant scope, compatibility or malformed status: %j', - async (change) => { - const response = success() - send.mockResolvedValue({ ...response, result: { ...response.result, ...change } }) - await expect(verifyRuntimeEnvironmentSshTunnel(environment(), 41000)).rejects.toThrow() - } - ) - - it('refuses inconsistent runtime identity between status and its envelope', async () => { - send.mockResolvedValue({ ...success(), _meta: { runtimeId: 'different-runtime' } }) - await expect(verifyRuntimeEnvironmentSshTunnel(environment(), 41000)).rejects.toThrow( - 'runtime identity' - ) - }) - - it('accepts a compatible host that does not publish an optional paired device id', async () => { - const response = success() - send.mockResolvedValue({ - ...response, - result: { ...response.result, pairedDeviceId: undefined } - }) - expect((await verifyRuntimeEnvironmentSshTunnel(environment(), 41000)).verifiedRuntimeId).toBe( - 'host-runtime' - ) - }) -}) diff --git a/src/main/ssh/system-transport/system-ssh-loopback-route-admission.test.ts b/src/main/ssh/system-transport/system-ssh-loopback-route-admission.test.ts index e601b650f13..3311532a379 100644 --- a/src/main/ssh/system-transport/system-ssh-loopback-route-admission.test.ts +++ b/src/main/ssh/system-transport/system-ssh-loopback-route-admission.test.ts @@ -3,10 +3,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterAll, describe, expect, it } from 'vitest' import type { SshTarget } from '../../../shared/ssh-types' -import { - admitSystemSshLoopbackRoute, - agentListingMayHoldSecurityKey -} from './system-ssh-loopback-route-admission' +import { admitSystemSshLoopbackRoute } from './system-ssh-loopback-route-admission' import { findSystemSsh } from './system-ssh-binary' const target: SshTarget = { @@ -151,22 +148,3 @@ describe.skipIf(findSystemSsh() === null)('admitSystemSshLoopbackRoute with real }) }) }) - -describe('agentListingMayHoldSecurityKey', () => { - const listing = (code: number | null, stdout = '', timedOut = false) => ({ - code, - stdout, - timedOut - }) - - it.each([ - ['an sk- key in the agent', listing(0, 'sk-ssh-ed25519@openssh.com AAAA k\n'), false, true], - ['only ordinary agent keys', listing(0, 'ssh-ed25519 AAAA k\n'), false, false], - ['an agent with no identities', listing(1), false, false], - ['no default agent to sign with', listing(2), false, false], - ['an unreachable configured IdentityAgent', listing(2), true, true], - ['an agent query that timed out', listing(null, '', true), false, true] - ])('%s → %s', (_name, result, agentConfigured, expected) => { - expect(agentListingMayHoldSecurityKey(result, agentConfigured)).toBe(expected) - }) -}) diff --git a/src/main/startup/http1-compatibility-profile-state.test.ts b/src/main/startup/http1-compatibility-profile-state.test.ts deleted file mode 100644 index 6b63b07a559..00000000000 --- a/src/main/startup/http1-compatibility-profile-state.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, describe, expect, it } from 'vitest' -import { - hasMissingProfileStateDatabaseWithRetainedAuthority, - readActiveProfileId, - readPersistedHttp1CompatibilityMode -} from './http1-compatibility-profile-state' -import { profileStateJsonExportPath } from '../persistence/profile-state/legacy-json/profile-state-export-path' - -const temporaryDirectories: string[] = [] - -afterEach(() => { - for (const directory of temporaryDirectories.splice(0)) { - rmSync(directory, { recursive: true, force: true }) - } -}) - -function createUserData(): string { - const directory = mkdtempSync(join(tmpdir(), 'orca-http1-profile-state-')) - temporaryDirectories.push(directory) - return directory -} - -function writeIndex(userDataPath: string, activeProfileId: string): void { - writeFileSync( - join(userDataPath, 'orca-profile-index.json'), - JSON.stringify({ - schemaVersion: 1, - activeProfileId, - profiles: [{ id: activeProfileId }] - }) - ) -} - -describe('pre-ready profile-state compatibility lookup', () => { - it('uses the legacy install-level JSON before a profile index exists', () => { - const userDataPath = createUserData() - writeFileSync( - join(userDataPath, 'orca-data.json'), - JSON.stringify({ settings: { electronHttp1CompatibilityMode: true } }) - ) - - expect(readActiveProfileId(userDataPath)).toBeUndefined() - expect(readPersistedHttp1CompatibilityMode(userDataPath)).toBe(true) - }) - - it('reads only the active profile JSON once the index is valid', () => { - const userDataPath = createUserData() - writeIndex(userDataPath, 'work') - const profileDirectory = join(userDataPath, 'profiles', 'work') - mkdirSync(profileDirectory, { recursive: true }) - writeFileSync( - join(userDataPath, 'orca-data.json'), - JSON.stringify({ settings: { electronHttp1CompatibilityMode: true } }) - ) - rmSync(join(userDataPath, 'orca-data.json')) - writeFileSync( - join(profileDirectory, 'orca-data.json'), - JSON.stringify({ settings: { electronHttp1CompatibilityMode: true } }) - ) - - expect(readActiveProfileId(userDataPath)).toBe('work') - expect(readPersistedHttp1CompatibilityMode(userDataPath)).toBe(true) - }) - - it.each(['-wal', '-shm', '-journal'])('fails closed when only SQLite %s remains', (suffix) => { - const userDataPath = createUserData() - writeIndex(userDataPath, 'work') - const profileDirectory = join(userDataPath, 'profiles', 'work') - mkdirSync(profileDirectory, { recursive: true }) - writeFileSync( - join(profileDirectory, 'orca-data.json'), - JSON.stringify({ settings: { electronHttp1CompatibilityMode: true } }) - ) - writeFileSync(join(profileDirectory, `profile-state.db${suffix}`), 'orphaned') - - expect(readPersistedHttp1CompatibilityMode(userDataPath)).toBe(false) - - writeFileSync(join(userDataPath, 'orca-profile-index.json'), '{ malformed') - writeFileSync( - join(userDataPath, 'orca-data.json'), - JSON.stringify({ settings: { electronHttp1CompatibilityMode: true } }) - ) - expect(readActiveProfileId(userDataPath)).toBe(null) - expect(readPersistedHttp1CompatibilityMode(userDataPath)).toBe(false) - }) - - it.each(['json-export', 'database-backup', 'authority-marker'])( - 'fails closed when SQLite is missing but a retained %s remains', - (artifact) => { - const userDataPath = createUserData() - writeIndex(userDataPath, 'work') - const profileDirectory = join(userDataPath, 'profiles', 'work') - mkdirSync(profileDirectory, { recursive: true }) - const dataFile = join(profileDirectory, 'orca-data.json') - writeFileSync( - dataFile, - JSON.stringify({ settings: { electronHttp1CompatibilityMode: true } }) - ) - writeFileSync( - artifact === 'json-export' - ? profileStateJsonExportPath(dataFile, 7) - : artifact === 'authority-marker' - ? join(profileDirectory, 'profile-state.db.authority') - : join( - profileDirectory, - 'profile-state.db.backup.1789999999999-00000000-0000-4000-8000-000000000000.db' - ), - readFileSync(dataFile) - ) - - expect(readPersistedHttp1CompatibilityMode(userDataPath)).toBe(false) - } - ) - - it.each(['json-export', 'database-backup', 'authority-marker'])( - 'detects a missing profile database with a retained %s', - (artifact) => { - const userDataPath = createUserData() - writeIndex(userDataPath, 'work') - const profileDirectory = join(userDataPath, 'profiles', 'work') - mkdirSync(profileDirectory, { recursive: true }) - const dataFile = join(profileDirectory, 'orca-data.json') - writeFileSync( - dataFile, - JSON.stringify({ settings: { electronHttp1CompatibilityMode: true } }) - ) - writeFileSync( - artifact === 'json-export' - ? profileStateJsonExportPath(dataFile, 7) - : artifact === 'authority-marker' - ? join(profileDirectory, 'profile-state.db.authority') - : join( - profileDirectory, - 'profile-state.db.backup.1789999999999-00000000-0000-4000-8000-000000000000.db' - ), - readFileSync(dataFile) - ) - - expect(hasMissingProfileStateDatabaseWithRetainedAuthority(userDataPath, 'work')).toBe(true) - } - ) -}) diff --git a/src/main/startup/serve-options.test.ts b/src/main/startup/serve-options.test.ts deleted file mode 100644 index 12625e574cd..00000000000 --- a/src/main/startup/serve-options.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { getServeOptions } from './serve-options' -import { normalizeServeModeArgv } from './serve-mode-argv' - -describe('getServeOptions', () => { - it('parses a valid launch', () => { - expect( - getServeOptions(['/AppRun', '--serve', '--serve-port', '6768', '--serve-no-pairing']) - ).toEqual({ - json: false, - wsPort: 6768, - pairingAddress: null, - noPairing: true, - mobilePairing: false, - grantDesktopControl: false, - recipeJson: false, - projectRoot: null - }) - }) - - it('accepts equals-form values in the normalized shape', () => { - expect( - getServeOptions([ - '/AppRun', - '--serve', - '--serve-port=6768', - '--serve-pairing-address=127.0.0.1', - '--serve-project-root=/tmp/repo' - ]) - ).toMatchObject({ - wsPort: 6768, - pairingAddress: '127.0.0.1', - projectRoot: '/tmp/repo' - }) - }) - - it('uses the final occurrence of each value flag', () => { - expect( - getServeOptions([ - '/AppRun', - '--serve', - '--serve-port', - '6768', - '--serve-port=6769', - '--serve-pairing-address', - 'first.example', - '--serve-pairing-address=last.example', - '--serve-project-root', - '/first', - '--serve-project-root=/last' - ]) - ).toMatchObject({ - wsPort: 6769, - pairingAddress: 'last.example', - projectRoot: '/last' - }) - }) - - it('applies missing or invalid values only to the final occurrence', () => { - expect( - getServeOptions(['/AppRun', '--serve', '--serve-port', '--serve-port', '6768']).wsPort - ).toBe(6768) - expect(() => - getServeOptions(['/AppRun', '--serve', '--serve-port', '6768', '--serve-port']) - ).toThrow('Missing value for --serve-port.') - expect(() => - getServeOptions(['/AppRun', '--serve', '--serve-port', '6768', '--serve-port=bad']) - ).toThrow('Invalid --serve-port value: bad') - }) - - it('uses the final value of mixed boolean aliases', () => { - expect( - getServeOptions(['/AppRun', '--serve', '--serve-no-pairing', '--no-pairing=false']).noPairing - ).toBe(false) - expect( - getServeOptions(['/AppRun', '--serve', '--no-pairing=false', '--serve-no-pairing']).noPairing - ).toBe(true) - expect( - getServeOptions(['/AppRun', '--serve', '--serve-mobile-pairing', '--mobile-pairing=0']) - .mobilePairing - ).toBe(false) - expect( - getServeOptions(['/AppRun', '--serve', '--serve-recipe-json', '--recipe-json=false']) - .recipeJson - ).toBe(false) - }) - - it('keeps JSON enabled for an equals-form global flag', () => { - expect(getServeOptions(['/AppRun', '--serve', '--json=false']).json).toBe(true) - }) - - it('accepts an equals-form value that resembles a pairing flag', () => { - const argv = normalizeServeModeArgv(['/AppRun', 'serve', '--pairing-address=--no-pairng']) - expect(getServeOptions(argv).pairingAddress).toBe('--no-pairng') - }) - - it('shares cross-flag validation with the CLI-form launch', () => { - const argv = normalizeServeModeArgv([ - '/opt/orca/orca-ide', - 'serve', - '--no-pairing', - '--mobile-pairing' - ]) - expect(() => getServeOptions(argv)).toThrow(/either --mobile-pairing or --no-pairing/i) - }) - - it('rejects recipe JSON without runtime pairing and a project root', () => { - expect(() => - getServeOptions([ - '/AppRun', - '--serve', - '--serve-recipe-json', - '--serve-no-pairing', - '--serve-project-root', - '/tmp/repo' - ]) - ).toThrow(/requires runtime pairing.*--no-pairing/i) - expect(() => getServeOptions(['/AppRun', '--serve', '--serve-recipe-json'])).toThrow( - /requires --project-root/i - ) - }) - - it('rejects a security-shaped typo while allowing Chromium switches', () => { - const normalized = normalizeServeModeArgv(['/AppRun', 'serve', '--no-pairng']) - expect(() => getServeOptions(normalized)).toThrow(/Unknown flag --no-pairng.*--no-pairing/i) - expect( - getServeOptions(['/AppRun', '--serve', '--disable-gpu', '--disable-features=Vulkan']) - .noPairing - ).toBe(false) - }) - - it('still rejects a flag-shaped space value, as the CLI does', () => { - expect(() => - getServeOptions(['/AppRun', '--serve', '--serve-pairing-address', '--no-pairng']) - ).toThrow(/Unknown flag --no-pairng.*--no-pairing/i) - }) - - it('ignores serve-looking arguments after the terminator', () => { - expect( - getServeOptions(['/AppRun', '--serve', '--', '--serve-port', '1', '--serve-no-pairing']) - ).toEqual({ - json: false, - pairingAddress: null, - noPairing: false, - mobilePairing: false, - grantDesktopControl: false, - recipeJson: false, - projectRoot: null - }) - }) - - it('requires a port value', () => { - expect(() => getServeOptions(['/AppRun', '--serve', '--serve-port'])).toThrow( - 'Missing value for --serve-port.' - ) - }) - - it.each(['', '--serve-json', '--'])('rejects an unusable port value %j', (value) => { - expect(() => getServeOptions(['/AppRun', '--serve', '--serve-port', value])).toThrow( - 'Missing value for --serve-port.' - ) - }) -}) diff --git a/src/main/telemetry/client-agent-dashboard-active.test.ts b/src/main/telemetry/client-agent-dashboard-active.test.ts deleted file mode 100644 index 634f9f4a5c0..00000000000 --- a/src/main/telemetry/client-agent-dashboard-active.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { initTelemetry, track } from './client' -import { - cleanupTelemetryClientTest, - setupTelemetryClientTest, - type TelemetryClientTestState -} from './client-test-harness' - -describe('Agent Dashboard daily visible use', () => { - let state: TelemetryClientTestState - - beforeEach(() => { - vi.useFakeTimers() - vi.setSystemTime(new Date('2026-10-22T12:00:00Z')) - state = setupTelemetryClientTest() - state.settings.experimentalAgentDashboardPopout = true - }) - - afterEach(() => { - cleanupTelemetryClientTest(state.envStash) - vi.useRealTimers() - }) - - it('caps repeated mounts and observations across windows to one event per surface per UTC day', () => { - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(true) - expect(track('agent_dashboard_active', { surface: 'in_window' })).toBe(true) - const settingsReads = vi.mocked(state.store.getSettings).mock.calls.length - for (let index = 0; index < 1500; index++) { - track('agent_dashboard_active', { surface: 'popout' }) - track('agent_dashboard_active', { surface: 'in_window' }) - } - - expect(state.mock.capture).toHaveBeenCalledTimes(2) - expect(state.store.updateSettings).toHaveBeenCalledTimes(2) - expect(state.store.getSettings).toHaveBeenCalledTimes(settingsReads) - expect(state.settings.telemetry?.agentDashboardActiveDays).toEqual({ - popout: Math.floor(Date.now() / 86_400_000), - in_window: Math.floor(Date.now() / 86_400_000) - }) - expect(track('app_opened', {})).toBe(true) - expect(state.mock.capture.mock.calls[0]?.[0].properties).toMatchObject({ - surface: 'popout', - $process_person_profile: false - }) - expect(state.mock.capture.mock.calls[0]?.[0].properties).not.toHaveProperty('day') - }) - - it('counts the next UTC day without a new app session or opening', () => { - vi.setSystemTime(new Date('2026-10-22T23:59:59Z')) - track('agent_dashboard_active', { surface: 'popout' }) - vi.setSystemTime(new Date('2026-10-23T00:00:00Z')) - - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(true) - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(false) - expect(state.mock.capture).toHaveBeenCalledTimes(2) - }) - - it('preserves the daily limit across restarts without consuming the event budget on repeats', () => { - track('agent_dashboard_active', { surface: 'popout' }) - initTelemetry(state.store, () => {}) - for (let index = 0; index < 1500; index++) { - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(false) - } - - expect(state.mock.capture).toHaveBeenCalledTimes(1) - expect(state.store.updateSettings).toHaveBeenCalledTimes(1) - expect(track('agent_dashboard_active', { surface: 'in_window' })).toBe(true) - expect(track('app_opened', {})).toBe(true) - }) - - it.each(['opt_out', 'pending_banner', 'do_not_track', 'orca_disabled', 'ci', 'dashboard_off'])( - 'does not send or consume the daily allowance while blocked by %s', - (block) => { - if (block === 'opt_out') { - state.settings.telemetry!.optedIn = false - } else if (block === 'pending_banner') { - state.settings.telemetry!.optedIn = null - } else if (block === 'dashboard_off') { - state.settings.experimentalAgentDashboardPopout = false - } else if (block === 'do_not_track') { - process.env.DO_NOT_TRACK = '1' - } else if (block === 'orca_disabled') { - process.env.ORCA_TELEMETRY_DISABLED = '1' - } else { - process.env.CI = '1' - } - - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(false) - expect(state.mock.capture).not.toHaveBeenCalled() - expect(state.store.updateSettings).not.toHaveBeenCalled() - - state.settings.telemetry!.optedIn = true - state.settings.experimentalAgentDashboardPopout = true - delete process.env.DO_NOT_TRACK - delete process.env.ORCA_TELEMETRY_DISABLED - delete process.env.CI - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(true) - } - ) - - it('does not consume a daily allowance for invalid payloads', () => { - const invalid = { surface: 'popout', card: 'private' } as const - expect(track('agent_dashboard_active', invalid)).toBe(false) - expect(state.store.updateSettings).not.toHaveBeenCalled() - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(true) - }) - - it('keeps the in-memory limit if saving the cursor fails', () => { - vi.mocked(state.store.updateSettings).mockImplementation(() => { - throw new Error('disk unavailable') - }) - - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(true) - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(false) - expect(state.mock.capture).toHaveBeenCalledTimes(1) - }) - - it('does not consume a daily allowance before the SDK has finished enabling', () => { - Object.assign(state.mock, { optedOut: true }) - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(false) - expect(state.store.updateSettings).not.toHaveBeenCalled() - expect(state.mock.capture).not.toHaveBeenCalled() - - Object.assign(state.mock, { optedOut: false }) - expect(track('agent_dashboard_active', { surface: 'popout' })).toBe(true) - }) -}) diff --git a/src/main/telemetry/wsl-move-telemetry.test.ts b/src/main/telemetry/wsl-move-telemetry.test.ts deleted file mode 100644 index fae57a9d13d..00000000000 --- a/src/main/telemetry/wsl-move-telemetry.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { eventSchemas } from '../../shared/telemetry-events' -import type { - WslMoveProgressEvent, - WslMoveResult -} from '../../shared/wsl-move/wsl-move-driver-types' -import type { WslMoveDriverDeps } from '../persistence/moving-wsl-projects/wsl-move-driver-ports' -import { runTrackedWslMove, wslMoveResetReason } from './wsl-move-telemetry' - -const track = vi.hoisted(() => vi.fn()) -vi.mock('./client', () => ({ track })) - -beforeEach(() => { - track.mockReset() -}) - -type Fake = { clock: { ms: number }; deps: WslMoveDriverDeps } - -function fakeDeps(): Fake { - const clock = { ms: 0 } - const partial: Pick = { - now: () => new Date(clock.ms), - progress: () => {} - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: runTrackedWslMove reads only `now` and `progress`, both provided. - return { clock, deps: partial as WslMoveDriverDeps } -} - -async function tracked( - result: WslMoveResult, - progress: WslMoveProgressEvent[] = [], - durationMs = 1_000 -): Promise { - const fake = fakeDeps() - const returned = await runTrackedWslMove(fake.deps, async (wrapped) => { - progress.forEach((event) => wrapped.progress(event)) - fake.clock.ms += durationMs - return result - }) - expect(returned).toBe(result) - return track.mock.calls.map(([name, props]) => { - expect(name).toBe('wsl_move') - return eventSchemas.wsl_move.parse(props) - }) -} - -describe('WSL move telemetry', () => { - it('reports the start, then a finish with its size and duration buckets', async () => { - const events = await tracked( - { outcome: 'moved', migrationId: 'm-1', phase: 'client-rewritten', reloadRequired: true }, - [ - { - distro: 'Ubuntu', - step: 'copying', - phase: 'destination-staged', - bytesTotal: 4 * 1024 * 1024 - } - ], - 20_000 - ) - expect(events).toEqual([ - expect.objectContaining({ outcome: 'started' }), - { - outcome: 'finished', - refusal: 'none', - phase: 'destination_committed', - reset_reason: 'none', - duration_bucket: '15s_60s', - bytes_bucket: '1mb_10mb' - } - ]) - }) - - it('reports a reset with the phase it reached and a reason code, never the detail', async () => { - const events = await tracked( - { - outcome: 'reset', - migrationId: 'move-id-1', - detail: 'orcad_migration_repository_path_conflict:/home/secret-user/app' - }, - [{ distro: 'MyDistro', step: 'copying', phase: 'destination-staged' }] - ) - expect(events[1]).toMatchObject({ - outcome: 'reset', - phase: 'destination_staged', - reset_reason: 'repository_path_conflict' - }) - expect(JSON.stringify(track.mock.calls)).not.toMatch(/secret-user|MyDistro|move-id-1/) - }) - - it('reports a refusal by its code, and a lost distro as such', async () => { - const events = await tracked({ - outcome: 'refused', - code: 'wsl_move_host_unavailable', - detail: 'distro_unregistered' - }) - expect(events[1]).toMatchObject({ - outcome: 'refused', - refusal: 'host_unavailable', - reset_reason: 'distro_unregistered' - }) - }) - - it('reports a move refused while one of its projects comes back', async () => { - const events = await tracked({ outcome: 'refused', code: 'wsl_move_undo_running' }) - expect(events[1]).toMatchObject({ outcome: 'refused', refusal: 'undo_running' }) - }) - - it('reports why a pause refusal after a reset happened', async () => { - const events = await tracked({ - outcome: 'refused', - code: 'wsl_move_pause_failed', - detail: 'terminals-started-after-pause' - }) - expect(events[1]).toMatchObject({ - refusal: 'pause_failed', - reset_reason: 'terminals_started' - }) - }) - - it('reports an outcome even when the run throws', async () => { - await expect( - runTrackedWslMove(fakeDeps().deps, async () => { - throw new Error('boom') - }) - ).rejects.toThrow('boom') - expect(track.mock.calls.map(([, props]) => props.outcome)).toEqual(['started', 'reset']) - }) - - it('reports a move left waiting for its host', async () => { - const events = await tracked( - { outcome: 'waiting-for-host', migrationId: 'm-1', detail: 'x; destination_unreachable' }, - [{ distro: 'Ubuntu', step: 'switching', phase: 'blobs-transferred' }] - ) - expect(events[1]).toMatchObject({ - outcome: 'waiting_for_host', - phase: 'blobs_transferred', - reset_reason: 'host_unreachable' - }) - }) - - it('still moves when telemetry throws', async () => { - track.mockImplementation(() => { - throw new Error('telemetry down') - }) - const result: WslMoveResult = { outcome: 'refused', code: 'wsl_move_cancelled' } - expect(await runTrackedWslMove(fakeDeps().deps, async () => result)).toBe(result) - }) - - it.each([ - ['orcad_migration_staged_claim_conflict:repository:repo-1', 'staged_claim_conflict'], - ['terminals-started-after-pause', 'terminals_started'], - ['exit-unverifiable: census_unavailable', 'exit_unverifiable'], - ['wsl_move_commit_not_committed:absent', 'commit_rejected'], - ['database connection is closed', 'other'], - ['something new', 'other'] - ])('narrows %s to %s', (detail, reason) => { - expect(wslMoveResetReason(detail)).toBe(reason) - }) -}) diff --git a/src/main/wsl/discovery/wsl-lost-list-output.test.ts b/src/main/wsl/discovery/wsl-lost-list-output.test.ts deleted file mode 100644 index d6c01c0fcdc..00000000000 --- a/src/main/wsl/discovery/wsl-lost-list-output.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { ProcessResult, ProcessSpec } from '@orca/process-host/process-spec' - -const { runProcessMock, runProcessSyncMock, consoleMock } = vi.hoisted(() => ({ - runProcessMock: vi.fn<(spec: ProcessSpec) => Promise>(), - runProcessSyncMock: vi.fn<(spec: ProcessSpec) => ProcessResult>(), - consoleMock: vi.fn<(registered: readonly string[], timeoutMs: number) => Promise>() -})) - -vi.mock('@orca/process-host', () => ({ - runProcess: runProcessMock, - runProcessSync: runProcessSyncMock -})) -vi.mock('./wsl-console-running-distros', () => ({ - listRunningWslDistrosViaConsole: consoleMock -})) - -import { - _resetWslCachesForTests, - listRunningWslDistrosAsync, - listWslDistros, - listWslDistrosAsync -} from './distro-catalog' - -// The reporter's capture of `wsl.exe --list --quiet` through process-host on WSL 3.0.3 (#35509). -const LOST_LIST_OUTPUT: ProcessResult = { - code: 0, - signal: null, - stdout: '', - stderr: '', - timedOut: false, - outputTruncated: false -} - -const LXSS = 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss' -const DEFAULT_ID = '0283592D-BE56-40D4-B935-3DC18C3AA007' -const REG_OUTPUT = [ - '', - LXSS, - ` DefaultDistribution REG_SZ {${DEFAULT_ID}}`, - ' DefaultVersion REG_DWORD 0x2', - '', - `${LXSS}\\{1b2c3d4e-0000-4000-8000-000000000001}`, - ' DistributionName REG_SZ docker-desktop', - '', - `${LXSS}\\{3c4d5e6f-0000-4000-8000-000000000002}`, - ' DistributionName REG_SZ Debian', - '', - `${LXSS}\\{${DEFAULT_ID}}`, - ' DistributionName REG_SZ Ubuntu-24.04', - ' Version REG_DWORD 0x2', - '' -].join('\r\n') - -function exited(stdout: string): ProcessResult { - return { code: 0, signal: null, stdout, stderr: '', timedOut: false } -} - -function host(wslList: ProcessResult, reg: ProcessResult): (spec: ProcessSpec) => ProcessResult { - return (spec) => (spec.program.toLowerCase().endsWith('reg.exe') ? reg : wslList) -} - -function wslCalls(): string[][] { - return runProcessMock.mock.calls - .map(([spec]) => spec) - .filter((spec) => spec.program === 'wsl.exe') - .map((spec) => [...(spec.args ?? [])]) -} - -async function onWindows(fn: () => Promise | T): Promise { - const original = process.platform - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - try { - return await fn() - } finally { - Object.defineProperty(process, 'platform', { configurable: true, value: original }) - } -} - -describe('a host where wsl.exe loses its redirected list output', () => { - afterEach(() => { - runProcessMock.mockReset() - runProcessSyncMock.mockReset() - consoleMock.mockReset() - _resetWslCachesForTests() - }) - - it('discovers the registered distros, default first, so WSL stays selectable', async () => { - runProcessMock.mockImplementation(async (spec) => - host(LOST_LIST_OUTPUT, exited(REG_OUTPUT))(spec) - ) - - await onWindows(async () => { - await expect(listWslDistrosAsync()).resolves.toEqual(['Ubuntu-24.04', 'Debian']) - }) - }) - - it('fills the synchronous path from the registry without a synchronous spawn', async () => { - runProcessSyncMock.mockReturnValue(LOST_LIST_OUTPUT) - runProcessMock.mockImplementation(async (spec) => - host(LOST_LIST_OUTPUT, exited(REG_OUTPUT))(spec) - ) - - await onWindows(async () => { - expect(listWslDistros()).toEqual([]) - await vi.waitFor(() => expect(listWslDistros()).toEqual(['Ubuntu-24.04', 'Debian'])) - expect(runProcessSyncMock.mock.calls.map(([spec]) => spec.program)).toEqual(['wsl.exe']) - }) - }) - - it('reads running distros through a console instead of reporting them stopped', async () => { - runProcessMock.mockImplementation(async (spec) => - host(LOST_LIST_OUTPUT, exited(REG_OUTPUT))(spec) - ) - consoleMock.mockResolvedValue(['Ubuntu-24.04']) - - await onWindows(async () => { - await expect(listRunningWslDistrosAsync({ requireConfirmed: true })).resolves.toEqual([ - 'Ubuntu-24.04' - ]) - expect(consoleMock).toHaveBeenCalledWith(['Ubuntu-24.04', 'Debian'], 5000) - // Only list calls: nothing here may start a stopped distro. - expect(wslCalls()).toEqual([ - ['--list', '--running', '--quiet'], - ['--list', '--quiet'] - ]) - }) - }) - - it('goes straight to the console once the host is known to lose list output', async () => { - runProcessMock.mockImplementation(async (spec) => - host(LOST_LIST_OUTPUT, exited(REG_OUTPUT))(spec) - ) - consoleMock.mockResolvedValue(['Ubuntu-24.04']) - - await onWindows(async () => { - await listRunningWslDistrosAsync({ requireConfirmed: true }) - await listRunningWslDistrosAsync({ requireConfirmed: true }) - expect(consoleMock).toHaveBeenCalledTimes(2) - expect(wslCalls()).toEqual([ - ['--list', '--running', '--quiet'], - ['--list', '--quiet'] - ]) - }) - }) - - it('leaves the answer unverifiable, never stopped, when the console fails too', async () => { - runProcessMock.mockImplementation(async (spec) => - host(LOST_LIST_OUTPUT, exited(REG_OUTPUT))(spec) - ) - consoleMock.mockRejectedValue(new Error('wsl.exe console list timed out')) - - await onWindows(async () => { - await expect(listRunningWslDistrosAsync({ requireConfirmed: true })).rejects.toThrow( - 'discovery is unavailable' - ) - }) - }) -}) diff --git a/src/main/wsl/execution/wsl-command-loop.test.ts b/src/main/wsl/execution/wsl-command-loop.test.ts index ae173ffb0b6..10756004375 100644 --- a/src/main/wsl/execution/wsl-command-loop.test.ts +++ b/src/main/wsl/execution/wsl-command-loop.test.ts @@ -84,16 +84,6 @@ describe.runIf(posix)('WSL command loop', () => { expect(sessionLeads).toBe('own\n') }) - it('runs concurrent commands on separate loops up to the cap, then declines', async () => { - const { pool: loops, conn } = pool({ maxLoops: 2 }) - const slow = [execCommand(conn, 'sleep 0.3; echo a'), execCommand(conn, 'sleep 0.3; echo b')] - await new Promise((resolve) => setTimeout(resolve, 50)) - await expect(loops.run('echo c')).resolves.toBeNull() - expect(await Promise.all(slow)).toEqual(['a\n', 'b\n']) - expect(await execCommand(conn, 'echo d')).toBe('d\n') - expect(spawned).toHaveLength(2) - }) - it('does not let a backgrounded writer leak into the next command', async () => { const dir = mkdtempSync(join(tmpdir(), 'orca-wsl-loop-')) try { diff --git a/src/main/wsl/filesystem/wsl-drive-project-new-default.test.ts b/src/main/wsl/filesystem/wsl-drive-project-new-default.test.ts deleted file mode 100644 index 0d7e2e4208e..00000000000 --- a/src/main/wsl/filesystem/wsl-drive-project-new-default.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { LocalWindowsRuntimePreference } from '../../../shared/project-execution-runtime' -import { - applyNewWindowsDriveProjectDefault, - newWindowsDriveProjectRuntimePreference, - readMainCheckoutWorktreeLinks, - type NewWindowsDriveProjectDefaultArgs -} from './wsl-drive-project-new-default' - -const gate = vi.hoisted(() => ({ open: true })) -vi.mock('../host-lifecycle/wsl-host-gate', () => ({ - wslHostsGateOpen: () => gate.open, - wslHostsOptedIn: (settings: { experimentalWslHosts?: boolean } | undefined) => - gate.open && settings?.experimentalWslHosts === true -})) - -const WSL_DEFAULT = { kind: 'wsl', distro: 'Ubuntu' } - -function args(overrides: Partial = {}) { - return { - platform: 'win32', - repo: { path: 'C:\\src\\app', kind: 'git' as const }, - globalWindowsRuntimeDefault: WSL_DEFAULT, - readWorktreeLinks: () => [], - ...overrides - } -} - -describe('newWindowsDriveProjectRuntimePreference', () => { - it('pins the global distro with Git for Windows for a drive project', () => { - expect(newWindowsDriveProjectRuntimePreference(args())).toEqual({ - kind: 'wsl', - distro: 'Ubuntu', - git: 'windows-host' - }) - }) - - it.each([ - ['off Windows', { platform: 'darwin' }], - ['under a Windows default', { globalWindowsRuntimeDefault: { kind: 'windows-host' } }], - ['with no default distro', { globalWindowsRuntimeDefault: { kind: 'wsl', distro: null } }], - ['inside a distro', { repo: { path: '\\\\wsl.localhost\\Ubuntu\\home\\me\\app' } }], - ['for a folder project', { repo: { path: 'C:\\src\\app', kind: 'folder' as const } }], - ['on another host', { repo: { path: 'C:\\src\\app', executionHostId: 'ssh:box' as const } }], - ['with WSL-authored worktrees', { readWorktreeLinks: () => ['/mnt/c/src/feature/.git'] }], - ['when its worktree links are unreadable', { readWorktreeLinks: () => null }] - ])('leaves the project inheriting %s', (_name, overrides) => { - expect(newWindowsDriveProjectRuntimePreference(args(overrides))).toBeNull() - }) - - it('accepts worktrees Windows Git made', () => { - expect( - newWindowsDriveProjectRuntimePreference( - args({ - readWorktreeLinks: () => [ - 'C:/src/feature/.git', - '//wsl.localhost/Ubuntu/home/me/fix/.git' - ] - }) - ) - ).not.toBeNull() - }) -}) - -describe('applyNewWindowsDriveProjectDefault', () => { - beforeEach(() => { - gate.open = true - }) - - function state( - localWindowsRuntimePreference?: LocalWindowsRuntimePreference, - experimentalWslHosts = true - ) { - return { - projects: [{ sourceRepoIds: ['r1'], localWindowsRuntimePreference }], - settings: { - localWindowsRuntimeDefault: { kind: 'wsl' as const, distro: 'Ubuntu' }, - experimentalWslHosts - } - } - } - const repo = { id: 'r1', path: 'C:\\src\\app', kind: 'git' as const } - const env = { platform: 'win32', readWorktreeLinks: () => [] } - - it("writes the default onto the repo's new project", () => { - const current = state() - expect(applyNewWindowsDriveProjectDefault(current, repo, env)).toBe(true) - expect(current.projects[0].localWindowsRuntimePreference).toEqual({ - kind: 'wsl', - distro: 'Ubuntu', - git: 'windows-host' - }) - }) - - it('keeps a preference the project already has', () => { - const current = state({ kind: 'windows-host' }) - expect(applyNewWindowsDriveProjectDefault(current, repo, env)).toBe(false) - expect(current.projects[0].localWindowsRuntimePreference).toEqual({ - kind: 'windows-host' - }) - }) - - // Stage 1 is opt-in: with the setting off, a new project keeps today's Git. - it('does nothing while the WSL hosts setting is off, though the gate is open', () => { - const current = state(undefined, false) - expect(applyNewWindowsDriveProjectDefault(current, repo, env)).toBe(false) - expect(current.projects[0].localWindowsRuntimePreference).toBeUndefined() - }) - - it('does nothing while the WSL hosts gate is closed', () => { - gate.open = false - const current = state() - expect(applyNewWindowsDriveProjectDefault(current, repo, env)).toBe(false) - expect(current.projects[0].localWindowsRuntimePreference).toBeUndefined() - }) -}) - -describe('readMainCheckoutWorktreeLinks', () => { - let dir: string - - beforeEach(() => { - dir = mkdtempSync(join(tmpdir(), 'orca-drive-default-')) - }) - - afterEach(() => { - rmSync(dir, { recursive: true, force: true }) - }) - - it("reads each linked worktree's gitdir link", () => { - mkdirSync(join(dir, '.git', 'worktrees', 'feature'), { recursive: true }) - writeFileSync(join(dir, '.git', 'worktrees', 'feature', 'gitdir'), '/mnt/c/src/feature/.git\n') - expect(readMainCheckoutWorktreeLinks(dir)).toEqual(['/mnt/c/src/feature/.git']) - }) - - it('reads a checkout with no linked worktrees as none', () => { - mkdirSync(join(dir, '.git')) - expect(readMainCheckoutWorktreeLinks(dir)).toEqual([]) - }) - - it('reads a linked worktree or missing checkout as unknown', () => { - expect(readMainCheckoutWorktreeLinks(dir)).toBeNull() - writeFileSync(join(dir, '.git'), 'gitdir: C:/src/app/.git/worktrees/x\n') - expect(readMainCheckoutWorktreeLinks(dir)).toBeNull() - }) -}) diff --git a/src/main/wsl/filesystem/wsl-unc-delete.test.ts b/src/main/wsl/filesystem/wsl-unc-delete.test.ts deleted file mode 100644 index 17ec0552e26..00000000000 --- a/src/main/wsl/filesystem/wsl-unc-delete.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { ProcessResult } from '@orca/process-host/process-spec' - -const { runProcessMock, wokenMock } = vi.hoisted(() => ({ - runProcessMock: vi.fn(), - wokenMock: vi.fn((_distro: string) => {}) -})) - -vi.mock('@orca/process-host', () => ({ runProcess: runProcessMock })) -vi.mock('../discovery/wsl-background-distro-gate', async (importOriginal) => ({ - ...(await importOriginal()), - noteWslDistroWoken: wokenMock -})) -vi.mock('../execution/wsl-interop-spawn-directory', () => ({ - resolveWslInteropSpawnCwd: () => 'C:\\Windows' -})) - -function exited(code: number, stderr = ''): ProcessResult { - return { code, signal: null, stdout: '', stderr, timedOut: false } -} - -import { tryDeleteWslUncPath } from './wsl-unc-delete' -import { isWslUserActFor } from '../discovery/wsl-user-act-scope' - -async function withPlatform(platform: NodeJS.Platform, run: () => Promise): Promise { - const original = Object.getOwnPropertyDescriptor(process, 'platform') - Object.defineProperty(process, 'platform', { configurable: true, value: platform }) - try { - return await run() - } finally { - if (original) { - Object.defineProperty(process, 'platform', original) - } - } -} - -describe('tryDeleteWslUncPath', () => { - beforeEach(() => { - runProcessMock.mockReset() - runProcessMock.mockResolvedValue(exited(0)) - }) - - it('returns false and never spawns for a normal local path', async () => { - await withPlatform('win32', async () => { - await expect(tryDeleteWslUncPath('C:\\Users\\me\\repo\\file.txt')).resolves.toBe(false) - }) - expect(runProcessMock).not.toHaveBeenCalled() - }) - - it('returns false off Windows so non-Windows callers keep trashing', async () => { - await withPlatform('darwin', async () => { - await expect( - tryDeleteWslUncPath('\\\\wsl.localhost\\Ubuntu\\home\\me\\repo\\file.txt') - ).resolves.toBe(false) - }) - expect(runProcessMock).not.toHaveBeenCalled() - }) - - it('hard-deletes a WSL UNC file via rm inside the distro', async () => { - await withPlatform('win32', async () => { - await expect( - tryDeleteWslUncPath('\\\\wsl.localhost\\Ubuntu\\home\\me\\repo\\file.txt') - ).resolves.toBe(true) - }) - - expect(runProcessMock).toHaveBeenCalledTimes(1) - const [{ program, args: spawnArgs, cwd, timeoutMs }] = runProcessMock.mock.calls[0] - expect(program).toBe('wsl.exe') - expect(timeoutMs).toBe(30000) - expect(cwd).toEqual('C:\\Windows') - expect(spawnArgs).toEqual([ - '-d', - 'Ubuntu', - '--exec', - 'rm', - '-f', - '--', - '/home/me/repo/file.txt' - ]) - }) - - it('runs approved-root deletion through the contained command', async () => { - await withPlatform('win32', async () => { - await expect( - tryDeleteWslUncPath('\\\\wsl.localhost\\Ubuntu\\home\\me\\vault\\real\\session', { - recursive: true, - approvedRoots: ['\\\\wsl.localhost\\Ubuntu\\home\\me\\vault'] - }) - ).resolves.toBe(true) - }) - - const [{ args: spawnArgs }] = runProcessMock.mock.calls[0] - expect(spawnArgs.slice(0, 5)).toEqual(['-d', 'Ubuntu', '--exec', 'sh', '-c']) - expect(spawnArgs.slice(-7)).toEqual([ - 'directory', - '3', - 'home', - 'me', - 'vault', - 'real', - 'session' - ]) - }) - - it('rejects an inspection failure instead of falling back to direct rm', async () => { - runProcessMock.mockResolvedValue(exited(65, 'ORCA_WSL_DELETE_REJECT:inspection')) - - await withPlatform('win32', async () => { - await expect( - tryDeleteWslUncPath('\\\\wsl.localhost\\Ubuntu\\home\\me\\vault\\session.json', { - approvedRoots: ['\\\\wsl.localhost\\Ubuntu\\home\\me\\vault'] - }) - ).rejects.toMatchObject({ reason: 'path-outside-known-roots' }) - }) - expect(runProcessMock).toHaveBeenCalledTimes(1) - }) - - it('rejects a target without a same-distro containing root before spawning', async () => { - await withPlatform('win32', async () => { - await expect( - tryDeleteWslUncPath('\\\\wsl.localhost\\Ubuntu\\home\\me\\vault\\session.json', { - approvedRoots: ['\\\\wsl.localhost\\Debian\\home\\me\\vault'] - }) - ).rejects.toMatchObject({ reason: 'path-outside-known-roots' }) - }) - expect(runProcessMock).not.toHaveBeenCalled() - }) - - it('also handles the legacy \\\\wsl$ UNC prefix', async () => { - await withPlatform('win32', async () => { - await tryDeleteWslUncPath('\\\\wsl$\\Debian\\srv\\app.log') - }) - - const [{ args: spawnArgs }] = runProcessMock.mock.calls[0] - expect(spawnArgs).toEqual(['-d', 'Debian', '--exec', 'rm', '-f', '--', '/srv/app.log']) - }) - - // A user's delete may start a stopped distro; background readers must then stop trusting "stopped". - it('runs the delete as a user act on its distro and notes it woken', async () => { - let insideAct = false - runProcessMock.mockImplementation(async () => { - insideAct = isWslUserActFor('Ubuntu') - return exited(0) - }) - wokenMock.mockClear() - await withPlatform('win32', async () => { - await expect( - tryDeleteWslUncPath('\\\\wsl.localhost\\Ubuntu\\home\\me\\old.jsonl') - ).resolves.toBe(true) - }) - expect(insideAct).toBe(true) - expect(wokenMock).toHaveBeenCalledWith('Ubuntu') - }) -}) diff --git a/src/main/wsl/orcad/wsl-orcad-slot-process-probe.test.ts b/src/main/wsl/orcad/wsl-orcad-slot-process-probe.test.ts index 8a8bed4d95f..cfcc1df9058 100644 --- a/src/main/wsl/orcad/wsl-orcad-slot-process-probe.test.ts +++ b/src/main/wsl/orcad/wsl-orcad-slot-process-probe.test.ts @@ -13,10 +13,7 @@ vi.mock('../../ssh/remote-exec/remote-exec-command', () => ({ isUnconfirmedSshCommandTermination: () => false })) -import { - isWslOrcadSlotInUse, - wslOrcadSlotProcessProbeCommand -} from './wsl-orcad-slot-process-probe' +import { wslOrcadSlotProcessProbeCommand } from './wsl-orcad-slot-process-probe' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' const host = getRemoteHostPlatform('linux-x64') @@ -107,38 +104,3 @@ describe.skipIf(process.platform === 'win32')('the slot process probe on a real .toBe('LIVE') }) }) - -describe('isWslOrcadSlotInUse', () => { - const slot = { - conn: Object.create(null), - host, - remoteHome: '/home/u', - nodePath: '/usr/bin/node', - userDataDir: '/home/u/.orca', - bindHost: '127.0.0.1', - port: 6768 - } - const dir = '/home/u/.orca-remote/orcad-1.6.0+bb01' - - beforeEach(() => { - vi.clearAllMocks() - mocks.orcadLive.mockResolvedValue(false) - }) - - it('is free only when the server and the process scan both prove nothing runs there', async () => { - mocks.exec.mockResolvedValue('DEAD\n') - expect(await isWslOrcadSlotInUse(slot, dir)).toBe(false) - }) - - it.each(['LIVE\n', 'UNKNOWN\n', ''])('stays in use when the scan answers %j', async (answer) => { - mocks.exec.mockResolvedValue(answer) - expect(await isWslOrcadSlotInUse(slot, dir)).toBe(true) - }) - - it('stays in use when the scan fails or the server still runs', async () => { - mocks.exec.mockRejectedValue(new Error('wsl.exe exited')) - expect(await isWslOrcadSlotInUse(slot, dir)).toBe(true) - mocks.orcadLive.mockResolvedValue(true) - expect(await isWslOrcadSlotInUse(slot, dir)).toBe(true) - }) -}) diff --git a/src/renderer/src/components/cmd-j/task-search-activation.test.ts b/src/renderer/src/components/cmd-j/task-search-activation.test.ts index 245a6df14c5..6f53b3e87f1 100644 --- a/src/renderer/src/components/cmd-j/task-search-activation.test.ts +++ b/src/renderer/src/components/cmd-j/task-search-activation.test.ts @@ -1,13 +1,9 @@ -import { afterEach, expect, it, vi } from 'vitest' +import { afterEach, expect, it } from 'vitest' import { useAppStore } from '@/store' -import { findCmdJTaskWorkspace, openCmdJTask } from './task-search-activation' +import { findCmdJTaskWorkspace } from './task-search-activation' import { jiraTaskIssue, jiraTaskSource, jiraTaskResult } from './task-search.test-fixture' -import { - makeWorktree, - makeFolderWorkspace -} from '../worktree-jump/worktree-jump-palette-test-fixtures' +import { makeWorktree } from '../worktree-jump/worktree-jump-palette-test-fixtures' import '@/store/workspace-record-selectors' -import { getFolderWorkspaceWorktree } from '../../../../shared/folder-workspaces/folder-workspace-worktree' const initial = useAppStore.getInitialState() afterEach(() => useAppStore.setState(initial, true)) @@ -20,14 +16,6 @@ const attachment = { taskSourceContext: jiraTaskSource } -it('uses the attachment source owner when a workspace executes on a different host', () => { - const workspace = makeWorktree('remote', 'Jira workspace', { - hostId: 'runtime:runner', - linkedItems: [attachment] - }) - expect(findCmdJTaskWorkspace(jiraTaskResult, [workspace])).toBe(workspace) -}) - it('rejects source host/account/site collisions even when the issue URL matches', () => { for (const source of [ { ...jiraTaskSource, hostId: 'runtime:other' as const }, @@ -57,65 +45,3 @@ it('rejects source host/account/site collisions even when the issue URL matches' expect(findCmdJTaskWorkspace(jiraTaskResult, [workspace])).toBeUndefined() } }) - -it('treats the Jira account label as display-only', () => { - const workspace = makeWorktree('labeled', 'Display name label', { - linkedItems: [ - { ...attachment, taskSourceContext: { ...jiraTaskSource, accountLabel: 'Other' } } - ] - }) - expect(findCmdJTaskWorkspace(jiraTaskResult, [workspace])).toBe(workspace) -}) - -it('rejects bare legacy identifiers and other Jira origins', () => { - const bare = makeWorktree('bare', 'Unscoped', { - linkedWorkItem: { - provider: 'jira', - type: 'issue', - number: 0, - title: '', - url: '', - jiraIdentifier: jiraTaskIssue.key - } - }) - const wrongSite = makeWorktree('wrong', 'Other site', { - linkedItems: [{ ...attachment, url: 'https://wrong.atlassian.net/browse/DEMO-12' }] - }) - expect(findCmdJTaskWorkspace(jiraTaskResult, [bare, wrongSite])).toBeUndefined() -}) - -it('matches linked folder workspaces and excludes archives', () => { - const folder = getFolderWorkspaceWorktree( - makeFolderWorkspace({ - linkedTask: { - provider: 'jira', - type: 'issue', - number: 0, - title: jiraTaskIssue.title, - url: jiraTaskIssue.url, - jiraIdentifier: jiraTaskIssue.key - }, - linkedTaskSourceContext: jiraTaskSource - }) - ) - expect(findCmdJTaskWorkspace(jiraTaskResult, [folder])).toBe(folder) - expect(findCmdJTaskWorkspace(jiraTaskResult, [{ ...folder, isArchived: true }])).toBeUndefined() -}) - -it('opens the existing composer with the exact task source and Jira identifier', async () => { - const openModal = vi.fn() - useAppStore.setState({ openModal, closeModal: vi.fn() }) - openCmdJTask(jiraTaskResult, true) - await Promise.resolve() - expect(openModal).toHaveBeenCalledWith( - 'new-workspace-composer', - expect.objectContaining({ - linkedWorkItem: expect.objectContaining({ - jiraIdentifier: jiraTaskIssue.key, - url: jiraTaskIssue.url - }), - taskSourceContext: jiraTaskSource, - telemetrySource: 'command_palette' - }) - ) -}) diff --git a/src/renderer/src/components/editor/EditorPanelShell.header.test.tsx b/src/renderer/src/components/editor/EditorPanelShell.header.test.tsx deleted file mode 100644 index fd33547a4e4..00000000000 --- a/src/renderer/src/components/editor/EditorPanelShell.header.test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it, vi } from 'vitest' -import type { OpenFile } from '@/store/slices/editor' -import { EditorPanelShell } from './EditorPanelShell' - -// Stub child renderers to test tab-mode layout without loading Monaco. -vi.mock('./EditorPanelHeader', () => ({ - EditorPanelHeader: () =>
-})) - -vi.mock('./EditorContent', () => ({ - EditorContent: () =>
-})) - -vi.mock('./UntitledFileRenameDialog', () => ({ - UntitledFileRenameDialog: () => null -})) - -vi.mock('@/store', () => ({ - useAppStore: Object.assign( - (selector: (state: Record) => unknown) => selector({ worktreesByRepo: {} }), - { getState: () => ({ worktreesByRepo: {} }) } - ) -})) - -function openFile(mode: OpenFile['mode']): OpenFile { - return { - id: `file-${mode}`, - filePath: '/home/alice/docs/report/index.html', - relativePath: 'report/index.html', - worktreeId: 'wt-1', - language: 'html', - mode - } as OpenFile -} - -function renderShell(file: OpenFile, isCombinedDiff = false): string { - const model = { - isCombinedDiff, - isSingleDiff: false, - isDiffSurface: false, - isMarkdown: false, - isMermaid: false, - isCsv: false, - isNotebook: false, - hasEditorToggle: false, - availableEditorToggleModes: [], - effectiveToggleValue: 'edit', - canOpenPreviewToSide: false, - canShowMarkdownPreview: false, - canShowMarkdownTableOfContents: false, - isMarkdownTableOfContentsDisabled: false, - shouldShowMarkdownExportAction: false, - canExportMarkdownToPdf: false, - openFileState: { canOpen: false }, - worktreeEntries: [], - resolvedLanguage: 'html', - mdViewMode: 'rich' - } - const noop = (): void => {} - return renderToStaticMarkup( - true} - onSaveForFile={async () => true} - onReloadContent={noop} - onCloseMarkdownTableOfContents={noop} - onCloseRenameDialog={noop} - onRenameConfirm={async () => {}} - markdownAnnotationsEnabled={false} - /> - ) -} - -describe('EditorPanelShell path header', () => { - // Cover ordinary tabs too so hiding every header cannot satisfy the check-details case. - it.each(['edit', 'diff', 'conflict-review', 'markdown-preview'] as const)( - 'keeps the header for a %s tab', - (mode) => { - expect(renderShell(openFile(mode))).toContain('data-editor-panel-header') - } - ) - - it('hides the header for check-details and combined diffs, as it always has', () => { - expect(renderShell(openFile('check-details'))).not.toContain('data-editor-panel-header') - expect(renderShell(openFile('edit'), true)).not.toContain('data-editor-panel-header') - }) - - it('renders the editor surface either way', () => { - expect(renderShell(openFile('check-details'))).toContain('data-editor-content') - expect(renderShell(openFile('edit'))).toContain('data-editor-content') - }) -}) diff --git a/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-section-connection.test.ts b/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-section-connection.test.ts index 831342686b9..9297954f5f8 100644 --- a/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-section-connection.test.ts +++ b/src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-section-connection.test.ts @@ -84,42 +84,4 @@ describe('getCombinedDiffSectionConnectionId', () => { getCombinedDiffSectionConnectionId(workspaceKey, '/home/neil/platform', 'README.md') ).toBeUndefined() }) - - it('composes Windows section paths with the workspace root separator', () => { - const workspaceKey = folderWorkspaceKey('folder-workspace-1') - // Mix local and SSH repos so whole-workspace resolution stays ambiguous and must fall through to the Windows section path. - useAppStore.setState({ - folderWorkspaces: [makeFolderWorkspace('C:\\Users\\neil\\platform')], - projectGroups: [makeGroup('C:\\Users\\neil\\platform')], - repos: [ - makeRepo({ - id: 'repo-local', - path: 'C:\\Users\\neil\\platform\\web', - projectGroupId: 'group-1' - }), - makeRepo({ - id: 'repo-ssh', - path: 'C:\\Users\\neil\\platform\\api', - projectGroupId: 'group-1', - connectionId: 'ssh-1' - }) - ], - worktreesByRepo: {} - }) - - expect( - getCombinedDiffSectionConnectionId( - workspaceKey, - 'C:\\Users\\neil\\platform', - 'api/src/index.ts' - ) - ).toBe('ssh-1') - expect( - getCombinedDiffSectionConnectionId( - workspaceKey, - 'C:\\Users\\neil\\platform', - 'web/src/index.ts' - ) - ).toBeUndefined() - }) }) diff --git a/src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.cross-workspace.test.tsx b/src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.cross-workspace.test.tsx deleted file mode 100644 index 685aee5e5eb..00000000000 --- a/src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.cross-workspace.test.tsx +++ /dev/null @@ -1,127 +0,0 @@ -// @vitest-environment happy-dom -// Workspace-scoped discards must evict every view of the shared record, including other workspaces. -import { act, useRef, useState } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import type { StoreApi } from 'zustand' -import type { AppState } from '@/store/types' -import type { OpenFile } from '@/store/slices/editor/types/open-file' -import { makeWorktree } from '@/store/slices/worktrees-slice-test-fixtures' -import { createEditorStore } from '../autosave/editor-autosave-controller-test-fixture' -import { ORCA_EDITOR_EXTERNAL_FILE_CHANGE_EVENT } from '../autosave/editor-autosave' -import type { DiffContent, FileContent } from '../editor-panel-content-types' -import { useEditorPanelExternalContentEvents } from './useEditorPanelExternalContentEvents' - -const storeRef = vi.hoisted((): { current: StoreApi | null } => ({ current: null })) -vi.mock('@/store/index', () => ({ - useAppStore: { getState: () => storeRef.current!.getState() } -})) - -type Calls = { - invalidate: ReturnType void>> - loadFile: ReturnType< - typeof vi.fn[0]['loadFileContent']> - > -} - -function PanelProbe({ - openFiles, - isVisible, - calls -}: { - openFiles: OpenFile[] - isVisible: boolean - calls: Calls -}): null { - const activeContentFileIdRef = useRef(openFiles[0].id) - const isVisibleRef = useRef(isVisible) - const openFilesRef = useRef(openFiles) - const editorViewModeRef = useRef({}) - const [, setFileContents] = useState>({}) - const [, setDiffContents] = useState>({}) - useEditorPanelExternalContentEvents({ - activeContentFileIdRef, - editorViewModeRef, - invalidateContent: calls.invalidate, - invalidateDiffContent: vi.fn(), - isVisibleRef, - loadDiffContent: vi.fn(async () => undefined), - loadFileContent: calls.loadFile, - openFilesRef, - setDiffContents, - setFileContents - }) - return null -} - -let container: HTMLDivElement -let root: Root -beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - container = document.body.appendChild(document.createElement('div')) - root = createRoot(container) -}) -afterEach(() => { - act(() => root.unmount()) - container.remove() -}) - -function openInTwoWorkspaces(): { a: OpenFile; b: OpenFile } { - const store = createEditorStore() - storeRef.current = store - store.setState({ - worktreesByRepo: { - 'repo-1': [ - makeWorktree({ id: 'wt-1', repoId: 'repo-1', path: '/repo', hostId: 'local' }), - makeWorktree({ id: 'wt-2', repoId: 'repo-1', path: '/repo', hostId: 'local' }) - ] - } - }) - for (const worktreeId of ['wt-1', 'wt-2']) { - store.getState().openFile({ - filePath: '/repo/notes.md', - relativePath: 'notes.md', - worktreeId, - language: 'markdown', - mode: 'edit' - }) - } - const [a, b] = store.getState().openFiles - return { a, b } -} - -function discardInWorkspaceA(): void { - act(() => { - window.dispatchEvent( - new CustomEvent(ORCA_EDITOR_EXTERNAL_FILE_CHANGE_EVENT, { - detail: { worktreeId: 'wt-1', worktreePath: '/repo', relativePath: 'notes.md' } - }) - ) - }) -} - -it.each([ - ['hidden', false], - ['visible', true] -] as const)( - "a change named by workspace A reaches B's %s view of the record", - (_label, visible) => { - const { b } = openInTwoWorkspaces() - const calls: Calls = { invalidate: vi.fn(), loadFile: vi.fn(async () => undefined) } - act(() => root.render()) - - discardInWorkspaceA() - - if (visible) { - expect(calls.loadFile).toHaveBeenCalledWith( - b.filePath, - b.id, - b.worktreeId, - b.relativePath, - expect.objectContaining({ force: true }) - ) - } else { - expect(calls.invalidate).toHaveBeenCalledWith([b.id]) - } - } -) diff --git a/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.test.tsx b/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.test.tsx deleted file mode 100644 index f064b99948a..00000000000 --- a/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -// @vitest-environment happy-dom - -import { Editor } from '@tiptap/react' -import StarterKit from '@tiptap/starter-kit' -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { RichMarkdownSlashMenu } from './RichMarkdownSlashMenu' -import './rich-markdown-slash-commands' -import { slashCommands } from './rich-markdown-slash-command-catalog' - -globalThis.IS_REACT_ACT_ENVIRONMENT = true - -vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback })) - -const ROW_HEIGHT = 30 -const VIEWPORT_HEIGHT = 90 - -let editor: Editor -let editorElement: HTMLDivElement -let host: HTMLDivElement -let reactRoot: Root - -beforeEach(() => { - editorElement = document.createElement('div') - document.body.append(editorElement) - editor = new Editor({ element: editorElement, extensions: [StarterKit], content: '

/

' }) - vi.spyOn(editor.view, 'coordsAtPos').mockReturnValue({ left: 0, right: 0, top: 0, bottom: 0 }) - host = document.createElement('div') - document.body.append(host) - reactRoot = createRoot(host) -}) - -afterEach(() => { - act(() => reactRoot.unmount()) - editor.destroy() - document.body.innerHTML = '' - vi.restoreAllMocks() -}) - -function renderMenu(selectedIndex: number, onClose: () => void): void { - act(() => - reactRoot.render( - - ) - ) -} - -function layOutResults(): HTMLElement { - const viewport = document.querySelector('.rich-markdown-slash-results') - if (!viewport) { - throw new Error('Missing slash results') - } - Object.defineProperty(viewport, 'clientHeight', { configurable: true, value: VIEWPORT_HEIGHT }) - viewport.querySelectorAll('[role="option"]').forEach((option, index) => { - Object.defineProperty(option, 'offsetTop', { configurable: true, value: index * ROW_HEIGHT }) - Object.defineProperty(option, 'offsetHeight', { configurable: true, value: ROW_HEIGHT }) - }) - return viewport -} - -it('scrolls keyboard selection into view in both directions', () => { - const onClose = vi.fn() - renderMenu(0, onClose) - const viewport = layOutResults() - - renderMenu(10, onClose) - expect(viewport.scrollTop).toBe(11 * ROW_HEIGHT - VIEWPORT_HEIGHT) - - renderMenu(2, onClose) - expect(viewport.scrollTop).toBe(2 * ROW_HEIGHT) -}) - -it('stays open for editor clicks and closes for presses outside the editor', async () => { - const onClose = vi.fn() - renderMenu(0, onClose) - // Radix arms its outside-pointer listener on the next task after mount. - await act(() => new Promise((resolve) => setTimeout(resolve, 0))) - const press = (target: Element): void => { - act(() => { - target.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, button: 0 })) - target.dispatchEvent(new MouseEvent('click', { bubbles: true, button: 0 })) - }) - } - - press(editor.view.dom) - expect(onClose).not.toHaveBeenCalled() - - const outside = document.createElement('button') - document.body.append(outside) - press(outside) - expect(onClose).toHaveBeenCalledTimes(1) -}) - -it('closes when its workspace is hidden while still painted (opacity-0 + inert)', async () => { - const workspace = document.createElement('div') - document.body.append(workspace) - workspace.append(editorElement) - const onClose = vi.fn() - renderMenu(0, onClose) - - workspace.className = 'absolute inset-0 flex' - await act(() => Promise.resolve()) - expect(onClose).not.toHaveBeenCalled() - - workspace.inert = true - workspace.setAttribute('aria-hidden', 'true') - await act(() => Promise.resolve()) - expect(onClose).toHaveBeenCalled() -}) diff --git a/src/renderer/src/components/editor/save/flush/editor-pending-flush.test.ts b/src/renderer/src/components/editor/save/flush/editor-pending-flush.test.ts deleted file mode 100644 index 7cc27c334cd..00000000000 --- a/src/renderer/src/components/editor/save/flush/editor-pending-flush.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { expect, it, vi } from 'vitest' -import { - runAfterEditorFramesFlush, - flushClosingEditorBuffers, - flushPendingEditorBuffer, - hasPendingEditorBufferChange, - registerEditorFrameFlush, - registerPendingEditorFlush -} from './editor-pending-flush' -import { EMPTY_EDITOR_BUFFER_VIEW_INDEX as NO_SHARING } from '@/store/slices/editor/buffers/editor-buffer-views' - -it('retains legacy replacement semantics and independently cleans up registrations', () => { - const earlier = vi.fn() - const later = vi.fn() - const removeEarlier = registerPendingEditorFlush('legacy', earlier) - const removeLater = registerPendingEditorFlush('legacy', later) - try { - flushPendingEditorBuffer(NO_SHARING, 'legacy') - expect(earlier).not.toHaveBeenCalled() - expect(later).toHaveBeenCalledTimes(1) - removeEarlier() - flushPendingEditorBuffer(NO_SHARING, 'legacy') - expect(later).toHaveBeenCalledTimes(2) - } finally { - removeEarlier() - removeLater() - } - flushPendingEditorBuffer(NO_SHARING, 'legacy') - expect(later).toHaveBeenCalledTimes(2) -}) - -it('checks pending changes across panes and retains the remaining registration on cleanup', () => { - const earlier = vi.fn() - const later = vi.fn() - const removeEarlier = registerPendingEditorFlush('panes', earlier, () => true) - const removeLater = registerPendingEditorFlush('panes', later, () => false) - try { - expect(hasPendingEditorBufferChange(NO_SHARING, 'panes')).toBe(true) - removeLater() - flushPendingEditorBuffer(NO_SHARING, 'panes') - expect(earlier).toHaveBeenCalledTimes(1) - expect(later).not.toHaveBeenCalled() - } finally { - removeEarlier() - removeLater() - } - expect(hasPendingEditorBufferChange(NO_SHARING, 'panes')).toBe(false) -}) - -it('flushes every pane of closing files and leaves other files editing', () => { - vi.spyOn(console, 'error').mockImplementation(() => {}) - const failingPane = vi.fn(() => { - throw new Error('serialize failed') - }) - const typingPane = vi.fn() - const keepFailedInput = vi.fn() - const keepTypedInput = vi.fn() - const closingCell = vi.fn() - const survivingCell = vi.fn() - const removers = [ - registerPendingEditorFlush('rich', typingPane, undefined, keepTypedInput), - registerPendingEditorFlush('rich', failingPane, undefined, keepFailedInput), - registerPendingEditorFlush('csv', closingCell, () => false), - registerPendingEditorFlush('survivor', survivingCell, () => true) - ] - try { - flushClosingEditorBuffers(NO_SHARING, ['rich', 'csv']) - expect(typingPane).toHaveBeenCalledTimes(1) - expect(failingPane).toHaveBeenCalledTimes(1) - expect(keepFailedInput).toHaveBeenCalledTimes(1) - expect(keepTypedInput).not.toHaveBeenCalled() - expect(closingCell).toHaveBeenCalledTimes(1) - expect(survivingCell).not.toHaveBeenCalled() - } finally { - removers.forEach((remove) => remove()) - } -}) - -const drainMicrotasks = () => new Promise((resolve) => setImmediate(resolve)) - -it('closes at once without frames, and after every frame of the closing files settles', async () => { - const close = vi.fn() - runAfterEditorFramesFlush(NO_SHARING, ['plain'], close) - expect(close).toHaveBeenCalledWith(false) - - let answer: (flushed: boolean) => void = () => {} - const removers = [ - registerEditorFrameFlush('frame', () => new Promise((resolve) => (answer = resolve))), - registerEditorFrameFlush('broken', () => Promise.reject(new Error('gone'))), - registerEditorFrameFlush('other', () => new Promise(() => {})) - ] - try { - const deferred = vi.fn() - runAfterEditorFramesFlush(NO_SHARING, ['frame', 'broken'], deferred) - await drainMicrotasks() - expect(deferred).not.toHaveBeenCalled() - // A failed or unanswered flush still closes; only the closing files' frames are asked. - answer(false) - await drainMicrotasks() - expect(deferred).toHaveBeenCalledWith(true) - } finally { - removers.forEach((remove) => remove()) - } -}) diff --git a/src/renderer/src/components/host-picker/host-list-menus.large-config.test.tsx b/src/renderer/src/components/host-picker/host-list-menus.large-config.test.tsx deleted file mode 100644 index de01c776448..00000000000 --- a/src/renderer/src/components/host-picker/host-list-menus.large-config.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -// @vitest-environment happy-dom -import { cleanup, fireEvent, render, screen } from '@testing-library/react' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { SidebarHostOption } from '../sidebar/sidebar-host-options' -import { HOST_LIST_ROW_LIMIT } from './bounded-host-list' -import { getDefaultSettings } from '../../../../shared/constants' - -// Why 13,692: the host count from the generated ~/.ssh/config in the renderer crash this guards. -const HOST_COUNT = 13_692 -const NODE_BUDGET = 3_000 -const names = Array.from({ length: HOST_COUNT }, (_, i) => `node-${String(i).padStart(5, '0')}`) -const lastName = names[HOST_COUNT - 1]! - -const hostOptions = names.map((name, i): SidebarHostOption => ({ - id: `ssh:ssh-${i}`, - label: name, - detail: '', - kind: 'ssh', - health: 'disconnected', - presence: 'configured' -})) - -// Why a pass-through menu: Radix only mounts sub-menu content after pointer or keyboard entry. -vi.mock('@/components/ui/dropdown-menu', () => { - const Pass = ({ children }: { children?: React.ReactNode }) =>
{children}
- return { - DropdownMenuSub: Pass, - DropdownMenuSubTrigger: Pass, - DropdownMenuSubContent: Pass, - DropdownMenuItem: ({ children }: { children?: React.ReactNode }) => ( -
{children}
- ), - DropdownMenuCheckboxItem: ({ children }: { children?: React.ReactNode }) => ( -
{children}
- ) - } -}) -vi.mock('../sidebar/use-sidebar-host-scope-options', () => ({ - useSidebarHostScopeOptions: () => ({ hostOptions, hostScopeOptions: [] }) -})) -vi.mock('../settings/notifications/use-notification-source-options', () => ({ - useNotificationSourceOptions: () => hostOptions -})) - -const { SidebarHostScopeMenuSection } = await import('../sidebar/SidebarHostScopeMenuSection') -const { NotificationHostToggles } = - await import('../settings/notifications/NotificationHostToggles') -const { WorkspaceDirectorySetting } = await import('../settings/general/WorkspaceDirectorySetting') - -function nodeCount(): number { - return document.body.getElementsByTagName('*').length -} - -afterEach(() => { - cleanup() -}) - -describe('host menus and settings with a 13,692-host ssh config', () => { - it('sidebar Hosts submenu keeps a filter’s checked hosts and bounds the rest', () => { - const lastId = hostOptions[HOST_COUNT - 1]!.id - render( - - ) - expect(nodeCount()).toBeLessThan(NODE_BUDGET) - expect(screen.getAllByRole('menuitemcheckbox').length).toBeLessThanOrEqual( - HOST_LIST_ROW_LIMIT + 2 - ) - expect(screen.getByText(lastName)).toBeTruthy() - }) - - it('Notifications > Machines keeps muted hosts and finds the rest by search', () => { - const lastId = hostOptions[HOST_COUNT - 1]!.id - render( - - ) - fireEvent.click(screen.getByText('Machines')) - expect(nodeCount()).toBeLessThan(NODE_BUDGET) - expect(screen.getByText(lastName)).toBeTruthy() - fireEvent.change(screen.getByRole('textbox', { name: 'Search hosts…' }), { - target: { value: 'node-00042' } - }) - expect(screen.getByText('node-00042')).toBeTruthy() - }) - - it('Settings > General workspace directory "Apply to" mounts no host rows until opened', () => { - render( - - ) - expect(nodeCount()).toBeLessThan(NODE_BUDGET) - expect(screen.queryByText(lastName)).toBeNull() - }) -}) diff --git a/src/renderer/src/components/native-chat/native-chat-launch-default-adoption.test.ts b/src/renderer/src/components/native-chat/native-chat-launch-default-adoption.test.ts deleted file mode 100644 index ffc3404f629..00000000000 --- a/src/renderer/src/components/native-chat/native-chat-launch-default-adoption.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { - clearNativeChatSessionOptionCacheForTests, - seedNativeChatAppliedSessionOptions -} from '../../../../shared/native-chat/input/native-chat-session-option-cache' -import { createNativeChatPtySessionOptions } from '../../../../shared/native-chat/input/native-chat-pty-session-options' -import { - resolveNativeChatSessionOptionDefaults, - updateNativeChatSessionOptionDefaults -} from '../../../../shared/native-chat/input/native-chat-session-option-defaults' -import type { - PersistedNativeChatSessionOptions, - SessionOptionValue -} from '../../../../shared/native-chat/input/native-chat-session-options' - -describe('native chat launch-default adoption', () => { - beforeEach(() => clearNativeChatSessionOptionCacheForTests()) - - // Model a host catalog that adds opus[1m] and drops the seeded opus id. - const HOST_CLAUDE_MODELS = [ - { id: 'opus[1m]', label: 'Opus (1M context)', options: [] }, - { id: 'sonnet', label: 'Sonnet', options: [] } - ] - - /** Apply picker persistence through settings so assertions see flags emitted for later chats. */ - const claudeLaunchDefaults = (): { - read: () => PersistedNativeChatSessionOptions - persistSelection: (args: { - modelId: string - optionId: string - value: SessionOptionValue - adoptModelAsLaunchDefault: boolean - }) => void - } => { - let persisted: PersistedNativeChatSessionOptions = {} - return { - read: () => persisted, - persistSelection: (args) => { - persisted = updateNativeChatSessionOptionDefaults({ persisted, agent: 'claude', ...args }) - } - } - } - - it('does not adopt a launch-flag model no list carries as the persisted launch default', async () => { - // Tracking an emitted launch flag does not validate its model id; unlisted ids must not become persisted defaults. - seedNativeChatAppliedSessionOptions('pty-1', 'claude', { model: 'claude-opus-5' }) - const defaults = claudeLaunchDefaults() - const surface = createNativeChatPtySessionOptions({ - agent: 'claude', - scopeKey: 'pty-1', - initialModels: HOST_CLAUDE_MODELS, - mode: 'live', - modelLabel: 'Model', - dispatchCommand: vi.fn().mockResolvedValue({ outcome: 'applied' }), - persistSelection: defaults.persistSelection - })! - - await surface.setOption('model', 'claude-opus-5') - - expect(defaults.read().claude?.model).toBeUndefined() - expect(resolveNativeChatSessionOptionDefaults(defaults.read(), 'claude')).toBeUndefined() - }) - - it('still adopts a model only the host catalog lists', async () => { - seedNativeChatAppliedSessionOptions('pty-1', 'claude', { model: 'sonnet' }) - const defaults = claudeLaunchDefaults() - const surface = createNativeChatPtySessionOptions({ - agent: 'claude', - scopeKey: 'pty-1', - initialModels: HOST_CLAUDE_MODELS, - mode: 'live', - modelLabel: 'Model', - dispatchCommand: vi.fn().mockResolvedValue({ outcome: 'applied' }), - persistSelection: defaults.persistSelection - })! - - await surface.setOption('model', 'opus[1m]') - - expect(defaults.read().claude?.model).toBe('opus[1m]') - }) - - it('still adopts a seeded alias the host catalog has stopped listing', async () => { - // `opus` is a real model the seed vouches for, and this catalog is not authoritative, - // so a CLI that lists only `opus[1m]` is no evidence against it. - seedNativeChatAppliedSessionOptions('pty-1', 'claude', { model: 'opus', effort: 'xhigh' }) - const defaults = claudeLaunchDefaults() - const surface = createNativeChatPtySessionOptions({ - agent: 'claude', - scopeKey: 'pty-1', - initialModels: HOST_CLAUDE_MODELS, - mode: 'live', - modelLabel: 'Model', - dispatchCommand: vi.fn(), - persistSelection: defaults.persistSelection - })! - - await surface.setOption('effort', 'high') - - expect(resolveNativeChatSessionOptionDefaults(defaults.read(), 'claude')).toMatchObject({ - model: 'opus', - effort: 'high' - }) - }) -}) diff --git a/src/renderer/src/components/native-chat/native-chat-runtime-owner-selector.test.ts b/src/renderer/src/components/native-chat/native-chat-runtime-owner-selector.test.ts deleted file mode 100644 index 63399c03d2a..00000000000 --- a/src/renderer/src/components/native-chat/native-chat-runtime-owner-selector.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { TerminalTab } from '../../../../shared/workspace-layout/terminal-tab-types' -import { - createNativeChatRuntimeSelector, - selectNativeChatRuntimeEnvironmentId, - type NativeChatRuntimeOwnerState -} from './native-chat-runtime-owner' - -function tab(id: string): TerminalTab { - return { - id, - ptyId: null, - worktreeId: 'owner', - title: id, - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 0 - } -} - -function state(): NativeChatRuntimeOwnerState { - return { - tabsByWorktree: { owner: [tab('target')] }, - worktreesByRepo: { repo: [{ id: 'owner', repoId: 'repo', hostId: 'runtime:first' }] } - } -} - -describe('createNativeChatRuntimeSelector', () => { - it('does not inspect terminal rows during unrelated publications and keeps runtime routing fresh', () => { - const initial = state() - const target = initial.tabsByWorktree.owner?.[0] - if (!target) { - throw new Error('Expected the target tab fixture') - } - let reads = 0 - Object.defineProperty(target, 'id', { - get: () => { - reads++ - return 'target' - } - }) - const select = createNativeChatRuntimeSelector('target') - expect(select(initial)).toBe('first') - expect(reads).toBe(1) - for (let i = 0; i < 100; i++) { - const next: NativeChatRuntimeOwnerState = { - ...initial, - worktreesByRepo: { repo: [{ id: 'owner', repoId: 'repo', hostId: `runtime:next-${i}` }] } - } - expect(select(next)).toBe(`next-${i}`) - } - expect(reads).toBe(1) - expect(select({ ...initial, tabsByWorktree: { ...initial.tabsByWorktree } })).toBe('first') - expect(reads).toBe(2) - }) - - it('preserves first terminal owner, move, close, and independent tab identities', () => { - const initial = state() - const withCollision: NativeChatRuntimeOwnerState = { - ...initial, - tabsByWorktree: { ...initial.tabsByWorktree, later: [tab('target'), tab('second')] }, - worktreesByRepo: { - repo: [ - ...(initial.worktreesByRepo?.repo ?? []), - { id: 'later', repoId: 'repo', hostId: 'runtime:later' } - ] - } - } - const select = createNativeChatRuntimeSelector('target') - const selectSecond = createNativeChatRuntimeSelector('second') - for (const next of [ - initial, - withCollision, - { ...withCollision, tabsByWorktree: { later: [tab('target'), tab('second')] } }, - { ...withCollision, tabsByWorktree: {} } - ]) { - expect(select(next)).toBe(selectNativeChatRuntimeEnvironmentId(next, 'target')) - expect(selectSecond(next)).toBe(selectNativeChatRuntimeEnvironmentId(next, 'second')) - } - }) - - it('ignores structured-only tabs and refreshes active, detected, folder, and repo host projections', () => { - const select = createNativeChatRuntimeSelector('target') - const initial = state() - const missing = { - ...initial, - tabsByWorktree: {}, - unifiedTabsByWorktree: { owner: [{ id: 'target', contentType: 'agent-session' }] } - } - expect(select(missing)).toBeNull() - const projections: NativeChatRuntimeOwnerState[] = [ - initial, - { - ...initial, - activeWorktreeId: 'owner', - activeWorkspaceExecutionHostId: 'runtime:active' as const - }, - { - ...initial, - detectedWorktreesByRepo: { - repo: { worktrees: [{ id: 'owner', repoId: 'repo', hostId: 'runtime:detected' }] } - } - }, - { - ...initial, - worktreesByRepo: { repo: [{ id: 'owner', repoId: 'repo' }] }, - repos: [{ id: 'repo', executionHostId: 'ssh:remote' as const }] - }, - { - ...initial, - tabsByWorktree: { 'folder:scope': [tab('target')] }, - folderWorkspaces: [ - { id: 'scope', projectGroupId: 'group', executionHostId: 'runtime:folder' as const } - ] - } - ] - for (const next of projections) { - expect(select(next)).toBe(selectNativeChatRuntimeEnvironmentId(next, 'target')) - } - }) -}) diff --git a/src/renderer/src/components/native-chat/native-chat-sent-prompt-history.test.ts b/src/renderer/src/components/native-chat/native-chat-sent-prompt-history.test.ts index 3fc2e13b399..bf057a92252 100644 --- a/src/renderer/src/components/native-chat/native-chat-sent-prompt-history.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-sent-prompt-history.test.ts @@ -22,26 +22,6 @@ function message( } describe('nativeChatSentPrompts', () => { - it('lists only prompts the user typed, oldest first', () => { - const prompts = nativeChatSentPrompts({ - messages: [ - message('1', 'user', 'first'), - message('2', 'assistant', 'reply'), - message('3', 'user', 'from a peer', { - from: { kind: 'agent', senders: [], orchestration: null } - }), - message('4', 'user', ''), - message('6', 'user', 'done'), - message('7', 'user', 'prompt a parent agent gave its subagent', { agentId: 'sub-1' }), - message('5', 'user', 'second\n\nwith a paragraph ') - ] - }) - expect(prompts).toEqual([ - { id: '1', prompt: 'first' }, - { id: '5', prompt: 'second\n\nwith a paragraph' } - ]) - }) - it('recalls a structured prompt without the markers of images recall cannot bring back', () => { const image = { type: 'image-ref' as const, path: '/tmp/a.png' } const messages = [ @@ -65,36 +45,6 @@ describe('nativeChatSentPrompts', () => { 'fix [Image #1]' ]) }) - - it('collapses a prompt sent back to back into its newest copy', () => { - const prompts = nativeChatSentPrompts({ - messages: [ - message('1', 'user', 'continue'), - message('2', 'assistant', 'ok'), - message('3', 'user', 'continue') - ] - }) - expect(prompts).toEqual([{ id: '3', prompt: 'continue' }]) - }) - - it('places a command the transcript holds no turn for by when it was sent', () => { - const prompts = nativeChatSentPrompts({ - messages: [ - message('1', 'user', 'before', { timestamp: 100 }), - message('2', 'user', 'after', { timestamp: 300 }) - ], - commands: [ - { id: 'a', command: '/model opus', sentAt: 200 }, - { id: 'b', command: '/compact', sentAt: 400 } - ] - }) - expect(prompts.map((entry) => entry.prompt)).toEqual([ - 'before', - '/model opus', - 'after', - '/compact' - ]) - }) }) describe('stepNativeChatPromptRecall', () => { @@ -112,14 +62,4 @@ describe('stepNativeChatPromptRecall', () => { }) expect(step?.draft).toBe('one') }) - - it('keeps its place when a new prompt lands mid-recall', () => { - const step = stepNativeChatPromptRecall({ - direction: 'back', - prompts: [...prompts, { id: '3', prompt: 'from another client' }], - position: { id: '2', recalled: 'two' }, - draft: 'two' - }) - expect(step?.draft).toBe('one') - }) }) diff --git a/src/renderer/src/components/native-chat/native-chat-work-run-pairing.test.ts b/src/renderer/src/components/native-chat/native-chat-work-run-pairing.test.ts deleted file mode 100644 index e38b07d333d..00000000000 --- a/src/renderer/src/components/native-chat/native-chat-work-run-pairing.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { AgentJournalRenderItem } from '../../../../shared/agent-session/journal/agent-session-journal-types' -import type { NativeChatMessage } from '../../../../shared/native-chat/native-chat-types' -import { foldToolMessages } from '../../../../shared/native-chat/tools/native-chat-tool-fold' -import { pairToolBlocks } from '../../../../shared/native-chat/tools/native-chat-tool-pairs' -import { - nativeChatWorkRunEditKey, - nativeChatWorkRunEntries -} from '../../../../shared/native-chat/native-chat-work-run' -import { projectStructuredAgentSessionMessages } from '../../../../shared/agent-session/view/structured-agent-session-message-projection' -import { buildEditCards } from '../../../../shared/native-chat/tools/native-chat-edit-cards' -import { buildNativeChatTranscriptSlots } from './native-chat-transcript-slots' - -function item( - itemId: string, - sequence: number, - body: AgentJournalRenderItem['body'] -): AgentJournalRenderItem { - return { itemId, sequence, revision: 1, observedAt: sequence, body } -} - -const prompt = item('u', 1, { - kind: 'message', - role: 'user', - blocks: [{ type: 'text', text: 'go' }] -}) -const quiet = item('cmd', 2, { - kind: 'tool-call', - name: 'shell', - callId: 'item_1', - input: { command: 'mkdir -p src' }, - state: 'completed' -}) -const thought = (id: string, sequence: number) => - item(id, sequence, { - kind: 'message', - role: 'reasoning', - blocks: [{ type: 'text', text: `thinking ${id}` }], - state: 'completed', - completedAt: sequence - }) -const diff = (id: string, sequence: number, file: string) => - item(id, sequence, { - kind: 'diff', - path: `src/${file}`, - patch: { - head: `@@ -1 +1 @@\n-${file} old\n+${file} new`, - digest: id, - byteLength: 20, - truncated: false - } - }) - -function messagesOf(items: AgentJournalRenderItem[]): NativeChatMessage[] { - return foldToolMessages(projectStructuredAgentSessionMessages(items, [], [])) -} - -function cards(blocks: NativeChatMessage['blocks']): [unknown, string[]][] { - return [...buildEditCards(blocks).editCards].map(([call, card]) => [ - call.type === 'tool-call' ? call.input : undefined, - card.files.map((file) => file.path) - ]) -} - -describe('tool results inside a work run', () => { - // Diff results lack provider call ids; cross-row pairing must not hand them to an unrelated quiet call. - it("keeps each row's result with its own call", () => { - const messages = messagesOf([ - prompt, - quiet, - thought('r1', 3), - diff('da', 4, 'a.ts'), - thought('r2', 5), - diff('db', 6, 'b.ts') - ]) - const run = buildNativeChatTranscriptSlots({ - messages, - turnKeys: messages.map(() => 'u'), - liveTurnKey: undefined, - receipts: new Map(), - turnStatuses: { active: null, completedByTurn: {} }, - turnDiffs: new Map(), - expandedTurnKeys: new Set(), - isWorking: false, - lifecycleWorking: false - }).find((slot) => slot.kind === 'message' && slot.workRun !== undefined) - const members = run?.kind === 'message' ? (run.workRun ?? []) : [] - expect(members.map((member) => member.id)).toEqual(['cmd', 'r1', 'da', 'r2', 'db']) - const { blocks } = nativeChatWorkRunEntries(members) - expect(pairToolBlocks(blocks)[0]?.result).toBeUndefined() - expect(cards(blocks)).toEqual([ - [{ path: 'src/a.ts' }, ['src/a.ts']], - [{ path: 'src/b.ts' }, ['src/b.ts']] - ]) - const key = nativeChatWorkRunEditKey(members, blocks, { messageId: 'da', editKey: 'Diff:0' }) - const target = [...buildEditCards(blocks).editCards.values()].find((card) => card.key === key) - expect(target?.files[0]?.path).toBe('src/a.ts') - }) - - it('keeps a result with its own call when rows fold into one message', () => { - const messages = messagesOf([prompt, quiet, diff('da', 3, 'a.ts')]) - expect(messages.map((message) => message.id)).toEqual(['u', 'cmd']) - expect(cards(messages[1]!.blocks)).toEqual([[{ path: 'src/a.ts' }, ['src/a.ts']]]) - }) -}) diff --git a/src/renderer/src/components/native-chat/use-native-chat-context-menu.find.test.tsx b/src/renderer/src/components/native-chat/use-native-chat-context-menu.find.test.tsx deleted file mode 100644 index 34bcca17105..00000000000 --- a/src/renderer/src/components/native-chat/use-native-chat-context-menu.find.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -// @vitest-environment happy-dom - -import '@testing-library/jest-dom/vitest' -import { useRef } from 'react' -import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' -import { afterEach, expect, it, vi } from 'vitest' -import { NativeChatFindBar } from './NativeChatFindBar' -import type { NativeChatComposerHandle } from './native-chat-composer-types' -import { - emptyNativeChatContextMenuActions, - useNativeChatContextMenu -} from './use-native-chat-context-menu' -import { useNativeChatFind } from './use-native-chat-find' -import { useNativeChatRootInputRouting } from './use-native-chat-root-input-routing' - -function Chat(): React.JSX.Element { - const rootRef = useRef(null) - const questionAnswerInputRef = useRef(null) - const composerElementRef = useRef(null) - const composerRef = useRef({ - focus: () => { - composerElementRef.current?.focus() - return true - }, - insertAgentReferences: () => false, - insertTypedText: () => true, - acceptsText: () => true, - appendText: () => {}, - handlePasteEvent: () => {}, - pasteFromClipboard: () => {}, - contains: () => false - }) - const messageListRef = useRef({ revealFindMatch: () => {} }) - const find = useNativeChatFind(true, rootRef, composerRef, messageListRef) - const rootInput = useNativeChatRootInputRouting({ - tabId: 'chat-tab', - rootRef, - composerRef, - questionAnswerInputRef, - isVisible: true, - isFocusedGroup: true, - composerReady: true - }) - const contextMenu = useNativeChatContextMenu({ - rootRef, - composerRef, - actions: { ...emptyNativeChatContextMenuActions, onPaste: () => {} }, - showTerminalPaneActions: false, - onFind: find.open - }) - return ( -
{ - contextMenu.onPointerDownCapture(event) - rootInput.onPointerDownCapture(event) - }} - onKeyDownCapture={(event) => { - find.onKeyDownCapture(event) - rootInput.onKeyDownCapture(event) - }} - > - {find.isOpen ? : null} -
-
Transcript words
-
-
-