Files

159 lines
8.0 KiB
Diff

diff --git a/src/main/runtime/orca-runtime-capture-provider-terminal-buffer.ts b/src/main/runtime/orca-runtime-capture-provider-terminal-buffer.ts
index acf4f60532..fc9ba31964 100644
--- a/src/main/runtime/orca-runtime-capture-provider-terminal-buffer.ts
+++ b/src/main/runtime/orca-runtime-capture-provider-terminal-buffer.ts
@@ -31,7 +31,7 @@ export class OrcaRuntimeWithCaptureProviderTerminalBuffer extends OrcaRuntimeWit
// Why: daemon PTYs survive an app relaunch before any renderer mounts.
// Mobile still needs their retained history without navigating desktop.
const snapshot = await this.ptyController?.serializeProviderBuffer?.(ptyId, opts)
- if (!snapshot || this.getPtyLifecycleGeneration(ptyId) !== generation) {
+ if (!snapshot || this.ptyLifecycleGenerationById.get(ptyId) !== generation) {
return null
}
const snapshotModeTracker = new TerminalKittyKeyboardModeTracker()
@@ -73,7 +73,10 @@ export class OrcaRuntimeWithCaptureProviderTerminalBuffer extends OrcaRuntimeWit
return null
} finally {
liveModeTrackers.delete(liveModeTracker)
- if (liveModeTrackers.size === 0) {
+ if (
+ liveModeTrackers.size === 0 &&
+ this.providerModeSnapshotScansByPtyId.get(ptyId) === liveModeTrackers
+ ) {
this.providerModeSnapshotScansByPtyId.delete(ptyId)
}
}
@@ -163,7 +166,7 @@ export class OrcaRuntimeWithCaptureProviderTerminalBuffer extends OrcaRuntimeWit
if (snapshotOptions.visibleScreenOnly) {
const projection = await this.parseVisibleSnapshot(snapshot)
// Live bytes ordered after the provider frame make that frame stale.
- return this.getPtyLifecycleGeneration(ptyId) === generation &&
+ return this.ptyLifecycleGenerationById.get(ptyId) === generation &&
this.getPtyOutputSequence(ptyId) <= snapshot.seq
? projection
: { lines: [] }
@@ -180,7 +183,7 @@ export class OrcaRuntimeWithCaptureProviderTerminalBuffer extends OrcaRuntimeWit
try {
await emulator.write(data)
const projection = projectTerminalTailLines(emulator, lineLimit)
- return this.getPtyLifecycleGeneration(ptyId) === generation &&
+ return this.ptyLifecycleGenerationById.get(ptyId) === generation &&
this.getPtyOutputSequence(ptyId) <= snapshot.seq
? projection
: { lines: [] }
diff --git a/src/main/runtime/orca-runtime-create-pty-headless-terminal-state.ts b/src/main/runtime/orca-runtime-create-pty-headless-terminal-state.ts
index da5d824ef6..b9f3547850 100644
--- a/src/main/runtime/orca-runtime-create-pty-headless-terminal-state.ts
+++ b/src/main/runtime/orca-runtime-create-pty-headless-terminal-state.ts
@@ -112,8 +112,11 @@ export class OrcaRuntimeWithCreatePtyHeadlessTerminalState extends OrcaRuntimeWi
this.headlessTerminals.set(ptyId, state)
state.writeChain = state.writeChain
.then(async () => {
+ if (this.headlessTerminals.get(ptyId) !== state) {
+ return
+ }
const snapshot = await this.serializeProviderTerminalBuffer(ptyId)
- if (!snapshot) {
+ if (this.headlessTerminals.get(ptyId) !== state || !snapshot) {
return
}
const data = `${snapshot.scrollbackAnsi ?? ''}${snapshot.data}`
@@ -123,6 +126,9 @@ export class OrcaRuntimeWithCreatePtyHeadlessTerminalState extends OrcaRuntimeWi
this.recordOsc7MetadataForPty(ptyId, data)
}
await state.emulator.write(data)
+ if (this.headlessTerminals.get(ptyId) !== state) {
+ return
+ }
if (snapshot.cwd !== undefined) {
state.emulator.setCwd(snapshot.cwd)
if (!this.terminalCwdByPtyId.has(ptyId) && snapshot.cwd?.trim()) {
@@ -141,7 +147,9 @@ export class OrcaRuntimeWithCreatePtyHeadlessTerminalState extends OrcaRuntimeWi
// Best-effort: live bytes already chain behind this replacement state.
})
.finally(() => {
- this.providerSnapshotPreferredPtys.delete(ptyId)
+ if (this.headlessTerminals.get(ptyId) === state) {
+ this.providerSnapshotPreferredPtys.delete(ptyId)
+ }
})
}
diff --git a/src/main/runtime/orca-runtime-maybe-hydrate-headless-from-renderer.ts b/src/main/runtime/orca-runtime-maybe-hydrate-headless-from-renderer.ts
index 163ffaecdb..63135f261b 100644
--- a/src/main/runtime/orca-runtime-maybe-hydrate-headless-from-renderer.ts
+++ b/src/main/runtime/orca-runtime-maybe-hydrate-headless-from-renderer.ts
@@ -51,6 +51,9 @@ export class OrcaRuntimeWithMaybeHydrateHeadlessFromRenderer extends OrcaRuntime
// setting headlessTerminals, the live byte would lazy-create a separate
// state and the seed-resolve would overwrite it, dropping live bytes.
state.writeChain = state.writeChain.then(async () => {
+ if (this.headlessTerminals.get(ptyId) !== state) {
+ return
+ }
try {
// Why the scrollback is not suppressed mid-TUI: the seed IS the model's
// normal buffer, so zeroing it while an alt-screen agent was up left the
@@ -58,7 +61,11 @@ export class OrcaRuntimeWithMaybeHydrateHeadlessFromRenderer extends OrcaRuntime
const rendered = await controller.serializeBuffer!(ptyId, {
scrollbackRows: MOBILE_SUBSCRIBE_SCROLLBACK_ROWS
})
- if (!rendered || rendered.data.length === 0) {
+ if (
+ this.headlessTerminals.get(ptyId) !== state ||
+ !rendered ||
+ rendered.data.length === 0
+ ) {
return
}
this.recordOsc7MetadataForPty(ptyId, rendered.data)
@@ -70,6 +77,9 @@ export class OrcaRuntimeWithMaybeHydrateHeadlessFromRenderer extends OrcaRuntime
state.emulator.resize(rendered.cols, rendered.rows)
}
await state.emulator.write(rendered.data)
+ if (this.headlessTerminals.get(ptyId) !== state) {
+ return
+ }
const ptyDims = this.getTerminalSize(ptyId)
if (ptyDims && (ptyDims.cols !== rendered.cols || ptyDims.rows !== rendered.rows)) {
state.emulator.resize(ptyDims.cols, ptyDims.rows)
@@ -91,7 +101,9 @@ export class OrcaRuntimeWithMaybeHydrateHeadlessFromRenderer extends OrcaRuntime
// Hydration is best-effort. Live writes continue via the same
// writeChain that this catch-arm leaves intact.
} finally {
- this.headlessHydrationState.set(ptyId, 'done')
+ if (this.headlessTerminals.get(ptyId) === state) {
+ this.headlessHydrationState.set(ptyId, 'done')
+ }
}
})
}
diff --git a/src/main/runtime/orca-runtime-serialize-main-terminal-buffer.ts b/src/main/runtime/orca-runtime-serialize-main-terminal-buffer.ts
index 6559dbfd34..5b6da61d14 100644
--- a/src/main/runtime/orca-runtime-serialize-main-terminal-buffer.ts
+++ b/src/main/runtime/orca-runtime-serialize-main-terminal-buffer.ts
@@ -134,15 +134,24 @@ export class OrcaRuntimeWithSerializeMainTerminalBuffer extends OrcaRuntimeWithA
this.recordRecentPtyOutputForPathProvenance(ptyId, data)
state.writeChain = state.writeChain
.then(async () => {
+ if (this.headlessTerminals.get(ptyId) !== state) {
+ return
+ }
// Why: seed writes never set forwardQueryReplies — the main-side
// replay guard. A snapshot containing old queries must answer no one.
await state.emulator.write(data)
+ if (this.headlessTerminals.get(ptyId) !== state) {
+ return
+ }
// Why AFTER the seed write: the snapshot payload cannot carry kitty
// pushes (rehydrateSequences deliberately omits them), but ordering
// behind it keeps the parse deterministic. Unflagged like the seed —
// re-applying flags must answer no one.
if (typeof metadata.kittyKeyboardFlags === 'number') {
await state.emulator.applyKittyKeyboardFlags(metadata.kittyKeyboardFlags)
+ if (this.headlessTerminals.get(ptyId) !== state) {
+ return
+ }
}
if (metadata.cwd !== undefined) {
state.emulator.setCwd(metadata.cwd)