@ai-setting/roy-task-plugin 0.4.2 → 0.5.0

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/dist/cli.js CHANGED
@@ -3,16 +3,432 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // ../../../packages/cli/src/daemon/runners/types.ts
6
- var RUNTIMES = Object.freeze(["opencode", "claude"]);
6
+ var RUNTIMES = Object.freeze(["opencode", "claude", "codex"]);
7
7
  var DEFAULT_RUNTIME = "opencode";
8
8
  var DEFAULT_RUNTIME_BIN = Object.freeze({
9
9
  opencode: "opencode",
10
- claude: "claude"
10
+ claude: "claude",
11
+ codex: "codex"
11
12
  });
12
13
  function isRuntimeName(value) {
13
14
  return RUNTIMES.includes(value);
14
15
  }
15
16
 
17
+ // ../../../packages/cli/src/runtime/spawn.ts
18
+ import { spawn } from "node:child_process";
19
+ function runCapture(cmd, opts = {}) {
20
+ return new Promise((resolve, reject) => {
21
+ let child;
22
+ try {
23
+ child = spawn(cmd[0], cmd.slice(1), {
24
+ stdio: [assertEofStdin(opts.stdin), "pipe", "pipe"],
25
+ env: opts.env ? normalizeEnv(opts.env) : process.env,
26
+ cwd: opts.cwd,
27
+ windowsHide: true
28
+ });
29
+ } catch (err) {
30
+ reject(err);
31
+ return;
32
+ }
33
+ let stdout = "";
34
+ let stderr = "";
35
+ let timedOut = false;
36
+ child.stdout?.setEncoding("utf8");
37
+ child.stderr?.setEncoding("utf8");
38
+ child.stdout?.on("data", (chunk) => {
39
+ stdout += chunk;
40
+ });
41
+ child.stderr?.on("data", (chunk) => {
42
+ stderr += chunk;
43
+ });
44
+ const timer = opts.timeoutMs && opts.timeoutMs > 0 ? setTimeout(() => {
45
+ timedOut = true;
46
+ try {
47
+ child.kill("SIGTERM");
48
+ } catch {}
49
+ setTimeout(() => {
50
+ try {
51
+ child.kill("SIGKILL");
52
+ } catch {}
53
+ }, 2000).unref?.();
54
+ }, opts.timeoutMs) : undefined;
55
+ timer?.unref?.();
56
+ child.on("error", (err) => {
57
+ if (timer)
58
+ clearTimeout(timer);
59
+ reject(err);
60
+ });
61
+ child.on("close", (code, signal) => {
62
+ if (timer)
63
+ clearTimeout(timer);
64
+ if (timedOut) {
65
+ resolve({ code: 124, stdout, stderr });
66
+ return;
67
+ }
68
+ resolve({ code: code ?? (signal ? 1 : 0), stdout, stderr });
69
+ });
70
+ });
71
+ }
72
+ function spawnStreaming(cmd, opts = {}) {
73
+ const child = spawn(cmd[0], cmd.slice(1), {
74
+ stdio: [assertEofStdin(opts.stdin), "pipe", "pipe"],
75
+ env: opts.env ? normalizeEnv(opts.env) : process.env,
76
+ cwd: opts.cwd,
77
+ windowsHide: true
78
+ });
79
+ let timedOut = false;
80
+ const timer = opts.timeoutMs && opts.timeoutMs > 0 ? setTimeout(() => {
81
+ timedOut = true;
82
+ try {
83
+ child.kill("SIGTERM");
84
+ } catch {}
85
+ }, opts.timeoutMs) : undefined;
86
+ timer?.unref?.();
87
+ if (opts.onStdout || opts.onStderr) {
88
+ child.stdout?.setEncoding("utf8");
89
+ child.stderr?.setEncoding("utf8");
90
+ child.stdout?.on("data", (chunk) => opts.onStdout?.(chunk));
91
+ child.stderr?.on("data", (chunk) => opts.onStderr?.(chunk));
92
+ } else {
93
+ child.stdout?.resume();
94
+ child.stderr?.resume();
95
+ }
96
+ const exited = new Promise((resolve, reject) => {
97
+ child.on("error", (err) => {
98
+ if (timer)
99
+ clearTimeout(timer);
100
+ reject(err);
101
+ });
102
+ child.on("close", (code, signal) => {
103
+ if (timer)
104
+ clearTimeout(timer);
105
+ if (timedOut) {
106
+ resolve(124);
107
+ return;
108
+ }
109
+ resolve(code ?? (signal ? 1 : 0));
110
+ });
111
+ });
112
+ return {
113
+ exited,
114
+ pid: child.pid,
115
+ kill(signal = "SIGTERM") {
116
+ try {
117
+ child.kill(signal);
118
+ } catch {}
119
+ }
120
+ };
121
+ }
122
+ function assertEofStdin(value) {
123
+ if (value !== undefined && value !== "ignore") {
124
+ throw new Error(`refusing to spawn with stdin=${String(value)}: fd 0 must be an EOF source ` + '(use "ignore"); an inherited socket hangs `opencode run` (see #4376)');
125
+ }
126
+ return "ignore";
127
+ }
128
+ function normalizeEnv(env) {
129
+ const out = {};
130
+ for (const [k, v] of Object.entries(env)) {
131
+ if (typeof v === "string")
132
+ out[k] = v;
133
+ }
134
+ return out;
135
+ }
136
+ function isMissingBinary(err) {
137
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
138
+ }
139
+
140
+ // ../../../packages/cli/src/daemon/ndjson.ts
141
+ class LineSplitter {
142
+ tail = "";
143
+ pushChunk(chunk) {
144
+ this.tail += chunk;
145
+ const out = [];
146
+ let idx = this.tail.indexOf(`
147
+ `);
148
+ while (idx !== -1) {
149
+ out.push(this.tail.slice(0, idx));
150
+ this.tail = this.tail.slice(idx + 1);
151
+ idx = this.tail.indexOf(`
152
+ `);
153
+ }
154
+ return out;
155
+ }
156
+ finish() {
157
+ if (!this.tail)
158
+ return [];
159
+ const line = this.tail;
160
+ this.tail = "";
161
+ return [line];
162
+ }
163
+ }
164
+
165
+ class NdjsonAssembler {
166
+ tail = "";
167
+ texts = [];
168
+ error;
169
+ toolCalls = 0;
170
+ sessionId;
171
+ tools = [];
172
+ pushChunk(chunk) {
173
+ this.tail += chunk;
174
+ let idx = this.tail.indexOf(`
175
+ `);
176
+ while (idx !== -1) {
177
+ const line = this.tail.slice(0, idx);
178
+ this.tail = this.tail.slice(idx + 1);
179
+ this.parseLine(line);
180
+ idx = this.tail.indexOf(`
181
+ `);
182
+ }
183
+ }
184
+ pushLine(line) {
185
+ this.parseLine(line);
186
+ }
187
+ finishIncomplete() {
188
+ return this.tail.trim();
189
+ }
190
+ parseLine(line) {
191
+ const trimmed = line.trim();
192
+ if (!trimmed || trimmed[0] !== "{")
193
+ return;
194
+ let parsed;
195
+ try {
196
+ parsed = JSON.parse(trimmed);
197
+ } catch {
198
+ return;
199
+ }
200
+ if (parsed.sessionID)
201
+ this.sessionId = parsed.sessionID;
202
+ if (parsed.type === "text" && typeof parsed.part?.text === "string") {
203
+ this.texts.push(parsed.part.text);
204
+ return;
205
+ }
206
+ if (parsed.type === "tool_use") {
207
+ this.toolCalls += 1;
208
+ const name = parsed.part?.tool ?? parsed.tool;
209
+ if (typeof name === "string" && name) {
210
+ this.tools.push(name);
211
+ }
212
+ return;
213
+ }
214
+ if (parsed.type === "error") {
215
+ if (this.error === undefined) {
216
+ this.error = parsed.error?.message ?? parsed.error?.type ?? "unknown opencode error";
217
+ }
218
+ }
219
+ }
220
+ finish() {
221
+ if (this.tail.trim()) {
222
+ this.parseLine(this.tail);
223
+ this.tail = "";
224
+ }
225
+ const text = this.texts.join("").trim();
226
+ return {
227
+ text,
228
+ error: this.error,
229
+ toolCalls: this.toolCalls,
230
+ sessionId: this.sessionId,
231
+ tools: this.tools
232
+ };
233
+ }
234
+ }
235
+
236
+ // ../../../packages/cli/src/daemon/runners/codex-stream.ts
237
+ var CODEX_ITEM_TYPES = Object.freeze({
238
+ AGENT_MESSAGE: "agent_message",
239
+ COMMAND_EXECUTION: "command_execution",
240
+ ERROR: "error"
241
+ });
242
+
243
+ class CodexStreamDecoder {
244
+ lines = new LineSplitter;
245
+ state = { tools: [], errors: [], turnCompleted: false };
246
+ pushChunk(chunk) {
247
+ for (const line of this.lines.pushChunk(chunk))
248
+ this.consume(line);
249
+ }
250
+ finish() {
251
+ for (const line of this.lines.finish())
252
+ this.consume(line);
253
+ return this.toResult();
254
+ }
255
+ snapshot() {
256
+ return { ...this.state, tools: [...this.state.tools], errors: [...this.state.errors] };
257
+ }
258
+ consume(rawLine) {
259
+ const line = rawLine.trim();
260
+ if (!line.startsWith("{"))
261
+ return;
262
+ let event;
263
+ try {
264
+ event = JSON.parse(line);
265
+ } catch {
266
+ return;
267
+ }
268
+ if (typeof event !== "object" || event === null)
269
+ return;
270
+ const ev = event;
271
+ if (ev.type === "thread.started") {
272
+ const id = ev.thread_id;
273
+ if (typeof id === "string" && id)
274
+ this.state.sessionId = id;
275
+ return;
276
+ }
277
+ if (ev.type === "turn.completed") {
278
+ this.state.turnCompleted = true;
279
+ return;
280
+ }
281
+ if (ev.type === "item.started" || ev.type === "item.completed") {
282
+ this.consumeItem(ev.item);
283
+ }
284
+ }
285
+ consumeItem(raw) {
286
+ if (typeof raw !== "object" || raw === null)
287
+ return;
288
+ const item = raw;
289
+ if (item.type === CODEX_ITEM_TYPES.AGENT_MESSAGE) {
290
+ if (typeof item.text === "string" && item.text.length > 0) {
291
+ this.state.text = item.text;
292
+ }
293
+ return;
294
+ }
295
+ if (item.type === CODEX_ITEM_TYPES.ERROR) {
296
+ if (typeof item.message === "string" && item.message.length > 0) {
297
+ this.state.errors.push(item.message);
298
+ }
299
+ return;
300
+ }
301
+ if (item.type === CODEX_ITEM_TYPES.COMMAND_EXECUTION) {
302
+ const id = item.id;
303
+ if (typeof id === "string" && id && this.state.tools.includes(id))
304
+ return;
305
+ this.state.tools.push(typeof id === "string" ? id : `cmd-${this.state.tools.length}`);
306
+ }
307
+ }
308
+ toResult() {
309
+ const result = {
310
+ text: this.state.text ?? ""
311
+ };
312
+ if (this.state.sessionId)
313
+ result.sessionId = this.state.sessionId;
314
+ if (this.state.tools.length > 0)
315
+ result.toolCalls = this.state.tools.length;
316
+ return result;
317
+ }
318
+ }
319
+
320
+ // ../../../packages/cli/src/daemon/runners/codex-sessions.ts
321
+ function createCodexSessionRegistry() {
322
+ const sessions = new Map;
323
+ return {
324
+ get: (key) => sessions.get(key),
325
+ set: (key, id) => {
326
+ sessions.set(key, id);
327
+ },
328
+ delete: (key) => {
329
+ sessions.delete(key);
330
+ },
331
+ size: () => sessions.size
332
+ };
333
+ }
334
+
335
+ // ../../../packages/cli/src/daemon/runners/codex.ts
336
+ var DEFAULT_CODEX_TIMEOUT_MS = 10 * 60 * 1000;
337
+ var CODEX_STDIO = Object.freeze([
338
+ "ignore",
339
+ "pipe",
340
+ "pipe"
341
+ ]);
342
+ var CODEX_SANDBOX_MODES = Object.freeze([
343
+ "read-only",
344
+ "workspace-write",
345
+ "danger-full-access"
346
+ ]);
347
+ function isCodexSandboxMode(value) {
348
+ return CODEX_SANDBOX_MODES.includes(value);
349
+ }
350
+ function buildCodexArgs(input) {
351
+ const args = ["exec", "--json"];
352
+ if (input.model)
353
+ args.push("--model", input.model);
354
+ args.push("--skip-git-repo-check");
355
+ args.push("-s", input.sandbox ?? "read-only");
356
+ args.push(input.prompt);
357
+ return args;
358
+ }
359
+ function buildCodexResumeArgs(sessionId, input) {
360
+ const args = ["exec", "resume", "--json"];
361
+ if (input.model)
362
+ args.push("--model", input.model);
363
+ args.push("--skip-git-repo-check");
364
+ if (input.sandbox === "danger-full-access") {
365
+ args.push("--dangerously-bypass-approvals-and-sandbox");
366
+ }
367
+ args.push(sessionId);
368
+ args.push(input.prompt);
369
+ return args;
370
+ }
371
+ function makeCodexRunner(opts = {}) {
372
+ const bin = opts.bin ?? "codex";
373
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_CODEX_TIMEOUT_MS;
374
+ const model = opts.model;
375
+ const sandbox = opts.sandbox ?? "read-only";
376
+ const sessions = opts.sessions ?? createCodexSessionRegistry();
377
+ return async function runAgent(input) {
378
+ const known = input.sessionId ? sessions.get(input.sessionId) : undefined;
379
+ const args = known ? buildCodexResumeArgs(known, { prompt: input.prompt, model, sandbox }) : buildCodexArgs({ prompt: input.prompt, model, sandbox });
380
+ const decoder = new CodexStreamDecoder;
381
+ let stderr = "";
382
+ let handle;
383
+ try {
384
+ handle = spawnStreaming([bin, ...args], {
385
+ stdin: CODEX_STDIO[0],
386
+ env: { ...process.env, NO_COLOR: "1" },
387
+ timeoutMs,
388
+ onStdout: (chunk) => decoder.pushChunk(chunk),
389
+ onStderr: (chunk) => {
390
+ if (stderr.length < 64 * 1024)
391
+ stderr += chunk;
392
+ }
393
+ });
394
+ } catch (err) {
395
+ return { text: "", error: `failed to spawn ${bin}: ${errMsg(err)}` };
396
+ }
397
+ let code;
398
+ try {
399
+ code = await handle.exited;
400
+ } catch (err) {
401
+ if (isMissingBinary(err)) {
402
+ return { text: "", error: `failed to spawn ${bin}: ${errMsg(err)}` };
403
+ }
404
+ return { text: "", error: errMsg(err) };
405
+ }
406
+ const result = decoder.finish();
407
+ if (input.sessionId && result.sessionId && !result.error) {
408
+ sessions.set(input.sessionId, result.sessionId);
409
+ }
410
+ if (code === 124) {
411
+ return { ...result, error: `codex timed out after ${timeoutMs}ms` };
412
+ }
413
+ if (result.error)
414
+ return result;
415
+ if (code !== 0 && !result.text) {
416
+ return {
417
+ text: "",
418
+ toolCalls: result.toolCalls,
419
+ tools: result.tools,
420
+ error: `codex exited ${code}: ${(stderr || "no stderr").trim().slice(0, 1000)}`
421
+ };
422
+ }
423
+ return result;
424
+ };
425
+ }
426
+ function errMsg(err) {
427
+ if (err instanceof Error)
428
+ return err.message;
429
+ return String(err);
430
+ }
431
+
16
432
  // ../../../packages/cli/src/args.ts
