@wjarka/cezarion 0.14.8 → 0.14.9-dev.829
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/README.md +76 -2
- package/dist/application-update/bin-recovery.d.ts +5 -0
- package/dist/application-update/bin-recovery.js +96 -0
- package/dist/application-update/bin-recovery.js.map +1 -0
- package/dist/application-update/discovery.d.ts +22 -0
- package/dist/application-update/discovery.js +91 -0
- package/dist/application-update/discovery.js.map +1 -0
- package/dist/application-update/helper.d.ts +57 -0
- package/dist/application-update/helper.js +374 -0
- package/dist/application-update/helper.js.map +1 -0
- package/dist/application-update/launcher.d.ts +8 -0
- package/dist/application-update/launcher.js +53 -0
- package/dist/application-update/launcher.js.map +1 -0
- package/dist/application-update/lock.d.ts +4 -0
- package/dist/application-update/lock.js +70 -0
- package/dist/application-update/lock.js.map +1 -0
- package/dist/application-update/npm-process.d.ts +17 -0
- package/dist/application-update/npm-process.js +133 -0
- package/dist/application-update/npm-process.js.map +1 -0
- package/dist/application-update/service.d.ts +69 -0
- package/dist/application-update/service.js +456 -0
- package/dist/application-update/service.js.map +1 -0
- package/dist/artifacts/cli.d.ts +1 -0
- package/dist/artifacts/cli.js +32 -0
- package/dist/artifacts/cli.js.map +1 -0
- package/dist/artifacts/lifecycle.d.ts +4 -0
- package/dist/artifacts/lifecycle.js +42 -0
- package/dist/artifacts/lifecycle.js.map +1 -0
- package/dist/artifacts/resolve.d.ts +18 -0
- package/dist/artifacts/resolve.js +102 -0
- package/dist/artifacts/resolve.js.map +1 -0
- package/dist/artifacts/store.d.ts +8 -0
- package/dist/artifacts/store.js +323 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/ci-wait/mcp.d.ts +3 -0
- package/dist/ci-wait/mcp.js +14 -3
- package/dist/ci-wait/mcp.js.map +1 -1
- package/dist/contract/application-update.d.ts +32 -0
- package/dist/contract/artifacts.d.ts +83 -0
- package/dist/contract/ask-schema.d.ts +32 -0
- package/dist/contract/ask.d.ts +2 -32
- package/dist/contract/conversations.d.ts +165 -0
- package/dist/contract/delegation.d.ts +39 -0
- package/dist/contract/events.d.ts +49 -0
- package/dist/contract/health.d.ts +12 -0
- package/dist/contract/index.d.ts +2 -0
- package/dist/contract/index.js +1374 -1247
- package/dist/contract/runs.d.ts +143 -0
- package/dist/contract/skills.d.ts +28 -0
- package/dist/core/agent-runner.d.ts +33 -4
- package/dist/core/agent-runner.js +5 -0
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/claude-cli-runner.d.ts +6 -12
- package/dist/core/claude-cli-runner.js +89 -19
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/codex-app-server-runner.d.ts +2 -1
- package/dist/core/codex-app-server-runner.js +327 -92
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.d.ts +13 -1
- package/dist/core/codex-app-server-transport.js +58 -17
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/cursor-acp-runner.d.ts +4 -1
- package/dist/core/cursor-acp-runner.js +3 -0
- package/dist/core/cursor-acp-runner.js.map +1 -1
- package/dist/core/cursor-provider-error.js +3 -1
- package/dist/core/cursor-provider-error.js.map +1 -1
- package/dist/core/input-submissions.d.ts +14 -0
- package/dist/core/input-submissions.js +26 -0
- package/dist/core/input-submissions.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +4 -1
- package/dist/core/opencode-server-runner.js +144 -21
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/pi-runner.d.ts +2 -1
- package/dist/core/pi-runner.js +72 -7
- package/dist/core/pi-runner.js.map +1 -1
- package/dist/delegation/cli.js +102 -6
- package/dist/delegation/cli.js.map +1 -1
- package/dist/delegation/conversations.js +13 -5
- package/dist/delegation/conversations.js.map +1 -1
- package/dist/delegation/input.d.ts +2 -1
- package/dist/delegation/input.js +6 -1
- package/dist/delegation/input.js.map +1 -1
- package/dist/delegation/provision.js +5 -3
- package/dist/delegation/provision.js.map +1 -1
- package/dist/delegation/questions.d.ts +19 -0
- package/dist/delegation/questions.js +45 -0
- package/dist/delegation/questions.js.map +1 -0
- package/dist/delegation/results.d.ts +1 -1
- package/dist/delegation/results.js +23 -13
- package/dist/delegation/results.js.map +1 -1
- package/dist/delegation/routes.d.ts +142 -15
- package/dist/delegation/routes.js +4 -1
- package/dist/delegation/routes.js.map +1 -1
- package/dist/delegation/service.d.ts +9 -5
- package/dist/delegation/service.js +106 -7
- package/dist/delegation/service.js.map +1 -1
- package/dist/delegation/transport.d.ts +138 -11
- package/dist/index.js +72 -26
- package/dist/index.js.map +1 -1
- package/dist/open-url.d.ts +2 -0
- package/dist/open-url.js +20 -0
- package/dist/open-url.js.map +1 -0
- package/dist/release/snapshot.d.ts +3 -3
- package/dist/release/snapshot.js +6 -6
- package/dist/release/snapshot.js.map +1 -1
- package/dist/runs/client-request.d.ts +11 -0
- package/dist/runs/client-request.js +26 -0
- package/dist/runs/client-request.js.map +1 -0
- package/dist/runs/delegation-state.d.ts +17 -0
- package/dist/runs/event-history.js +6 -0
- package/dist/runs/event-history.js.map +1 -1
- package/dist/runs/store.d.ts +78 -6
- package/dist/runs/store.js +163 -1
- package/dist/runs/store.js.map +1 -1
- package/dist/server/server.d.ts +3761 -1648
- package/dist/server/server.js +170 -4
- package/dist/server/server.js.map +1 -1
- package/dist/task-cli/cli.d.ts +38 -0
- package/dist/task-cli/cli.js +463 -0
- package/dist/task-cli/cli.js.map +1 -0
- package/dist/task-cli/discovery.d.ts +22 -0
- package/dist/task-cli/discovery.js +95 -0
- package/dist/task-cli/discovery.js.map +1 -0
- package/dist/task-cli/http.d.ts +43 -0
- package/dist/task-cli/http.js +101 -0
- package/dist/task-cli/http.js.map +1 -0
- package/dist/task-cli/projections.d.ts +40 -0
- package/dist/task-cli/projections.js +45 -0
- package/dist/task-cli/projections.js.map +1 -0
- package/dist/task-cli/watch.d.ts +78 -0
- package/dist/task-cli/watch.js +336 -0
- package/dist/task-cli/watch.js.map +1 -0
- package/dist/workflows/run.d.ts +93 -0
- package/dist/workflows/run.js +816 -81
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/projects-cli.js +1 -1
- package/package.json +3 -3
- package/scripts/mock-claude.mjs +38 -2
- package/scripts/mock-codex-app-server.mjs +98 -1
- package/scripts/mock-cursor-acp.mjs +3 -0
- package/scripts/mock-opencode-serve.mjs +69 -2
- package/scripts/mock-pi-rpc.mjs +63 -2
- package/web/dist/assets/agent-accounts-JPfZPgKn.js +1 -0
- package/web/dist/assets/centered-state-HwCqQkH3.js +43 -0
- package/web/dist/assets/collapsible-CTymFdL7.js +1 -0
- package/web/dist/assets/commit-list-BZpYGQl2.js +1 -0
- package/web/dist/assets/compare-variants-9VkybaQ7.js +1 -0
- package/web/dist/assets/{diff-tRE59UNb2.js → diff-CZcD4D1r2.js} +2 -2
- package/web/dist/assets/diff-controls-CpcHG3Mm.js +1 -0
- package/web/dist/assets/{diff-stat-Cgnl_hYF.js → diff-stat-Cn7rzy1H.js} +1 -1
- package/web/dist/assets/{diff-view-DN1ort7Q.js → diff-view-C46nHiP2.js} +2 -2
- package/web/dist/assets/dropdown-menu-_YAzCaoQ.js +1 -0
- package/web/dist/assets/git-toolbar-BvKDz6uS.js +1 -0
- package/web/dist/assets/github-CgjJCGrD.js +1 -0
- package/web/dist/assets/{image-preview-C2JGcBlC.js → image-preview-B618kDBH.js} +1 -1
- package/web/dist/assets/index-BJtve0Pm.js +7 -0
- package/web/dist/assets/index-D0WAgmTQ.css +1 -0
- package/web/dist/assets/markdown-CFWZnvMh.js +2 -0
- package/web/dist/assets/pill-QfwkuR50.js +1 -0
- package/web/dist/assets/project-router-CkAA39N5.js +1 -0
- package/web/dist/assets/prompt-templates-Cfna_S0T.js +15 -0
- package/web/dist/assets/repo-git-DgzgJ0cP.js +1 -0
- package/web/dist/assets/{run-diff-CyG0Nciv.js → run-diff-YbCEQn5I.js} +2 -2
- package/web/dist/assets/run-header-DMgIbr1d.js +1 -0
- package/web/dist/assets/skills-BW79kUTd.js +1 -0
- package/web/dist/assets/{tab-link-BWNp3mLY.js → tab-link-MIHvczEm.js} +1 -1
- package/web/dist/assets/task-changes-C96ekt94.js +1 -0
- package/web/dist/assets/task-commits-DBfcMZyG.js +1 -0
- package/web/dist/assets/task-files-BKpH8dKQ.js +2 -0
- package/web/dist/assets/task-thread-Bw_nQuTx.css +1 -0
- package/web/dist/assets/task-thread-Dnq7Ek6W.js +9 -0
- package/web/dist/assets/textarea-D2vAVgYW.js +1 -0
- package/web/dist/assets/thread-loading-NW8Mthco.js +1 -0
- package/web/dist/assets/token-metrics-D188e6_0.js +1 -0
- package/web/dist/assets/utils-DKheTxGD.js +64 -0
- package/web/dist/assets/workflows-BFY1hgp3.js +11 -0
- package/web/dist/assets/{zoomable-image-Z3JDnm0b.js → zoomable-image-YW3Qdrr_.js} +1 -1
- package/web/dist/index.html +14 -14
- package/web/dist/assets/agent-accounts-BZm_2WAv.js +0 -1
- package/web/dist/assets/alert-dialog-B422RI-k.js +0 -43
- package/web/dist/assets/collapsible-Cy_Fmkrd.js +0 -1
- package/web/dist/assets/commit-list-Dcnk7TnI.js +0 -1
- package/web/dist/assets/compare-variants-Ciqwvd_f.js +0 -1
- package/web/dist/assets/diff-controls-BGrXGi7O.js +0 -1
- package/web/dist/assets/dropdown-menu-dwDGT8MM.js +0 -1
- package/web/dist/assets/git-toolbar-UiHnGvE0.js +0 -1
- package/web/dist/assets/github-D-MrkbJ7.js +0 -1
- package/web/dist/assets/ibm-plex-mono-regular-BF3vfe7_.ttf +0 -0
- package/web/dist/assets/index-BszfYvBq.js +0 -7
- package/web/dist/assets/index-CWh4pKP3.css +0 -1
- package/web/dist/assets/markdown-C_IcKVQd.js +0 -2
- package/web/dist/assets/pill-BsbO6G7y.js +0 -1
- package/web/dist/assets/project-router-DbLkll25.js +0 -1
- package/web/dist/assets/prompt-templates-BlTGJB5W.js +0 -15
- package/web/dist/assets/repo-git-Bm3s6Why.js +0 -1
- package/web/dist/assets/run-header-IFyIGCey.js +0 -1
- package/web/dist/assets/skills-C-C4DVFY.js +0 -1
- package/web/dist/assets/task-changes-D2PHtWUW.js +0 -1
- package/web/dist/assets/task-commits-N5u9U0_R.js +0 -1
- package/web/dist/assets/task-files-BS7c7CFm.js +0 -2
- package/web/dist/assets/task-thread-BQFdz1dJ.css +0 -1
- package/web/dist/assets/task-thread-DGM2dZV-.js +0 -9
- package/web/dist/assets/textarea-FjaU370i.js +0 -1
- package/web/dist/assets/thread-loading-77sJ8Yu_.js +0 -1
- package/web/dist/assets/token-metrics-Clxh03bE.js +0 -1
- package/web/dist/assets/utils-PYYcpCFZ.js +0 -64
- package/web/dist/assets/workflows-Czl0ZfXH.js +0 -11
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { parseEffort } from '../contract/index.js';
|
|
2
3
|
import { isSignalTerminationExit, prependSystemPrompt, trackChildExit } from './agent-runner.js';
|
|
3
4
|
import { AUTO_END_DELAY_MS, DEFAULT_RUN_TIMEOUT_MS, KILL_GRACE_MS, } from './runner-runtime.js';
|
|
4
5
|
import { parseAskRequest } from './ask.js';
|
|
5
6
|
import { readNdjson } from './ndjson.js';
|
|
6
7
|
import { V1TextCoalescer } from './v1-text-coalescer.js';
|
|
8
|
+
import { InputSubmissions } from './input-submissions.js';
|
|
7
9
|
import { codexTurnOutcome } from './codex-turn-outcome.js';
|
|
8
|
-
import { CodexAppServerRpc, codexSpawnError, endCodexAppServer, resolveCodexExecutable, spawnCodexAppServer, waitForCodexAppServerExit, } from './codex-app-server-transport.js';
|
|
10
|
+
import { CodexAppServerRpc, CodexRpcResponseError, codexSpawnError, endCodexAppServer, resolveCodexExecutable, spawnCodexAppServer, waitForCodexAppServerExit, } from './codex-app-server-transport.js';
|
|
9
11
|
import { codexSessionStarted, createCodexUiState, mapCodexNotification, } from './codex-ui-mapper.js';
|
|
12
|
+
// One startup budget, including handshake ACKs and the first main-thread turn.
|
|
13
|
+
// This is not a model-turn or human-question lifetime limit.
|
|
14
|
+
const STARTUP_TIMEOUT_MS = 60_000;
|
|
15
|
+
// After real process exit, allow buffered output to drain before closing pipes
|
|
16
|
+
// a descendant may have inherited. This timer never proves termination.
|
|
17
|
+
const EXIT_DRAIN_MS = 250;
|
|
10
18
|
/**
|
|
11
19
|
* `AgentRunner` over `codex app-server` — the same JSONL transport the VS Code
|
|
12
20
|
* extension and desktop app use (JSON-RPC 2.0, newline-delimited, over
|
|
@@ -50,6 +58,12 @@ export const CODEX_SPEC_SUPPORT = {
|
|
|
50
58
|
export class CodexAppServerRunner {
|
|
51
59
|
backend = 'codex';
|
|
52
60
|
specSupport = CODEX_SPEC_SUPPORT;
|
|
61
|
+
inputDelivery = {
|
|
62
|
+
mode: 'steer', consumption: 'observable',
|
|
63
|
+
// The userMessage item marks the input entering the thread's history (probe 2026-09-24:
|
|
64
|
+
// 0.8 s and 26.7 s after the steer, both before the tool ended); the next model call reads it.
|
|
65
|
+
via: 'turn/steer with clientUserMessageId; item/started userMessage clientId once in the thread history',
|
|
66
|
+
};
|
|
53
67
|
bin;
|
|
54
68
|
timeoutMs;
|
|
55
69
|
lastSession = null;
|
|
@@ -82,6 +96,17 @@ class CodexSession {
|
|
|
82
96
|
threadId;
|
|
83
97
|
activeTurnId;
|
|
84
98
|
agentInputReady = false;
|
|
99
|
+
/** Accepted agent input not yet seen as a consumed userMessage item (#505). */
|
|
100
|
+
submissions = new InputSubmissions();
|
|
101
|
+
inFlightSubmissionId;
|
|
102
|
+
refusedBeforeStartup = false;
|
|
103
|
+
humanPromptsPending = 0;
|
|
104
|
+
refusedForHuman = false;
|
|
105
|
+
/** Submissions that opened a turn via turn/start rather than steering one. */
|
|
106
|
+
turnStartSubmissions = new Set();
|
|
107
|
+
/** Recent main-thread turn outcomes (true = completed without error), for a late turn/start response. */
|
|
108
|
+
completedTurns = new Map();
|
|
109
|
+
turnCompletions = 0;
|
|
85
110
|
agentSubmissionPending = false;
|
|
86
111
|
turnBoundaryVersion = 0;
|
|
87
112
|
pendingUserInput;
|
|
@@ -97,6 +122,14 @@ class CodexSession {
|
|
|
97
122
|
});
|
|
98
123
|
tokensUsed = 0;
|
|
99
124
|
ready;
|
|
125
|
+
startupPhase = 'initialize';
|
|
126
|
+
openingAcknowledged = false;
|
|
127
|
+
firstTurnStarted = false;
|
|
128
|
+
startupComplete = false;
|
|
129
|
+
startupTimer;
|
|
130
|
+
stopKillTimer;
|
|
131
|
+
hardStopStarted = false;
|
|
132
|
+
failure;
|
|
100
133
|
autoEndTimer;
|
|
101
134
|
eofTermTimer;
|
|
102
135
|
eofKillTimer;
|
|
@@ -121,98 +154,130 @@ class CodexSession {
|
|
|
121
154
|
this.opts = opts;
|
|
122
155
|
try {
|
|
123
156
|
this.child = spawnCodexAppServer(bin, spec.cwd, spec.env);
|
|
124
|
-
this.rpc = new CodexAppServerRpc(this.child)
|
|
157
|
+
this.rpc = new CodexAppServerRpc(this.child, (error) => {
|
|
158
|
+
if (!this.hardStopStarted && !this.hasExited())
|
|
159
|
+
this.fail(error);
|
|
160
|
+
});
|
|
125
161
|
}
|
|
126
162
|
catch (err) {
|
|
127
163
|
throw codexSpawnError(err, bin);
|
|
128
164
|
}
|
|
129
|
-
|
|
165
|
+
const hasExited = trackChildExit(this.child);
|
|
166
|
+
this.hasExited = hasExited;
|
|
167
|
+
// Observe termination BEFORE bootstrap. Pending RPCs cannot own the only
|
|
168
|
+
// path to discovering that their process has already gone (#493).
|
|
169
|
+
const exited = waitForCodexAppServerExit(this.child);
|
|
170
|
+
let drainTimer;
|
|
130
171
|
this.child.on('error', (err) => {
|
|
131
|
-
this.
|
|
172
|
+
if (this.child.pid === undefined) {
|
|
173
|
+
this.spawnFailed = codexSpawnError(err, bin);
|
|
174
|
+
this.closeInput(this.spawnFailed.message);
|
|
175
|
+
this.child.stdout.destroy();
|
|
176
|
+
}
|
|
177
|
+
else if (this.stdinOpen) {
|
|
178
|
+
this.fail(new Error(`codex app-server process error while waiting for ${this.startupPhase}`));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
this.child.once('exit', (code, signal) => {
|
|
182
|
+
if (this.stdinOpen && !this.startupComplete) {
|
|
183
|
+
this.failure ??= new Error(`Codex startup exited (${signal ?? code ?? 'unknown'}) while waiting for ${this.startupPhase}`);
|
|
184
|
+
}
|
|
185
|
+
// Do not discard final buffered text on a healthy exit. Revoke RPCs
|
|
186
|
+
// immediately, then bound pipe draining ONLY after physical termination.
|
|
187
|
+
this.rpc.close('codex app-server exited');
|
|
188
|
+
drainTimer = setTimeout(() => this.child.stdout.destroy(), EXIT_DRAIN_MS);
|
|
189
|
+
drainTimer.unref?.();
|
|
190
|
+
if (this.stopKillTimer)
|
|
191
|
+
clearTimeout(this.stopKillTimer);
|
|
132
192
|
});
|
|
133
193
|
const stderrChunks = [];
|
|
134
194
|
this.child.stderr.setEncoding('utf8');
|
|
135
195
|
this.child.stderr.on('data', (chunk) => stderrChunks.push(chunk));
|
|
136
|
-
//
|
|
196
|
+
// Whole-session timeouts remain optional; startup is always bounded.
|
|
137
197
|
const limitMs = spec.timeoutMs ?? timeoutMs;
|
|
138
|
-
let killTimer;
|
|
139
198
|
let deadline;
|
|
140
199
|
if (limitMs > 0) {
|
|
141
200
|
deadline = setTimeout(() => {
|
|
142
201
|
this.timedOut = true;
|
|
143
202
|
this.interrupt();
|
|
144
|
-
this.child.stdout.destroy();
|
|
145
|
-
killTimer = setTimeout(() => {
|
|
146
|
-
if (!this.hasExited()) {
|
|
147
|
-
this.terminatedByCezar = true;
|
|
148
|
-
this.child.kill('SIGKILL');
|
|
149
|
-
}
|
|
150
|
-
}, KILL_GRACE_MS);
|
|
151
|
-
killTimer.unref?.();
|
|
152
203
|
}, limitMs);
|
|
153
204
|
deadline.unref?.();
|
|
154
205
|
}
|
|
155
|
-
|
|
156
|
-
|
|
206
|
+
this.startupTimer = setTimeout(() => {
|
|
207
|
+
this.fail(new Error(`Codex startup timed out after 60s while waiting for ${this.startupPhase}`), 'timed out after 60s');
|
|
208
|
+
}, STARTUP_TIMEOUT_MS);
|
|
209
|
+
this.startupTimer.unref?.();
|
|
157
210
|
this.ready = this.bootstrap();
|
|
158
|
-
|
|
159
|
-
let sessionError;
|
|
211
|
+
const reader = (async () => {
|
|
160
212
|
try {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
let msg;
|
|
166
|
-
try {
|
|
167
|
-
msg = JSON.parse(line);
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
continue; // not JSON-RPC — skip
|
|
171
|
-
}
|
|
172
|
-
// A sub-agent child thread's turn lifecycle must reach neither channel (#600):
|
|
173
|
-
// v1 would emit a bogus `turn-end`, and the v2 mapper — which carries no thread
|
|
174
|
-
// identity — would record the child turn as the parent's, clearing its turn-scoped
|
|
175
|
-
// plan/reasoning state and resetting the current turn id. Child ITEM events still
|
|
176
|
-
// flow, so nested sub-agent activity keeps rendering.
|
|
177
|
-
if (this.isForeignTurnLifecycle(msg))
|
|
178
|
-
continue;
|
|
179
|
-
this.emitUi((state) => mapCodexNotification(msg, state));
|
|
180
|
-
this.dispatch(msg);
|
|
213
|
+
for await (const line of readNdjson(this.child.stdout)) {
|
|
214
|
+
let msg;
|
|
215
|
+
try {
|
|
216
|
+
msg = JSON.parse(line);
|
|
181
217
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
218
|
+
catch {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// Explicit shutdown suppresses late lifecycle, not passive output.
|
|
222
|
+
// Natural exit can overtake the final turn frame: retain its outcome
|
|
223
|
+
// without restoring ACK/ask/input authority on the dead connection.
|
|
224
|
+
const finalOutcome = this.stdinOpen && hasExited() &&
|
|
225
|
+
(msg.method === 'turn/completed' || msg.method === 'turn/failed');
|
|
226
|
+
if (!this.open && (msg.id !== undefined || (!PASSIVE_OUTPUT_METHODS.has(msg.method ?? '') && !finalOutcome)))
|
|
227
|
+
continue;
|
|
228
|
+
// Child turn lifecycle must reach neither channel (#600).
|
|
229
|
+
if (this.isForeignTurnLifecycle(msg))
|
|
230
|
+
continue;
|
|
231
|
+
this.emitUi((state) => mapCodexNotification(msg, state));
|
|
232
|
+
this.dispatch(msg);
|
|
233
|
+
}
|
|
234
|
+
if (this.stdinOpen && !hasExited()) {
|
|
235
|
+
if (!this.startupComplete)
|
|
236
|
+
this.fail(new Error(`Codex startup stdout closed while waiting for ${this.startupPhase}`));
|
|
237
|
+
else
|
|
238
|
+
this.end();
|
|
239
|
+
}
|
|
187
240
|
}
|
|
188
|
-
catch
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
241
|
+
catch {
|
|
242
|
+
if (this.stdinOpen && !hasExited()) {
|
|
243
|
+
this.fail(new Error(`codex app-server output failed while waiting for ${this.startupPhase}`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
})();
|
|
247
|
+
this.result = (async () => {
|
|
248
|
+
let exitCode;
|
|
249
|
+
try {
|
|
250
|
+
try {
|
|
251
|
+
await Promise.all([this.ready, reader]);
|
|
195
252
|
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
if (this.stdinOpen && !hasExited())
|
|
255
|
+
this.fail(err instanceof Error ? err : new Error(String(err)));
|
|
256
|
+
}
|
|
257
|
+
// RPC cancellation is not a receipt for process termination. Keep the
|
|
258
|
+
// TERM→KILL watchdog alive until this independently owned promise settles.
|
|
259
|
+
exitCode = await exited;
|
|
260
|
+
await reader;
|
|
196
261
|
}
|
|
197
262
|
finally {
|
|
198
263
|
if (deadline)
|
|
199
264
|
clearTimeout(deadline);
|
|
200
|
-
if (
|
|
201
|
-
clearTimeout(
|
|
202
|
-
if (this.
|
|
203
|
-
clearTimeout(this.
|
|
204
|
-
this.
|
|
265
|
+
if (drainTimer)
|
|
266
|
+
clearTimeout(drainTimer);
|
|
267
|
+
if (this.stopKillTimer)
|
|
268
|
+
clearTimeout(this.stopKillTimer);
|
|
269
|
+
if (this.eofTermTimer)
|
|
270
|
+
clearTimeout(this.eofTermTimer);
|
|
271
|
+
if (this.eofKillTimer)
|
|
272
|
+
clearTimeout(this.eofKillTimer);
|
|
273
|
+
this.closeInput('codex app-server exited');
|
|
274
|
+
this.child.stdin.destroy();
|
|
275
|
+
this.child.stderr.destroy();
|
|
205
276
|
}
|
|
206
|
-
const exitCode = await waitForCodexAppServerExit(this.child);
|
|
207
|
-
if (this.eofTermTimer)
|
|
208
|
-
clearTimeout(this.eofTermTimer);
|
|
209
|
-
if (this.eofKillTimer)
|
|
210
|
-
clearTimeout(this.eofKillTimer);
|
|
211
|
-
this.rpc.rejectPending();
|
|
212
277
|
if (this.spawnFailed)
|
|
213
278
|
throw this.spawnFailed;
|
|
214
|
-
if (
|
|
215
|
-
throw
|
|
279
|
+
if (this.failure)
|
|
280
|
+
throw this.failure;
|
|
216
281
|
// Timeout/interrupt can end the read loop mid-item — recover buffered prose.
|
|
217
282
|
this.textCoalescer.flush();
|
|
218
283
|
const text = this.textChunks.join('\n').trim();
|
|
@@ -250,26 +315,45 @@ class CodexSession {
|
|
|
250
315
|
})();
|
|
251
316
|
}
|
|
252
317
|
get open() {
|
|
253
|
-
return this.stdinOpen;
|
|
318
|
+
return this.stdinOpen && this.rpc.open && !this.hasExited();
|
|
254
319
|
}
|
|
255
320
|
get pid() {
|
|
256
321
|
return this.child.pid;
|
|
257
322
|
}
|
|
258
|
-
sendAgentMessage(content) {
|
|
259
|
-
|
|
323
|
+
sendAgentMessage(content, inputIds = []) {
|
|
324
|
+
// #505: a running turn is steered with turn/steer; only an unanswered native ask
|
|
325
|
+
// or an in-flight submission refuses.
|
|
326
|
+
// Until the thread and its first turn exist there is nothing to steer or start;
|
|
327
|
+
// a refusal here earns one readiness hint when startup completes.
|
|
328
|
+
if (this.open && !this.startupComplete) {
|
|
329
|
+
this.refusedBeforeStartup = true;
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
// A human prompt still on its way to the wire goes first (#505 local review).
|
|
333
|
+
if (this.open && this.humanPromptsPending > 0) {
|
|
334
|
+
this.refusedForHuman = true;
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
if (!this.open || this.pendingUserInput || this.agentSubmissionPending)
|
|
260
338
|
return false;
|
|
261
339
|
this.agentInputReady = false;
|
|
262
340
|
this.agentSubmissionPending = true;
|
|
263
341
|
if (this.autoEndTimer)
|
|
264
342
|
clearTimeout(this.autoEndTimer);
|
|
265
343
|
this.autoEndTimer = undefined;
|
|
344
|
+
const submissionId = randomUUID();
|
|
345
|
+
this.submissions.accept(submissionId, inputIds, '');
|
|
346
|
+
this.inFlightSubmissionId = submissionId;
|
|
266
347
|
// Reserve synchronously; only the matching RPC result acknowledges delivery.
|
|
267
|
-
return this.startOrSteerTurn(textOf(content)).catch((err) => {
|
|
348
|
+
return this.startOrSteerTurn(textOf(content), submissionId).catch((err) => {
|
|
349
|
+
this.submissions.consume(submissionId); // never accepted: the caller keeps ownership
|
|
268
350
|
if (this.stdinOpen)
|
|
269
351
|
this.emit({ type: 'error', message: `codex: agent input failed: ${String(err)}` });
|
|
270
352
|
throw err;
|
|
271
353
|
}).finally(() => {
|
|
272
354
|
this.agentSubmissionPending = false;
|
|
355
|
+
if (this.inFlightSubmissionId === submissionId)
|
|
356
|
+
this.inFlightSubmissionId = undefined;
|
|
273
357
|
if (this.stdinOpen && this.agentInputReady && !this.pendingUserInput) {
|
|
274
358
|
this.opts.onAgentInputReady?.();
|
|
275
359
|
this.scheduleAutoEnd();
|
|
@@ -277,7 +361,7 @@ class CodexSession {
|
|
|
277
361
|
});
|
|
278
362
|
}
|
|
279
363
|
scheduleAutoEnd() {
|
|
280
|
-
if (!this.opts.autoEndAfterFirstTurn || !this.
|
|
364
|
+
if (!this.opts.autoEndAfterFirstTurn || !this.open || this.autoEndTimer || this.agentSubmissionPending)
|
|
281
365
|
return;
|
|
282
366
|
this.autoEndTimer = setTimeout(() => {
|
|
283
367
|
this.autoEndTimer = undefined;
|
|
@@ -288,7 +372,7 @@ class CodexSession {
|
|
|
288
372
|
}
|
|
289
373
|
sendMessage(content) {
|
|
290
374
|
this.agentInputReady = false;
|
|
291
|
-
if (!this.
|
|
375
|
+
if (!this.open)
|
|
292
376
|
return false;
|
|
293
377
|
if (this.autoEndTimer) {
|
|
294
378
|
clearTimeout(this.autoEndTimer);
|
|
@@ -301,14 +385,26 @@ class CodexSession {
|
|
|
301
385
|
const pending = this.pendingUserInput;
|
|
302
386
|
this.pendingUserInput = undefined;
|
|
303
387
|
this.rpc.respond({ id: pending.rpcId, result: { answers: userInputAnswers(pending.questions, text) } });
|
|
304
|
-
return
|
|
388
|
+
return this.open;
|
|
305
389
|
}
|
|
306
|
-
// Wait for the thread to exist, then steer the live turn or start a new one.
|
|
390
|
+
// Wait for the thread to exist, then steer the live turn or start a new one. Agent input
|
|
391
|
+
// waits until this prompt's request settles, so it can never overtake it.
|
|
392
|
+
this.humanPromptsPending += 1;
|
|
307
393
|
void this.ready
|
|
308
394
|
.then(() => this.startOrSteerTurn(text))
|
|
309
395
|
.catch((err) => {
|
|
310
|
-
|
|
311
|
-
|
|
396
|
+
if (this.stdinOpen) {
|
|
397
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
398
|
+
this.emit({ type: 'note', message: `codex: turn failed: ${message}` });
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
.finally(() => {
|
|
402
|
+
this.humanPromptsPending -= 1;
|
|
403
|
+
if (this.humanPromptsPending === 0 && this.refusedForHuman) {
|
|
404
|
+
this.refusedForHuman = false;
|
|
405
|
+
if (this.open && !this.pendingUserInput && !this.agentSubmissionPending)
|
|
406
|
+
this.opts.onAgentInputReady?.();
|
|
407
|
+
}
|
|
312
408
|
});
|
|
313
409
|
return true;
|
|
314
410
|
}
|
|
@@ -316,8 +412,7 @@ class CodexSession {
|
|
|
316
412
|
end() {
|
|
317
413
|
if (!this.stdinOpen)
|
|
318
414
|
return;
|
|
319
|
-
this.
|
|
320
|
-
this.stdinOpen = false;
|
|
415
|
+
this.closeInput('session ended');
|
|
321
416
|
try {
|
|
322
417
|
endCodexAppServer(this.child, (term, kill) => {
|
|
323
418
|
this.eofTermTimer = term;
|
|
@@ -331,20 +426,79 @@ class CodexSession {
|
|
|
331
426
|
}
|
|
332
427
|
}
|
|
333
428
|
interrupt() {
|
|
334
|
-
this.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (this.
|
|
429
|
+
if (this.hardStopStarted || this.hasExited())
|
|
430
|
+
return;
|
|
431
|
+
this.hardStopStarted = true;
|
|
432
|
+
if (this.eofTermTimer)
|
|
433
|
+
clearTimeout(this.eofTermTimer);
|
|
434
|
+
if (this.eofKillTimer)
|
|
435
|
+
clearTimeout(this.eofKillTimer);
|
|
436
|
+
// Native cancellation is best effort; never wait for its ACK to stop.
|
|
437
|
+
if (this.stdinOpen && this.threadId && this.activeTurnId) {
|
|
338
438
|
void this.rpc.request('turn/interrupt', { threadId: this.threadId, turnId: this.activeTurnId }).catch(() => undefined);
|
|
339
439
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
440
|
+
this.closeInput('codex session stopped');
|
|
441
|
+
this.terminatedByCezar = true;
|
|
442
|
+
this.child.kill('SIGTERM');
|
|
443
|
+
this.stopKillTimer = setTimeout(() => {
|
|
444
|
+
if (!this.hasExited())
|
|
445
|
+
this.child.kill('SIGKILL');
|
|
446
|
+
}, KILL_GRACE_MS);
|
|
447
|
+
this.stopKillTimer.unref?.();
|
|
448
|
+
}
|
|
449
|
+
closeInput(reason) {
|
|
450
|
+
this.stdinOpen = false;
|
|
451
|
+
this.rejectPendingUserInput(reason);
|
|
452
|
+
this.agentInputReady = false;
|
|
453
|
+
if (this.startupTimer)
|
|
454
|
+
clearTimeout(this.startupTimer);
|
|
455
|
+
if (this.autoEndTimer)
|
|
456
|
+
clearTimeout(this.autoEndTimer);
|
|
457
|
+
this.rpc.close(reason);
|
|
458
|
+
}
|
|
459
|
+
fail(error, diagnostic = 'failed') {
|
|
460
|
+
if (!this.stdinOpen)
|
|
461
|
+
return;
|
|
462
|
+
this.failure ??= error;
|
|
463
|
+
// Stage-only diagnostics never include prompt/config/env or raw stderr.
|
|
464
|
+
const message = this.startupComplete ? 'Codex app-server failed; closing app-server'
|
|
465
|
+
: `Codex startup ${diagnostic} while waiting for ${this.startupPhase}; closing app-server`;
|
|
466
|
+
try {
|
|
467
|
+
this.emit({ type: 'note', message });
|
|
343
468
|
}
|
|
469
|
+
catch { /* A failing diagnostic sink cannot release a still-live process. */ }
|
|
470
|
+
this.interrupt();
|
|
471
|
+
}
|
|
472
|
+
assertOpen() {
|
|
473
|
+
if (!this.open)
|
|
474
|
+
throw new Error('codex session closed');
|
|
475
|
+
}
|
|
476
|
+
startupWaitingFor(phase) {
|
|
477
|
+
this.assertOpen();
|
|
478
|
+
this.startupPhase = phase;
|
|
479
|
+
this.emit({ type: 'note', message: `Codex startup: waiting for ${phase}` });
|
|
480
|
+
}
|
|
481
|
+
checkStartupComplete() {
|
|
482
|
+
if (!this.openingAcknowledged || !this.firstTurnStarted || !this.stdinOpen)
|
|
483
|
+
return;
|
|
484
|
+
if (this.startupComplete)
|
|
485
|
+
return;
|
|
486
|
+
this.startupComplete = true;
|
|
487
|
+
if (this.startupTimer)
|
|
488
|
+
clearTimeout(this.startupTimer);
|
|
489
|
+
// Input refused during startup can now steer the opening turn (#505).
|
|
490
|
+
if (this.refusedBeforeStartup)
|
|
491
|
+
queueMicrotask(() => {
|
|
492
|
+
this.refusedBeforeStartup = false;
|
|
493
|
+
if (this.open && !this.pendingUserInput && !this.agentSubmissionPending)
|
|
494
|
+
this.opts.onAgentInputReady?.();
|
|
495
|
+
});
|
|
344
496
|
}
|
|
345
497
|
// ---- protocol -----------------------------------------------------------
|
|
346
498
|
async bootstrap() {
|
|
499
|
+
this.startupWaitingFor('initialize');
|
|
347
500
|
await this.rpc.initialize();
|
|
501
|
+
this.assertOpen();
|
|
348
502
|
const overrides = {
|
|
349
503
|
model: this.spec.model,
|
|
350
504
|
cwd: this.spec.cwd,
|
|
@@ -360,11 +514,15 @@ class CodexSession {
|
|
|
360
514
|
} } : {}),
|
|
361
515
|
};
|
|
362
516
|
if (this.spec.resume && this.spec.sessionId) {
|
|
517
|
+
this.startupWaitingFor('thread/resume');
|
|
363
518
|
await this.rpc.request('thread/resume', { threadId: this.spec.sessionId, ...clean(overrides) });
|
|
519
|
+
this.assertOpen();
|
|
364
520
|
this.threadId = this.spec.sessionId;
|
|
365
521
|
}
|
|
366
522
|
else {
|
|
523
|
+
this.startupWaitingFor('thread/start');
|
|
367
524
|
const res = await this.rpc.request('thread/start', clean(overrides));
|
|
525
|
+
this.assertOpen();
|
|
368
526
|
this.threadId = threadIdOf(res) ?? this.spec.sessionId;
|
|
369
527
|
}
|
|
370
528
|
if (this.threadId) {
|
|
@@ -378,31 +536,73 @@ class CodexSession {
|
|
|
378
536
|
// has no dedicated app-server field, so it rides along as a leading block
|
|
379
537
|
// of the opening message.
|
|
380
538
|
const first = prependSystemPrompt(this.spec.systemPrompt, this.spec.userPrompt);
|
|
539
|
+
this.startupWaitingFor('turn/start');
|
|
381
540
|
await this.startOrSteerTurn(first);
|
|
541
|
+
this.assertOpen();
|
|
542
|
+
this.openingAcknowledged = true;
|
|
543
|
+
this.checkStartupComplete();
|
|
544
|
+
if (!this.startupComplete)
|
|
545
|
+
this.startupWaitingFor('first main-thread turn');
|
|
382
546
|
}
|
|
383
|
-
async startOrSteerTurn(text) {
|
|
547
|
+
async startOrSteerTurn(text, clientUserMessageId) {
|
|
548
|
+
this.assertOpen();
|
|
384
549
|
if (!this.threadId)
|
|
385
|
-
return;
|
|
550
|
+
throw new Error('codex app-server did not return a thread id');
|
|
386
551
|
this.agentInputReady = false;
|
|
387
552
|
const input = [{ type: 'text', text, text_elements: [] }];
|
|
553
|
+
// Echoed as the consumed userMessage item's `clientId` (#505).
|
|
554
|
+
const ids = clientUserMessageId ? { clientUserMessageId } : {};
|
|
388
555
|
if (this.activeTurnId) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
556
|
+
const boundary = this.turnBoundaryVersion;
|
|
557
|
+
try {
|
|
558
|
+
await this.rpc.request('turn/steer', {
|
|
559
|
+
threadId: this.threadId,
|
|
560
|
+
input,
|
|
561
|
+
expectedTurnId: this.activeTurnId,
|
|
562
|
+
...ids,
|
|
563
|
+
});
|
|
564
|
+
// Accepted, but the turn completed before the model read it: nothing will read
|
|
565
|
+
// it now, so start a turn with it under the same client id (#505).
|
|
566
|
+
const stranded = clientUserMessageId !== undefined && this.turnBoundaryVersion !== boundary &&
|
|
567
|
+
!this.activeTurnId && this.submissions.has(clientUserMessageId);
|
|
568
|
+
if (!stranded)
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
catch (err) {
|
|
572
|
+
// Only a definitive refusal falls back: the server answered with an error AND
|
|
573
|
+
// the turn ended meanwhile. A timeout or closed transport is ambiguous — the
|
|
574
|
+
// steer may have landed — so it rejects without a retry (#505).
|
|
575
|
+
if (!(err instanceof CodexRpcResponseError) || this.turnBoundaryVersion === boundary || this.activeTurnId)
|
|
576
|
+
throw err;
|
|
577
|
+
}
|
|
395
578
|
}
|
|
396
579
|
// Ask the app-server for reasoning summaries; without this the model runs
|
|
397
580
|
// with its default (no summary), so the reasoning thread stays empty even
|
|
398
581
|
// though the mapper and UI can render it. The override persists for this
|
|
399
582
|
// turn and every subsequent turn, so seeding it on turn/start is enough.
|
|
400
583
|
const boundaryVersion = this.turnBoundaryVersion;
|
|
584
|
+
const completionsBefore = this.turnCompletions;
|
|
401
585
|
const res = await this.rpc.request('turn/start', {
|
|
402
586
|
threadId: this.threadId,
|
|
403
587
|
input,
|
|
588
|
+
...ids,
|
|
404
589
|
...codexTurnStartExtras(this.spec),
|
|
405
590
|
});
|
|
591
|
+
if (clientUserMessageId) {
|
|
592
|
+
// This turn's own input: its completion proves the model processed it (#505). A response
|
|
593
|
+
// can arrive after that turn already completed; settle it from the recorded outcome.
|
|
594
|
+
const startedTurn = turnIdOf(res);
|
|
595
|
+
const outcome = startedTurn && this.turnCompletions > completionsBefore ? this.completedTurns.get(startedTurn) : undefined;
|
|
596
|
+
if (outcome === undefined)
|
|
597
|
+
this.turnStartSubmissions.add(clientUserMessageId);
|
|
598
|
+
else {
|
|
599
|
+
const settled = this.submissions.consume(clientUserMessageId);
|
|
600
|
+
if (settled.length && outcome)
|
|
601
|
+
this.opts.onAgentInputConsumed?.(settled);
|
|
602
|
+
else if (settled.length)
|
|
603
|
+
this.emit({ type: 'input-unconsumed', inputIds: settled });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
406
606
|
if (this.turnBoundaryVersion === boundaryVersion)
|
|
407
607
|
this.activeTurnId = turnIdOf(res) ?? this.activeTurnId;
|
|
408
608
|
}
|
|
@@ -459,6 +659,10 @@ class CodexSession {
|
|
|
459
659
|
this.turnBoundaryVersion += 1;
|
|
460
660
|
if (this.isForeignThreadTurn(params))
|
|
461
661
|
break; // sub-agent child thread — not our turn (#600)
|
|
662
|
+
if (this.threadId && turnIdOf(params)) {
|
|
663
|
+
this.firstTurnStarted = true;
|
|
664
|
+
this.checkStartupComplete();
|
|
665
|
+
}
|
|
462
666
|
this.activeTurnId = turnIdOf(params) ?? this.activeTurnId;
|
|
463
667
|
break;
|
|
464
668
|
}
|
|
@@ -475,6 +679,13 @@ class CodexSession {
|
|
|
475
679
|
case 'item/started': {
|
|
476
680
|
const item = params.item ?? {};
|
|
477
681
|
const type = stringField(item, 'type');
|
|
682
|
+
if (type === 'userMessage' && !this.isForeignThreadTurn(params)) {
|
|
683
|
+
// The model received this input now; `clientId` names our submission (#505).
|
|
684
|
+
const clientId = stringField(item, 'clientId');
|
|
685
|
+
const ids = clientId ? this.submissions.consume(clientId) : [];
|
|
686
|
+
if (ids.length)
|
|
687
|
+
this.opts.onAgentInputConsumed?.(ids);
|
|
688
|
+
}
|
|
478
689
|
// Only tool-like items become tool events; message/reasoning stream as text.
|
|
479
690
|
if (type && !NON_TOOL_ITEMS.has(type)) {
|
|
480
691
|
const id = stringField(item, 'id') ?? `item-${this.rpc.allocateId()}`;
|
|
@@ -523,11 +734,29 @@ class CodexSession {
|
|
|
523
734
|
// partial prose before the turn boundary (run.ts reads markers there).
|
|
524
735
|
this.textCoalescer.flush();
|
|
525
736
|
const outcome = codexTurnOutcome(method, params);
|
|
737
|
+
const completedId = turnIdOf(params);
|
|
738
|
+
if (completedId) {
|
|
739
|
+
this.completedTurns.set(completedId, outcome.error === undefined);
|
|
740
|
+
if (this.completedTurns.size > 32)
|
|
741
|
+
this.completedTurns.delete(this.completedTurns.keys().next().value);
|
|
742
|
+
}
|
|
743
|
+
this.turnCompletions += 1;
|
|
526
744
|
if (outcome.error !== undefined && !this.terminatedByCezar) {
|
|
527
745
|
this.emit({ type: 'error', message: outcome.error });
|
|
528
746
|
}
|
|
529
|
-
this.agentInputReady =
|
|
530
|
-
|
|
747
|
+
this.agentInputReady = this.open;
|
|
748
|
+
// A completed turn processed its own input; a failed one may never have reached the
|
|
749
|
+
// model, so its input stays pending and is reported unconsumed below.
|
|
750
|
+
const started = outcome.error === undefined ? [...this.turnStartSubmissions].flatMap(id => this.submissions.consume(id)) : [];
|
|
751
|
+
this.turnStartSubmissions.clear();
|
|
752
|
+
if (started.length)
|
|
753
|
+
this.opts.onAgentInputConsumed?.(started);
|
|
754
|
+
// An in-flight steer is owned by its RPC outcome, not by this boundary.
|
|
755
|
+
const inFlight = this.inFlightSubmissionId ? this.submissions.consume(this.inFlightSubmissionId) : [];
|
|
756
|
+
const unconsumedInputIds = this.submissions.takeUnconsumed();
|
|
757
|
+
if (this.inFlightSubmissionId && inFlight.length)
|
|
758
|
+
this.submissions.accept(this.inFlightSubmissionId, inFlight, '');
|
|
759
|
+
this.emit(unconsumedInputIds.length ? { type: 'turn-end', unconsumedInputIds } : { type: 'turn-end' });
|
|
531
760
|
this.scheduleAutoEnd();
|
|
532
761
|
break;
|
|
533
762
|
}
|
|
@@ -601,6 +830,12 @@ const NON_TOOL_ITEMS = new Set(['agentMessage', 'userMessage', 'reasoning', 'pla
|
|
|
601
830
|
/** Turn-lifecycle notification methods — the only frames whose child-thread copies must be
|
|
602
831
|
* dropped so a sub-agent turn can't be mistaken for the parent's (#600). */
|
|
603
832
|
const TURN_LIFECYCLE_METHODS = new Set(['turn/started', 'turn/completed', 'turn/failed']);
|
|
833
|
+
/** Notifications that only collect output; never dispatch control after close. */
|
|
834
|
+
const PASSIVE_OUTPUT_METHODS = new Set([
|
|
835
|
+
'item/started', 'item/updated', 'item/completed', 'item/agentMessage/delta',
|
|
836
|
+
'item/reasoning/textDelta', 'item/reasoning/summaryDelta', 'item/reasoning/summaryTextDelta',
|
|
837
|
+
'item/commandExecution/outputDelta', 'thread/tokenUsage/updated', 'turn/plan/updated',
|
|
838
|
+
]);
|
|
604
839
|
const REASONING_SUMMARIES = new Set(['auto', 'concise', 'detailed', 'none']);
|
|
605
840
|
/**
|
|
606
841
|
* The reasoning-summary override sent on `turn/start` (TurnStartParams.summary).
|