@agfpd/iapeer 0.2.82 → 0.2.84
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/lifecycle/index.ts +20 -7
- package/src/supervisor/daemon.ts +27 -2
- package/src/transport/index.ts +2 -2
package/package.json
CHANGED
package/src/lifecycle/index.ts
CHANGED
|
@@ -592,7 +592,7 @@ export function resolveWakeMode(
|
|
|
592
592
|
: { resume: false, cause: 'idle-reaped-human-no-resume' }
|
|
593
593
|
}
|
|
594
594
|
// executor: RESUME is an INTENTIONAL "continue THIS work" signal — it happens ONLY when
|
|
595
|
-
// the incoming message carries a topic that matches the session's
|
|
595
|
+
// the incoming message carries a topic that matches the session's topic set. A DIFFERENT
|
|
596
596
|
// topic (new work) OR NO topic at all → FRESH. Rationale: a topicless ping after an idle
|
|
597
597
|
// gap is not a request to resume specific work; defaulting it to resume revived stale
|
|
598
598
|
// context (e.g. a 3-day-old session). The sender opts INTO continuity by passing the topic.
|
|
@@ -1160,11 +1160,19 @@ export interface SuperviseOutcome {
|
|
|
1160
1160
|
export interface SuperviseDeps {
|
|
1161
1161
|
env?: NodeJS.ProcessEnv
|
|
1162
1162
|
nowMs?: number
|
|
1163
|
+
/** Activity proxy seam (default: the runtime adapter's transcript/session mtime). Tests inject a
|
|
1164
|
+
* controlled value to exercise the idle-age floor without a real transcript on disk. */
|
|
1165
|
+
newestActivityMtime?: (runtime: string, cwd: string) => number | null
|
|
1166
|
+
/** Liveness seam (default: the real `sessionAlive` = pty-host OR tmux-fallback). Tests inject a
|
|
1167
|
+
* fixed verdict so the idle path is exercised hermetically — no live tmux/pty session needed. */
|
|
1168
|
+
sessionAlive?: (sock: string, identity: string) => boolean
|
|
1163
1169
|
}
|
|
1164
1170
|
|
|
1165
1171
|
export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): SuperviseOutcome[] {
|
|
1166
1172
|
const env = deps.env ?? process.env
|
|
1167
1173
|
const nowMs = deps.nowMs ?? Date.now()
|
|
1174
|
+
const activityMtime = deps.newestActivityMtime ?? ((rt: string, c: string) => getAdapter(rt as Runtime).newestActivityMtime(c))
|
|
1175
|
+
const isAlive = deps.sessionAlive ?? sessionAlive
|
|
1168
1176
|
const verbose = superviseLogVerbose(env)
|
|
1169
1177
|
// tmux command-log volume cap (killer-hunt socket-command layer, launch/cmdlog.ts):
|
|
1170
1178
|
// a -v server log under a busy TUI grows ~7 KB/s, so each tick tail-keeps any log
|
|
@@ -1186,7 +1194,7 @@ export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): S
|
|
|
1186
1194
|
continue
|
|
1187
1195
|
}
|
|
1188
1196
|
const sock = buildSocketPath(s.runtime, s.personality, cfg.sockDir)
|
|
1189
|
-
if (!
|
|
1197
|
+
if (!isAlive(sock, s.identity)) {
|
|
1190
1198
|
// A DELIBERATE stop is not a death: the stop verb parks clean
|
|
1191
1199
|
// (.stopped + .idle-reaped) and kills the session itself. Catch-up branch for
|
|
1192
1200
|
// a stop that raced this tick (or a pre-fix stop): drop the state quietly —
|
|
@@ -1235,14 +1243,19 @@ export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): S
|
|
|
1235
1243
|
continue
|
|
1236
1244
|
}
|
|
1237
1245
|
// Idle accounting via the runtime adapter's activity proxy (claude transcript
|
|
1238
|
-
// / codex session mtime)
|
|
1239
|
-
//
|
|
1240
|
-
//
|
|
1246
|
+
// / codex session mtime), FLOORED at wokeAt: a session cannot be idle longer than
|
|
1247
|
+
// it has been awake. A wake that produced no model turn (e.g. an attach-only resume)
|
|
1248
|
+
// leaves the proxy at its PRE-wake value; the old `?? wokeAt` floored only the NULL
|
|
1249
|
+
// case, so a stale-but-non-null proxy (older than this wake) made a freshly-woken
|
|
1250
|
+
// session look idle for its entire prior life → idle-reaped on the very next tick,
|
|
1251
|
+
// out from under an attaching operator. max(), not ??, is the fix — and it still
|
|
1252
|
+
// reaps a session that produced zero further turns (mt=wokeAt → reaped idleSecs
|
|
1253
|
+
// after the wake), and falls back to wokeAt when no adapter exists for the runtime.
|
|
1241
1254
|
let mt = s.wokeAt
|
|
1242
1255
|
try {
|
|
1243
|
-
mt =
|
|
1256
|
+
mt = Math.max(activityMtime(s.runtime, s.cwd) ?? 0, s.wokeAt)
|
|
1244
1257
|
} catch {
|
|
1245
|
-
/* no adapter for this runtime yet → wokeAt
|
|
1258
|
+
/* no adapter for this runtime yet → wokeAt baseline */
|
|
1246
1259
|
}
|
|
1247
1260
|
const ageSecs = Math.floor((nowMs - mt) / 1000)
|
|
1248
1261
|
// wake_policy:ephemeral M2 — die-after-reply: an ARMED ephemeral session (the
|
package/src/supervisor/daemon.ts
CHANGED
|
@@ -163,6 +163,12 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
|
|
|
163
163
|
const clients = new Set<unknown>()
|
|
164
164
|
const writers = new WeakMap<object, BackpressureWriter>()
|
|
165
165
|
const framers = new WeakMap<object, (chunk: Uint8Array) => void>()
|
|
166
|
+
// FIRST-PAINT TRACKING (Bug B fix): a client gets the full scrollback dump EXACTLY ONCE — on its first
|
|
167
|
+
// FRAME_RESIZE (which runClient always sends on connect, at the real terminal size). Re-dumping
|
|
168
|
+
// scrollback on every attach/resize STACKS duplicate histories in a terminal that ignores \x1b[3J
|
|
169
|
+
// (Apple Terminal has NO E3 capability) → smeared/overlapping scroll-up history. Subsequent resizes
|
|
170
|
+
// refresh the VIEWPORT only (repaintViewport), never the scrollback.
|
|
171
|
+
const painted = new WeakSet<object>()
|
|
166
172
|
// ATTACHED-CLIENT MARKER (spawn-flip Ф0b-3 slice 3c): keep `<runDir>/<session>.attached` in sync with
|
|
167
173
|
// clients.size — present iff ≥1 operator is attached. The warm-deliver path (a DIFFERENT process)
|
|
168
174
|
// reads it to gate the hosted busy-composer hold: hold ONLY when a human is attached (their unfinished
|
|
@@ -253,6 +259,16 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
|
|
|
253
259
|
const snap = '\x1b[2J\x1b[3J\x1b[H' + stripSpinnerScrollback(ser.serialize(), xterm.rows)
|
|
254
260
|
sendClient(s, frame(FRAME_DATA, Buffer.from(snap, 'utf8')))
|
|
255
261
|
}
|
|
262
|
+
/** Refresh ONLY the visible viewport (current screen), never the scrollback — for a resize AFTER the
|
|
263
|
+
* first full paint. `\x1b[2J\x1b[H` clears the screen and homes WITHOUT touching scrollback (no
|
|
264
|
+
* \x1b[3J), then we write exactly the viewport rows (no scrollback segments), so nothing scrolls into
|
|
265
|
+
* the client's native scrollback. This is what keeps a mid-session resize from stacking another
|
|
266
|
+
* history copy in a terminal that ignores \x1b[3J (Bug B). */
|
|
267
|
+
const repaintViewport = (s: { write(b: Buffer): number }): void => {
|
|
268
|
+
const lines = ser.serialize().split('\r\n')
|
|
269
|
+
const viewport = lines.slice(Math.max(0, lines.length - xterm.rows)).join('\r\n')
|
|
270
|
+
sendClient(s, frame(FRAME_DATA, Buffer.from('\x1b[2J\x1b[H' + viewport, 'utf8')))
|
|
271
|
+
}
|
|
256
272
|
|
|
257
273
|
// Resize the model + child pty to (c, r). Shared by client FRAME_RESIZE and the last-detach restore.
|
|
258
274
|
const applyGeometry = (c: number, r: number): void => {
|
|
@@ -287,7 +303,12 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
|
|
|
287
303
|
} else if (type === FRAME_RESIZE) {
|
|
288
304
|
const sz = parseSize(payload)
|
|
289
305
|
applyGeometry(sz.cols, sz.rows)
|
|
290
|
-
|
|
306
|
+
if (!painted.has(s as object)) {
|
|
307
|
+
painted.add(s as object)
|
|
308
|
+
repaint(s) // FIRST paint for this client: full snapshot (scrollback + viewport) at its real size
|
|
309
|
+
} else {
|
|
310
|
+
repaintViewport(s) // later resize: refresh the screen only — never re-dump scrollback (anti-stack)
|
|
311
|
+
}
|
|
291
312
|
}
|
|
292
313
|
}
|
|
293
314
|
|
|
@@ -303,7 +324,11 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
|
|
|
303
324
|
framers.set(s as object, makeFramer((t, p) => onClientFrame(s, t, p)))
|
|
304
325
|
clients.add(s)
|
|
305
326
|
syncAttached() // ≥1 client now attached → marker present
|
|
306
|
-
repaint
|
|
327
|
+
// NO repaint here. A viewer's runClient sends its real terminal size as the FIRST FRAME_RESIZE on
|
|
328
|
+
// connect (client.ts open → "repaint at our size"); we paint THEN, ONCE, at the correct geometry.
|
|
329
|
+
// Painting here at the serve geometry (e.g. 220×50) would send a second, wrong-width dump that
|
|
330
|
+
// STACKS in a terminal ignoring \x1b[3J (Apple Terminal) → smeared scroll-up history (Bug B). A
|
|
331
|
+
// deliver client (deliverToHost) never resizes and ignores output → it gets no wasted paint.
|
|
307
332
|
},
|
|
308
333
|
data(s: object, d: Uint8Array) {
|
|
309
334
|
framers.get(s)?.(d)
|
package/src/transport/index.ts
CHANGED
|
@@ -494,7 +494,7 @@ export function deliverViaTmux(target: DeliveryTarget, envelope: string, cwd?: s
|
|
|
494
494
|
} while (monotonicMs() < graceDeadline)
|
|
495
495
|
return err(
|
|
496
496
|
`peer "${target.personality}" (${target.runtime}) is listed live but did not accept the message ` +
|
|
497
|
-
`(no input-clear, no transcript advance within ${livenessGraceMs()}ms) —
|
|
497
|
+
`(no input-clear, no transcript advance within ${livenessGraceMs()}ms) — live but unresponsive to this delivery (not dead); message NOT delivered`,
|
|
498
498
|
)
|
|
499
499
|
}
|
|
500
500
|
sleepSync(PASTE_SETTLE_MS)
|
|
@@ -579,7 +579,7 @@ async function deliverViaHost(
|
|
|
579
579
|
} while (monotonicMs() < graceDeadline)
|
|
580
580
|
return err(
|
|
581
581
|
`hosted peer "${target.personality}" (${target.runtime}) is listed live but did not accept the message ` +
|
|
582
|
-
`(no transcript advance within ${graceMs}ms) —
|
|
582
|
+
`(no transcript advance within ${graceMs}ms) — live but unresponsive to this delivery (not dead); message NOT delivered`,
|
|
583
583
|
)
|
|
584
584
|
}
|
|
585
585
|
|