@proagentstore/cli 0.4.26 → 0.4.27
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.
|
@@ -255,10 +255,24 @@ export class HeadlessSession {
|
|
|
255
255
|
env: mergeEnv(process.env, this.config.env),
|
|
256
256
|
stdio: ["ignore", "pipe", "pipe"],
|
|
257
257
|
});
|
|
258
|
+
// A turn already running is aborted before its replacement starts. `input()` never killed
|
|
259
|
+
// the previous process, and the raw idle heuristic declares idle after a >1.5s output
|
|
260
|
+
// pause — so a long build could be judged idle, the brain sends turn 2, and TWO engine
|
|
261
|
+
// processes edit the same repo at once.
|
|
262
|
+
if (this.procAlive) {
|
|
263
|
+
try {
|
|
264
|
+
this.proc?.kill();
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
/* already gone */
|
|
268
|
+
}
|
|
269
|
+
}
|
|
258
270
|
this.proc = proc;
|
|
259
271
|
// Without an 'error' listener a spawn failure (binary not on PATH) is an uncaught
|
|
260
272
|
// exception that takes the whole runner down, not just this session.
|
|
261
273
|
proc.on("error", (err) => {
|
|
274
|
+
if (this.proc !== proc)
|
|
275
|
+
return; // stale: a newer turn owns the session now
|
|
262
276
|
this.push(`[${this.config.clientType}] failed to start: ${err.message}`);
|
|
263
277
|
this.run = "idle";
|
|
264
278
|
this.proc = null;
|
|
@@ -271,13 +285,29 @@ export class HeadlessSession {
|
|
|
271
285
|
// looked like an idle session for a whole afternoon.
|
|
272
286
|
if (code)
|
|
273
287
|
this.push(`[${this.config.clientType} exited with code ${code}]`);
|
|
288
|
+
// STALENESS GUARD, matching the persistent path's. Without it, an older turn's
|
|
289
|
+
// `close` clobbered the newer one: `run = "idle"` while process B was still working
|
|
290
|
+
// (so the brain acted on a half-done turn and could double-send), and `proc = null`
|
|
291
|
+
// so stop()/interrupt()/end() could no longer kill B — ending the session left a
|
|
292
|
+
// codex process still editing the repo, invisible to `alive`, diagnostics and
|
|
293
|
+
// kill-tmux.
|
|
294
|
+
if (this.proc !== proc)
|
|
295
|
+
return;
|
|
274
296
|
this.run = "idle";
|
|
275
297
|
this.proc = null;
|
|
276
298
|
});
|
|
277
299
|
}
|
|
278
|
-
/**
|
|
279
|
-
|
|
280
|
-
|
|
300
|
+
/**
|
|
301
|
+
* No TTY in headless mode; control is via messages. Kept for interface parity — the human
|
|
302
|
+
* takeover path can still route a keypress here.
|
|
303
|
+
*
|
|
304
|
+
* RECORDED rather than silently dropped. As a pure no-op it was indistinguishable from
|
|
305
|
+
* success: `act` returned an ordinary snapshot with an unchanged pane, so the caller could not
|
|
306
|
+
* tell "sent, nothing happened" from "never sent". The transcript is what the brain and the
|
|
307
|
+
* console both read, so the truth belongs there.
|
|
308
|
+
*/
|
|
309
|
+
key(keys) {
|
|
310
|
+
this.push(`[ignored keypress ${keys.slice(0, 40)} — this session has no terminal attached]`);
|
|
281
311
|
}
|
|
282
312
|
/** Abort the current turn (SIGINT, like Ctrl-C). The process stays usable. */
|
|
283
313
|
interrupt() {
|