@adecore/agents 0.39.0 → 0.40.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/async.d.ts +5 -0
- package/dist/async.js +13 -5
- package/dist/chat/acp-protocol.js +3 -5
- package/dist/chat/chat-core.js +0 -2
- package/dist/chat/chat-process.d.ts +1 -0
- package/dist/chat/chat-process.js +1 -1
- package/dist/chat/chat-session.js +6 -8
- package/dist/chat/claude-protocol.js +1 -6
- package/dist/chat/claude-transcript.js +1 -3
- package/dist/chat/codex-protocol.js +1 -6
- package/dist/chat/projector.js +1 -3
- package/dist/chat/request-summary.js +1 -3
- package/dist/coded-error.js +1 -5
- package/dist/context/argv.js +1 -2
- package/dist/context/verb.js +1 -3
- package/dist/error-text.d.ts +1 -0
- package/dist/error-text.js +4 -0
- package/dist/fields.d.ts +3 -0
- package/dist/fields.js +12 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +8 -4
- package/dist/lineage.js +11 -5
- package/dist/messages/notice-store.js +3 -4
- package/dist/providers/accounts/acp-account.js +3 -20
- package/dist/providers/accounts/service.d.ts +1 -2
- package/dist/providers/accounts/service.js +5 -9
- package/dist/providers/accounts/status.js +1 -3
- package/dist/providers/acp-agent-state.js +1 -3
- package/dist/providers/claude.js +0 -1
- package/dist/providers/codex.js +0 -1
- package/dist/providers/detect.js +1 -1
- package/dist/providers/shell-commands.js +1 -2
- package/dist/run-process.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -8
- package/dist/tasks/background-limit.js +3 -2
- package/dist/tasks/task-coordinator.js +11 -17
- package/dist/testing/watch.js +0 -1
- package/dist/usage/limits/monitor.js +1 -2
- package/dist/usage/readers/claude.js +3 -10
- package/dist/usage/readers/codex.js +2 -9
- package/dist/usage/readers/grok.js +3 -10
- package/dist/usage/readers/host-log.js +3 -10
- package/dist/usage/record.d.ts +2 -0
- package/dist/usage/record.js +13 -0
- package/package.json +6 -6
- package/src/async.ts +23 -5
- package/src/chat/acp-backend.ts +121 -0
- package/src/chat/acp-protocol.ts +172 -6
- package/src/chat/attachment-store.ts +21 -0
- package/src/chat/backend.ts +0 -2
- package/src/chat/bookmark-store.ts +23 -0
- package/src/chat/chat-core.ts +208 -7
- package/src/chat/chat-log.ts +19 -0
- package/src/chat/chat-process.ts +52 -1
- package/src/chat/chat-session.ts +401 -10
- package/src/chat/chat-store.ts +50 -0
- package/src/chat/chat-title.ts +9 -0
- package/src/chat/claude-backend.ts +50 -0
- package/src/chat/claude-protocol.ts +155 -8
- package/src/chat/claude-title.ts +10 -0
- package/src/chat/claude-transcript.ts +47 -4
- package/src/chat/codex-backend.ts +131 -0
- package/src/chat/codex-protocol.ts +180 -8
- package/src/chat/codex-thread.ts +23 -0
- package/src/chat/composer-preferences.ts +6 -0
- package/src/chat/context-breakdown.ts +11 -0
- package/src/chat/delta-coalescer.ts +8 -0
- package/src/chat/external-sessions.ts +28 -0
- package/src/chat/generated-image.ts +25 -0
- package/src/chat/index.ts +5 -0
- package/src/chat/input.ts +13 -0
- package/src/chat/json-rpc-transport.ts +28 -0
- package/src/chat/limit-resume.ts +13 -0
- package/src/chat/projector.ts +160 -4
- package/src/chat/readable-text.ts +3 -0
- package/src/chat/request-summary.ts +18 -4
- package/src/chat/subagent-projection.ts +3 -0
- package/src/chat/subagent-reader.ts +76 -0
- package/src/chat/subagent-settlement.ts +24 -0
- package/src/chat/thread-search.ts +13 -0
- package/src/chat/thread.ts +31 -0
- package/src/chat/ui-queries.ts +122 -0
- package/src/chat/ui-replies.ts +34 -0
- package/src/chat/visual-store.ts +49 -0
- package/src/chat/wake-chat.ts +3 -0
- package/src/client-sinks.ts +2 -0
- package/src/coded-error.ts +1 -5
- package/src/context/argv.ts +18 -2
- package/src/context/index.ts +1 -0
- package/src/context/refusal.ts +3 -0
- package/src/context/verb.ts +32 -4
- package/src/coordination/index.ts +10 -0
- package/src/error-text.ts +7 -0
- package/src/fields.ts +14 -0
- package/src/fs.ts +20 -4
- package/src/host/agent-host.ts +20 -0
- package/src/host/environment.ts +6 -0
- package/src/host/handlers.ts +18 -0
- package/src/host/memory-port.ts +4 -0
- package/src/host/wiring.ts +10 -0
- package/src/index.ts +1 -0
- package/src/lineage.ts +24 -5
- package/src/messages/deliver-message.ts +6 -0
- package/src/messages/deliver-notice.ts +13 -0
- package/src/messages/notice-store.ts +16 -4
- package/src/modes.ts +1 -0
- package/src/outbox/outbox-worker.ts +31 -0
- package/src/outbox/outbox.ts +5 -0
- package/src/providers/accounts/accounts.ts +36 -0
- package/src/providers/accounts/acp-account.ts +22 -27
- package/src/providers/accounts/launch.ts +4 -0
- package/src/providers/accounts/service.ts +110 -12
- package/src/providers/accounts/shadow-home.ts +10 -0
- package/src/providers/accounts/status.ts +13 -4
- package/src/providers/accounts/store.ts +25 -0
- package/src/providers/accounts/variables.ts +11 -1
- package/src/providers/acp-agent-state.ts +24 -3
- package/src/providers/acp-provider.ts +5 -0
- package/src/providers/acp-usage-log.ts +2 -0
- package/src/providers/catalog.ts +17 -0
- package/src/providers/claude.ts +10 -1
- package/src/providers/codex-rules.ts +3 -0
- package/src/providers/codex.ts +2 -1
- package/src/providers/detect.ts +6 -1
- package/src/providers/provider.ts +1 -1
- package/src/providers/registry.ts +18 -2
- package/src/providers/shell-commands.ts +37 -2
- package/src/record-directory.ts +19 -0
- package/src/run-process.ts +24 -3
- package/src/serializer.ts +3 -0
- package/src/skills.ts +73 -10
- package/src/tasks/background-limit.ts +9 -2
- package/src/tasks/end-children.ts +9 -0
- package/src/tasks/give-task.ts +8 -0
- package/src/tasks/task-coordinator.ts +81 -17
- package/src/tasks/task-store.ts +28 -0
- package/src/tasks/waiting-child.ts +21 -0
- package/src/tasks/wake-parent.ts +26 -0
- package/src/tasks/wiring.ts +22 -0
- package/src/testing/accounts.ts +2 -0
- package/src/testing/fake-acp-capture.ts +16 -0
- package/src/testing/fake-acp.ts +66 -0
- package/src/testing/fake-claude.ts +177 -0
- package/src/testing/fake-cli.ts +29 -0
- package/src/testing/fake-codex.ts +122 -0
- package/src/testing/manual-clock.ts +2 -0
- package/src/testing/watch.ts +7 -1
- package/src/title-file.ts +12 -0
- package/src/usage/accounts.ts +6 -0
- package/src/usage/aggregate.ts +19 -0
- package/src/usage/exchange.ts +12 -0
- package/src/usage/index-file.ts +27 -0
- package/src/usage/limits/monitor.ts +31 -3
- package/src/usage/limits/normalize.ts +30 -0
- package/src/usage/limits/probe.ts +17 -0
- package/src/usage/pricing.ts +35 -0
- package/src/usage/projects.ts +8 -0
- package/src/usage/readers/claude.ts +12 -9
- package/src/usage/readers/codex.ts +40 -8
- package/src/usage/readers/grok.ts +17 -9
- package/src/usage/readers/host-log.ts +9 -9
- package/src/usage/record.ts +22 -0
- package/src/usage/roots.ts +6 -0
- package/src/usage/scanner.ts +61 -0
- package/src/usage/usage-service.ts +14 -0
- package/src/watch-seam.ts +8 -0
- package/src/windows-command.ts +12 -0
|
@@ -5,6 +5,7 @@ import { readCodexLimits } from '../usage/limits/normalize.ts';
|
|
|
5
5
|
import { codexRuntimeMode } from '../providers/codex.ts';
|
|
6
6
|
import type { ApprovalDecision, BackendEvent } from './backend.ts';
|
|
7
7
|
import type { CodexFrame } from './codex-transport.ts';
|
|
8
|
+
import { num, str } from '../fields.ts';
|
|
8
9
|
import { cleanTitle } from '../title-file.ts';
|
|
9
10
|
|
|
10
11
|
type CodexRpcId = number | string;
|
|
@@ -14,14 +15,6 @@ type CodexAnswer = { kind: 'respond'; rpcId: CodexRpcId; result: unknown } | { k
|
|
|
14
15
|
|
|
15
16
|
type Frame = Record<string, unknown>;
|
|
16
17
|
|
|
17
|
-
function str(value: unknown): string | null {
|
|
18
|
-
return typeof value === 'string' ? value : null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function num(value: unknown): number {
|
|
22
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
18
|
const GOAL_STATUSES: Record<string, ChatGoalStatus> = {
|
|
26
19
|
active: 'active',
|
|
27
20
|
paused: 'paused',
|
|
@@ -36,15 +29,20 @@ export function codexGoal(raw: unknown): ChatGoal | null {
|
|
|
36
29
|
if (!isRecord(raw)) {
|
|
37
30
|
return null;
|
|
38
31
|
}
|
|
32
|
+
|
|
39
33
|
const objective = str(raw.objective)?.trim() ?? '';
|
|
40
34
|
const status = GOAL_STATUSES[str(raw.status) ?? ''];
|
|
35
|
+
|
|
41
36
|
if (objective === '' || status === undefined) {
|
|
42
37
|
return null;
|
|
43
38
|
}
|
|
39
|
+
|
|
44
40
|
const count = (value: unknown): number | undefined => (typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined);
|
|
41
|
+
|
|
45
42
|
const tokensUsed = count(raw.tokensUsed);
|
|
46
43
|
const tokenBudget = count(raw.tokenBudget);
|
|
47
44
|
const secondsUsed = count(raw.timeUsedSeconds);
|
|
45
|
+
|
|
48
46
|
return {
|
|
49
47
|
objective,
|
|
50
48
|
status,
|
|
@@ -59,11 +57,14 @@ const SHELL_WRAPPER = /^(?:\/bin\/|\/usr\/bin\/)?(?:zsh|bash|sh) -lc (.*)$/s;
|
|
|
59
57
|
|
|
60
58
|
export function unwrapCommand(command: string): string {
|
|
61
59
|
const match = SHELL_WRAPPER.exec(command);
|
|
60
|
+
|
|
62
61
|
if (!match) {
|
|
63
62
|
return command;
|
|
64
63
|
}
|
|
64
|
+
|
|
65
65
|
const inner = match[1]!;
|
|
66
66
|
const quoted = /^(["'])([\s\S]*)\1$/.exec(inner);
|
|
67
|
+
|
|
67
68
|
return quoted ? quoted[2]! : inner;
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -71,9 +72,11 @@ function textOf(content: unknown): string {
|
|
|
71
72
|
if (typeof content === 'string') {
|
|
72
73
|
return content;
|
|
73
74
|
}
|
|
75
|
+
|
|
74
76
|
if (!Array.isArray(content)) {
|
|
75
77
|
return '';
|
|
76
78
|
}
|
|
79
|
+
|
|
77
80
|
return content
|
|
78
81
|
.map((block) => (isRecord(block) ? (str(block.text) ?? null) : null))
|
|
79
82
|
.filter((text): text is string => text !== null)
|
|
@@ -88,10 +91,12 @@ function collabAgentOutput(states: unknown): string {
|
|
|
88
91
|
if (!isRecord(states)) {
|
|
89
92
|
return '';
|
|
90
93
|
}
|
|
94
|
+
|
|
91
95
|
return Object.entries(states)
|
|
92
96
|
.map(([thread, state]) => {
|
|
93
97
|
const status = isRecord(state) ? (str(state.status) ?? 'unknown') : 'unknown';
|
|
94
98
|
const message = isRecord(state) ? str(state.message) : null;
|
|
99
|
+
|
|
95
100
|
return message ? `${thread}: ${status}, ${message}` : `${thread}: ${status}`;
|
|
96
101
|
})
|
|
97
102
|
.join('\n');
|
|
@@ -102,6 +107,7 @@ function parseBlockingQuestions(questions: unknown): ChatQuestion[] {
|
|
|
102
107
|
if (!Array.isArray(questions)) {
|
|
103
108
|
return [];
|
|
104
109
|
}
|
|
110
|
+
|
|
105
111
|
return questions.filter(isRecord).map((question, index) => ({
|
|
106
112
|
id: str(question.id) ?? String(index),
|
|
107
113
|
header: str(question.header) ?? '',
|
|
@@ -118,6 +124,7 @@ function parseAsyncQuestions(questions: unknown): ChatQuestion[] {
|
|
|
118
124
|
if (!Array.isArray(questions)) {
|
|
119
125
|
return [];
|
|
120
126
|
}
|
|
127
|
+
|
|
121
128
|
return questions.filter(isRecord).map((question, index) => ({
|
|
122
129
|
id: String(index),
|
|
123
130
|
header: '',
|
|
@@ -140,8 +147,10 @@ function parseChanges(value: unknown): ChatFileChange[] {
|
|
|
140
147
|
if (!Array.isArray(value)) {
|
|
141
148
|
return [];
|
|
142
149
|
}
|
|
150
|
+
|
|
143
151
|
return value.filter(isRecord).map((change) => {
|
|
144
152
|
const kind = isRecord(change.kind) ? str(change.kind.type) : str(change.kind);
|
|
153
|
+
|
|
145
154
|
return {
|
|
146
155
|
path: str(change.path) ?? '',
|
|
147
156
|
kind: kind !== null && CHANGE_KINDS.has(kind as ChatFileChange['kind']) ? (kind as ChatFileChange['kind']) : 'update',
|
|
@@ -153,13 +162,17 @@ function parseChanges(value: unknown): ChatFileChange[] {
|
|
|
153
162
|
// Partial command output arrives as text or as the raw bytes of it, depending on the build.
|
|
154
163
|
function outputChunk(params: Frame): string | null {
|
|
155
164
|
const text = str(params.delta) ?? str(params.chunk);
|
|
165
|
+
|
|
156
166
|
if (text !== null) {
|
|
157
167
|
return text;
|
|
158
168
|
}
|
|
169
|
+
|
|
159
170
|
const bytes = params.chunk ?? params.delta;
|
|
171
|
+
|
|
160
172
|
if (!Array.isArray(bytes)) {
|
|
161
173
|
return null;
|
|
162
174
|
}
|
|
175
|
+
|
|
163
176
|
return new TextDecoder().decode(Uint8Array.from(bytes.filter((byte): byte is number => typeof byte === 'number')));
|
|
164
177
|
}
|
|
165
178
|
|
|
@@ -187,26 +200,31 @@ function isConfirmation(params: Frame): boolean {
|
|
|
187
200
|
/* The widest remembering the MCP server offers, which the one "always" button of a card stands for. */
|
|
188
201
|
function elicitationPersist(meta: Frame): ElicitationPersist | null {
|
|
189
202
|
const offered = Array.isArray(meta.persist) ? meta.persist : [];
|
|
203
|
+
|
|
190
204
|
return offered.includes('always') ? 'always' : offered.includes('session') ? 'session' : null;
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
/* The arguments as the MCP server shows them to a person, keyed by name, so the card sums up the call. */
|
|
194
208
|
function elicitationInput(meta: Frame): Frame {
|
|
195
209
|
const input: Frame = {};
|
|
210
|
+
|
|
196
211
|
for (const param of Array.isArray(meta.tool_params_display) ? meta.tool_params_display : []) {
|
|
197
212
|
if (isRecord(param) && typeof param.name === 'string') {
|
|
198
213
|
input[param.name] = param.value;
|
|
199
214
|
}
|
|
200
215
|
}
|
|
216
|
+
|
|
201
217
|
return input;
|
|
202
218
|
}
|
|
203
219
|
|
|
204
220
|
// The latest reset of every spent window; one without a known reset leaves resuming to a person.
|
|
205
221
|
function spentUntil(windows: readonly UsageWindow[]): number | null {
|
|
206
222
|
const spent = windows.filter((window) => window.used >= 1);
|
|
223
|
+
|
|
207
224
|
if (spent.length === 0 || spent.some((window) => window.resetsAt === null)) {
|
|
208
225
|
return null;
|
|
209
226
|
}
|
|
227
|
+
|
|
210
228
|
return Math.max(...spent.map((window) => window.resetsAt!));
|
|
211
229
|
}
|
|
212
230
|
|
|
@@ -247,6 +265,7 @@ export class CodexProtocol {
|
|
|
247
265
|
const policy = isRecord(result) ? str(result.approvalPolicy) : null;
|
|
248
266
|
const sandbox = isRecord(result) && isRecord(result.sandbox) ? str(result.sandbox.type) : null;
|
|
249
267
|
const effectiveRuntimeMode = policy && sandbox ? codexRuntimeMode(policy, sandbox) : undefined;
|
|
268
|
+
|
|
250
269
|
return [
|
|
251
270
|
{
|
|
252
271
|
type: 'session',
|
|
@@ -261,28 +280,38 @@ export class CodexProtocol {
|
|
|
261
280
|
|
|
262
281
|
handle(frame: CodexFrame): BackendEvent[] {
|
|
263
282
|
const events: BackendEvent[] = [];
|
|
283
|
+
|
|
264
284
|
if (typeof frame.method !== 'string') {
|
|
265
285
|
return events;
|
|
266
286
|
}
|
|
287
|
+
|
|
267
288
|
const params = isRecord(frame.params) ? frame.params : {};
|
|
289
|
+
|
|
268
290
|
if (frame.id !== undefined) {
|
|
269
291
|
this.handleServerRequest(frame.method, frame.id as CodexRpcId, params, events);
|
|
292
|
+
|
|
270
293
|
return events;
|
|
271
294
|
}
|
|
295
|
+
|
|
272
296
|
if (frame.method === 'serverRequest/resolved') {
|
|
273
297
|
const rpcId = params.requestId;
|
|
298
|
+
|
|
274
299
|
if (typeof rpcId === 'number' || typeof rpcId === 'string') {
|
|
275
300
|
const requestId = this.requestId(rpcId);
|
|
276
301
|
this.pending.delete(requestId);
|
|
277
302
|
events.push({ type: 'request.withdrawn', requestId });
|
|
278
303
|
}
|
|
304
|
+
|
|
279
305
|
return events;
|
|
280
306
|
}
|
|
307
|
+
|
|
281
308
|
if (frame.method === 'item/started' || frame.method === 'item/completed') {
|
|
282
309
|
this.rememberPatch(params);
|
|
283
310
|
}
|
|
311
|
+
|
|
284
312
|
// Codex 0.156.1 streams a spawned agent's turns, items and usage here too, under that agent's thread id.
|
|
285
313
|
const threadId = frame.method === 'thread/started' ? (isRecord(params.thread) ? str(params.thread.id) : null) : str(params.threadId);
|
|
314
|
+
|
|
286
315
|
if (threadId !== null && this.ownThreadId !== null && threadId !== this.ownThreadId) {
|
|
287
316
|
if (frame.method === 'turn/completed') {
|
|
288
317
|
this.withdrawOwner(
|
|
@@ -294,167 +323,213 @@ export class CodexProtocol {
|
|
|
294
323
|
} else if (frame.method === 'thread/closed') {
|
|
295
324
|
this.withdrawOwner(threadId, null, events);
|
|
296
325
|
}
|
|
326
|
+
|
|
297
327
|
return events;
|
|
298
328
|
}
|
|
329
|
+
|
|
299
330
|
switch (frame.method) {
|
|
300
331
|
case 'thread/started':
|
|
301
332
|
this.ownThreadId = threadId ?? this.ownThreadId;
|
|
302
333
|
events.push({ type: 'session', agentSessionId: threadId, model: null });
|
|
334
|
+
|
|
303
335
|
break;
|
|
304
336
|
case 'thread/name/updated': {
|
|
305
337
|
const title = cleanTitle(params.threadName);
|
|
338
|
+
|
|
306
339
|
if (title !== null) {
|
|
307
340
|
events.push({ type: 'title', title });
|
|
308
341
|
}
|
|
342
|
+
|
|
309
343
|
break;
|
|
310
344
|
}
|
|
311
345
|
case 'turn/started':
|
|
312
346
|
this.codexTurnId = isRecord(params.turn) ? str(params.turn.id) : null;
|
|
347
|
+
|
|
313
348
|
break;
|
|
314
349
|
case 'turn/completed':
|
|
315
350
|
this.handleTurnCompleted(params, events);
|
|
351
|
+
|
|
316
352
|
break;
|
|
317
353
|
case 'thread/goal/updated':
|
|
318
354
|
events.push({ type: 'goal', goal: codexGoal(params.goal) });
|
|
355
|
+
|
|
319
356
|
break;
|
|
320
357
|
case 'thread/goal/cleared':
|
|
321
358
|
events.push({ type: 'goal', goal: null });
|
|
359
|
+
|
|
322
360
|
break;
|
|
323
361
|
case 'thread/closed':
|
|
324
362
|
this.withdrawOwner(threadId ?? this.ownThreadId, null, events);
|
|
363
|
+
|
|
325
364
|
break;
|
|
326
365
|
case 'item/started':
|
|
327
366
|
case 'item/completed':
|
|
328
367
|
this.handleItem(params, frame.method === 'item/completed', events);
|
|
368
|
+
|
|
329
369
|
break;
|
|
330
370
|
case 'item/agentMessage/delta': {
|
|
331
371
|
const ref = str(params.itemId);
|
|
332
372
|
const text = str(params.delta);
|
|
373
|
+
|
|
333
374
|
if (ref && text) {
|
|
334
375
|
events.push({ type: 'text.delta', ref, text });
|
|
335
376
|
}
|
|
377
|
+
|
|
336
378
|
break;
|
|
337
379
|
}
|
|
338
380
|
case 'item/reasoning/summaryTextDelta':
|
|
339
381
|
case 'item/reasoning/textDelta': {
|
|
340
382
|
const ref = str(params.itemId);
|
|
341
383
|
const text = str(params.delta);
|
|
384
|
+
|
|
342
385
|
if (ref && text) {
|
|
343
386
|
events.push({ type: 'thinking.delta', ref, text });
|
|
344
387
|
}
|
|
388
|
+
|
|
345
389
|
break;
|
|
346
390
|
}
|
|
347
391
|
case 'item/reasoning/summaryPartAdded': {
|
|
348
392
|
// A new part of the same summary; without a break the parts run into each other.
|
|
349
393
|
const ref = str(params.itemId);
|
|
394
|
+
|
|
350
395
|
if (ref && num(params.summaryIndex) > 0) {
|
|
351
396
|
events.push({ type: 'thinking.delta', ref, text: '\n\n' });
|
|
352
397
|
}
|
|
398
|
+
|
|
353
399
|
break;
|
|
354
400
|
}
|
|
355
401
|
case 'item/commandExecution/outputDelta': {
|
|
356
402
|
const ref = str(params.itemId);
|
|
357
403
|
const text = outputChunk(params);
|
|
404
|
+
|
|
358
405
|
if (ref && text) {
|
|
359
406
|
events.push({ type: 'tool.output', ref, text });
|
|
360
407
|
}
|
|
408
|
+
|
|
361
409
|
break;
|
|
362
410
|
}
|
|
363
411
|
case 'thread/tokenUsage/updated': {
|
|
364
412
|
const usage = isRecord(params.tokenUsage) ? params.tokenUsage : {};
|
|
365
413
|
const last = isRecord(usage.last) ? usage.last : {};
|
|
366
414
|
const contextWindow = num(usage.modelContextWindow);
|
|
415
|
+
|
|
367
416
|
events.push({
|
|
368
417
|
type: 'usage',
|
|
369
418
|
contextTokens: num(last.totalTokens),
|
|
370
419
|
...(contextWindow > 0 ? { contextWindow } : {})
|
|
371
420
|
});
|
|
421
|
+
|
|
372
422
|
break;
|
|
373
423
|
}
|
|
374
424
|
case 'error': {
|
|
375
425
|
const error = isRecord(params.error) ? params.error : {};
|
|
376
426
|
events.push({ type: 'note', level: params.willRetry === true ? 'warning' : 'error', text: str(error.message) ?? 'Codex reported an error' });
|
|
427
|
+
|
|
377
428
|
break;
|
|
378
429
|
}
|
|
379
430
|
case 'account/rateLimits/updated': {
|
|
380
431
|
// The plan's own numbers, sent beside a token usage tick; the usage monitor keeps them.
|
|
381
432
|
const reading = readCodexLimits(isRecord(params.rateLimits) ? params.rateLimits : params);
|
|
433
|
+
|
|
382
434
|
if (reading !== null) {
|
|
383
435
|
this.spentUntil = spentUntil(reading.windows);
|
|
436
|
+
|
|
384
437
|
if (reading.windows.length > 0) {
|
|
385
438
|
events.push({ type: 'limits', update: { kind: 'codex', plan: reading.plan, windows: reading.windows } });
|
|
386
439
|
}
|
|
387
440
|
}
|
|
441
|
+
|
|
388
442
|
break;
|
|
389
443
|
}
|
|
390
444
|
case 'model/rerouted': {
|
|
391
445
|
const model = str(params.toModel);
|
|
446
|
+
|
|
392
447
|
if (model) {
|
|
393
448
|
events.push({ type: 'model', model });
|
|
394
449
|
}
|
|
450
|
+
|
|
395
451
|
break;
|
|
396
452
|
}
|
|
397
453
|
default:
|
|
398
454
|
break;
|
|
399
455
|
}
|
|
456
|
+
|
|
400
457
|
return events;
|
|
401
458
|
}
|
|
402
459
|
|
|
403
460
|
/* The reply that settles an approval, or null when nothing waits under that id. */
|
|
404
461
|
approvalDecision(requestId: string, decision: ApprovalDecision): { rpcId: CodexRpcId; result: unknown } | null {
|
|
405
462
|
const pending = this.pending.get(requestId);
|
|
463
|
+
|
|
406
464
|
if (pending?.type === 'permissions') {
|
|
407
465
|
this.pending.delete(requestId);
|
|
466
|
+
|
|
408
467
|
return {
|
|
409
468
|
rpcId: pending.rpcId,
|
|
410
469
|
result: { permissions: decision === 'deny' ? {} : pending.permissions, scope: decision === 'allow-always' ? 'session' : 'turn' }
|
|
411
470
|
};
|
|
412
471
|
}
|
|
472
|
+
|
|
413
473
|
if (pending?.type === 'elicitation') {
|
|
414
474
|
this.pending.delete(requestId);
|
|
475
|
+
|
|
415
476
|
return { rpcId: pending.rpcId, result: elicitationAnswer(decision, pending.persist) };
|
|
416
477
|
}
|
|
478
|
+
|
|
417
479
|
if (pending?.type !== 'approval') {
|
|
418
480
|
return null;
|
|
419
481
|
}
|
|
482
|
+
|
|
420
483
|
this.pending.delete(requestId);
|
|
484
|
+
|
|
421
485
|
return { rpcId: pending.rpcId, result: { decision: codexDecision(decision, pending) } };
|
|
422
486
|
}
|
|
423
487
|
|
|
424
488
|
/* An RPC reply, or plain text that continues the chat, for a question that still waits. */
|
|
425
489
|
questionAnswer(requestId: string, answers: Record<string, string>): CodexAnswer | null {
|
|
426
490
|
const pending = this.pending.get(requestId);
|
|
491
|
+
|
|
427
492
|
if (pending?.type !== 'question') {
|
|
428
493
|
return null;
|
|
429
494
|
}
|
|
495
|
+
|
|
430
496
|
this.pending.delete(requestId);
|
|
431
497
|
const given = pending.questionIds.map((id) => answers[id]).filter((answer): answer is string => answer !== undefined);
|
|
498
|
+
|
|
432
499
|
if (pending.rpcId === null) {
|
|
433
500
|
// An async question is answered like a message from the person, into the running turn.
|
|
434
501
|
return { kind: 'steer', text: given.join('\n') };
|
|
435
502
|
}
|
|
503
|
+
|
|
436
504
|
const byId: Record<string, { answers: string[] }> = {};
|
|
505
|
+
|
|
437
506
|
for (const id of pending.questionIds) {
|
|
438
507
|
const answer = answers[id];
|
|
508
|
+
|
|
439
509
|
if (answer !== undefined) {
|
|
440
510
|
byId[id] = { answers: [answer] };
|
|
441
511
|
}
|
|
442
512
|
}
|
|
513
|
+
|
|
443
514
|
return { kind: 'respond', rpcId: pending.rpcId, result: { answers: byId } };
|
|
444
515
|
}
|
|
445
516
|
|
|
446
517
|
dismissQuestionAnswer(requestId: string): CodexAnswer | null {
|
|
447
518
|
const pending = this.pending.get(requestId);
|
|
519
|
+
|
|
448
520
|
return pending?.type === 'question' && pending.async === true && pending.rpcId !== null ? this.questionAnswer(requestId, {}) : null;
|
|
449
521
|
}
|
|
450
522
|
|
|
451
523
|
/* Drops an asynchronous question the person walked away from; a blocking one has to be answered. */
|
|
452
524
|
dismissQuestion(requestId: string): boolean {
|
|
453
525
|
const pending = this.pending.get(requestId);
|
|
526
|
+
|
|
454
527
|
if (pending?.type !== 'question' || pending.rpcId !== null) {
|
|
455
528
|
return false;
|
|
456
529
|
}
|
|
530
|
+
|
|
457
531
|
this.pending.delete(requestId);
|
|
532
|
+
|
|
458
533
|
return true;
|
|
459
534
|
}
|
|
460
535
|
|
|
@@ -466,18 +541,23 @@ export class CodexProtocol {
|
|
|
466
541
|
|
|
467
542
|
declineRequest(requestId: string): { rpcId: CodexRpcId; result: unknown } | null {
|
|
468
543
|
const pending = this.pending.get(requestId);
|
|
544
|
+
|
|
469
545
|
if (!pending) {
|
|
470
546
|
return null;
|
|
471
547
|
}
|
|
548
|
+
|
|
472
549
|
if (pending.type !== 'question') {
|
|
473
550
|
return this.approvalDecision(requestId, 'deny');
|
|
474
551
|
}
|
|
552
|
+
|
|
475
553
|
this.pending.delete(requestId);
|
|
554
|
+
|
|
476
555
|
return pending.rpcId === null ? null : { rpcId: pending.rpcId, result: { answers: {} } };
|
|
477
556
|
}
|
|
478
557
|
|
|
479
558
|
private owner(params: Frame): RequestOwner {
|
|
480
559
|
const threadId = str(params.threadId) ?? this.ownThreadId;
|
|
560
|
+
|
|
481
561
|
return { threadId, turnId: str(params.turnId) ?? (threadId === this.ownThreadId ? this.codexTurnId : null) };
|
|
482
562
|
}
|
|
483
563
|
|
|
@@ -487,12 +567,15 @@ export class CodexProtocol {
|
|
|
487
567
|
|
|
488
568
|
private rememberPatch(params: Frame): void {
|
|
489
569
|
const item = isRecord(params.item) ? params.item : null;
|
|
570
|
+
|
|
490
571
|
if (item?.type !== 'fileChange' || typeof item.id !== 'string') {
|
|
491
572
|
return;
|
|
492
573
|
}
|
|
574
|
+
|
|
493
575
|
const owner = this.owner(params);
|
|
494
576
|
const changes = parseChanges(item.changes);
|
|
495
577
|
this.toolInputs.set(this.toolKey(owner.threadId, item.id), { ...owner, input: { summary: changes.map((change) => change.path).join(', ') }, changes });
|
|
578
|
+
|
|
496
579
|
// An approval copies its input into the event, so evicting cached metadata cannot change an open card.
|
|
497
580
|
if (this.toolInputs.size > 512) {
|
|
498
581
|
this.toolInputs.delete(this.toolInputs.keys().next().value!);
|
|
@@ -501,12 +584,14 @@ export class CodexProtocol {
|
|
|
501
584
|
|
|
502
585
|
private withdrawOwner(threadId: string | null, turnId: string | null, events: BackendEvent[], keepAsyncQuestions = false): void {
|
|
503
586
|
const belongs = (owner: RequestOwner): boolean => owner.threadId === threadId && (turnId === null || owner.turnId === null || owner.turnId === turnId);
|
|
587
|
+
|
|
504
588
|
for (const [requestId, pending] of this.pending) {
|
|
505
589
|
if (belongs(pending) && !(keepAsyncQuestions && pending.type === 'question' && pending.async === true)) {
|
|
506
590
|
this.pending.delete(requestId);
|
|
507
591
|
events.push({ type: 'request.withdrawn', requestId });
|
|
508
592
|
}
|
|
509
593
|
}
|
|
594
|
+
|
|
510
595
|
for (const [key, input] of this.toolInputs) {
|
|
511
596
|
if (belongs(input)) {
|
|
512
597
|
this.toolInputs.delete(key);
|
|
@@ -516,6 +601,7 @@ export class CodexProtocol {
|
|
|
516
601
|
|
|
517
602
|
private isChildRequest(params: Frame): boolean {
|
|
518
603
|
const threadId = str(params.threadId);
|
|
604
|
+
|
|
519
605
|
return threadId !== null && this.ownThreadId !== null && threadId !== this.ownThreadId;
|
|
520
606
|
}
|
|
521
607
|
|
|
@@ -523,8 +609,10 @@ export class CodexProtocol {
|
|
|
523
609
|
if (!this.isChildRequest(params)) {
|
|
524
610
|
return description;
|
|
525
611
|
}
|
|
612
|
+
|
|
526
613
|
const threadId = str(params.threadId)!;
|
|
527
614
|
const label = this.agentRows.get(threadId)?.label || threadId;
|
|
615
|
+
|
|
528
616
|
return [`Requested by subagent ${label}.`, description].filter(Boolean).join(' ');
|
|
529
617
|
}
|
|
530
618
|
|
|
@@ -534,11 +622,14 @@ export class CodexProtocol {
|
|
|
534
622
|
|
|
535
623
|
private handleServerRequest(method: string, rpcId: CodexRpcId, params: Frame, events: BackendEvent[]): void {
|
|
536
624
|
const requestId = this.requestId(rpcId);
|
|
625
|
+
|
|
537
626
|
if (method === 'item/tool/requestUserInput') {
|
|
538
627
|
const questions = parseBlockingQuestions(params.questions);
|
|
628
|
+
|
|
539
629
|
if (questions.length === 0) {
|
|
540
630
|
return;
|
|
541
631
|
}
|
|
632
|
+
|
|
542
633
|
this.pending.set(requestId, {
|
|
543
634
|
...this.owner(params),
|
|
544
635
|
type: 'question',
|
|
@@ -546,6 +637,7 @@ export class CodexProtocol {
|
|
|
546
637
|
questionIds: questions.map((question) => question.id),
|
|
547
638
|
async: params.isBlocking === false
|
|
548
639
|
});
|
|
640
|
+
|
|
549
641
|
events.push({
|
|
550
642
|
type: 'question.requested',
|
|
551
643
|
requestId,
|
|
@@ -553,14 +645,18 @@ export class CodexProtocol {
|
|
|
553
645
|
...(params.isBlocking === false ? { async: true } : {}),
|
|
554
646
|
...(this.isChildRequest(params) ? { background: true } : {})
|
|
555
647
|
});
|
|
648
|
+
|
|
556
649
|
return;
|
|
557
650
|
}
|
|
651
|
+
|
|
558
652
|
if (method === 'item/permissions/requestApproval') {
|
|
559
653
|
if (!isRecord(params.permissions)) {
|
|
560
654
|
return;
|
|
561
655
|
}
|
|
656
|
+
|
|
562
657
|
const permissions = params.permissions;
|
|
563
658
|
this.pending.set(requestId, { ...this.owner(params), type: 'permissions', rpcId, permissions });
|
|
659
|
+
|
|
564
660
|
events.push({
|
|
565
661
|
type: 'approval.requested',
|
|
566
662
|
requestId,
|
|
@@ -572,31 +668,41 @@ export class CodexProtocol {
|
|
|
572
668
|
allowAlways: { label: 'Allow for this session', description: 'Grant these permissions for the rest of this session.' },
|
|
573
669
|
...(this.isChildRequest(params) ? { background: true } : {})
|
|
574
670
|
});
|
|
671
|
+
|
|
575
672
|
return;
|
|
576
673
|
}
|
|
674
|
+
|
|
577
675
|
if (method === 'mcpServer/elicitation/request') {
|
|
578
676
|
this.elicitation(requestId, rpcId, params, events);
|
|
677
|
+
|
|
579
678
|
return;
|
|
580
679
|
}
|
|
680
|
+
|
|
581
681
|
const kind = method === 'item/commandExecution/requestApproval' ? 'command' : method === 'item/fileChange/requestApproval' ? 'fileChange' : null;
|
|
682
|
+
|
|
582
683
|
if (!kind) {
|
|
583
684
|
return;
|
|
584
685
|
}
|
|
686
|
+
|
|
585
687
|
const itemId = str(params.itemId);
|
|
586
688
|
const known = itemId ? this.toolInputs.get(this.toolKey(this.owner(params).threadId, itemId)) : undefined;
|
|
689
|
+
|
|
587
690
|
const decisions = Array.isArray(params.availableDecisions)
|
|
588
691
|
? params.availableDecisions.map((decision) =>
|
|
589
692
|
typeof decision === 'string' ? decision : (Object.keys(isRecord(decision) ? decision : {})[0] ?? '')
|
|
590
693
|
)
|
|
591
694
|
: [];
|
|
695
|
+
|
|
592
696
|
const amendment = Array.isArray(params.proposedExecpolicyAmendment)
|
|
593
697
|
? params.proposedExecpolicyAmendment.filter((word): word is string => typeof word === 'string')
|
|
594
698
|
: null;
|
|
699
|
+
|
|
595
700
|
// The person must see what they are approving, so a file change repeats its diffs here.
|
|
596
701
|
const input =
|
|
597
702
|
kind === 'command'
|
|
598
703
|
? { command: unwrapCommand(str(params.command) ?? ''), cwd: str(params.cwd) ?? undefined }
|
|
599
704
|
: { ...(isRecord(known?.input) ? known.input : { itemId }), changes: known?.changes ?? [] };
|
|
705
|
+
|
|
600
706
|
this.pending.set(requestId, {
|
|
601
707
|
...this.owner(params),
|
|
602
708
|
type: 'approval',
|
|
@@ -605,6 +711,7 @@ export class CodexProtocol {
|
|
|
605
711
|
amendment: amendment && amendment.length > 0 ? amendment : null,
|
|
606
712
|
decisions
|
|
607
713
|
});
|
|
714
|
+
|
|
608
715
|
events.push({
|
|
609
716
|
type: 'approval.requested',
|
|
610
717
|
requestId,
|
|
@@ -635,9 +742,11 @@ export class CodexProtocol {
|
|
|
635
742
|
if (!isConfirmation(params)) {
|
|
636
743
|
return;
|
|
637
744
|
}
|
|
745
|
+
|
|
638
746
|
const meta = isRecord(params._meta) ? params._meta : {};
|
|
639
747
|
const persist = elicitationPersist(meta);
|
|
640
748
|
this.pending.set(requestId, { ...this.owner(params), type: 'elicitation', rpcId, persist });
|
|
749
|
+
|
|
641
750
|
events.push({
|
|
642
751
|
type: 'approval.requested',
|
|
643
752
|
requestId,
|
|
@@ -658,17 +767,21 @@ export class CodexProtocol {
|
|
|
658
767
|
private handleItem(params: Frame, completed: boolean, events: BackendEvent[]): void {
|
|
659
768
|
const item = isRecord(params.item) ? params.item : null;
|
|
660
769
|
const ref = item ? str(item.id) : null;
|
|
770
|
+
|
|
661
771
|
if (!item || !ref) {
|
|
662
772
|
return;
|
|
663
773
|
}
|
|
774
|
+
|
|
664
775
|
switch (item.type) {
|
|
665
776
|
case 'agentMessage': {
|
|
666
777
|
const questions = parseAsyncQuestions(item.questions);
|
|
778
|
+
|
|
667
779
|
if (questions.length > 0) {
|
|
668
780
|
// Codex keeps polling for an answer to this one; the id it carries is the item's own.
|
|
669
781
|
if (!completed || this.pending.has(ref)) {
|
|
670
782
|
return;
|
|
671
783
|
}
|
|
784
|
+
|
|
672
785
|
this.pending.set(ref, {
|
|
673
786
|
...this.owner(params),
|
|
674
787
|
type: 'question',
|
|
@@ -676,21 +789,28 @@ export class CodexProtocol {
|
|
|
676
789
|
questionIds: questions.map((question) => question.id),
|
|
677
790
|
async: true
|
|
678
791
|
});
|
|
792
|
+
|
|
679
793
|
events.push({ type: 'question.requested', requestId: ref, questions, async: true });
|
|
794
|
+
|
|
680
795
|
return;
|
|
681
796
|
}
|
|
797
|
+
|
|
682
798
|
this.text(ref, str(item.text) ?? '', completed, events, str(item.phase) ?? undefined);
|
|
799
|
+
|
|
683
800
|
return;
|
|
684
801
|
}
|
|
685
802
|
case 'plan':
|
|
686
803
|
this.text(`plan-${ref}`, str(item.text) ?? '', completed, events);
|
|
804
|
+
|
|
687
805
|
return;
|
|
688
806
|
case 'reasoning': {
|
|
689
807
|
// The deltas already streamed this; the completed item is what a resumed thread replays.
|
|
690
808
|
const lines = [...toLines(item.summary), ...toLines(item.content)];
|
|
809
|
+
|
|
691
810
|
if (completed && lines.length > 0) {
|
|
692
811
|
events.push({ type: 'thinking.done', ref, text: lines.join('\n\n') });
|
|
693
812
|
}
|
|
813
|
+
|
|
694
814
|
return;
|
|
695
815
|
}
|
|
696
816
|
case 'commandExecution': {
|
|
@@ -698,18 +818,23 @@ export class CodexProtocol {
|
|
|
698
818
|
const succeeded = str(item.status) === 'completed';
|
|
699
819
|
// Its turn is long over, so only the row learns how it ended; a start again would open a turn nobody ends.
|
|
700
820
|
const backgroundId = this.backgroundTerminals.get(ref);
|
|
821
|
+
|
|
701
822
|
if (completed && backgroundId !== undefined) {
|
|
702
823
|
this.backgroundTerminals.delete(ref);
|
|
703
824
|
events.push({ type: 'tool.done', ref, output, state: succeeded ? 'done' : 'error' });
|
|
704
825
|
events.push({ type: 'background.ended', taskId: backgroundId });
|
|
826
|
+
|
|
705
827
|
return;
|
|
706
828
|
}
|
|
829
|
+
|
|
707
830
|
const processId = str(item.processId);
|
|
831
|
+
|
|
708
832
|
if (!completed && processId !== null) {
|
|
709
833
|
this.openTerminals.set(ref, processId);
|
|
710
834
|
} else {
|
|
711
835
|
this.openTerminals.delete(ref);
|
|
712
836
|
}
|
|
837
|
+
|
|
713
838
|
this.tool(
|
|
714
839
|
ref,
|
|
715
840
|
'Bash',
|
|
@@ -719,10 +844,12 @@ export class CodexProtocol {
|
|
|
719
844
|
succeeded,
|
|
720
845
|
events
|
|
721
846
|
);
|
|
847
|
+
|
|
722
848
|
return;
|
|
723
849
|
}
|
|
724
850
|
case 'fileChange': {
|
|
725
851
|
const changes = parseChanges(item.changes);
|
|
852
|
+
|
|
726
853
|
this.tool(
|
|
727
854
|
ref,
|
|
728
855
|
'ApplyPatch',
|
|
@@ -733,11 +860,13 @@ export class CodexProtocol {
|
|
|
733
860
|
events,
|
|
734
861
|
changes
|
|
735
862
|
);
|
|
863
|
+
|
|
736
864
|
return;
|
|
737
865
|
}
|
|
738
866
|
case 'mcpToolCall': {
|
|
739
867
|
const result = isRecord(item.result) ? textOf(item.result.content) : '';
|
|
740
868
|
const error = isRecord(item.error) ? (str(item.error.message) ?? '') : '';
|
|
869
|
+
|
|
741
870
|
this.tool(
|
|
742
871
|
ref,
|
|
743
872
|
`${str(item.server) ?? 'mcp'}/${str(item.tool) ?? 'tool'}`,
|
|
@@ -747,62 +876,78 @@ export class CodexProtocol {
|
|
|
747
876
|
str(item.status) === 'completed',
|
|
748
877
|
events
|
|
749
878
|
);
|
|
879
|
+
|
|
750
880
|
return;
|
|
751
881
|
}
|
|
752
882
|
case 'dynamicToolCall':
|
|
753
883
|
this.tool(ref, str(item.tool) ?? 'tool', item.arguments ?? {}, completed, textOf(item.contentItems), item.success !== false, events);
|
|
884
|
+
|
|
754
885
|
return;
|
|
755
886
|
case 'imageGeneration': {
|
|
756
887
|
const revisedPrompt = str(item.revisedPrompt) ?? undefined;
|
|
757
888
|
const transparentBackground = typeof item.transparentBackground === 'boolean' ? item.transparentBackground : undefined;
|
|
889
|
+
|
|
758
890
|
const input = {
|
|
759
891
|
...(revisedPrompt === undefined ? {} : { revisedPrompt }),
|
|
760
892
|
...(transparentBackground === undefined ? {} : { transparentBackground })
|
|
761
893
|
};
|
|
894
|
+
|
|
762
895
|
if (!completed) {
|
|
763
896
|
this.tool(ref, 'ImageGeneration', input, false, '', true, events);
|
|
897
|
+
|
|
764
898
|
return;
|
|
765
899
|
}
|
|
900
|
+
|
|
766
901
|
try {
|
|
767
902
|
if (item.failure || item.status !== 'completed') {
|
|
768
903
|
throw new Error(
|
|
769
904
|
isRecord(item.failure) ? (str(item.failure.message) ?? 'Image generation failed') : (str(item.failure) ?? 'Image generation failed')
|
|
770
905
|
);
|
|
771
906
|
}
|
|
907
|
+
|
|
772
908
|
const image = generatedImage(str(item.result) ?? '', revisedPrompt);
|
|
773
909
|
events.push({ type: 'image.generated', ref, ...image, ...input });
|
|
774
910
|
} catch (error) {
|
|
775
911
|
this.tool(ref, 'ImageGeneration', input, true, error instanceof Error ? error.message : 'Image generation failed', false, events);
|
|
776
912
|
}
|
|
913
|
+
|
|
777
914
|
return;
|
|
778
915
|
}
|
|
779
916
|
case 'imageView':
|
|
780
917
|
this.tool(ref, 'ImageView', { path: str(item.path) ?? '' }, completed, '', true, events);
|
|
918
|
+
|
|
781
919
|
return;
|
|
782
920
|
case 'webSearch': {
|
|
783
921
|
const action = isRecord(item.action) ? item.action : {};
|
|
784
922
|
this.tool(ref, 'WebSearch', { query: str(item.query) ?? str(action.query) ?? '' }, completed, '', true, events);
|
|
923
|
+
|
|
785
924
|
return;
|
|
786
925
|
}
|
|
787
926
|
case 'collabAgentToolCall': {
|
|
788
927
|
// Codex's own multi-agent calls; spawnAgent is the one handled as a delegation.
|
|
789
928
|
const tool = str(item.tool) ?? 'collabAgent';
|
|
790
929
|
const input = { tool, prompt: str(item.prompt) ?? undefined, model: str(item.model) ?? undefined, threads: item.receiverThreadIds ?? [] };
|
|
930
|
+
|
|
791
931
|
if (tool === 'spawnAgent') {
|
|
792
932
|
this.spawnedAgent(ref, item, input, events);
|
|
933
|
+
|
|
793
934
|
return;
|
|
794
935
|
}
|
|
936
|
+
|
|
795
937
|
this.tool(ref, tool, input, completed, collabAgentOutput(item.agentsStates), str(item.status) === 'completed', events);
|
|
796
938
|
this.settleEndedAgents(item.agentsStates, events);
|
|
939
|
+
|
|
797
940
|
return;
|
|
798
941
|
}
|
|
799
942
|
case 'subAgentActivity':
|
|
800
943
|
this.subAgentActivity(item, events);
|
|
944
|
+
|
|
801
945
|
return;
|
|
802
946
|
case 'contextCompaction':
|
|
803
947
|
if (completed) {
|
|
804
948
|
events.push({ type: 'compaction', preTokens: null });
|
|
805
949
|
}
|
|
950
|
+
|
|
806
951
|
return;
|
|
807
952
|
default:
|
|
808
953
|
return;
|
|
@@ -817,12 +962,15 @@ export class CodexProtocol {
|
|
|
817
962
|
const turnId = str(turn.id) ?? this.codexTurnId;
|
|
818
963
|
this.codexTurnId = null;
|
|
819
964
|
this.withdrawOwner(str(params.threadId) ?? this.ownThreadId, turnId, events, status === 'completed');
|
|
965
|
+
|
|
820
966
|
// A terminal session still open as the turn ends is one Codex left running; it only completes once its process does.
|
|
821
967
|
for (const [ref, processId] of this.openTerminals) {
|
|
822
968
|
this.backgroundTerminals.set(ref, processId);
|
|
823
969
|
events.push({ type: 'background.started', taskId: processId, ref, monitor: false, description: null });
|
|
824
970
|
}
|
|
971
|
+
|
|
825
972
|
this.openTerminals.clear();
|
|
973
|
+
|
|
826
974
|
events.push({
|
|
827
975
|
type: 'turn.done',
|
|
828
976
|
state: status === 'interrupted' ? 'aborted' : status === 'failed' ? 'error' : 'done',
|
|
@@ -843,9 +991,11 @@ export class CodexProtocol {
|
|
|
843
991
|
if (info === 'usageLimitExceeded') {
|
|
844
992
|
return { kind: 'usage', ...(this.spentUntil === null ? {} : { resetsAt: this.spentUntil }) };
|
|
845
993
|
}
|
|
994
|
+
|
|
846
995
|
if (info === 'serverOverloaded' || info === 'rateLimitExceeded') {
|
|
847
996
|
return { kind: 'overload' };
|
|
848
997
|
}
|
|
998
|
+
|
|
849
999
|
return null;
|
|
850
1000
|
}
|
|
851
1001
|
|
|
@@ -863,6 +1013,7 @@ export class CodexProtocol {
|
|
|
863
1013
|
// One spawn opens one thread; Codex only knows its id once the call went through.
|
|
864
1014
|
const threadId = Array.isArray(item.receiverThreadIds) ? str(item.receiverThreadIds[0]) : null;
|
|
865
1015
|
events.push({ type: 'tool.started', ref, name: 'Agent', input, parentRef: null });
|
|
1016
|
+
|
|
866
1017
|
events.push({
|
|
867
1018
|
type: 'task.started',
|
|
868
1019
|
ref,
|
|
@@ -873,15 +1024,19 @@ export class CodexProtocol {
|
|
|
873
1024
|
background: true,
|
|
874
1025
|
...(threadId ? { threadId } : {})
|
|
875
1026
|
});
|
|
1027
|
+
|
|
876
1028
|
if (threadId === null) {
|
|
877
1029
|
if (str(item.status) === 'failed') {
|
|
878
1030
|
events.push({ type: 'task.done', ref, summary: summary || null, ok: false });
|
|
879
1031
|
}
|
|
1032
|
+
|
|
880
1033
|
return;
|
|
881
1034
|
}
|
|
1035
|
+
|
|
882
1036
|
if (!this.agentRows.has(threadId)) {
|
|
883
1037
|
this.agentRows.set(threadId, { ref, label: (str(item.prompt) ?? '').split('\n')[0] ?? '', settled: false });
|
|
884
1038
|
}
|
|
1039
|
+
|
|
885
1040
|
if (!this.settleEndedAgents(item.agentsStates, events)) {
|
|
886
1041
|
events.push({ type: 'task.progress', ref, summary: summary || null, lastTool: null, usage: null });
|
|
887
1042
|
}
|
|
@@ -890,17 +1045,21 @@ export class CodexProtocol {
|
|
|
890
1045
|
/* Settles the row of every agent a collab call reports stopped; true when one of them did just now. */
|
|
891
1046
|
private settleEndedAgents(states: unknown, events: BackendEvent[]): boolean {
|
|
892
1047
|
let settled = false;
|
|
1048
|
+
|
|
893
1049
|
for (const [threadId, state] of Object.entries(isRecord(states) ? states : {})) {
|
|
894
1050
|
const status = isRecord(state) ? str(state.status) : null;
|
|
895
1051
|
const row = this.agentRows.get(threadId);
|
|
1052
|
+
|
|
896
1053
|
if (row === undefined || row.settled || status === null || !ENDED_AGENT_STATES.has(status)) {
|
|
897
1054
|
continue;
|
|
898
1055
|
}
|
|
1056
|
+
|
|
899
1057
|
row.settled = true;
|
|
900
1058
|
this.withdrawOwner(threadId, null, events, status === 'completed');
|
|
901
1059
|
settled = true;
|
|
902
1060
|
events.push({ type: 'task.done', ref: row.ref, summary: collabAgentOutput({ [threadId]: state }), ok: status === 'completed' });
|
|
903
1061
|
}
|
|
1062
|
+
|
|
904
1063
|
return settled;
|
|
905
1064
|
}
|
|
906
1065
|
|
|
@@ -913,21 +1072,27 @@ export class CodexProtocol {
|
|
|
913
1072
|
const ref = str(item.id);
|
|
914
1073
|
const threadId = str(item.agentThreadId);
|
|
915
1074
|
const kind = str(item.kind);
|
|
1075
|
+
|
|
916
1076
|
if (ref === null || threadId === null) {
|
|
917
1077
|
return;
|
|
918
1078
|
}
|
|
1079
|
+
|
|
919
1080
|
const row = this.agentRows.get(threadId);
|
|
1081
|
+
|
|
920
1082
|
if ((kind === 'started' || kind === 'interacted') && row === undefined) {
|
|
921
1083
|
// The path ends in the name the parent gave the agent, which is all the stream says about its task.
|
|
922
1084
|
const path = str(item.agentPath) ?? '';
|
|
923
1085
|
this.agentRows.set(threadId, { ref, label: path.split('/').at(-1) ?? '', settled: false, ...(kind === 'interacted' ? { interaction: ref } : {}) });
|
|
924
1086
|
events.push({ type: 'tool.started', ref, name: 'Agent', input: { agentPath: path }, parentRef: null });
|
|
925
1087
|
events.push({ type: 'task.started', ref, description: path.split('/').at(-1) ?? '', subagentType: null, prompt: null, background: true, threadId });
|
|
1088
|
+
|
|
926
1089
|
return;
|
|
927
1090
|
}
|
|
1091
|
+
|
|
928
1092
|
if (kind === 'interacted' && row !== undefined && row.interaction !== ref) {
|
|
929
1093
|
row.interaction = ref;
|
|
930
1094
|
row.settled = false;
|
|
1095
|
+
|
|
931
1096
|
events.push({
|
|
932
1097
|
type: 'task.started',
|
|
933
1098
|
ref: row.ref,
|
|
@@ -938,8 +1103,10 @@ export class CodexProtocol {
|
|
|
938
1103
|
threadId,
|
|
939
1104
|
resumed: true
|
|
940
1105
|
});
|
|
1106
|
+
|
|
941
1107
|
return;
|
|
942
1108
|
}
|
|
1109
|
+
|
|
943
1110
|
if ((kind === 'completed' || kind === 'interrupted') && row !== undefined && !row.settled) {
|
|
944
1111
|
row.settled = true;
|
|
945
1112
|
this.withdrawOwner(threadId, null, events, kind === 'completed');
|
|
@@ -958,6 +1125,7 @@ export class CodexProtocol {
|
|
|
958
1125
|
changes?: ChatFileChange[]
|
|
959
1126
|
): void {
|
|
960
1127
|
events.push({ type: 'tool.started', ref, name, input, parentRef: null, ...(changes && changes.length > 0 ? { changes } : {}) });
|
|
1128
|
+
|
|
961
1129
|
if (completed) {
|
|
962
1130
|
events.push({ type: 'tool.done', ref, output, state: succeeded ? 'done' : 'error', ...(changes && changes.length > 0 ? { changes } : {}) });
|
|
963
1131
|
}
|
|
@@ -972,12 +1140,15 @@ function codexDecision(decision: ApprovalDecision, pending: Extract<Pending, { t
|
|
|
972
1140
|
if (decision === 'deny') {
|
|
973
1141
|
return 'decline';
|
|
974
1142
|
}
|
|
1143
|
+
|
|
975
1144
|
if (decision === 'allow') {
|
|
976
1145
|
return 'accept';
|
|
977
1146
|
}
|
|
1147
|
+
|
|
978
1148
|
if (pending.amendment) {
|
|
979
1149
|
return { acceptWithExecpolicyAmendment: { execpolicy_amendment: pending.amendment } };
|
|
980
1150
|
}
|
|
1151
|
+
|
|
981
1152
|
return pending.kind === 'fileChange' || pending.decisions.includes('acceptForSession') ? 'acceptForSession' : 'accept';
|
|
982
1153
|
}
|
|
983
1154
|
|
|
@@ -986,5 +1157,6 @@ function elicitationAnswer(decision: ApprovalDecision, persist: ElicitationPersi
|
|
|
986
1157
|
if (decision === 'deny') {
|
|
987
1158
|
return { action: 'decline' };
|
|
988
1159
|
}
|
|
1160
|
+
|
|
989
1161
|
return decision === 'allow-always' && persist !== null ? { action: 'accept', content: {}, _meta: { persist } } : { action: 'accept', content: {} };
|
|
990
1162
|
}
|