@ckanner/flywheel 0.32.1 → 0.33.1

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.
Files changed (134) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +7 -0
  3. package/README.zh-CN.md +5 -0
  4. package/THIRD_PARTY_NOTICES.md +42 -0
  5. package/bin/runtime.js +25 -31
  6. package/cordis.patch.yml +5 -0
  7. package/dist/bridge.mjs +421 -0
  8. package/dist/claude-code-pro-bridge.mjs +82 -0
  9. package/dist/claude-code-pro-worker.mjs +5 -0
  10. package/dist/client.js +4140 -0
  11. package/dist/codex-pro-bridge.mjs +75 -0
  12. package/dist/codex-pro-worker.mjs +5 -0
  13. package/dist/console-read.mjs +51 -0
  14. package/dist/control.mjs +218 -0
  15. package/dist/index.mjs +185 -0
  16. package/dist/installation.mjs +56 -0
  17. package/dist/machine-worker.mjs +241 -0
  18. package/dist/policy.mjs +91 -0
  19. package/dist/prewarm.mjs +136 -0
  20. package/dist/protocol.mjs +260 -0
  21. package/dist/task-association.mjs +127 -0
  22. package/dist/worker.mjs +52 -0
  23. package/dist/workspaces.mjs +42 -0
  24. package/docs/README.md +1 -1
  25. package/docs/README.zh-CN.md +1 -1
  26. package/docs/design.md +8 -5
  27. package/docs/en/configuration.md +1 -1
  28. package/docs/en/contributing.md +5 -6
  29. package/docs/en/flow-console.md +2 -2
  30. package/docs/en/getting-started.md +9 -2
  31. package/docs/en/releasing.md +132 -174
  32. package/docs/validation/2026-11-13-release-0.32.1.md +83 -0
  33. package/docs/validation/2026-11-14-release-0.33.0.md +49 -0
  34. package/docs/validation/2026-11-14-single-package.md +60 -0
  35. package/docs/validation/README.md +3 -0
  36. package/docs/zh-CN/configuration.md +1 -1
  37. package/docs/zh-CN/contributing.md +4 -5
  38. package/docs/zh-CN/flow-console.md +2 -2
  39. package/docs/zh-CN/getting-started.md +7 -3
  40. package/docs/zh-CN/releasing.md +97 -81
  41. package/native/darwin-amd64/flywheel +0 -0
  42. package/native/darwin-arm64/flywheel +0 -0
  43. package/native/linux-amd64/flywheel +0 -0
  44. package/native/linux-arm64/flywheel +0 -0
  45. package/native/manifest.json +22 -0
  46. package/node_modules/dsh-subagent-codex-pro/CHANGELOG.md +403 -0
  47. package/node_modules/dsh-subagent-codex-pro/LICENSE +21 -0
  48. package/node_modules/dsh-subagent-codex-pro/README.md +557 -0
  49. package/node_modules/dsh-subagent-codex-pro/cordis.patch.yml +30 -0
  50. package/node_modules/dsh-subagent-codex-pro/docs/README.zh.md +355 -0
  51. package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle-validation.md +65 -0
  52. package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle.md +144 -0
  53. package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle.zh.md +98 -0
  54. package/node_modules/dsh-subagent-codex-pro/docs/machine.md +194 -0
  55. package/node_modules/dsh-subagent-codex-pro/docs/orchestration.zh.md +86 -0
  56. package/node_modules/dsh-subagent-codex-pro/lib/codex-exec.js +68 -0
  57. package/node_modules/dsh-subagent-codex-pro/lib/codex-exec.js.map +1 -0
  58. package/node_modules/dsh-subagent-codex-pro/lib/dsh-types.js +15 -0
  59. package/node_modules/dsh-subagent-codex-pro/lib/dsh-types.js.map +1 -0
  60. package/node_modules/dsh-subagent-codex-pro/lib/index.js +830 -0
  61. package/node_modules/dsh-subagent-codex-pro/lib/index.js.map +1 -0
  62. package/node_modules/dsh-subagent-codex-pro/lib/jobs.js +35 -0
  63. package/node_modules/dsh-subagent-codex-pro/lib/jobs.js.map +1 -0
  64. package/node_modules/dsh-subagent-codex-pro/lib/jsonrpc.js +229 -0
  65. package/node_modules/dsh-subagent-codex-pro/lib/jsonrpc.js.map +1 -0
  66. package/node_modules/dsh-subagent-codex-pro/lib/machine-cli.js +101 -0
  67. package/node_modules/dsh-subagent-codex-pro/lib/machine-cli.js.map +1 -0
  68. package/node_modules/dsh-subagent-codex-pro/lib/machine-local.js +122 -0
  69. package/node_modules/dsh-subagent-codex-pro/lib/machine-local.js.map +1 -0
  70. package/node_modules/dsh-subagent-codex-pro/lib/machine-recovery.js +167 -0
  71. package/node_modules/dsh-subagent-codex-pro/lib/machine-recovery.js.map +1 -0
  72. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervision.js +141 -0
  73. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervision.js.map +1 -0
  74. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervisor.js +128 -0
  75. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervisor.js.map +1 -0
  76. package/node_modules/dsh-subagent-codex-pro/lib/machine.js +104 -0
  77. package/node_modules/dsh-subagent-codex-pro/lib/machine.js.map +1 -0
  78. package/node_modules/dsh-subagent-codex-pro/lib/process-guardian.js +156 -0
  79. package/node_modules/dsh-subagent-codex-pro/lib/process-guardian.js.map +1 -0
  80. package/node_modules/dsh-subagent-codex-pro/lib/run.js +842 -0
  81. package/node_modules/dsh-subagent-codex-pro/lib/run.js.map +1 -0
  82. package/node_modules/dsh-subagent-codex-pro/lib/structured.js +329 -0
  83. package/node_modules/dsh-subagent-codex-pro/lib/structured.js.map +1 -0
  84. package/node_modules/dsh-subagent-codex-pro/lib/tool.js +295 -0
  85. package/node_modules/dsh-subagent-codex-pro/lib/tool.js.map +1 -0
  86. package/node_modules/dsh-subagent-codex-pro/lib/types/codex-exec.d.ts +88 -0
  87. package/node_modules/dsh-subagent-codex-pro/lib/types/codex-exec.d.ts.map +1 -0
  88. package/node_modules/dsh-subagent-codex-pro/lib/types/dsh-types.d.ts +179 -0
  89. package/node_modules/dsh-subagent-codex-pro/lib/types/dsh-types.d.ts.map +1 -0
  90. package/node_modules/dsh-subagent-codex-pro/lib/types/index.d.ts +266 -0
  91. package/node_modules/dsh-subagent-codex-pro/lib/types/index.d.ts.map +1 -0
  92. package/node_modules/dsh-subagent-codex-pro/lib/types/jobs.d.ts +34 -0
  93. package/node_modules/dsh-subagent-codex-pro/lib/types/jobs.d.ts.map +1 -0
  94. package/node_modules/dsh-subagent-codex-pro/lib/types/jsonrpc.d.ts +113 -0
  95. package/node_modules/dsh-subagent-codex-pro/lib/types/jsonrpc.d.ts.map +1 -0
  96. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-cli.d.ts +3 -0
  97. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-cli.d.ts.map +1 -0
  98. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-local.d.ts +4 -0
  99. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-local.d.ts.map +1 -0
  100. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-recovery.d.ts +53 -0
  101. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-recovery.d.ts.map +1 -0
  102. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-supervision.d.ts +8 -0
  103. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-supervision.d.ts.map +1 -0
  104. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-supervisor.d.ts +2 -0
  105. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-supervisor.d.ts.map +1 -0
  106. package/node_modules/dsh-subagent-codex-pro/lib/types/machine.d.ts +70 -0
  107. package/node_modules/dsh-subagent-codex-pro/lib/types/machine.d.ts.map +1 -0
  108. package/node_modules/dsh-subagent-codex-pro/lib/types/process-guardian.d.ts +22 -0
  109. package/node_modules/dsh-subagent-codex-pro/lib/types/process-guardian.d.ts.map +1 -0
  110. package/node_modules/dsh-subagent-codex-pro/lib/types/run.d.ts +386 -0
  111. package/node_modules/dsh-subagent-codex-pro/lib/types/run.d.ts.map +1 -0
  112. package/node_modules/dsh-subagent-codex-pro/lib/types/structured.d.ts +35 -0
  113. package/node_modules/dsh-subagent-codex-pro/lib/types/structured.d.ts.map +1 -0
  114. package/node_modules/dsh-subagent-codex-pro/lib/types/tool.d.ts +107 -0
  115. package/node_modules/dsh-subagent-codex-pro/lib/types/tool.d.ts.map +1 -0
  116. package/node_modules/dsh-subagent-codex-pro/lib/types/usage.d.ts +131 -0
  117. package/node_modules/dsh-subagent-codex-pro/lib/types/usage.d.ts.map +1 -0
  118. package/node_modules/dsh-subagent-codex-pro/lib/types/wire.d.ts +321 -0
  119. package/node_modules/dsh-subagent-codex-pro/lib/types/wire.d.ts.map +1 -0
  120. package/node_modules/dsh-subagent-codex-pro/lib/usage.js +356 -0
  121. package/node_modules/dsh-subagent-codex-pro/lib/usage.js.map +1 -0
  122. package/node_modules/dsh-subagent-codex-pro/lib/wire.js +986 -0
  123. package/node_modules/dsh-subagent-codex-pro/lib/wire.js.map +1 -0
  124. package/node_modules/dsh-subagent-codex-pro/package.json +76 -0
  125. package/npm/install.js +59 -6
  126. package/package.json +74 -12
  127. package/plugins/dsh/LICENSE +21 -0
  128. package/plugins/dsh/PROTOCOL.md +609 -0
  129. package/plugins/dsh/README.en.md +4 -0
  130. package/plugins/dsh/README.md +342 -0
  131. package/plugins/dsh/README.zh-CN.md +279 -0
  132. package/plugins/dsh/THIRD_PARTY_NOTICES.md +42 -0
  133. package/plugins/dsh/vendor/README.md +17 -0
  134. package/plugins/dsh/vendor/codex-pro.json +16 -0
