@agfpd/iapeer 0.4.56 → 0.4.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/supervisor/boot.ts +36 -0
- package/src/supervisor/daemon.ts +11 -1
package/package.json
CHANGED
package/src/supervisor/boot.ts
CHANGED
|
@@ -54,3 +54,39 @@ export function nextNagAction(adapter: NagPredicate, viewport: string, enc: KeyE
|
|
|
54
54
|
if (keys && keys.length) return { kind: 'dismiss', keys, bytes: keysToBytes(keys, enc) }
|
|
55
55
|
return { kind: 'none' }
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
// ── В60 — stuck-gate for the nag-watcher (timing signal, closer to the root than text) ────────────
|
|
59
|
+
// A REAL blocked modal freezes the pty: claude stops writing entirely (the dialog is static and the
|
|
60
|
+
// session cannot proceed). A LIVE peer that merely RENDERS the modal text — reviewing this code,
|
|
61
|
+
// quoting a bug report, editing the adapter (the В40 live incident class) — keeps WRITING (streamed
|
|
62
|
+
// output, spinner frames). So "the pane has written NOTHING for a while" discriminates a genuinely
|
|
63
|
+
// stuck modal from a working peer displaying the same glyphs, where any text/position match
|
|
64
|
+
// structurally cannot. The gate composes WITH the В40 position match (both must pass); the watcher
|
|
65
|
+
// stays load-bearing (В59 suppression-at-source is expected but unproven) — this gate just limits its
|
|
66
|
+
// fire to a genuinely wedged session.
|
|
67
|
+
|
|
68
|
+
/** Mutable gate state the caller owns (one per watched session). */
|
|
69
|
+
export interface StuckGate {
|
|
70
|
+
/** The last write-sequence observed (any pty output bumps it). -1 = not yet baselined. */
|
|
71
|
+
lastSeq: number
|
|
72
|
+
/** Wall-clock ms when the sequence last CHANGED (progress observed). */
|
|
73
|
+
stableSinceMs: number
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function newStuckGate(nowMs: number): StuckGate {
|
|
77
|
+
return { lastSeq: -1, stableSinceMs: nowMs }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* One gate step: feed the current write-sequence + clock; returns true iff the pane has been
|
|
82
|
+
* COMPLETELY static (no pty writes) for at least `thresholdMs`. Any progress re-arms the gate.
|
|
83
|
+
* Pure w.r.t. time (clock injected) — unit-testable.
|
|
84
|
+
*/
|
|
85
|
+
export function paneIsStuck(gate: StuckGate, seq: number, nowMs: number, thresholdMs: number): boolean {
|
|
86
|
+
if (seq !== gate.lastSeq) {
|
|
87
|
+
gate.lastSeq = seq
|
|
88
|
+
gate.stableSinceMs = nowMs
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
return nowMs - gate.stableSinceMs >= thresholdMs
|
|
92
|
+
}
|
package/src/supervisor/daemon.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
stripQueries,
|
|
27
27
|
} from './protocol.ts'
|
|
28
28
|
import { attachedPath, geometryPath, pidPath, servePath, sockPath, writeGeometry, writePidFile } from './paths.ts'
|
|
29
|
-
import { nextBootAction, nextNagAction, type BootPredicates, type NagPredicate } from './boot.ts'
|
|
29
|
+
import { newStuckGate, nextBootAction, nextNagAction, paneIsStuck, type BootPredicates, type NagPredicate } from './boot.ts'
|
|
30
30
|
import { modelToPlainText } from './render.ts'
|
|
31
31
|
import { codexAdapter } from '../launch/adapters/codex.ts'
|
|
32
32
|
import { claudeAdapter } from '../launch/adapters/claude.ts'
|
|
@@ -87,6 +87,10 @@ const BOOT_DRIVE_MS = 120_000
|
|
|
87
87
|
// survives the hold (our key did not take), the next tick re-fires, so retries stay bounded, not lost.
|
|
88
88
|
const NAG_POLL_MS = 2000
|
|
89
89
|
const NAG_COOLDOWN_MS = 4000
|
|
90
|
+
/** В60 — how long the pane must be COMPLETELY static (no pty writes) before the nag-watcher may
|
|
91
|
+
* fire. A genuinely blocked modal freezes the session forever, so a ~10 s delay costs nothing;
|
|
92
|
+
* a live peer rendering the modal text keeps writing (stream/spinner) and never crosses this. */
|
|
93
|
+
const NAG_STUCK_MS = 10_000
|
|
90
94
|
// В25 — cap the attach-snapshot stabilization wait: under saturating output the model never goes quiet,
|
|
91
95
|
// so without a budget firstAttach would never complete (frozen attach). On timeout we snapshot the
|
|
92
96
|
// current model; live-forward delivers the rest once the gate lifts.
|
|
@@ -610,11 +614,17 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
|
|
|
610
614
|
const nagAdapter = BOOT_ADAPTERS[runtime] as NagPredicate | undefined
|
|
611
615
|
if (nagAdapter?.nagDismissKeys) {
|
|
612
616
|
let nagFiredMs = 0
|
|
617
|
+
// В60 — stuck-gate: a REAL blocked modal freezes the pty entirely, while a live peer merely
|
|
618
|
+
// RENDERING the modal text (code review / quoted report — the В40 false-fire class) keeps
|
|
619
|
+
// writing. Fire the dismiss ONLY when the pane has been completely static (writeSeq unchanged)
|
|
620
|
+
// for NAG_STUCK_MS — the timing signal composes with the adapter's position match.
|
|
621
|
+
const stuckGate = newStuckGate(Date.now())
|
|
613
622
|
const nagWatch = setInterval(() => {
|
|
614
623
|
if (down) {
|
|
615
624
|
clearInterval(nagWatch)
|
|
616
625
|
return
|
|
617
626
|
}
|
|
627
|
+
if (!paneIsStuck(stuckGate, writeSeq, Date.now(), NAG_STUCK_MS)) return // progressing → not a wedged modal
|
|
618
628
|
if (Date.now() - nagFiredMs < NAG_COOLDOWN_MS) return // let a just-answered modal repaint away first
|
|
619
629
|
let action
|
|
620
630
|
try {
|