mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 16:02:24 +00:00
159 lines
8.0 KiB
Diff
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)
|