@proagentstore/cli 0.4.23 → 0.4.25

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.
@@ -106,7 +106,31 @@ export class HeadlessSession {
106
106
  return this.transcript.join("\n");
107
107
  }
108
108
  /** Launch (or resume) the agent process. Idempotent if already alive. */
109
+ /**
110
+ * Raw engines run ONE-SHOT PER TURN, not as a persistent interactive process.
111
+ *
112
+ * The persistent design assumed an interactive CLI reading stdin — which is what tmux used to
113
+ * provide, via a PTY. Without one, `codex` (and grok, and any TUI) dies immediately with
114
+ * "stdin is not a terminal", so three of the four engines were simply broken.
115
+ *
116
+ * AgentCoder solved this before PAGS existed and ran fine on it: `claude -p '<instruction>'`
117
+ * per turn, plain exec, no PTY and no tmux. Codex's exact analogue is `codex exec`. So the
118
+ * process is spawned when a turn ARRIVES and exits when the turn ends, rather than being
119
+ * kept alive for stdin writes that a non-interactive binary will never read.
120
+ *
121
+ * Claude keeps its persistent stream-json process — it is explicitly non-interactive and
122
+ * multi-turn, which is why it survived the migration untouched.
123
+ */
124
+ get oneShot() {
125
+ return this.mode === "raw";
126
+ }
109
127
  start() {
128
+ // A one-shot engine has nothing to start until there is a turn to run; starting it here
129
+ // is what produced the instant "exited with code 1".
130
+ if (this.oneShot) {
131
+ this.run = "idle";
132
+ return;
133
+ }
110
134
  if (this.alive)
111
135
  return;
112
136
  // stream-json: we own the structural flags + --resume; merge the user's extras
@@ -173,14 +197,56 @@ export class HeadlessSession {
173
197
  this.proc?.stdin?.write(`${msg}\n`);
174
198
  }
175
199
  else {
176
- // Raw CLI: write the line to stdin as if typed.
177
- this.proc?.stdin?.write(`${text}\n`);
200
+ // Raw CLI: spawn THIS turn. See `oneShot` — there is no persistent process to
201
+ // write to, because a non-interactive binary would never have read it.
202
+ this.runOneShot(text);
178
203
  }
179
204
  }
180
205
  catch {
181
206
  /* process may have died; the next snapshot reports not-alive */
182
207
  }
183
208
  }
209
+ /**
210
+ * Run ONE turn as its own process, the way AgentCoder did (`claude -p '<instruction>'`).
211
+ *
212
+ * Contract, deliberately general so it is not a Codex special case: **the preset command is a
213
+ * prefix and the turn text is appended as the final argument.** That makes every prompt-in /
214
+ * text-out CLI usable by configuration alone —
215
+ *
216
+ * codex exec → codex exec "<turn>"
217
+ * claude -p → claude -p "<turn>"
218
+ * ollama run llama3 → ollama run llama3 "<turn>"
219
+ * my-model --flag → my-model --flag "<turn>"
220
+ *
221
+ * — including a local model, with no platform change and no cloud key. Whoever configures the
222
+ * preset decides what runs and what it costs; the platform does not need to know the engine.
223
+ */
224
+ runOneShot(text) {
225
+ const proc = spawn(this.cmdBin, [...this.cmdArgs, text], {
226
+ cwd: this.config.workDir,
227
+ env: mergeEnv(process.env, this.config.env),
228
+ stdio: ["ignore", "pipe", "pipe"],
229
+ });
230
+ this.proc = proc;
231
+ // Without an 'error' listener a spawn failure (binary not on PATH) is an uncaught
232
+ // exception that takes the whole runner down, not just this session.
233
+ proc.on("error", (err) => {
234
+ this.push(`[${this.config.clientType}] failed to start: ${err.message}`);
235
+ this.run = "idle";
236
+ this.proc = null;
237
+ });
238
+ proc.stdout?.on("data", (d) => this.onStdout(d.toString()));
239
+ proc.stderr?.on("data", (d) => this.onStdout(d.toString()));
240
+ proc.on("close", (code) => {
241
+ // A non-zero exit is the engine's own failure (bad flags, not signed in) and the
242
+ // operator needs to see it — silently going idle is how "stdin is not a terminal"
243
+ // looked like an idle session for a whole afternoon.
244
+ if (code)
245
+ this.push(`[${this.config.clientType} exited with code ${code}]`);
246
+ this.run = "idle";
247
+ this.proc = null;
248
+ });
249
+ }
184
250
  /** No TTY in headless mode; control is via messages. Kept for interface parity. */
185
251
  key(_keys) {
186
252
  /* intentionally a no-op — there are no raw keystrokes without a terminal */
@@ -296,14 +362,23 @@ export class HeadlessSession {
296
362
  function stamp() {
297
363
  return new Date().toTimeString().slice(0, 8);
298
364
  }
299
- /** Split a launch command into bin + args, respecting single/double quotes. */
365
+ /**
366
+ * Split a launch command into bin + args, respecting single/double quotes — the way a shell
367
+ * would, because that is what a user editing an engine preset is writing.
368
+ *
369
+ * Quotes are stripped wherever they appear in a token, not only when they wrap the whole thing.
370
+ * The earlier alternation (`"…" | '…' | \S+`) only recognised a fully-quoted token, so
371
+ * `-c model="o3"` reached the engine as the literal `model="o3"` and
372
+ * `--append-system-prompt="be terse"` split at the space. A preset is free text; it has to
373
+ * tokenize like the command line it looks like.
374
+ */
300
375
  export function parseCommand(command) {
301
376
  const tokens = [];
302
- const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
303
- let m;
304
- // biome-ignore lint/suspicious/noAssignInExpressions: tokenizer drain
305
- while ((m = re.exec(command ?? "")) !== null)
306
- tokens.push(m[1] ?? m[2] ?? m[3] ?? "");
377
+ // One token = a run of unquoted chars and/or quoted spans, glued together (`"a b"c` → `a bc`).
378
+ const re = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g;
379
+ for (const raw of (command ?? "").match(re) ?? []) {
380
+ tokens.push(raw.replace(/"([^"]*)"|'([^']*)'/g, (_m, d, s) => d ?? s ?? ""));
381
+ }
307
382
  return { bin: tokens[0] ?? "", args: tokens.slice(1) };
308
383
  }
309
384
  /** 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.23",
3
+ "version": "0.4.25",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",