@proagentstore/cli 0.4.23 → 0.4.24
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:
|
|
177
|
-
|
|
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 */
|