@@ -0,0 +1,842 @@
1
+ /**
2
+ * One-shot Codex child lifecycle over the real app-server.
3
+ *
4
+ * The run has one deadline from before startup through the terminal turn. Every
5
+ * startup RPC (initialize, thread/start, thread/resume, thread/name/set) and the
6
+ * turn use the same derived abort signal, so a server that stops answering
7
+ * cannot keep a delegation alive past its budget. Cleanup is a separate, finite
8
+ * grace: the deadline ends work, the grace ends the process range.
9
+ *
10
+ * The child is spawned as the leader of its own POSIX process group. Termination
11
+ * targets the group and then proves it is gone; a descendant that ignores
12
+ * SIGTERM or outlives its parent is reported instead of being silently assumed
13
+ * dead.
14
+ *
15
+ * @module dsh-subagent-codex-pro/run
16
+ */
17
+ import { spawn } from 'node:child_process';
18
+ import { randomUUID } from 'node:crypto';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { LineTransport } from "./jsonrpc.js";
21
+ import { verifyStructuredOutput } from "./structured.js";
22
+ import { CodexAppServerWire, DEFAULT_ORIGINATOR, raceAbort, } from "./wire.js";
23
+ /** Default grace between termination tiers. */
24
+ const DEFAULT_DISPOSE_GRACE_MS = 3_000;
25
+ /** Hard upper bound on observing a process group disappear after SIGKILL. */
26
+ const GROUP_REAP_TIMEOUT_MS = 2_000;
27
+ /** A dead leader cannot lend an infinite stdout lease to a descendant. */
28
+ const EXIT_DRAIN_TIMEOUT_MS = 1_000;
29
+ /** Bound protocol and diagnostic output retained/forwarded by this adapter. */
30
+ const DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
31
+ const MAX_TIMER_MS = 2_147_483_647;
32
+ /**
33
+ * How long one delegation may take before it is cancelled.
34
+ *
35
+ * A delegation that hangs is invisible: the job stays `running`, nothing is
36
+ * logged, and the session waits. The deadline turns that into a failure with a
37
+ * reason. Ten minutes is generous against a package whose own README says a
38
+ * delegation takes "a minute or more", and `0` disables it.
39
+ */
40
+ export const DEFAULT_RUN_TIMEOUT_MS = 600_000;
41
+ /** A run failure, kept for its structured facts and any cleanup it observed. */
42
+ export class CodexRunFailure extends Error {
43
+ facts;
44
+ cleanup;
45
+ execution;
46
+ configuration;
47
+ usage;
48
+ rateLimits;
49
+ turnConfiguration;
50
+ constructor(facts, cause, cleanup, observations) {
51
+ const cleanupDetail = cleanup?.confirmed === false
52
+ ? `; process cleanup unconfirmed${cleanup.detail === undefined ? '' : `: ${cleanup.detail}`}`
53
+ : '';
54
+ super(`codex-pro: product subagent failure (${describeFacts(facts)})${cleanupDetail}`, cause === undefined ? undefined : { cause });
55
+ this.facts = facts;
56
+ this.cleanup = cleanup;
57
+ this.execution = observations?.execution;
58
+ this.configuration = observations?.configuration;
59
+ this.usage = observations?.usage;
60
+ this.rateLimits = observations?.rateLimits;
61
+ this.turnConfiguration = observations?.turnConfiguration;
62
+ this.name = 'CodexRunFailure';
63
+ }
64
+ }
65
+ /** The diagnostic for a delegation the deadline stopped. */
66
+ export function describeTimeout(timeoutMs) {
67
+ return `codex-pro: the delegation exceeded its ${String(timeoutMs)} ms deadline and was cancelled`;
68
+ }
69
+ /** Render failure facts for a diagnostic string. */
70
+ function describeFacts(facts) {
71
+ const fields = ['product: Codex', `stage: ${facts.stage}`, `category: ${facts.category}`];
72
+ if (facts.httpStatus !== undefined)
73
+ fields.push(`HTTP status: ${facts.httpStatus}`);
74
+ return fields.join('; ');
75
+ }
76
+ /** Normalise an unknown throwable. */
77
+ function thrown(value) {
78
+ return value instanceof Error ? value : new Error(String(value));
79
+ }
80
+ /** Append a bounded reason to an optional diagnostic. */
81
+ function appendDiagnostic(diagnostic, detail) {
82
+ return diagnostic === undefined ? detail : `${diagnostic}; ${detail}`;
83
+ }
84
+ /** A valid snapshot before interruption is a known lower bound, not a final bill. */
85
+ function partialUsage(usage, reason) {
86
+ if (usage.status !== 'complete')
87
+ return usage;
88
+ return { ...usage, status: 'partial', reason: usage.reason === undefined ? reason : `${usage.reason}; ${reason}` };
89
+ }
90
+ /** A short non-throwing delay. */
91
+ function delay(ms) {
92
+ return new Promise((resolve) => { setTimeout(resolve, ms); });
93
+ }
94
+ /**
95
+ * Validate and preserve the one-shot task before crossing the process boundary.
96
+ *
97
+ * @param prompt - the delegation's content blocks.
98
+ * @returns the exact non-empty text block sequence.
99
+ */
100
+ export function textTask(prompt) {
101
+ if (prompt.length === 0)
102
+ throw new Error('codex-pro: a delegation prompt must be text; images reach Codex through the '
103
+ + '`images` parameter, which takes absolute paths');
104
+ const texts = [];
105
+ for (const block of prompt) {
106
+ if (block.type !== 'text' || typeof block.text !== 'string') {
107
+ throw new Error('codex-pro: a delegation prompt must be text; images reach Codex through the '
108
+ + '`images` parameter, which takes absolute paths');
109
+ }
110
+ texts.push(block.text);
111
+ }
112
+ if (texts.every((text) => text.trim().length === 0)) {
113
+ throw new Error('codex-pro: the one-shot task must not be empty');
114
+ }
115
+ return texts;
116
+ }
117
+ /**
118
+ * Spawn the app-server and wrap its lifecycle.
119
+ *
120
+ * The child is detached into its own process group on POSIX so termination can
121
+ * target everything the delegation owns without a global `killall`. Termination
122
+ * is idempotent and resolves with a confirmation report rather than assuming
123
+ * that a signal is the same thing as a dead process.
124
+ *
125
+ * @param spec - the executable, working directory, and environment.
126
+ * @returns the managed child.
127
+ */
128
+ function spawnAppServer(spec) {
129
+ const posix = process.platform !== 'win32';
130
+ const graceMs = spec.disposeGraceMs ?? DEFAULT_DISPOSE_GRACE_MS;
131
+ // tsc rewrites import specifiers, not new URL strings. Native TS tests and
132
+ // the built npm package must each start their matching guardian artifact.
133
+ const extension = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
134
+ const guardian = fileURLToPath(new URL(`./process-guardian${extension}`, import.meta.url));
135
+ const proc = spawn(posix ? process.execPath : spec.command, posix ? [guardian, spec.command, String(graceMs)] : ['app-server', '--stdio'], {
136
+ cwd: spec.cwd,
137
+ env: { ...process.env, ...spec.env },
138
+ stdio: posix ? ['pipe', 'pipe', 'pipe', 'ipc'] : ['pipe', 'pipe', 'pipe'],
139
+ detached: posix,
140
+ windowsHide: true,
141
+ });
142
+ let actualPid = posix ? null : proc.pid ?? null;
143
+ let actualExit;
144
+ let stdoutEnded = false;
145
+ let drainTimer;
146
+ let outputIncomplete = false;
147
+ let supervisorFailed = false;
148
+ let supervisorExit;
149
+ let spawnError = null;
150
+ let terminationRequested = false;
151
+ let completeActual;
152
+ let failActual;
153
+ const done = new Promise((resolve, reject) => {
154
+ completeActual = resolve;
155
+ failActual = reject;
156
+ });
157
+ done.catch(() => { });
158
+ proc.stdout.once('end', () => {
159
+ stdoutEnded = true;
160
+ if (drainTimer !== undefined)
161
+ clearTimeout(drainTimer);
162
+ drainTimer = undefined;
163
+ if (actualExit !== undefined)
164
+ completeActual(actualExit);
165
+ else if (supervisorFailed || supervisorExit !== undefined) {
166
+ failActual(new CodexRunFailure({ stage: 'supervisor', category: 'process' }));
167
+ }
168
+ });
169
+ proc.on('message', (message) => {
170
+ if (message === null || typeof message !== 'object')
171
+ return;
172
+ const value = message;
173
+ if (value.type === 'started' && Number.isSafeInteger(value.pid) && Number(value.pid) > 0) {
174
+ actualPid = Number(value.pid);
175
+ }
176
+ else if (value.type === 'exit') {
177
+ actualExit = {
178
+ exitCode: typeof value.exitCode === 'number' ? value.exitCode : null,
179
+ signal: typeof value.signal === 'string' ? value.signal : null,
180
+ };
181
+ if (stdoutEnded)
182
+ completeActual(actualExit);
183
+ else if (drainTimer === undefined) {
184
+ // IPC and stdout use different pipes. Drain already-written final
185
+ // frames before declaring an early exit, but never wait indefinitely
186
+ // for a descendant that inherited stdout (even with runTimeoutMs=0).
187
+ drainTimer = setTimeout(() => {
188
+ outputIncomplete = true;
189
+ failActual(new Error('app-server exited but its output did not drain within 1000 ms'));
190
+ }, EXIT_DRAIN_TIMEOUT_MS);
191
+ drainTimer.unref?.();
192
+ }
193
+ }
194
+ else if (value.type === 'spawn-error') {
195
+ spawnError = typeof value.message === 'string' ? value.message : 'app-server spawn failed';
196
+ failActual(new Error(spawnError));
197
+ }
198
+ });
199
+ const supervisorDone = new Promise((resolve) => {
200
+ proc.once('error', (error) => {
201
+ spawnError = error.message;
202
+ failActual(error);
203
+ resolve();
204
+ });
205
+ proc.once('exit', (exitCode, signal) => {
206
+ if (posix) {
207
+ supervisorExit = { exitCode, signal };
208
+ supervisorFailed ||= !terminationRequested;
209
+ // Guardian liveness cannot depend on stdio close: its Codex child may
210
+ // still hold the inherited pipe. Allow queued final IPC/stdout frames
211
+ // a bounded drain, then fail and let the caller terminate the group.
212
+ if (actualExit === undefined && drainTimer === undefined) {
213
+ drainTimer = setTimeout(() => {
214
+ outputIncomplete = !stdoutEnded;
215
+ failActual(new CodexRunFailure({ stage: 'supervisor', category: 'process' }));
216
+ }, EXIT_DRAIN_TIMEOUT_MS);
217
+ drainTimer.unref?.();
218
+ }
219
+ }
220
+ else {
221
+ actualExit = { exitCode, signal };
222
+ if (stdoutEnded)
223
+ completeActual(actualExit);
224
+ else if (drainTimer === undefined) {
225
+ drainTimer = setTimeout(() => {
226
+ outputIncomplete = true;
227
+ failActual(new CodexRunFailure({ stage: 'process', category: 'output-incomplete' }));
228
+ }, EXIT_DRAIN_TIMEOUT_MS);
229
+ drainTimer.unref?.();
230
+ }
231
+ }
232
+ resolve();
233
+ });
234
+ proc.once('disconnect', () => {
235
+ if (!posix || terminationRequested)
236
+ return;
237
+ supervisorFailed = true;
238
+ if (actualExit !== undefined && stdoutEnded)
239
+ completeActual(actualExit);
240
+ else if (stdoutEnded)
241
+ failActual(new CodexRunFailure({ stage: 'supervisor', category: 'process' }));
242
+ else if (drainTimer === undefined) {
243
+ drainTimer = setTimeout(() => {
244
+ outputIncomplete = !stdoutEnded;
245
+ failActual(new CodexRunFailure({ stage: 'supervisor', category: 'process' }));
246
+ }, EXIT_DRAIN_TIMEOUT_MS);
247
+ drainTimer.unref?.();
248
+ }
249
+ });
250
+ proc.once('close', (exitCode, signal) => {
251
+ if (drainTimer !== undefined)
252
+ clearTimeout(drainTimer);
253
+ if (posix)
254
+ supervisorExit = { exitCode, signal };
255
+ else {
256
+ actualExit = { exitCode, signal };
257
+ completeActual(actualExit);
258
+ }
259
+ // A guardian exit is not an app-server exit. In particular, a group KILL
260
+ // can prevent the guardian from sending the actual child's final facts.
261
+ if (actualExit === undefined)
262
+ failActual(new Error('Codex guardian exited before an app-server exit receipt'));
263
+ resolve();
264
+ });
265
+ });
266
+ const groupPid = posix ? proc.pid : undefined;
267
+ let termination;
268
+ const signalErrors = [];
269
+ const groupState = () => {
270
+ if (groupPid !== undefined) {
271
+ try {
272
+ process.kill(-groupPid, 0);
273
+ return { alive: true };
274
+ }
275
+ catch (error) {
276
+ if (error.code === 'ESRCH')
277
+ return { alive: false };
278
+ return { alive: undefined, detail: `cannot inspect the owned process group: ${thrown(error).message}` };
279
+ }
280
+ }
281
+ return { alive: proc.exitCode === null && proc.signalCode === null };
282
+ };
283
+ const sendSignal = (signal) => {
284
+ terminationRequested = true;
285
+ try {
286
+ if (groupPid !== undefined)
287
+ process.kill(-groupPid, signal);
288
+ else
289
+ proc.kill(signal);
290
+ }
291
+ catch (error) {
292
+ if (error.code !== 'ESRCH') {
293
+ signalErrors.push(`${signal}: ${thrown(error).message}`);
294
+ }
295
+ }
296
+ };
297
+ const waitForExit = (timeoutMs) => new Promise((resolve) => {
298
+ const timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
299
+ supervisorDone.then(() => { clearTimeout(timer); resolve(true); });
300
+ });
301
+ const terminate = (grace) => {
302
+ if (termination !== undefined)
303
+ return termination;
304
+ termination = (async () => {
305
+ if (proc.pid === undefined)
306
+ return { confirmed: true };
307
+ sendSignal('SIGTERM');
308
+ await waitForExit(grace);
309
+ // A caller-requested guardian exit can happen before a descendant that
310
+ // inherited stdout has let the stream drain. Give the existing drain
311
+ // timer its bounded window before killing the group, so a held pipe is
312
+ // still reported as output-incomplete instead of being silently closed.
313
+ if (actualExit !== undefined && !stdoutEnded && !outputIncomplete) {
314
+ await delay(EXIT_DRAIN_TIMEOUT_MS);
315
+ }
316
+ if (groupState().alive !== false) {
317
+ sendSignal('SIGKILL');
318
+ await waitForExit(Math.min(Math.max(grace, 250), GROUP_REAP_TIMEOUT_MS));
319
+ }
320
+ const until = performance.now() + GROUP_REAP_TIMEOUT_MS;
321
+ let state = groupState();
322
+ while (state.alive !== false && performance.now() < until) {
323
+ await delay(25);
324
+ state = groupState();
325
+ }
326
+ // A signal can race the guardian's own group KILL. In particular, Darwin
327
+ // can return EPERM while a group contains only exiting/zombie members.
328
+ // That error is not proof of disappearance, but a later independent
329
+ // ESRCH is. Do not let a historical signal error override that proof.
330
+ if (state.alive === false)
331
+ return { confirmed: true };
332
+ return {
333
+ confirmed: false,
334
+ detail: [...signalErrors, state.detail ?? 'the owned process group was still alive after SIGKILL'].join('; '),
335
+ };
336
+ })();
337
+ return termination;
338
+ };
339
+ return {
340
+ proc, done, terminate, waitForExit,
341
+ outputIncomplete: () => outputIncomplete,
342
+ supervisorFailed: () => supervisorFailed,
343
+ releaseUnconfirmed: () => {
344
+ // A failed signal must not keep the caller alive forever through its
345
+ // own pipe/IPC references. Closing the lease lets the guardian make an
346
+ // independent cleanup attempt. This is not confirmation: the already
347
+ // failed receipt stays failed even if that later attempt succeeds.
348
+ try {
349
+ if (proc.connected)
350
+ proc.disconnect();
351
+ }
352
+ catch { /* lease already closed */ }
353
+ for (const stream of [proc.stdin, proc.stdout, proc.stderr]) {
354
+ try {
355
+ stream.destroy();
356
+ }
357
+ catch { /* already closed */ }
358
+ }
359
+ proc.unref();
360
+ },
361
+ facts: () => ({
362
+ pid: actualPid,
363
+ supervisorPid: posix ? proc.pid ?? null : null,
364
+ exitCode: actualExit?.exitCode ?? null,
365
+ signal: actualExit?.signal ?? null,
366
+ supervisorExitCode: supervisorExit?.exitCode ?? null,
367
+ supervisorSignal: supervisorExit?.signal ?? null,
368
+ terminationRequested,
369
+ spawnError,
370
+ }),
371
+ };
372
+ }
373
+ /** Build the argv, for diagnostics and tests. */
374
+ export function codexAppServerArgv(command) {
375
+ return [command, 'app-server', '--stdio'];
376
+ }
377
+ /** Build one failure for an unpublished startup. */
378
+ function startupFailure(stage, error, flags, cleanup) {
379
+ if (error instanceof CodexRunFailure) {
380
+ return new CodexRunFailure(error.facts, error.cause, cleanup);
381
+ }
382
+ const category = flags.timedOut
383
+ ? 'timeout'
384
+ : flags.callerAborted
385
+ ? 'aborted'
386
+ : stage === 'process'
387
+ ? 'process'
388
+ : 'protocol';
389
+ return new CodexRunFailure({ stage, category }, error, cleanup);
390
+ }
391
+ /**
392
+ * Start the app-server child and publish its one-shot run.
393
+ *
394
+ * Resolves only after initialization and thread creation/resume, so a failure
395
+ * before that point rejects with no run published. After publication the result
396
+ * never rejects; even a failed run carries its observed usage, images, thread,
397
+ * configuration, and cleanup report.
398
+ *
399
+ * @param prompt - the delegation's content blocks.
400
+ * @param spec - working directory, executable, permission mode, and choices.
401
+ * @param signal - caller cancellation.
402
+ * @returns the published run.
403
+ */
404
+ export async function startCodexRun(prompt, spec, signal) {
405
+ const id = spec.runId ?? randomUUID();
406
+ const startedAt = Date.now();
407
+ const startedMonotonic = performance.now();
408
+ let timedOut = false;
409
+ let callerAborted = false;
410
+ let truncated = false;
411
+ let initialSpawnError = null;
412
+ let observedChild;
413
+ let observedWire;
414
+ const execution = (failure) => ({
415
+ runId: id,
416
+ dispatch: observedWire?.dispatch ?? 'not-dispatched',
417
+ startedAt: new Date(startedAt).toISOString(),
418
+ finishedAt: new Date().toISOString(),
419
+ durationMs: Math.max(0, performance.now() - startedMonotonic),
420
+ threadId: observedWire?.threadId ?? null,
421
+ turnId: observedWire?.turnId ?? null,
422
+ ...(observedChild?.facts() ?? {
423
+ pid: null, supervisorPid: null, exitCode: null, signal: null,
424
+ supervisorExitCode: null, supervisorSignal: null,
425
+ terminationRequested: false, spawnError: initialSpawnError,
426
+ }),
427
+ timedOut, cancelled: callerAborted || signal.aborted,
428
+ truncated: truncated || observedChild?.outputIncomplete() === true, failure,
429
+ ownership: process.platform === 'win32' ? 'owned-process' : 'supervised-group',
430
+ });
431
+ const graceMs = spec.disposeGraceMs ?? DEFAULT_DISPOSE_GRACE_MS;
432
+ const requestedTimeout = spec.runTimeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
433
+ const maxOutputBytes = spec.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
434
+ let texts;
435
+ try {
436
+ texts = textTask(prompt);
437
+ if (!Number.isSafeInteger(graceMs) || graceMs < 0 || graceMs > 30_000) {
438
+ throw new Error('disposeGraceMs must be an integer between 0 and 30000');
439
+ }
440
+ if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes <= 0) {
441
+ throw new Error('maxOutputBytes must be a positive safe integer');
442
+ }
443
+ if (!Number.isSafeInteger(requestedTimeout) || requestedTimeout < 0 || requestedTimeout > MAX_TIMER_MS) {
444
+ throw new Error('runTimeoutMs must be an integer between 0 and 2147483647');
445
+ }
446
+ if (spec.deadlineAt !== undefined && (!Number.isSafeInteger(spec.deadlineAt) || spec.deadlineAt <= 0
447
+ || spec.deadlineAt - startedAt > MAX_TIMER_MS)) {
448
+ throw new Error('deadlineAt must be a positive safe timestamp within the supported timer range');
449
+ }
450
+ }
451
+ catch (error) {
452
+ const facts = { stage: 'request', category: 'invalid-request' };
453
+ throw new CodexRunFailure(facts, error, { confirmed: true }, { execution: execution(facts) });
454
+ }
455
+ if (signal.aborted) {
456
+ callerAborted = true;
457
+ const facts = { stage: 'initialize', category: 'aborted' };
458
+ throw new CodexRunFailure(facts, undefined, { confirmed: true }, { execution: execution(facts) });
459
+ }
460
+ const deadlineAt = spec.deadlineAt
461
+ ?? (requestedTimeout > 0 ? startedAt + requestedTimeout : undefined);
462
+ const timeoutDescriptionMs = deadlineAt === undefined ? requestedTimeout : Math.max(1, deadlineAt - startedAt);
463
+ let child;
464
+ try {
465
+ child = spawnAppServer(spec);
466
+ observedChild = child;
467
+ }
468
+ catch (error) {
469
+ initialSpawnError = thrown(error).message;
470
+ const facts = { stage: 'initialize', category: 'process' };
471
+ throw new CodexRunFailure(facts, error, { confirmed: true }, { execution: execution(facts) });
472
+ }
473
+ const runAbort = new AbortController();
474
+ let outputBytes = 0;
475
+ const limitOutput = (chunk) => {
476
+ outputBytes += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.byteLength;
477
+ if (outputBytes <= maxOutputBytes || truncated)
478
+ return;
479
+ truncated = true;
480
+ runAbort.abort(new Error(`codex-pro: output exceeded ${String(maxOutputBytes)} bytes`));
481
+ child.proc.stdout.destroy();
482
+ child.proc.stderr.destroy();
483
+ };
484
+ child.proc.stdout.on('data', limitOutput);
485
+ child.proc.stderr.on('data', limitOutput);
486
+ const transport = new LineTransport(child.proc.stdin, child.proc.stdout);
487
+ const wire = new CodexAppServerWire(child.proc.stdout, child.proc.stdin, transport, {
488
+ permissionMode: spec.permissionMode,
489
+ model: spec.model,
490
+ threadMode: spec.threadMode,
491
+ originator: spec.originator ?? DEFAULT_ORIGINATOR,
492
+ });
493
+ observedWire = wire;
494
+ if (spec.onProgress !== undefined)
495
+ wire.setProgressReporter(spec.onProgress);
496
+ const forwardStderr = (chunk) => {
497
+ if (truncated)
498
+ return;
499
+ try {
500
+ process.stderr.write(typeof chunk === 'string' ? chunk : chunk.toString('utf8'));
501
+ }
502
+ catch {
503
+ // Diagnostics must never take the run down.
504
+ }
505
+ };
506
+ child.proc.stderr.on('data', forwardStderr);
507
+ let timer;
508
+ const abortRun = () => {
509
+ if (runAbort.signal.aborted)
510
+ return;
511
+ runAbort.abort(new Error('codex-pro: run cancelled locally'));
512
+ wire.interrupt();
513
+ };
514
+ const requestCancel = () => {
515
+ callerAborted = true;
516
+ abortRun();
517
+ };
518
+ const onCallerAbort = () => {
519
+ requestCancel();
520
+ };
521
+ if (deadlineAt !== undefined) {
522
+ timer = setTimeout(() => {
523
+ timedOut = true;
524
+ abortRun();
525
+ }, Math.max(0, deadlineAt - Date.now()));
526
+ timer.unref?.();
527
+ }
528
+ signal.addEventListener('abort', onCallerAbort, { once: true });
529
+ // A signal that aborted between the pre-spawn check and this listener would
530
+ // otherwise never deliver its event; the run must still observe it.
531
+ if (signal.aborted)
532
+ onCallerAbort();
533
+ const teardown = async () => {
534
+ if (timer !== undefined)
535
+ clearTimeout(timer);
536
+ timer = undefined;
537
+ wire.close();
538
+ try {
539
+ child.proc.stdin.end();
540
+ }
541
+ catch { /* already closed */ }
542
+ const cleanup = await child.terminate(graceMs);
543
+ if (!cleanup.confirmed)
544
+ child.releaseUnconfirmed();
545
+ signal.removeEventListener('abort', onCallerAbort);
546
+ child.proc.stderr.off('data', forwardStderr);
547
+ child.proc.stdout.off('data', limitOutput);
548
+ child.proc.stderr.off('data', limitOutput);
549
+ return cleanup;
550
+ };
551
+ let tornDown;
552
+ const teardownOnce = () => (tornDown ??= teardown());
553
+ let processFailureFacts;
554
+ const processFailure = child.done.then(() => {
555
+ processFailureFacts = { stage: 'process', category: 'process' };
556
+ throw new CodexRunFailure(processFailureFacts);
557
+ }, (error) => {
558
+ processFailureFacts = error instanceof CodexRunFailure ? error.facts : { stage: 'process', category: 'process' };
559
+ throw new CodexRunFailure(processFailureFacts, error);
560
+ });
561
+ processFailure.catch(() => { });
562
+ const lifecycle = spec.onLifecycle === undefined ? undefined : async (event) => {
563
+ await raceAbort(Promise.race([Promise.resolve().then(() => spec.onLifecycle({ event, runId: id,
564
+ threadId: wire.threadId ?? null, turnId: wire.turnId ?? null, configuration: wire.configuration ?? null })),
565
+ processFailure]), runAbort.signal);
566
+ };
567
+ let startupStage = 'initialize';
568
+ try {
569
+ await Promise.race([wire.initialize(runAbort.signal), processFailure]);
570
+ if (spec.resumeThreadId === undefined) {
571
+ startupStage = 'thread-start';
572
+ await Promise.race([wire.startThread(spec.cwd, runAbort.signal), processFailure]);
573
+ }
574
+ else {
575
+ startupStage = 'thread-resume';
576
+ const overrides = {
577
+ cwd: spec.cwd,
578
+ ...(spec.model === undefined ? {} : { model: spec.model }),
579
+ permission: spec.permissionMode,
580
+ };
581
+ await Promise.race([wire.resumeThread(spec.resumeThreadId, overrides, runAbort.signal), processFailure]);
582
+ }
583
+ // A persistent thread is named so the Codex application's list says where it
584
+ // came from; the id alone cannot, because the originator has to be the value
585
+ // that client filters on.
586
+ if (spec.threadMode === 'persistent' && spec.threadName !== undefined) {
587
+ startupStage = 'thread-name';
588
+ await Promise.race([wire.setThreadName(spec.threadName, runAbort.signal), processFailure]);
589
+ }
590
+ if (lifecycle !== undefined)
591
+ await lifecycle('thread-ready');
592
+ }
593
+ catch (error) {
594
+ const failureStage = processFailureFacts === undefined ? startupStage : 'process';
595
+ const cleanup = await teardownOnce();
596
+ const failure = startupFailure(failureStage, error, { timedOut, callerAborted }, cleanup);
597
+ const facts = truncated ? { stage: startupStage, category: 'output-limit' }
598
+ : child.outputIncomplete() ? { stage: startupStage, category: 'output-incomplete' }
599
+ : failure.facts;
600
+ throw new CodexRunFailure(facts, error, cleanup, {
601
+ execution: execution(facts), configuration: wire.configuration,
602
+ usage: partialUsage(wire.usageReport, 'startup ended without a completed turn'), rateLimits: wire.rateLimitsReport,
603
+ turnConfiguration: wire.turnConfiguration,
604
+ });
605
+ }
606
+ const finish = async (turn, error) => {
607
+ let failure = error instanceof CodexRunFailure
608
+ ? error.facts
609
+ : error === undefined ? null : { stage: 'turn', category: 'protocol' };
610
+ const cleanup = await teardownOnce();
611
+ callerAborted ||= signal.aborted;
612
+ const effectiveTruncated = truncated || child.outputIncomplete();
613
+ const usage = turn === undefined || timedOut || callerAborted || effectiveTruncated || !cleanup.confirmed
614
+ ? partialUsage(wire.usageReport, 'run ended without a reliable terminal usage boundary')
615
+ : wire.usageReport;
616
+ const rateLimits = wire.rateLimitsReport;
617
+ const configuration = wire.configuration;
618
+ const observedImages = turn?.images ?? wire.images;
619
+ let output = turn?.output ?? wire.partialOutput;
620
+ let stopReason = effectiveTruncated ? 'error'
621
+ : timedOut ? 'timeout'
622
+ : callerAborted ? 'aborted'
623
+ : turn?.stopReason ?? 'error';
624
+ const approvalDiagnostic = wire.collectDiagnostic();
625
+ let diagnostic = approvalDiagnostic;
626
+ let structured;
627
+ let schemaError;
628
+ const processFacts = child.facts();
629
+ if (child.supervisorFailed()) {
630
+ if (stopReason === 'completed')
631
+ stopReason = 'error';
632
+ failure = { stage: 'supervisor', category: 'process' };
633
+ diagnostic = appendDiagnostic(diagnostic, 'Codex guardian exited before cleanup was requested');
634
+ }
635
+ if ((processFacts.exitCode !== null && processFacts.exitCode !== 0)
636
+ || (processFacts.signal !== null && !['SIGTERM', 'SIGKILL'].includes(processFacts.signal))) {
637
+ if (stopReason === 'completed')
638
+ stopReason = 'error';
639
+ failure = { stage: 'process', category: 'process' };
640
+ diagnostic = appendDiagnostic(diagnostic, `app-server exited unexpectedly (code=${String(processFacts.exitCode)}, signal=${String(processFacts.signal)})`);
641
+ }
642
+ if (spec.outputSchema !== undefined && stopReason === 'completed') {
643
+ const text = output.map((block) => block.text).join('\n\n');
644
+ const verification = verifyStructuredOutput(text, spec.outputSchema);
645
+ if (verification.ok) {
646
+ structured = verification.value;
647
+ }
648
+ else {
649
+ schemaError = verification.error;
650
+ // A schema was requested: an unparseable or invalid answer is not a
651
+ // structured success. Preserve the raw output and say why it failed.
652
+ if (stopReason === 'completed')
653
+ stopReason = 'error';
654
+ failure = { stage: 'output', category: 'schema' };
655
+ diagnostic = diagnostic === undefined ? schemaError : `${diagnostic}; ${schemaError}`;
656
+ }
657
+ }
658
+ if (!cleanup.confirmed) {
659
+ if (stopReason === 'completed')
660
+ stopReason = 'error';
661
+ failure ??= { stage: 'cleanup', category: 'cleanup' };
662
+ const cleanupDetail = `process cleanup could not be confirmed${cleanup.detail === undefined ? '' : `: ${cleanup.detail}`}`;
663
+ diagnostic = diagnostic === undefined ? cleanupDetail : `${diagnostic}; ${cleanupDetail}`;
664
+ }
665
+ if (effectiveTruncated) {
666
+ if (truncated)
667
+ failure = { stage: 'output', category: 'output-limit' };
668
+ else
669
+ failure ??= { stage: 'output', category: 'output-incomplete' };
670
+ const detail = truncated
671
+ ? `codex-pro: output exceeded ${String(maxOutputBytes)} bytes and was truncated`
672
+ : 'codex-pro: app-server output could not be completely drained';
673
+ diagnostic = diagnostic === undefined ? detail : `${diagnostic}; ${detail}`;
674
+ }
675
+ else if (timedOut) {
676
+ failure = { stage: 'turn', category: 'timeout' };
677
+ const timeoutDetail = describeTimeout(timeoutDescriptionMs);
678
+ diagnostic = diagnostic === undefined ? timeoutDetail : `${diagnostic}; ${timeoutDetail}`;
679
+ }
680
+ else if (callerAborted) {
681
+ failure = { stage: 'turn', category: 'aborted' };
682
+ const abortDetail = 'codex-pro: the delegation was cancelled by its caller';
683
+ diagnostic = diagnostic === undefined ? abortDetail : `${diagnostic}; ${abortDetail}`;
684
+ }
685
+ else if (error !== undefined && diagnostic === undefined) {
686
+ diagnostic = error instanceof Error ? error.message : String(error);
687
+ }
688
+ else if (error !== undefined && !(error instanceof CodexRunFailure)) {
689
+ diagnostic = `${diagnostic}; ${error instanceof Error ? error.message : String(error)}`;
690
+ }
691
+ if (stopReason !== 'completed' && failure === null) {
692
+ failure = { stage: 'turn', category: stopReason };
693
+ }
694
+ return {
695
+ output,
696
+ stopReason,
697
+ ...(diagnostic === undefined ? {} : { diagnostic }),
698
+ ...(approvalDiagnostic === undefined ? {} : { approvalDiagnostic }),
699
+ threadId: wire.threadId,
700
+ images: observedImages,
701
+ usage,
702
+ rateLimits,
703
+ ...(configuration === undefined ? {} : { configuration }),
704
+ cleanup,
705
+ execution: execution(failure),
706
+ ...(wire.turnConfiguration === undefined ? {} : { turnConfiguration: wire.turnConfiguration }),
707
+ ...(structured === undefined ? {} : { structured }),
708
+ ...(schemaError === undefined ? {} : { schemaError }),
709
+ };
710
+ };
711
+ const result = (async () => {
712
+ try {
713
+ const turn = await Promise.race([
714
+ wire.runTurn(texts, runAbort.signal, spec.effort ?? spec.reasoningEffort, spec.images ?? [], spec.outputSchema, lifecycle),
715
+ processFailure,
716
+ ]);
717
+ return await finish(turn, undefined);
718
+ }
719
+ catch (error) {
720
+ return await finish(undefined, error);
721
+ }
722
+ })();
723
+ return {
724
+ id,
725
+ result,
726
+ requestCancel,
727
+ teardown: teardownOnce,
728
+ };
729
+ }
730
+ /**
731
+ * Decide which thread one call continues.
732
+ *
733
+ * `session` mode has one thread per Agent session, but a thread carries one turn
734
+ * at a time: two delegations sent together would otherwise both reach for it and
735
+ * the second would fail. A remembered thread that another delegation is already
736
+ * using is therefore skipped, and this call gets a thread of its own — which is
737
+ * what `persistent` does anyway, so parallel work degrades to the mode that
738
+ * supports it instead of failing and retrying.
739
+ *
740
+ * An id the caller passed explicitly is never skipped: continuing *that* thread
741
+ * was the request, and silently running somewhere else would answer a different
742
+ * question.
743
+ *
744
+ * @param input - the call's explicit id, the configured mode, the session's
745
+ * remembered thread, and the threads currently in flight.
746
+ * @returns the thread to continue and the remembered one that was busy.
747
+ */
748
+ export function chooseThread(input) {
749
+ if (input.explicit !== undefined)
750
+ return { reuse: input.explicit, skippedBusy: undefined };
751
+ if (input.configured !== 'session' || input.remembered === undefined) {
752
+ return { reuse: undefined, skippedBusy: undefined };
753
+ }
754
+ if (input.inFlight.has(input.remembered)) {
755
+ return { reuse: undefined, skippedBusy: input.remembered };
756
+ }
757
+ return { reuse: input.remembered, skippedBusy: undefined };
758
+ }
759
+ /**
760
+ * A delegation that did not complete.
761
+ *
762
+ * Carries the thread, images, and (when publication reached the run) the whole
763
+ * outcome, so a caller can report observed usage even though the run failed.
764
+ */
765
+ export class CodexDelegationFailure extends Error {
766
+ /** The thread the failed run used, when the app-server had got that far. */
767
+ threadId;
768
+ /** Images the failed run produced before it failed. */
769
+ images;
770
+ /** Everything observed about the failed run, when one was published. */
771
+ outcome;
772
+ constructor(message, threadId, images = [], outcome) {
773
+ super(message);
774
+ this.name = 'CodexDelegationFailure';
775
+ this.threadId = threadId;
776
+ this.images = images;
777
+ this.outcome = outcome;
778
+ }
779
+ }
780
+ /** Run one delegation and return its settled outcome. */
781
+ export async function runCodexOnce(options) {
782
+ const run = await startCodexRun([{ type: 'text', text: options.prompt }], {
783
+ cwd: options.cwd,
784
+ command: options.command,
785
+ permissionMode: options.permissionMode,
786
+ model: options.model,
787
+ effort: options.reasoningEffort,
788
+ threadMode: options.threadMode,
789
+ resumeThreadId: options.threadId,
790
+ originator: options.originator,
791
+ runTimeoutMs: options.runTimeoutMs,
792
+ ...(options.deadlineAt === undefined ? {} : { deadlineAt: options.deadlineAt }),
793
+ images: options.images,
794
+ outputSchema: options.outputSchema,
795
+ threadName: options.threadName,
796
+ env: options.env,
797
+ disposeGraceMs: options.disposeGraceMs,
798
+ maxOutputBytes: options.maxOutputBytes,
799
+ onProgress: options.onProgress,
800
+ }, options.signal);
801
+ options.onStart?.(run.id);
802
+ try {
803
+ return await run.result;
804
+ }
805
+ finally {
806
+ await run.teardown();
807
+ }
808
+ }
809
+ /**
810
+ * Run one delegation to completion and return its final text.
811
+ *
812
+ * The result keeps the thread, images, usage, configuration and cleanup report
813
+ * available to callers that need to report more than the answer. A run that
814
+ * does not complete rejects with the whole outcome attached.
815
+ *
816
+ * @param options - the task, its working directory, and the choices.
817
+ * @returns the child's final message and its observability fields.
818
+ * @throws `CodexDelegationFailure` when the run does not complete.
819
+ */
820
+ export async function delegateOnce(options) {
821
+ const outcome = await runCodexOnce(options);
822
+ const text = outcome.output.map((block) => block.text).join('\n\n');
823
+ if (outcome.stopReason !== 'completed'
824
+ || text === ''
825
+ || outcome.schemaError !== undefined
826
+ || !outcome.cleanup.confirmed) {
827
+ throw new CodexDelegationFailure(outcome.diagnostic ?? `codex-pro: the run ended as ${outcome.stopReason}`, outcome.threadId, outcome.images ?? [], outcome);
828
+ }
829
+ return {
830
+ text,
831
+ threadId: outcome.threadId,
832
+ images: outcome.images ?? [],
833
+ usage: outcome.usage,
834
+ rateLimits: outcome.rateLimits,
835
+ ...(outcome.configuration === undefined ? {} : { configuration: outcome.configuration }),
836
+ cleanup: outcome.cleanup,
837
+ execution: outcome.execution,
838
+ ...(outcome.turnConfiguration === undefined ? {} : { turnConfiguration: outcome.turnConfiguration }),
839
+ ...(outcome.structured === undefined ? {} : { structured: outcome.structured }),
840
+ };
841
+ }
842
+ //# sourceMappingURL=run.js.map