17
433
  var DEFAULT_PROFILE = "default";
18
434
  var DEFAULT_AGENT = "roy";
@@ -107,18 +523,38 @@ function parseServe(argv) {
107
523
  serve.runtime = v;
108
524
  break;
109
525
  }
110
- case "--runtime-bin": {
526
+ case "--runtime-bin": {
527
+ const v = take();
528
+ if (v instanceof Error)
529
+ return { command: "unknown", error: v.message };
530
+ serve.runtimeBin = v;
531
+ break;
532
+ }
533
+ case "--opencode-bin": {
534
+ const v = take();
535
+ if (v instanceof Error)
536
+ return { command: "unknown", error: v.message };
537
+ serve.opencodeBin = v;
538
+ break;
539
+ }
540
+ case "--codex-model": {
111
541
  const v = take();
112
542
  if (v instanceof Error)
113
543
  return { command: "unknown", error: v.message };
114
- serve.runtimeBin = v;
544
+ serve.codexModel = v;
115
545
  break;
116
546
  }
117
- case "--opencode-bin": {
547
+ case "--codex-sandbox": {
118
548
  const v = take();
119
549
  if (v instanceof Error)
120
550
  return { command: "unknown", error: v.message };
121
- serve.opencodeBin = v;
551
+ if (!isCodexSandboxMode(v)) {
552
+ return {
553
+ command: "unknown",
554
+ error: `--codex-sandbox must be one of ${CODEX_SANDBOX_MODES.join("|")}, got "${v}"`
555
+ };
556
+ }
557
+ serve.codexSandbox = v;
122
558
  break;
123
559
  }
124
560
  case "--max-concurrency": {
@@ -198,7 +634,7 @@ function resolveRuntimeBin(options) {
198
634
  return;
199
635
  }
200
636
  function usage() {
201
- return `roy-task — bridge Feishu (Lark) messages to a roy agent (opencode or claude)
637
+ return `roy-task — bridge Feishu (Lark) messages to a roy agent (opencode, claude or codex)
202
638
 
203
639
  USAGE
204
640
  roy-task [serve] [options] start the daemon (default command)
@@ -220,6 +656,9 @@ SERVE OPTIONS
220
656
  --runtime-bin <path> binary for the selected runtime (default: per-runtime)
221
657
  --opencode-bin <path> DEPRECATED alias for --runtime-bin
222
658
  (only applies when --runtime opencode)
659
+ --codex-model <name> model for codex exec --model (default: codex config)
660
+ --codex-sandbox <mode> codex exec -s policy (default: read-only)
661
+ one of: ${CODEX_SANDBOX_MODES.join(" | ")}
223
662
  --max-concurrency <n> max simultaneous agent runs (default: 1)
224
663
  ⚠️ >1 only safe across DIFFERENT chats;
225
664
  same-session concurrency cross-contaminates.
@@ -871,129 +1310,6 @@ function createEventListener(config) {
871
1310
  // ../../../packages/cli/src/daemon/runners/claude.ts
872
1311
  import { createHash as createHash2 } from "node:crypto";
873
1312
 
874
- // ../../../packages/cli/src/runtime/spawn.ts
875
- import { spawn } from "node:child_process";
876
- function runCapture(cmd, opts = {}) {
877
- return new Promise((resolve, reject) => {
878
- let child;
879
- try {
880
- child = spawn(cmd[0], cmd.slice(1), {
881
- stdio: [assertEofStdin(opts.stdin), "pipe", "pipe"],
882
- env: opts.env ? normalizeEnv(opts.env) : process.env,
883
- cwd: opts.cwd,
884
- windowsHide: true
885
- });
886
- } catch (err) {
887
- reject(err);
888
- return;
889
- }
890
- let stdout = "";
891
- let stderr = "";
892
- let timedOut = false;
893
- child.stdout?.setEncoding("utf8");
894
- child.stderr?.setEncoding("utf8");
895
- child.stdout?.on("data", (chunk) => {
896
- stdout += chunk;
897
- });
898
- child.stderr?.on("data", (chunk) => {
899
- stderr += chunk;
900
- });
901
- const timer = opts.timeoutMs && opts.timeoutMs > 0 ? setTimeout(() => {
902
- timedOut = true;
903
- try {
904
- child.kill("SIGTERM");
905
- } catch {}
906
- setTimeout(() => {
907
- try {
908
- child.kill("SIGKILL");
909
- } catch {}
910
- }, 2000).unref?.();
911
- }, opts.timeoutMs) : undefined;
912
- timer?.unref?.();
913
- child.on("error", (err) => {
914
- if (timer)
915
- clearTimeout(timer);
916
- reject(err);
917
- });
918
- child.on("close", (code, signal) => {
919
- if (timer)
920
- clearTimeout(timer);
921
- if (timedOut) {
922
- resolve({ code: 124, stdout, stderr });
923
- return;
924
- }
925
- resolve({ code: code ?? (signal ? 1 : 0), stdout, stderr });
926
- });
927
- });
928
- }
929
- function spawnStreaming(cmd, opts = {}) {
930
- const child = spawn(cmd[0], cmd.slice(1), {
931
- stdio: [assertEofStdin(opts.stdin), "pipe", "pipe"],
932
- env: opts.env ? normalizeEnv(opts.env) : process.env,
933
- cwd: opts.cwd,
934
- windowsHide: true
935
- });
936
- let timedOut = false;
937
- const timer = opts.timeoutMs && opts.timeoutMs > 0 ? setTimeout(() => {
938
- timedOut = true;
939
- try {
940
- child.kill("SIGTERM");
941
- } catch {}
942
- }, opts.timeoutMs) : undefined;
943
- timer?.unref?.();
944
- if (opts.onStdout || opts.onStderr) {
945
- child.stdout?.setEncoding("utf8");
946
- child.stderr?.setEncoding("utf8");
947
- child.stdout?.on("data", (chunk) => opts.onStdout?.(chunk));
948
- child.stderr?.on("data", (chunk) => opts.onStderr?.(chunk));
949
- } else {
950
- child.stdout?.resume();
951
- child.stderr?.resume();
952
- }
953
- const exited = new Promise((resolve, reject) => {
954
- child.on("error", (err) => {
955
- if (timer)
956
- clearTimeout(timer);
957
- reject(err);
958
- });
959
- child.on("close", (code, signal) => {
960
- if (timer)
961
- clearTimeout(timer);
962
- if (timedOut) {
963
- resolve(124);
964
- return;
965
- }
966
- resolve(code ?? (signal ? 1 : 0));
967
- });
968
- });
969
- return {
970
- exited,
971
- pid: child.pid,
972
- kill(signal = "SIGTERM") {
973
- try {
974
- child.kill(signal);
975
- } catch {}
976
- }
977
- };
978
- }
979
- function assertEofStdin(value) {
980
- if (value !== undefined && value !== "ignore") {
981
- throw new Error(`refusing to spawn with stdin=${String(value)}: fd 0 must be an EOF source ` + '(use "ignore"); an inherited socket hangs `opencode run` (see #4376)');
982
- }
983
- return "ignore";
984
- }
985
- function normalizeEnv(env) {
986
- const out = {};
987
- for (const [k, v] of Object.entries(env)) {
988
- if (typeof v === "string")
989
- out[k] = v;
990
- }
991
- return out;
992
- }
993
- function isMissingBinary(err) {
994
- return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
995
- }
996
-
997
1313
  // ../../../packages/cli/src/daemon/runners/claude-agents.ts
998
1314
  var CLAUDE_ROY_AGENT_NAME = "roy";
999
1315
  var ROY_AGENT_DESCRIPTION = "Roy — task-as-first-class-citizen agent. Uses the roy-agent task lifecycle " + "(task_tree → task_create → task_update → task_operation_create → task_complete) " + "so every piece of work is tracked in the local task store.";
@@ -1142,115 +1458,7 @@ function createClaudeSessionRegistry() {
1142
1458
  };
1143
1459
  }
1144
1460
 
1145
- // ../../../packages/cli/src/daemon/ndjson.ts
1146
- class LineSplitter {
1147
- tail = "";
1148
- pushChunk(chunk) {
1149
- this.tail += chunk;
1150
- const out = [];
1151
- let idx = this.tail.indexOf(`
1152
- `);
1153
- while (idx !== -1) {
1154
- out.push(this.tail.slice(0, idx));
1155
- this.tail = this.tail.slice(idx + 1);
1156
- idx = this.tail.indexOf(`
1157
- `);
1158
- }
1159
- return out;
1160
- }
1161
- finish() {
1162
- if (!this.tail)
1163
- return [];
1164
- const line = this.tail;
1165
- this.tail = "";
1166
- return [line];
1167
- }
1168
- }
1169
-
1170
- class NdjsonAssembler {
1171
- tail = "";
1172
- texts = [];
1173
- error;
1174
- toolCalls = 0;
1175
- sessionId;
1176
- tools = [];
1177
- pushChunk(chunk) {
1178
- this.tail += chunk;
1179
- let idx = this.tail.indexOf(`
1180
- `);
1181
- while (idx !== -1) {
1182
- const line = this.tail.slice(0, idx);
1183
- this.tail = this.tail.slice(idx + 1);
1184
- this.parseLine(line);
1185
- idx = this.tail.indexOf(`
1186
- `);
1187
- }
1188
- }
1189
- pushLine(line) {
1190
- this.parseLine(line);
1191
- }
1192
- finishIncomplete() {
1193
- return this.tail.trim();
1194
- }
1195
- parseLine(line) {
1196
- const trimmed = line.trim();
1197
- if (!trimmed || trimmed[0] !== "{")
1198
- return;
1199
- let parsed;
1200
- try {
1201
- parsed = JSON.parse(trimmed);
1202
- } catch {
1203
- return;
1204
- }
1205
- if (parsed.sessionID)
1206
- this.sessionId = parsed.sessionID;
1207
- if (parsed.type === "text" && typeof parsed.part?.text === "string") {
1208
- this.texts.push(parsed.part.text);
1209
- return;
1210
- }
1211
- if (parsed.type === "tool_use") {
1212
- this.toolCalls += 1;
1213
- const name = parsed.part?.tool ?? parsed.tool;
1214
- if (typeof name === "string" && name) {
1215
- this.tools.push(name);
1216
- }
1217
- return;
1218
- }
1219
- if (parsed.type === "error") {
1220
- if (this.error === undefined) {
1221
- this.error = parsed.error?.message ?? parsed.error?.type ?? "unknown opencode error";
1222
- }
1223
- }
1224
- }
1225
- finish() {
1226
- if (this.tail.trim()) {
1227
- this.parseLine(this.tail);
1228
- this.tail = "";
1229
- }
1230
- const text = this.texts.join("").trim();
1231
- return {
1232
- text,
1233
- error: this.error,
1234
- toolCalls: this.toolCalls,
1235
- sessionId: this.sessionId,
1236
- tools: this.tools
1237
- };
1238
- }
1239
- }
1240
-
1241
1461
  // ../../../packages/cli/src/daemon/runners/claude-stream.ts
1242
- function errorDetail(result) {
1243
- if (typeof result.result === "string" && result.result.trim() !== "") {
1244
- return result.result.trim();
1245
- }
1246
- if (Array.isArray(result.errors)) {
1247
- const parts = result.errors.map((e) => typeof e === "string" ? e.trim() : "").filter((e) => e !== "");
1248
- if (parts.length > 0)
1249
- return parts.join("; ");
1250
- }
1251
- return result.subtype === undefined ? "claude reported is_error" : `claude reported ${result.subtype}`;
1252
- }
1253
-
1254
1462
  class ClaudeStreamDecoder {
1255
1463
  lines = new LineSplitter;
1256
1464
  state = { assistantText: [], tools: [] };
@@ -1312,7 +1520,7 @@ class ClaudeStreamDecoder {
1312
1520
  const toolCalls = this.state.tools.length > 0 ? this.state.tools.length : undefined;
1313
1521
  const tools = this.state.tools.length > 0 ? [...this.state.tools] : undefined;
1314
1522
  if (result?.is_error) {
1315
- const detail = errorDetail(result);
1523
+ const detail = typeof result.result === "string" && result.result.trim() !== "" ? result.result.trim() : result.subtype === undefined ? "claude reported is_error" : `claude reported ${result.subtype}`;
1316
1524
  return { text: "", sessionId: this.state.sessionId, toolCalls, tools, error: detail.slice(0, 1000) };
1317
1525
  }
1318
1526
  if (typeof result?.result === "string" && result.result.trim() !== "") {
@@ -1387,50 +1595,42 @@ function makeClaudeRunner(opts = {}) {
1387
1595
  }
1388
1596
  });
1389
1597
  } catch (err) {
1390
- return { text: "", error: `failed to spawn ${bin}: ${errMsg(err)}` };
1598
+ return { text: "", error: `failed to spawn ${bin}: ${errMsg2(err)}` };
1391
1599
  }
1392
1600
  let code;
1393
1601
  try {
1394
1602
  code = await handle.exited;
1395
1603
  } catch (err) {
1396
1604
  if (isMissingBinary(err)) {
1397
- return { text: "", error: `failed to spawn ${bin}: ${errMsg(err)}` };
1605
+ return { text: "", error: `failed to spawn ${bin}: ${errMsg2(err)}` };
1398
1606
  }
1399
- return { text: "", error: errMsg(err) };
1607
+ return { text: "", error: errMsg2(err) };
1400
1608
  }
1401
1609
  const result = decoder.finish();
1402
- const stderrText = stderr.trim();
1403
- const combined = result.error ? stderrText && !result.error.includes(stderrText) ? `${result.error} | ${stderrText}` : result.error : undefined;
1404
- const effective = combined ? { ...result, error: combined.slice(0, 1000) } : result;
1405
- if (input.sessionId && effective.sessionId) {
1406
- sessions.set(input.sessionId, effective.sessionId);
1610
+ if (input.sessionId && result.sessionId && !result.error) {
1611
+ sessions.set(input.sessionId, result.sessionId);
1407
1612
  }
1408
1613
  if (code === 124) {
1409
- return { ...effective, error: `claude timed out after ${timeoutMs}ms` };
1614
+ return { ...result, error: `claude timed out after ${timeoutMs}ms` };
1410
1615
  }
1411
- if (effective.error) {
1412
- if (input.sessionId && STALE_SESSION_RE.test(effective.error)) {
1616
+ if (result.error) {
1617
+ if (input.sessionId && /No conversation found|not a UUID|session title/i.test(result.error)) {
1413
1618
  sessions.delete(input.sessionId);
1414
1619
  }
1415
- return effective;
1620
+ return result;
1416
1621
  }
1417
- if (code !== 0 && !effective.text) {
1418
- const error = `claude exited ${code}: ${(stderr || "no stderr").trim().slice(0, 1000)}`;
1419
- if (input.sessionId && STALE_SESSION_RE.test(error)) {
1420
- sessions.delete(input.sessionId);
1421
- }
1622
+ if (code !== 0 && !result.text) {
1422
1623
  return {
1423
1624
  text: "",
1424
- toolCalls: effective.toolCalls,
1425
- tools: effective.tools,
1426
- error
1625
+ toolCalls: result.toolCalls,
1626
+ tools: result.tools,
1627
+ error: `claude exited ${code}: ${(stderr || "no stderr").trim().slice(0, 1000)}`
1427
1628
  };
1428
1629
  }
1429
- return effective;
1630
+ return result;
1430
1631
  };
1431
1632
  }
1432
- var STALE_SESSION_RE = /No conversation found|not a UUID|session title/i;
1433
- function errMsg(err) {
1633
+ function errMsg2(err) {
1434
1634
  if (err instanceof Error)
1435
1635
  return err.message;
1436
1636
  return String(err);
@@ -1462,16 +1662,16 @@ function makeOpencodeRunner(opts = {}) {
1462
1662
  }
1463
1663
  });
1464
1664
  } catch (err) {
1465
- return { text: "", error: `failed to spawn ${bin}: ${errMsg2(err)}` };
1665
+ return { text: "", error: `failed to spawn ${bin}: ${errMsg3(err)}` };
1466
1666
  }
1467
1667
  let code;
1468
1668
  try {
1469
1669
  code = await handle.exited;
1470
1670
  } catch (err) {
1471
1671
  if (isMissingBinary(err)) {
1472
- return { text: "", error: `failed to spawn ${bin}: ${errMsg2(err)}` };
1672
+ return { text: "", error: `failed to spawn ${bin}: ${errMsg3(err)}` };
1473
1673
  }
1474
- return { text: "", error: errMsg2(err) };
1674
+ return { text: "", error: errMsg3(err) };
1475
1675
  }
1476
1676
  const result = assembler.finish();
1477
1677
  if (code === 124) {
@@ -1508,7 +1708,7 @@ function makeOpencodeRunner(opts = {}) {
1508
1708
  };
1509
1709
  };
1510
1710
  }
1511
- function errMsg2(err) {
1711
+ function errMsg3(err) {
1512
1712
  if (err instanceof Error)
1513
1713
  return err.message;
1514
1714
  return String(err);
@@ -1521,6 +1721,12 @@ var FACTORIES = {
1521
1721
  bin: input.bin,
1522
1722
  timeoutMs: input.timeoutMs,
1523
1723
  agentsJson: input.claudeAgentsJson
1724
+ }),
1725
+ codex: (input) => makeCodexRunner({
1726
+ bin: input.bin,
1727
+ timeoutMs: input.timeoutMs,
1728
+ model: input.codexModel,
1729
+ sandbox: isCodexSandboxMode(input.codexSandbox ?? "") ? input.codexSandbox : undefined
1524
1730
  })
1525
1731
  };
1526
1732
  function makeRunner(input = {}) {
@@ -1629,7 +1835,9 @@ async function startServe(options, deps) {
1629
1835
  runAgent: deps.runAgent ?? makeRunner({
1630
1836
  runtime: options.runtime,
1631
1837
  bin: resolveRuntimeBin(options),
1632
- timeoutMs: options.timeoutMs
1838
+ timeoutMs: options.timeoutMs,
1839
+ codexModel: options.codexModel,
1840
+ codexSandbox: options.codexSandbox
1633
1841
  }),
1634
1842
  reply: deps.reply ?? defaultReplyRunner,
1635
1843
  gate,
@@ -1658,7 +1866,7 @@ async function startServe(options, deps) {
1658
1866
  try {
1659
1867
  await listener.stop();
1660
1868
  } catch (err) {
1661
- log(`[roy-task] listener.stop() failed: ${errMsg3(err)}`);
1869
+ log(`[roy-task] listener.stop() failed: ${errMsg4(err)}`);
1662
1870
  }
1663
1871
  log("[roy-task] stopped");
1664
1872
  resolveDone();
@@ -1680,7 +1888,7 @@ async function startServe(options, deps) {
1680
1888
  await stop();
1681
1889
  }
1682
1890
  } catch (err) {
1683
- log(`[roy-task] ${label} dispatch threw: ${errMsg3(err)}`);
1891
+ log(`[roy-task] ${label} dispatch threw: ${errMsg4(err)}`);
1684
1892
  if (options.once)
1685
1893
  await stop();
1686
1894
  }
@@ -1690,7 +1898,7 @@ async function startServe(options, deps) {
1690
1898
  try {
1691
1899
  await listener.start();
1692
1900
  } catch (err) {
1693
- log(`[roy-task] failed to start event listener: ${errMsg3(err)}`);
1901
+ log(`[roy-task] failed to start event listener: ${errMsg4(err)}`);
1694
1902
  clearInterval(sweeper);
1695
1903
  resolveDone();
1696
1904
  throw err;
@@ -1709,6 +1917,10 @@ function formatBanner(options, commands, maxConcurrency) {
1709
1917
  ` agent : ${options.agent}`,
1710
1918
  ` runtime : ${options.runtime}`,
1711
1919
  ` runtime bin : ${resolveRuntimeBin(options) ?? "(default)"}`,
1920
+ ...options.runtime === "codex" ? [
1921
+ ` codex model : ${options.codexModel ?? "(codex config)"}`,
1922
+ ` codex sandbox : ${options.codexSandbox ?? "read-only"}`
1923
+ ] : [],
1712
1924
  ` max concurrency : ${maxConcurrency}`,
1713
1925
  ` reply enabled : ${options.replyEnabled}`,
1714
1926
  ` progress cards : ${options.cardEnabled && !options.dryRun}`,
@@ -1750,7 +1962,7 @@ function formatResult(label, result) {
1750
1962
  return `[roy-task] ${label} skipped: ${result.reason}`;
1751
1963
  }
1752
1964
  }
1753
- function errMsg3(err) {
1965
+ function errMsg4(err) {
1754
1966
  return err instanceof Error ? err.message : String(err);
1755
1967
  }
1756
1968
 
@@ -1875,7 +2087,7 @@ function formatStatus(report) {
1875
2087
  }
1876
2088
 
1877
2089
  // ../../../packages/cli/src/main.ts
1878
- var VERSION = "0.4.2";
2090
+ var VERSION = "0.5.0";
1879
2091
  async function main(argv) {
1880
2092
  const parsed = parseArgs(argv);
1881
2093
  switch (parsed.command) {