@proagentstore/cli 0.4.24 → 0.4.26

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.
@@ -42,6 +42,8 @@ export class HeadlessSession {
42
42
  sawOutputSinceInput = false;
43
43
  /** Raw-mode: when the current turn started (absolute idle backstop). */
44
44
  turnStartedAt = 0;
45
+ /** Set by stop() — the only thing that ends a one-shot session (see `alive`). */
46
+ stopped = false;
45
47
  constructor(config) {
46
48
  this.config = config;
47
49
  this.sessionName = `pags-${config.clientType}-${config.id}`;
@@ -60,13 +62,36 @@ export class HeadlessSession {
60
62
  this.cmdArgs = bin ? args : fallback.args;
61
63
  this.binName = (this.cmdBin.split("/").pop() || this.cmdBin) || "cli";
62
64
  }
63
- /** True while the agent process is running. NOTE: do NOT use `proc.killed` — Node sets
64
- * it true the instant a signal is DELIVERED, not when the process exits. interrupt()
65
- * SIGINTs to abort a turn while the process keeps running, so `killed` gave a false
66
- * "dead" → input() then spawned a SECOND process (orphaning the first). exitCode is
67
- * null while running and set on normal exit; signalCode is set when a signal actually
68
- * terminated it — together they're the real liveness signal. */
65
+ /**
66
+ * Can this session take a turn? — NOT "is a process executing right now".
67
+ *
68
+ * The distinction only appeared with one-shot engines. A persistent Claude session has a
69
+ * process alive between turns, so the two questions had the same answer and one getter served
70
+ * both. A one-shot engine has NO process between turns — that is its resting state — so the
71
+ * process-liveness answer is `false` for every question asked while the session sits idle,
72
+ * which is exactly when the Pilot asks.
73
+ *
74
+ * The cost of conflating them was total: `runCodingLoop` opens with
75
+ * `if (!snap.alive) return failed("coding session is not running")`, so every delegated goal
76
+ * on codex/grok/gemini/ollama died at iteration 0 having done nothing, reporting a dead
77
+ * session that was in fact healthy and answering interactive turns fine.
78
+ *
79
+ * So: a one-shot session is alive until `stop()`. Whether a turn is currently executing is
80
+ * `runState`, which is the question the loop actually has a use for.
81
+ *
82
+ * NOTE for the persistent path: do NOT use `proc.killed` — Node sets it true the instant a
83
+ * signal is DELIVERED, not when the process exits. interrupt() SIGINTs to abort a turn while
84
+ * the process keeps running, so `killed` gave a false "dead" → input() then spawned a SECOND
85
+ * process (orphaning the first). exitCode is null while running and set on normal exit;
86
+ * signalCode is set when a signal actually terminated it — together they're the real signal.
87
+ */
69
88
  get alive() {
89
+ if (this.oneShot)
90
+ return !this.stopped;
91
+ return this.procAlive;
92
+ }
93
+ /** Is a process running THIS instant? The persistent engine's liveness, and the spawn guard. */
94
+ get procAlive() {
70
95
  return this.proc !== null && this.proc.exitCode === null && this.proc.signalCode === null;
71
96
  }
72
97
  /** Idle = ready for the next instruction. */
@@ -125,13 +150,16 @@ export class HeadlessSession {
125
150
  return this.mode === "raw";
126
151
  }
127
152
  start() {
153
+ // Starting always un-stops: `stop()` is what ends a one-shot session, so a (re)start
154
+ // after one has to bring it back or the session is permanently unusable.
155
+ this.stopped = false;
128
156
  // A one-shot engine has nothing to start until there is a turn to run; starting it here
129
157
  // is what produced the instant "exited with code 1".
130
158
  if (this.oneShot) {
131
159
  this.run = "idle";
132
160
  return;
133
161
  }
134
- if (this.alive)
162
+ if (this.procAlive)
135
163
  return;
136
164
  // stream-json: we own the structural flags + --resume; merge the user's extras
137
165
  // (e.g. --model) without letting them clobber or orphan-value our flags. raw:
@@ -262,7 +290,7 @@ export class HeadlessSession {
262
290
  this.run = "idle";
263
291
  this.lastOutputAt = Date.now();
264
292
  }
265
- /** Tear the process down. */
293
+ /** Tear the process down. For a one-shot engine this is the ONLY thing that ends the session. */
266
294
  stop() {
267
295
  try {
268
296
  this.proc?.kill();
@@ -272,6 +300,7 @@ export class HeadlessSession {
272
300
  }
273
301
  this.proc = null;
274
302
  this.run = "idle";
303
+ this.stopped = true;
275
304
  }
276
305
  // ── internals ────────────────────────────────────────────────────────────
277
306
  onStdout(chunk) {
@@ -362,14 +391,23 @@ export class HeadlessSession {
362
391
  function stamp() {
363
392
  return new Date().toTimeString().slice(0, 8);
364
393
  }
365
- /** Split a launch command into bin + args, respecting single/double quotes. */
394
+ /**
395
+ * Split a launch command into bin + args, respecting single/double quotes — the way a shell
396
+ * would, because that is what a user editing an engine preset is writing.
397
+ *
398
+ * Quotes are stripped wherever they appear in a token, not only when they wrap the whole thing.
399
+ * The earlier alternation (`"…" | '…' | \S+`) only recognised a fully-quoted token, so
400
+ * `-c model="o3"` reached the engine as the literal `model="o3"` and
401
+ * `--append-system-prompt="be terse"` split at the space. A preset is free text; it has to
402
+ * tokenize like the command line it looks like.
403
+ */
366
404
  export function parseCommand(command) {
367
405
  const tokens = [];
368
- const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
369
- let m;
370
- // biome-ignore lint/suspicious/noAssignInExpressions: tokenizer drain
371
- while ((m = re.exec(command ?? "")) !== null)
372
- tokens.push(m[1] ?? m[2] ?? m[3] ?? "");
406
+ // One token = a run of unquoted chars and/or quoted spans, glued together (`"a b"c` → `a bc`).
407
+ const re = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g;
408
+ for (const raw of (command ?? "").match(re) ?? []) {
409
+ tokens.push(raw.replace(/"([^"]*)"|'([^']*)'/g, (_m, d, s) => d ?? s ?? ""));
410
+ }
373
411
  return { bin: tokens[0] ?? "", args: tokens.slice(1) };
374
412
  }
375
413
  /** Structural flags PAGS owns for the Claude stream-json engine — a user command
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",