@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
- /** No TTY in headless mode; control is via messages. Kept for interface parity. */
279
- key(_keys) {
280
- /* intentionally a no-op — there are no raw keystrokes without a terminal */
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.26",
3
+ "version": "0.4.27",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",