@ag-eco/agentplate-cli 0.15.0 → 0.15.2
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.
- package/package.json +1 -1
- package/src/runtimes/codex.test.ts +74 -35
- package/src/runtimes/codex.ts +36 -7
- package/src/runtimes/opencode.test.ts +25 -9
- package/src/runtimes/opencode.ts +25 -16
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ag-eco/agentplate-cli",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "Multi-agent orchestration for AI coding agents — spawn workers in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution. Pluggable runtime adapters for Claude Code, Pi, and more.",
|
|
5
5
|
"author": "Jaymin West",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,7 @@ describe("CodexRuntime", () => {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
describe("buildSpawnCommand", () => {
|
|
23
|
-
test("basic command uses interactive codex with
|
|
23
|
+
test("basic command uses interactive codex with workspace-write sandbox + no approvals", () => {
|
|
24
24
|
const opts: SpawnOpts = {
|
|
25
25
|
model: "gpt-5-codex",
|
|
26
26
|
permissionMode: "bypass",
|
|
@@ -28,7 +28,7 @@ describe("CodexRuntime", () => {
|
|
|
28
28
|
env: {},
|
|
29
29
|
};
|
|
30
30
|
const cmd = runtime.buildSpawnCommand(opts);
|
|
31
|
-
expect(cmd).toContain("codex --
|
|
31
|
+
expect(cmd).toContain("codex --sandbox workspace-write --ask-for-approval never");
|
|
32
32
|
expect(cmd).toContain("--model gpt-5-codex");
|
|
33
33
|
expect(cmd).toContain("Read AGENTS.md");
|
|
34
34
|
});
|
|
@@ -42,7 +42,7 @@ describe("CodexRuntime", () => {
|
|
|
42
42
|
env: {},
|
|
43
43
|
};
|
|
44
44
|
const cmd = runtime.buildSpawnCommand(opts);
|
|
45
|
-
expect(cmd).toContain("codex --
|
|
45
|
+
expect(cmd).toContain("codex --sandbox workspace-write --ask-for-approval never");
|
|
46
46
|
expect(cmd).not.toContain(" --model ");
|
|
47
47
|
}
|
|
48
48
|
});
|
|
@@ -173,7 +173,7 @@ describe("CodexRuntime", () => {
|
|
|
173
173
|
};
|
|
174
174
|
const cmd = runtime.buildSpawnCommand(opts);
|
|
175
175
|
expect(cmd).toBe(
|
|
176
|
-
"codex --
|
|
176
|
+
"codex --sandbox workspace-write --ask-for-approval never --model gpt-5-codex 'Read AGENTS.md for your task assignment and begin immediately.'",
|
|
177
177
|
);
|
|
178
178
|
});
|
|
179
179
|
|
|
@@ -256,7 +256,14 @@ describe("CodexRuntime", () => {
|
|
|
256
256
|
describe("buildPrintCommand", () => {
|
|
257
257
|
test("basic command without model", () => {
|
|
258
258
|
const argv = runtime.buildPrintCommand("Summarize this diff");
|
|
259
|
-
expect(argv).toEqual([
|
|
259
|
+
expect(argv).toEqual([
|
|
260
|
+
"codex",
|
|
261
|
+
"exec",
|
|
262
|
+
"--sandbox",
|
|
263
|
+
"workspace-write",
|
|
264
|
+
"--ephemeral",
|
|
265
|
+
"Summarize this diff",
|
|
266
|
+
]);
|
|
260
267
|
});
|
|
261
268
|
|
|
262
269
|
test("command with model override", () => {
|
|
@@ -264,7 +271,8 @@ describe("CodexRuntime", () => {
|
|
|
264
271
|
expect(argv).toEqual([
|
|
265
272
|
"codex",
|
|
266
273
|
"exec",
|
|
267
|
-
"--
|
|
274
|
+
"--sandbox",
|
|
275
|
+
"workspace-write",
|
|
268
276
|
"--ephemeral",
|
|
269
277
|
"--model",
|
|
270
278
|
"gpt-5-codex",
|
|
@@ -277,7 +285,8 @@ describe("CodexRuntime", () => {
|
|
|
277
285
|
expect(argv).toEqual([
|
|
278
286
|
"codex",
|
|
279
287
|
"exec",
|
|
280
|
-
"--
|
|
288
|
+
"--sandbox",
|
|
289
|
+
"workspace-write",
|
|
281
290
|
"--ephemeral",
|
|
282
291
|
"--model",
|
|
283
292
|
"gpt-5.4",
|
|
@@ -296,14 +305,14 @@ describe("CodexRuntime", () => {
|
|
|
296
305
|
expect(argv[argv.length - 1]).toBe(prompt);
|
|
297
306
|
});
|
|
298
307
|
|
|
299
|
-
test("without model, argv has exactly
|
|
308
|
+
test("without model, argv has exactly 6 elements", () => {
|
|
300
309
|
const argv = runtime.buildPrintCommand("prompt text");
|
|
301
|
-
expect(argv.length).toBe(
|
|
310
|
+
expect(argv.length).toBe(6);
|
|
302
311
|
});
|
|
303
312
|
|
|
304
|
-
test("with model, argv has exactly
|
|
313
|
+
test("with model, argv has exactly 8 elements", () => {
|
|
305
314
|
const argv = runtime.buildPrintCommand("prompt text", "gpt-5-codex");
|
|
306
|
-
expect(argv.length).toBe(
|
|
315
|
+
expect(argv.length).toBe(8);
|
|
307
316
|
});
|
|
308
317
|
|
|
309
318
|
test("does not include --json (print expects plain text stdout)", () => {
|
|
@@ -318,27 +327,53 @@ describe("CodexRuntime", () => {
|
|
|
318
327
|
});
|
|
319
328
|
|
|
320
329
|
describe("detectReady", () => {
|
|
321
|
-
test("returns
|
|
322
|
-
|
|
323
|
-
expect(
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("returns ready
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
330
|
+
test("returns loading for empty / pre-header pane", () => {
|
|
331
|
+
expect(runtime.detectReady("")).toEqual({ phase: "loading" });
|
|
332
|
+
expect(runtime.detectReady("Loading Codex...\nPlease wait")).toEqual({ phase: "loading" });
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("returns ready once the Codex header box renders", () => {
|
|
336
|
+
const pane = [
|
|
337
|
+
"╭──────────────────────────────────────╮",
|
|
338
|
+
"│ >_ OpenAI Codex (v0.128.0) │",
|
|
339
|
+
"│ model: gpt-5.5 high /model to change│",
|
|
340
|
+
"╰──────────────────────────────────────╯",
|
|
341
|
+
"› Explain this codebase",
|
|
342
|
+
].join("\n");
|
|
343
|
+
expect(runtime.detectReady(pane)).toEqual({ phase: "ready" });
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("skips the blocking 'Update available' modal via type:2", () => {
|
|
347
|
+
const pane = [
|
|
348
|
+
"✨ Update available! 0.128.0 -> 0.131.0",
|
|
349
|
+
"› 1. Update now (runs `npm install -g @openai/codex`)",
|
|
350
|
+
" 2. Skip",
|
|
351
|
+
" 3. Skip until next version",
|
|
352
|
+
" Press enter to continue",
|
|
353
|
+
].join("\n");
|
|
354
|
+
expect(runtime.detectReady(pane)).toEqual({ phase: "dialog", action: "type:2" });
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("passive post-skip update banner is not treated as a modal", () => {
|
|
358
|
+
// The non-blocking banner says "Run npm install" and has no numbered options.
|
|
359
|
+
const pane = [
|
|
360
|
+
"╭───────────────────────────────────────────╮",
|
|
361
|
+
"│ ✨ Update available! 0.128.0 -> 0.135.0 │",
|
|
362
|
+
"│ Run npm install -g @openai/codex to update. │",
|
|
363
|
+
"╰───────────────────────────────────────────╯",
|
|
364
|
+
"│ >_ OpenAI Codex (v0.128.0) │",
|
|
365
|
+
].join("\n");
|
|
366
|
+
expect(runtime.detectReady(pane)).toEqual({ phase: "ready" });
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("confirms the trust-directory prompt via retryable type:1", () => {
|
|
370
|
+
const pane = [
|
|
371
|
+
"Do you trust the contents of this directory?",
|
|
372
|
+
"› 1. Yes, continue",
|
|
373
|
+
" 2. No, quit",
|
|
374
|
+
" Press enter to continue",
|
|
375
|
+
].join("\n");
|
|
376
|
+
expect(runtime.detectReady(pane)).toEqual({ phase: "dialog", action: "type:1" });
|
|
342
377
|
});
|
|
343
378
|
});
|
|
344
379
|
|
|
@@ -728,7 +763,7 @@ describe("CodexRuntime integration: spawn command structure", () => {
|
|
|
728
763
|
env: { AGENTPLATE_AGENT_NAME: "builder-1" },
|
|
729
764
|
});
|
|
730
765
|
expect(cmd).toBe(
|
|
731
|
-
"codex --
|
|
766
|
+
"codex --sandbox workspace-write --ask-for-approval never --model gpt-5-codex 'Read AGENTS.md for your task assignment and begin immediately.'",
|
|
732
767
|
);
|
|
733
768
|
});
|
|
734
769
|
|
|
@@ -741,7 +776,9 @@ describe("CodexRuntime integration: spawn command structure", () => {
|
|
|
741
776
|
appendSystemPrompt: baseDefinition,
|
|
742
777
|
env: { AGENTPLATE_AGENT_NAME: "coordinator" },
|
|
743
778
|
});
|
|
744
|
-
expect(cmd).toContain(
|
|
779
|
+
expect(cmd).toContain(
|
|
780
|
+
"codex --sandbox workspace-write --ask-for-approval never --model gpt-5-codex",
|
|
781
|
+
);
|
|
745
782
|
expect(cmd).toContain("# Coordinator");
|
|
746
783
|
expect(cmd).toContain("You are the coordinator agent.");
|
|
747
784
|
expect(cmd).toContain("Read AGENTS.md");
|
|
@@ -755,7 +792,9 @@ describe("CodexRuntime integration: spawn command structure", () => {
|
|
|
755
792
|
appendSystemPromptFile: "/project/.agentplate/agent-defs/coordinator.md",
|
|
756
793
|
env: { AGENTPLATE_AGENT_NAME: "coordinator" },
|
|
757
794
|
});
|
|
758
|
-
expect(cmd).toContain(
|
|
795
|
+
expect(cmd).toContain(
|
|
796
|
+
"codex --sandbox workspace-write --ask-for-approval never --model gpt-5-codex",
|
|
797
|
+
);
|
|
759
798
|
expect(cmd).toContain("$(cat '/project/.agentplate/agent-defs/coordinator.md')");
|
|
760
799
|
expect(cmd).toContain("Read AGENTS.md");
|
|
761
800
|
});
|
package/src/runtimes/codex.ts
CHANGED
|
@@ -95,7 +95,10 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
95
95
|
const bareModel = CodexRuntime.stripProviderPrefix(opts.model);
|
|
96
96
|
// When model comes from default manifest aliases (sonnet/opus/haiku),
|
|
97
97
|
// omit --model so Codex uses the user's configured default model.
|
|
98
|
-
|
|
98
|
+
// `--full-auto` was removed in modern codex-cli; the equivalent (write to
|
|
99
|
+
// the workspace without per-command approval prompts) is
|
|
100
|
+
// `--sandbox workspace-write --ask-for-approval never`.
|
|
101
|
+
let cmd = "codex --sandbox workspace-write --ask-for-approval never";
|
|
99
102
|
if (!CodexRuntime.MANIFEST_ALIASES.has(bareModel)) {
|
|
100
103
|
cmd += ` --model ${bareModel}`;
|
|
101
104
|
}
|
|
@@ -135,7 +138,9 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
135
138
|
* @returns Argv array for Bun.spawn
|
|
136
139
|
*/
|
|
137
140
|
buildPrintCommand(prompt: string, model?: string): string[] {
|
|
138
|
-
|
|
141
|
+
// `--full-auto` is deprecated in `codex exec` (still works, warns); the
|
|
142
|
+
// non-deprecated form is `--sandbox workspace-write`.
|
|
143
|
+
const cmd = ["codex", "exec", "--sandbox", "workspace-write", "--ephemeral"];
|
|
139
144
|
if (model !== undefined) {
|
|
140
145
|
// Strip provider prefix — Codex CLI expects bare model names.
|
|
141
146
|
cmd.push("--model", CodexRuntime.stripProviderPrefix(model));
|
|
@@ -174,13 +179,37 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
174
179
|
}
|
|
175
180
|
|
|
176
181
|
/**
|
|
177
|
-
* Codex
|
|
182
|
+
* Detect Codex TUI readiness from a tmux pane content snapshot.
|
|
178
183
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
184
|
+
* Observed against codex-cli 0.128.x, the interactive TUI can show up to two
|
|
185
|
+
* blocking dialogs before it is usable, which we clear via dialog actions:
|
|
186
|
+
* - "Update available" modal (outdated install): "1. Update now / 2. Skip …".
|
|
187
|
+
* Selected via `type:2` (Skip). Distinguished from the passive post-skip
|
|
188
|
+
* "Run npm install …" banner by the "Update now" option text.
|
|
189
|
+
* - "Do you trust the contents of this directory?" prompt: confirm the default
|
|
190
|
+
* "Yes, continue" with Enter.
|
|
191
|
+
* Ready once the Codex header box ("OpenAI Codex") renders.
|
|
192
|
+
*
|
|
193
|
+
* @param paneContent - Captured tmux pane content
|
|
194
|
+
* @returns Current readiness phase
|
|
181
195
|
*/
|
|
182
|
-
detectReady(
|
|
183
|
-
|
|
196
|
+
detectReady(paneContent: string): ReadyState {
|
|
197
|
+
// Blocking update modal (numbered options) — skip it. The passive banner
|
|
198
|
+
// shown afterwards says "Run npm install" and lacks the "Update now" option.
|
|
199
|
+
if (paneContent.includes("Update now") && paneContent.includes("Skip")) {
|
|
200
|
+
return { phase: "dialog", action: "type:2" };
|
|
201
|
+
}
|
|
202
|
+
// Trust-directory prompt → select "1. Yes, continue". Use a `type:` action
|
|
203
|
+
// (not bare Enter) so the readiness loop re-fires it until the dialog clears
|
|
204
|
+
// — non-type dialog actions are sent only once (see waitForTuiReady).
|
|
205
|
+
if (paneContent.includes("Do you trust")) {
|
|
206
|
+
return { phase: "dialog", action: "type:1" };
|
|
207
|
+
}
|
|
208
|
+
// Ready: the Codex header box has rendered.
|
|
209
|
+
if (paneContent.includes("OpenAI Codex")) {
|
|
210
|
+
return { phase: "ready" };
|
|
211
|
+
}
|
|
212
|
+
return { phase: "loading" };
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
/**
|
|
@@ -143,20 +143,36 @@ describe("OpenCodeRuntime", () => {
|
|
|
143
143
|
});
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
describe("detectReady
|
|
147
|
-
|
|
146
|
+
describe("detectReady", () => {
|
|
147
|
+
// Snapshot of the opencode 1.14.x ready TUI (input box + status bar).
|
|
148
|
+
const readyPane = [
|
|
149
|
+
'┃ Ask anything... "What is the tech stack of this project?"',
|
|
150
|
+
"┃ Build · Big Pickle OpenCode Zen",
|
|
151
|
+
" tab agents ctrl+p commands",
|
|
152
|
+
" /tmp/project:main 1.14.39",
|
|
153
|
+
].join("\n");
|
|
154
|
+
|
|
155
|
+
// Same TUI with the "Update Available" modal overlaid (outdated install).
|
|
156
|
+
const modalPane = [
|
|
157
|
+
" Update Available esc",
|
|
158
|
+
" A new release v1.15.12 is available. Would you like to",
|
|
159
|
+
" update now?",
|
|
160
|
+
" Skip Confirm",
|
|
161
|
+
'┃ Ask anything... "What is the tech stack of this project?"',
|
|
162
|
+
" tab agents ctrl+p commands",
|
|
163
|
+
].join("\n");
|
|
164
|
+
|
|
165
|
+
test("returns loading for empty / partial pane", () => {
|
|
148
166
|
expect(runtime.detectReady("")).toEqual({ phase: "loading" });
|
|
167
|
+
expect(runtime.detectReady("starting opencode...")).toEqual({ phase: "loading" });
|
|
149
168
|
});
|
|
150
169
|
|
|
151
|
-
test("returns
|
|
152
|
-
expect(runtime.detectReady(
|
|
153
|
-
expect(runtime.detectReady("ready")).toEqual({ phase: "loading" });
|
|
154
|
-
expect(runtime.detectReady("opencode started")).toEqual({ phase: "loading" });
|
|
170
|
+
test("returns ready once the input box + status bar render", () => {
|
|
171
|
+
expect(runtime.detectReady(readyPane)).toEqual({ phase: "ready" });
|
|
155
172
|
});
|
|
156
173
|
|
|
157
|
-
test("
|
|
158
|
-
|
|
159
|
-
expect(state.phase).not.toBe("dialog");
|
|
174
|
+
test("surfaces the Update Available modal as an Escape dialog (checked before ready)", () => {
|
|
175
|
+
expect(runtime.detectReady(modalPane)).toEqual({ phase: "dialog", action: "Escape" });
|
|
160
176
|
});
|
|
161
177
|
});
|
|
162
178
|
|
package/src/runtimes/opencode.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// - Uses `opencode` CLI for interactive sessions
|
|
6
6
|
// - Instruction file: AGENTS.md (unverified — needs confirmation against real OpenCode install)
|
|
7
7
|
// - No hooks: OpenCode does not support Claude Code's hook mechanism
|
|
8
|
-
// - detectReady
|
|
8
|
+
// - detectReady: implemented from observed opencode 1.14.x TUI strings
|
|
9
9
|
// - parseTranscript returns null: output format not yet verified
|
|
10
10
|
|
|
11
11
|
import { mkdir } from "node:fs/promises";
|
|
@@ -28,14 +28,13 @@ import type {
|
|
|
28
28
|
* - Uses `--model` flag for model selection
|
|
29
29
|
* - Instruction file lives at `AGENTS.md` (unverified — confirm against real install)
|
|
30
30
|
* - No hooks deployment (OpenCode has no Claude Code hook mechanism)
|
|
31
|
-
* - `detectReady`
|
|
31
|
+
* - `detectReady` — implemented from observed opencode 1.14.x TUI strings
|
|
32
32
|
* - `parseTranscript` returns null — `opencode` output format not yet verified
|
|
33
33
|
*
|
|
34
|
-
* TODO: Once
|
|
34
|
+
* TODO: Once deeper OpenCode integration is needed:
|
|
35
35
|
* 1. Verify `instructionPath` — run `opencode` and check which file it reads
|
|
36
|
-
* 2. Fill in `
|
|
37
|
-
* 3. Fill in `
|
|
38
|
-
* 4. Fill in `getTranscriptDir` — check where OpenCode writes session files
|
|
36
|
+
* 2. Fill in `parseTranscript` — run `opencode run --format json` and inspect output
|
|
37
|
+
* 3. Fill in `getTranscriptDir` — check where OpenCode writes session files
|
|
39
38
|
*/
|
|
40
39
|
export class OpenCodeRuntime implements AgentRuntime {
|
|
41
40
|
/** Unique identifier for this runtime. */
|
|
@@ -126,18 +125,28 @@ export class OpenCodeRuntime implements AgentRuntime {
|
|
|
126
125
|
/**
|
|
127
126
|
* Detect OpenCode TUI readiness from a tmux pane content snapshot.
|
|
128
127
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
128
|
+
* Observed against opencode 1.14.x in tmux:
|
|
129
|
+
* - Ready TUI: the input placeholder "Ask anything..." plus a status bar with
|
|
130
|
+
* command hints ("ctrl+p commands" / "tab agents").
|
|
131
|
+
* - On outdated installs, an "Update Available" modal blocks the TUI until
|
|
132
|
+
* dismissed; we surface it as a dialog so the readiness loop sends Escape
|
|
133
|
+
* (the modal's documented dismiss key) and then re-checks for the ready TUI.
|
|
134
134
|
*
|
|
135
|
-
* @param
|
|
136
|
-
* @returns
|
|
135
|
+
* @param paneContent - Captured tmux pane content
|
|
136
|
+
* @returns Current readiness phase
|
|
137
137
|
*/
|
|
138
|
-
detectReady(
|
|
139
|
-
//
|
|
140
|
-
//
|
|
138
|
+
detectReady(paneContent: string): ReadyState {
|
|
139
|
+
// "Update Available" modal (shown on outdated opencode) intercepts input —
|
|
140
|
+
// dismiss it with Escape before treating the TUI as ready. Checked first
|
|
141
|
+
// because the ready TUI renders behind the modal.
|
|
142
|
+
if (paneContent.includes("Update Available")) {
|
|
143
|
+
return { phase: "dialog", action: "Escape" };
|
|
144
|
+
}
|
|
145
|
+
const hasPrompt = paneContent.includes("Ask anything");
|
|
146
|
+
const hasStatusBar = paneContent.includes("ctrl+p") || paneContent.includes("tab agents");
|
|
147
|
+
if (hasPrompt && hasStatusBar) {
|
|
148
|
+
return { phase: "ready" };
|
|
149
|
+
}
|
|
141
150
|
return { phase: "loading" };
|
|
142
151
|
}
|
|
143
152
|
|
package/src/version.ts
CHANGED