Repository navigation
refactor(agents): agent, session and port calls route by their workspace owner - #27283
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ➖ Normal Merge Risk: 🔵 Low · up to Input for an ownerless remote terminal may be misrouted or falsely reported as delivered; resolve this bounded routing risk before relying on the new behavior. Pre-merge checks |
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
4b8fa85d-db37-4cda-8a65-3ace60b4df31
📒 Files selected for processing (113)
config/focus-setting-read-baseline.txtconfig/owner-routing-baseline.txtconfig/scripts/check-owner-routing-ratchet.mjsconfig/scripts/check-owner-routing-ratchet.test.mjssrc/renderer/src/components/native-chat/NativeChatComposer.paste-remount.test.tsxsrc/renderer/src/components/native-chat/NativeChatComposer.test.tsxsrc/renderer/src/components/native-chat/NativeChatComposer.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.tsxsrc/renderer/src/components/native-chat/claude-model-switch-confirmation.test.tssrc/renderer/src/components/native-chat/claude-model-switch-confirmation.tssrc/renderer/src/components/native-chat/native-chat-composer-prompt-recall.test.tsxsrc/renderer/src/components/native-chat/native-chat-composer-target.tssrc/renderer/src/components/native-chat/native-chat-input-clear.tssrc/renderer/src/components/native-chat/native-chat-observed-send.test.tssrc/renderer/src/components/native-chat/native-chat-observed-send.tssrc/renderer/src/components/native-chat/native-chat-queue-send-confirm.test.tsxsrc/renderer/src/components/native-chat/native-chat-runtime-image-send.tssrc/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.tssrc/renderer/src/components/native-chat/native-chat-runtime-send.test.tssrc/renderer/src/components/native-chat/native-chat-runtime-send.tssrc/renderer/src/components/native-chat/native-chat-structured-send-composition-clear.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-composer-attachments.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-composer-interrupt.tssrc/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-interactive-send.tssrc/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.tssrc/renderer/src/components/native-chat/use-native-chat-pty-composer-send.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-pty-composer-send.tssrc/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-session-option-command.tssrc/renderer/src/components/ports/WorkspacePortScanner.test.tsxsrc/renderer/src/components/ports/WorkspacePortScanner.tsxsrc/renderer/src/components/tab-bar/TabBar.os-file-drop.test.tsxsrc/renderer/src/components/tab-bar/TabBar.worktree-write-gate.test.tsxsrc/renderer/src/components/tab-bar/TabBar.worktree-write-gate.windows.test.tsxsrc/renderer/src/components/tab-bar/use-tab-bar-runtime-model-worktree-write-probe.tssrc/renderer/src/components/tab-bar/use-tab-bar-runtime-model.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-attention-dispatch.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-completion-replay-guard.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-dispose-leak.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-done-quiet-window.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-title-precedence.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-monitoring-turn-end.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-process-exit-turn-boundary.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-queued-inspection-disposal.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-stamped-turn-boundary.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-stamped-turn-replay.test.tssrc/renderer/src/components/terminal-pane/agent-completion-coordinator-types.tssrc/renderer/src/components/terminal-pane/agent-completion-no-evidence-cadence.test.tssrc/renderer/src/components/terminal-pane/agent-completion-process-monitor.tssrc/renderer/src/components/terminal-pane/agent-completion-stale-evidence-backoff.test.tssrc/renderer/src/components/terminal-pane/agent-completion-steady-state-opt-in.test.tssrc/renderer/src/components/terminal-pane/pane-foreground-process-exit-retire.test.tssrc/renderer/src/components/terminal-pane/pending-pane-close-confirmation.test.tssrc/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.tssrc/renderer/src/components/terminal-pane/pty-connection/pane-serializer-settle.tssrc/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.tssrc/renderer/src/components/terminal-pane/terminal-paste-operation-order.test.tssrc/renderer/src/components/terminal-pane/use-terminal-pane-close-actions.tssrc/renderer/src/components/terminal/pty-running-work-probe-child-evidence.test.tssrc/renderer/src/components/terminal/pty-running-work-probe.tssrc/renderer/src/components/terminal/running-terminal-close-guard.test.tssrc/renderer/src/components/terminal/running-terminal-close-guard.tssrc/renderer/src/components/terminal/window-close-running-work.test.tssrc/renderer/src/components/terminal/window-close-running-work.tssrc/renderer/src/hooks/agent-hook-completion-notifications.tssrc/renderer/src/lib/activate-ai-vault-structured-session-reveal.test.tssrc/renderer/src/lib/activate-ai-vault-structured-session.tssrc/renderer/src/lib/active-agent-note-send.tssrc/renderer/src/lib/active-agent-note-target.tssrc/renderer/src/lib/active-agent-terminal-send-readiness.tssrc/renderer/src/lib/agent-draft-paste-content.tssrc/renderer/src/lib/agent-draft-readiness.test.tssrc/renderer/src/lib/agent-draft-readiness.tssrc/renderer/src/lib/agent-followup-delivery.test.tssrc/renderer/src/lib/agent-followup-delivery.tssrc/renderer/src/lib/agent-paste-draft-readiness-budget.test.tssrc/renderer/src/lib/agent-paste-draft-submit-retry.test.tssrc/renderer/src/lib/agent-paste-draft.test.tssrc/renderer/src/lib/agent-paste-draft.tssrc/renderer/src/lib/agent-ready-wait.tssrc/renderer/src/lib/automation-session-observer.tssrc/renderer/src/lib/codex-pane-selection-lane.test.tssrc/renderer/src/lib/codex-pane-selection-lane.tssrc/renderer/src/lib/codex-session-restart-shell-flap.test.tssrc/renderer/src/lib/codex-session-restart.test.tssrc/renderer/src/lib/codex-session-restart.tssrc/renderer/src/lib/default-creation-host.test.tssrc/renderer/src/lib/default-creation-host.tssrc/renderer/src/lib/launch-agent-background-session.tssrc/renderer/src/lib/launch-agent-new-tab-host-route-readiness.test.tssrc/renderer/src/lib/launch-worktree-background-terminals.test.tssrc/renderer/src/lib/launch-worktree-background-terminals.tssrc/renderer/src/lib/new-workspace.test.tssrc/renderer/src/lib/new-workspace.tssrc/renderer/src/lib/resolve-owner.test.tssrc/renderer/src/lib/resolve-owner.tssrc/renderer/src/lib/startup-draft-input-kind.test.tssrc/renderer/src/lib/structured-agent-session-tab-activation.test.tssrc/renderer/src/lib/structured-agent-session-tab-activation.tssrc/renderer/src/runtime/runtime-client-target.test.tssrc/renderer/src/runtime/runtime-client-target.tssrc/renderer/src/runtime/runtime-terminal-inspection.test.tssrc/renderer/src/runtime/runtime-terminal-inspection.tssrc/renderer/src/runtime/runtime-terminal-stream.test.tssrc/renderer/src/runtime/runtime-terminal-stream.tssrc/renderer/src/runtime/runtime-terminal-verified-input.test.tssrc/renderer/src/runtime/runtime-terminal-verified-input.tssrc/renderer/src/runtime/web-session-tabs-agent-completion-notifications.test.ts
💤 Files with no reviewable changes (33)
- src/renderer/src/hooks/agent-hook-completion-notifications.ts
- src/renderer/src/components/native-chat/native-chat-structured-send-composition-clear.test.tsx
- src/renderer/src/components/native-chat/native-chat-queue-send-confirm.test.tsx
- src/renderer/src/runtime/web-session-tabs-agent-completion-notifications.test.ts
- src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts
- src/renderer/src/components/tab-bar/TabBar.os-file-drop.test.tsx
- src/renderer/src/components/tab-bar/TabBar.worktree-write-gate.windows.test.tsx
- src/renderer/src/components/native-chat/native-chat-composer-target.ts
- src/renderer/src/components/tab-bar/use-tab-bar-runtime-model-worktree-write-probe.ts
- src/renderer/src/components/native-chat/native-chat-composer-prompt-recall.test.tsx
- src/renderer/src/components/native-chat/claude-model-switch-confirmation.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-exit-turn-boundary.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-no-evidence-cadence.test.ts
- src/renderer/src/components/native-chat/NativeChatComposer.paste-remount.test.tsx
- src/renderer/src/components/terminal-pane/pane-foreground-process-exit-retire.test.ts
- src/renderer/src/components/tab-bar/TabBar.worktree-write-gate.test.tsx
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-monitoring-turn-end.test.ts
- src/renderer/src/lib/startup-draft-input-kind.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-dispose-leak.test.ts
- src/renderer/src/components/terminal-pane/terminal-paste-operation-order.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-queued-inspection-disposal.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-stale-evidence-backoff.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-completion-replay-guard.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-stamped-turn-replay.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-attention-dispatch.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-types.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-title-precedence.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-done-quiet-window.test.ts
- src/renderer/src/lib/new-workspace.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-stamped-turn-boundary.test.ts
- src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts
- config/owner-routing-baseline.txt
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.
| const owner = getRemoteRuntimePtyOwner(ptyId) | ||
| const remote = isRemoteInspectionPtyId(ptyId) | ||
| if (target.kind !== 'environment' || !terminal) { | ||
| if (!owner) { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '65,105p' src/renderer/src/runtime/runtime-terminal-inspection.ts
sed -n '145,195p' src/renderer/src/runtime/runtime-terminal-inspection.ts
sed -n '15,90p' src/renderer/src/runtime/runtime-terminal-verified-input.ts
sed -n '25,70p' src/renderer/src/runtime/runtime-terminal-stream.tsRepository: stablyai/orca
Length of output: 6829
🏁 Script executed:
set -o pipefail
printf '%s\n' '--- parser, predicates, owner helper ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'parseRemoteRuntimePtyId' src/ || test "$?" -eq 1
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'isRemoteInspectionPtyId' src/ || test "$?" -eq 1
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'getRemoteRuntimePtyOwner' src/ || test "$?" -eq 1
printf '%s\n' '--- PTY IPC symbols and receivers ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'inspectProcess' src/ | head -120
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'writeAccepted' src/ | head -120
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'pty.write' src/ | head -120
printf '%s\n' '--- remote ID generation and ownerless fixtures ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'remote:' src/ | head -160
rg -n -i -F --glob '*.ts' --glob '*.tsx' -- 'ownerless' src/ | head -160
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'remote<' src/ | head -80
printf '%s\n' '--- likely parser/helper files ---'
find src -type f \( -name '*.ts' -o -name '*.tsx' \) | grep -E 'runtime|pty|terminal|ipc|preload' | head -160Repository: stablyai/orca
Length of output: 41638
🏁 Script executed:
set -o pipefail
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'parseRemoteRuntimePtyId' src/ || test "$?" -eq 1
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'isRemoteInspectionPtyId' src/ || test "$?" -eq 1
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'getRemoteRuntimePtyOwner' src/ || test "$?" -eq 1
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'inspectProcess' src/ | head -120
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'writeAccepted' src/ | head -120
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'pty.write' src/ | head -120
rg -n -i -F --glob '*.ts' --glob '*.tsx' -- 'ownerless' src/ | head -160
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'remote:' src/ | head -160Repository: stablyai/orca
Length of output: 41994
🏁 Script executed:
set -o pipefail
printf '%s\n' '--- remote ID contract ---'
nl -ba src/shared/remote-runtime-pty-id.ts | sed -n '1,80p'
printf '%s\n' '--- inspection predicate and wrappers ---'
nl -ba src/renderer/src/runtime/runtime-terminal-inspection.ts | sed -n '1,115p'
nl -ba src/renderer/src/runtime/runtime-terminal-inspection.ts | sed -n '155,205p'
nl -ba src/renderer/src/runtime/runtime-terminal-verified-input.ts | sed -n '1,90p'
printf '%s\n' '--- provider routing ---'
nl -ba src/main/ipc/pty/provider/registry.ts | sed -n '85,140p'
printf '%s\n' '--- PTY write/inspection IPC bindings ---'
rg -n -F --glob '*.ts' -- 'pty:write' src/main src/preload | head -80 || test \"$?\" -eq 1
rg -n -F --glob '*.ts' -- 'writeAccepted' src/main src/preload | head -100 || test \"$?\" -eq 1
rg -n -F --glob '*.ts' -- 'registerPtyIpc' src/main | head -40 || test \"$?\" -eq 1
nl -ba src/main/ipc/pty/ipc/inspect.ts | sed -n '150,230p'
printf '%s\n' '--- ownerless tests ---'
nl -ba src/renderer/src/runtime/runtime-terminal-stream.test.ts | sed -n '1,55p'
nl -ba src/renderer/src/runtime/runtime-terminal-verified-input.test.ts | sed -n '90,130p'
nl -ba src/main/ipc/pty/provider/registry-host-routing.test.ts | sed -n '1,90p'Repository: stablyai/orca
Length of output: 32791
🏁 Script executed:
set -o pipefail
nl -ba src/main/ipc/pty/ipc/write.ts | sed -n '1,90p'
nl -ba src/main/ipc/pty/ipc/write-input.ts | sed -n '1,95p'
nl -ba src/main/ipc/pty/register-handlers.ts | sed -n '115,155p'Repository: stablyai/orca
Length of output: 9861
🏁 Script executed:
set -o pipefail
nl -ba src/main/ipc/pty/ipc/write-input.ts | sed -n '88,190p'
nl -ba src/renderer/src/runtime/runtime-terminal-inspection.ts | sed -n '115,175p'Repository: stablyai/orca
Length of output: 7017
Reject ownerless remote: IDs before local and deferred input paths.
remote:<handle> is a valid ownerless remote ID. isRemoteInspectionPtyId does not recognize it because it requires an environment ID. Use isRemoteRuntimePtyId for this guard.
The local receiver cannot route this ID to a provider, but sendRuntimePtyInput can return true before its helper runs, and verified input can convert a local refusal into a successful fire-and-forget fallback.
🐛 Suggested fix
const owner = getRemoteRuntimePtyOwner(ptyId)
const remote = isRemoteInspectionPtyId(ptyId)
+ if (isRemoteRuntimePtyId(ptyId) && !owner) {
+ return clientOnlyUnverifiableInspection('terminal_gone')
+ }
if (!owner) {Guard the exported ordinary-input function before deferred size measurement:
): boolean {
+ if (isRemoteRuntimePtyId(ptyId) && !getRemoteRuntimePtyOwner(ptyId)) {
+ return false
+ }
const tooLarge = isRuntimePtyInputTooLarge(data)Apply the same prefix-based guard after the size check in sendRuntimePtyInputVerified:
const owner = getRemoteRuntimePtyOwner(ptyId)
+ if (isRemoteRuntimePtyId(ptyId) && !owner) {
+ return false
+ }
if (!owner) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const owner = getRemoteRuntimePtyOwner(ptyId) | |
| const remote = isRemoteInspectionPtyId(ptyId) | |
| if (target.kind !== 'environment' || !terminal) { | |
| if (!owner) { | |
| const owner = getRemoteRuntimePtyOwner(ptyId) | |
| const remote = isRemoteInspectionPtyId(ptyId) | |
| if (isRemoteRuntimePtyId(ptyId) && !owner) { | |
| return clientOnlyUnverifiableInspection('terminal_gone') | |
| } | |
| if (!owner) { |
e2d4eb9 to
58e754b
Compare
…ace owner Agent note sends, background agent and terminal launches, structured chat reveal and activation, and the tab strip's Windows shell probe take their transport from the workspace owner's environment instead of building owner-shaped settings for getActiveRuntimeTarget. The automation observer and the Codex selection lane read the owner from the PTY id alone, so an ownerless remote id is never assigned the focused server. Local advertised-URL events always rescan this computer, even when a server is the default host.
58e754b to
a39b0c0
Compare
ELI5
Agent notes, background agent launches, reopening saved chats and the tab strip's shell menu used to make up a fake copy of the user's settings, with the workspace's owner swapped in as the "Active Server". They then read the target server back out of that copy. Now they ask the workspace for its owner directly. Two places that still fell back to the Active Server now go only by the terminal's own id. One more fix: the ports list now refreshes this computer's ports when a local dev server starts, even if a server is set as the default host.
Builds on #27279 and #27275 (both merged).
What Changed
getActiveRuntimeTarget(getSettingsForWorktreeRuntimeOwner(state, id))orgetActiveRuntimeTarget({ activeRuntimeEnvironmentId: ownerId })now callruntimeTargetForOwnerEnvironment(getRuntimeEnvironmentIdForWorktree(state, id)). The result is the same, but nothing is shaped like settings any more.automation-session-observerusesgetRemoteRuntimePtyOwner, which comes from refactor(terminals): terminal input and inspection route by the PTY's owner #27279.codex-pane-selection-laneuses the id's own owner segment.WorkspacePortScannerno longer reads the Active Server. The single-host view key is the only scan target, which is what the old code amounted to. Local advertised-URL events always refresh{ kind: 'local' }.ActiveTerminalNoteTargetStatedrops its unusedsettingsfield.Why
Making a settings object only to read one field back out is the pattern the owner-routing ratchet exists to catch. Passing the owner's id straight through keeps the result the same and removes the place where the Active Server could slip back in.
For ports, we considered keeping the "only when focus is local" gate. We rejected it because the event always describes this computer's processes, so tying it to the default host was a bug.
Linked Issue
None. This is part of the host-ownership routing stack (D4 G1).
Visual Proof
N/A. There is no UI change. The only visible effect is that local ports appear sooner when a server is the default host.
Testing
I manually tested these changes locally
Automated tests added/updated, or explained why not below
WorkspacePortScanner.test.tsx: "rescans this computer on a local URL change while a server is the default host". It fails on the parent branch, because the event listener was never installed. The test that asserted a focus change cancels a poll was removed, because focus no longer drives the scanner.codex-pane-selection-lane.test.ts: an ownerless remote pane is never given the focused server.structured-agent-session-tab-activation,activate-ai-vault-structured-session-revealandlaunch-worktree-background-terminalstests now stub the owner lookup instead ofgetActiveRuntimeTarget.Ran:
pnpm tc:web, focusedpnpm test(lib, hooks, tab-bar, ports, terminal-pane),oxlint,oxfmt,pnpm run check:code-quality:changed, andpnpm check:owner-routing-ratchet.Review
Agent skill upstream boundary
Notes
No wire change. Folder workspaces resolve through
getRuntimeEnvironmentIdForWorktree, as before. SSH workspaces stay on this app's transport.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)