Files
orca/src/shared/transcript-native-watcher.ts
T
Neil 02790c53e8 Fix Codex status after Ctrl+C copy and side-chat navigation (#24339)
* fix: preserve Codex status on ambiguous Ctrl+C input

* fix: confirm Codex turn cancellations from host rollout records

* fix: keep ephemeral side hooks separate from the Codex main turn

* perf: watch active Codex rollouts and skip unrelated records

* fix: retain confirmed Codex cancellation across late relay events
2026-10-01 17:33:58 -07:00

92 lines
2.6 KiB
TypeScript

import { watch, type FSWatcher } from 'node:fs'
import { basename, dirname } from 'node:path'
export type TranscriptNativeWatcher = {
/** Best-effort bind; false keeps the caller in reconciliation-only mode. */
bind: () => boolean
/** Detach from an identity that may no longer represent the watched path. */
invalidate: () => void
needsRebind: () => boolean
dispose: () => void
}
/**
* Optional fs.watch acceleration for transcript reconciliation. Native watches
* can fail on otherwise-readable remote filesystems, so binding is retryable
* and never owns transcript liveness; the caller's polling loop does.
*/
export function createTranscriptNativeWatcher(
filePath: string,
onEvent: () => void,
onError: () => void,
options: { watchParent?: boolean } = {}
): TranscriptNativeWatcher {
const watchedName = basename(filePath)
let disposed = false
let watcher: FSWatcher | null = null
let rebindNeeded = true
function invalidateCandidate(candidate: FSWatcher): void {
if (watcher !== candidate) {
return
}
watcher = null
rebindNeeded = true
candidate.close()
}
return {
bind(): boolean {
if (disposed || watcher) {
return watcher !== null
}
let nextWatcher: FSWatcher
try {
// Parent watches survive replacement; append-only readers can watch one file to avoid sibling events.
nextWatcher = watch(
options.watchParent === false ? filePath : dirname(filePath),
(event, changedName) => {
if (changedName !== null && changedName.toString() !== watchedName) {
return
}
// Why: a parent replacement may emit rename without a watcher error.
if (event === 'rename') {
invalidateCandidate(nextWatcher)
}
onEvent()
}
)
} catch {
rebindNeeded = true
return false
}
// Why: an active tail should not keep a headless runtime alive during shutdown.
nextWatcher.unref?.()
nextWatcher.on('error', () => {
if (disposed || watcher !== nextWatcher) {
return
}
invalidateCandidate(nextWatcher)
onError()
})
watcher = nextWatcher
rebindNeeded = false
return true
},
invalidate(): void {
if (watcher) {
invalidateCandidate(watcher)
} else {
rebindNeeded = true
}
},
needsRebind: () => rebindNeeded,
dispose(): void {
disposed = true
watcher?.close()
watcher = null
rebindNeeded = false
}
}
}