@adecore/agents 0.39.0 → 0.40.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.
- 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-models.json +3 -2
- 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/prices-snapshot.json +8 -0
- 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-models.json +3 -2
- 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/prices-snapshot.json +8 -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
|
@@ -108,6 +108,7 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
108
108
|
const turnCompleted = (status: 'completed' | 'interrupted' | 'failed', error: string | null = null, codexErrorInfo: string | null = null): void => {
|
|
109
109
|
usage();
|
|
110
110
|
notify('thread/status/changed', { threadId, status: { type: 'idle' } });
|
|
111
|
+
|
|
111
112
|
notify('turn/completed', {
|
|
112
113
|
threadId,
|
|
113
114
|
turn: {
|
|
@@ -118,6 +119,7 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
118
119
|
error: error ? { message: error, codexErrorInfo, additionalDetails: null } : null
|
|
119
120
|
}
|
|
120
121
|
});
|
|
122
|
+
|
|
121
123
|
turnId = '';
|
|
122
124
|
};
|
|
123
125
|
|
|
@@ -137,11 +139,14 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
137
139
|
goalSteps += 1;
|
|
138
140
|
agentMessage(`goal step ${goalSteps}`);
|
|
139
141
|
const needed = Number(/steps:(\d+)/.exec(String(goal?.objective ?? ''))?.[1] ?? '1');
|
|
142
|
+
|
|
140
143
|
if (goal !== null) {
|
|
141
144
|
goal = { ...goal, tokensUsed: Number(goal.tokensUsed) + 100, status: goalSteps >= needed ? 'complete' : goal.status };
|
|
142
145
|
goalChanged();
|
|
143
146
|
}
|
|
147
|
+
|
|
144
148
|
turnCompleted('completed');
|
|
149
|
+
|
|
145
150
|
if (goal?.status === 'active') {
|
|
146
151
|
io.later(() => {
|
|
147
152
|
if (goal?.status === 'active') {
|
|
@@ -160,6 +165,7 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
160
165
|
const askApproval = (kind: 'command' | 'fileChange', entry: Frame, params: Frame): void => {
|
|
161
166
|
pendingApproval = { rpcId: serverRequestId, entry, kind };
|
|
162
167
|
notify('thread/status/changed', { threadId, status: { type: 'active', activeFlags: ['waitingOnApproval'] } });
|
|
168
|
+
|
|
163
169
|
out({
|
|
164
170
|
method: kind === 'command' ? 'item/commandExecution/requestApproval' : 'item/fileChange/requestApproval',
|
|
165
171
|
id: serverRequestId++,
|
|
@@ -173,56 +179,77 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
173
179
|
const raw = input?.text ?? '';
|
|
174
180
|
const gap = raw.indexOf('\n\n');
|
|
175
181
|
const noted = options.resumeLead !== undefined && raw.startsWith(options.resumeLead) && gap !== -1;
|
|
182
|
+
|
|
176
183
|
if (noted) {
|
|
177
184
|
pasted = raw.slice(0, gap);
|
|
178
185
|
}
|
|
186
|
+
|
|
179
187
|
const text = noted ? raw.slice(gap + 2) : raw;
|
|
188
|
+
|
|
180
189
|
if (text === 'name?') {
|
|
181
190
|
turnStarted();
|
|
182
191
|
agentMessage(threadName ?? 'unnamed');
|
|
183
192
|
turnCompleted('completed');
|
|
193
|
+
|
|
184
194
|
return;
|
|
185
195
|
}
|
|
196
|
+
|
|
186
197
|
if (text === 'note?' || text === 'pasted?') {
|
|
187
198
|
turnStarted();
|
|
188
199
|
agentMessage((text === 'note?' ? threadInstructions.get(threadId) : pasted) ?? 'nothing');
|
|
189
200
|
turnCompleted('completed');
|
|
201
|
+
|
|
190
202
|
return;
|
|
191
203
|
}
|
|
204
|
+
|
|
192
205
|
turnStarted();
|
|
193
206
|
const user = item('userMessage', { clientId: null, content: [{ type: 'text', text, text_elements: [] }] });
|
|
194
207
|
started(user);
|
|
195
208
|
completed(user);
|
|
209
|
+
|
|
196
210
|
if (goal !== null && (text === goal.objective || text === '')) {
|
|
197
211
|
io.later(goalStep);
|
|
212
|
+
|
|
198
213
|
return;
|
|
199
214
|
}
|
|
215
|
+
|
|
200
216
|
if (text === 'crash') {
|
|
201
217
|
io.exit(1);
|
|
218
|
+
|
|
202
219
|
return;
|
|
203
220
|
}
|
|
221
|
+
|
|
204
222
|
if (text === 'crash loudly') {
|
|
205
223
|
io.err('Error: the fake lost its thread\n');
|
|
206
224
|
io.exit(1);
|
|
225
|
+
|
|
207
226
|
return;
|
|
208
227
|
}
|
|
228
|
+
|
|
209
229
|
if (text === 'fail') {
|
|
210
230
|
turnCompleted('failed', 'The model is overloaded');
|
|
231
|
+
|
|
211
232
|
return;
|
|
212
233
|
}
|
|
234
|
+
|
|
213
235
|
// Lines of their own, so a task brief around them still fails the way Codex 0.156.1 does.
|
|
214
236
|
const lines = text.split('\n');
|
|
237
|
+
|
|
215
238
|
// A dev server Codex leaves running past the turn; its process ends once the test says later is.
|
|
216
239
|
if (lines.includes('serve')) {
|
|
217
240
|
const command = item('commandExecution', { command: "/bin/zsh -lc 'bun dev'", cwd: io.cwd, processId: `pty-${nonce}`, status: 'inProgress' });
|
|
218
241
|
started(command);
|
|
219
242
|
io.later(() => completed({ ...command, status: 'completed', aggregatedOutput: '', exitCode: 0 }));
|
|
220
243
|
}
|
|
244
|
+
|
|
221
245
|
if (lines.includes('overloaded')) {
|
|
222
246
|
turnCompleted('failed', 'Selected model is at capacity. Please try a different model.', 'serverOverloaded');
|
|
247
|
+
|
|
223
248
|
return;
|
|
224
249
|
}
|
|
250
|
+
|
|
225
251
|
const limit = lines.find((line) => line.startsWith('limit:'));
|
|
252
|
+
|
|
226
253
|
if (limit !== undefined) {
|
|
227
254
|
notify('account/rateLimits/updated', {
|
|
228
255
|
rateLimits: {
|
|
@@ -231,11 +258,15 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
231
258
|
secondary: { usedPercent: 40, windowDurationMins: 10080, resetsAt: Number(limit.slice(6)) + 86_400 }
|
|
232
259
|
}
|
|
233
260
|
});
|
|
261
|
+
|
|
234
262
|
turnCompleted('failed', "You've hit your usage limit.", 'usageLimitExceeded');
|
|
263
|
+
|
|
235
264
|
return;
|
|
236
265
|
}
|
|
266
|
+
|
|
237
267
|
if (text.startsWith('image:')) {
|
|
238
268
|
const scenario = text.slice(6);
|
|
269
|
+
|
|
239
270
|
const entry = item('imageGeneration', {
|
|
240
271
|
status: 'inProgress',
|
|
241
272
|
result: '',
|
|
@@ -243,7 +274,9 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
243
274
|
revisedPrompt: 'A rabbit',
|
|
244
275
|
transparentBackground: true
|
|
245
276
|
});
|
|
277
|
+
|
|
246
278
|
started(entry);
|
|
279
|
+
|
|
247
280
|
const result =
|
|
248
281
|
scenario === 'empty'
|
|
249
282
|
? ''
|
|
@@ -254,27 +287,36 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
254
287
|
: scenario === 'too-large'
|
|
255
288
|
? Buffer.alloc(10 * 1024 * 1024 + 1).toString('base64')
|
|
256
289
|
: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jRZkAAAAASUVORK5CYII=';
|
|
290
|
+
|
|
257
291
|
const done = {
|
|
258
292
|
...entry,
|
|
259
293
|
status: scenario === 'failed' ? 'failed' : 'completed',
|
|
260
294
|
result,
|
|
261
295
|
...(scenario === 'failed' ? { failure: { message: 'Image generation failed' } } : {})
|
|
262
296
|
};
|
|
297
|
+
|
|
263
298
|
completed(done);
|
|
299
|
+
|
|
264
300
|
if (scenario === 'replay') {
|
|
265
301
|
completed(done);
|
|
266
302
|
}
|
|
303
|
+
|
|
267
304
|
agentMessage('Here is the generated image.');
|
|
268
305
|
turnCompleted('completed');
|
|
306
|
+
|
|
269
307
|
return;
|
|
270
308
|
}
|
|
309
|
+
|
|
271
310
|
if (text === 'slow') {
|
|
272
311
|
slow = true;
|
|
273
312
|
started(item('agentMessage', { text: '', phase: 'final_answer', memoryCitation: null, delivery: null, questions: null }));
|
|
313
|
+
|
|
274
314
|
return;
|
|
275
315
|
}
|
|
316
|
+
|
|
276
317
|
if (text.startsWith('tool:')) {
|
|
277
318
|
const command = text.slice(5).trim();
|
|
319
|
+
|
|
278
320
|
const entry = item('commandExecution', {
|
|
279
321
|
command: `/bin/zsh -lc ${command}`,
|
|
280
322
|
cwd: io.cwd,
|
|
@@ -282,7 +324,9 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
282
324
|
aggregatedOutput: null,
|
|
283
325
|
exitCode: null
|
|
284
326
|
});
|
|
327
|
+
|
|
285
328
|
started(entry);
|
|
329
|
+
|
|
286
330
|
askApproval('command', entry, {
|
|
287
331
|
kind: 'command',
|
|
288
332
|
command: `/bin/zsh -lc ${command}`,
|
|
@@ -291,20 +335,26 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
291
335
|
proposedExecpolicyAmendment: [command.split(' ')[0]],
|
|
292
336
|
availableDecisions: ['accept', { acceptWithExecpolicyAmendment: { execpolicy_amendment: [command.split(' ')[0]] } }, 'cancel']
|
|
293
337
|
});
|
|
338
|
+
|
|
294
339
|
return;
|
|
295
340
|
}
|
|
341
|
+
|
|
296
342
|
if (text.startsWith('edit:')) {
|
|
297
343
|
const path = text.slice(5).trim();
|
|
298
344
|
const entry = item('fileChange', { changes: [{ path, kind: { type: 'add' }, diff: `+++ ${path}\n+hello\n` }], status: 'inProgress' });
|
|
299
345
|
started(entry);
|
|
300
346
|
askApproval('fileChange', entry, { reason: 'Write outside the sandbox' });
|
|
347
|
+
|
|
301
348
|
return;
|
|
302
349
|
}
|
|
350
|
+
|
|
303
351
|
const finish = text.startsWith('spawn and finish:');
|
|
352
|
+
|
|
304
353
|
if (finish || text.startsWith('spawn:')) {
|
|
305
354
|
const prompt = text.slice(text.indexOf(':') + 1).trim();
|
|
306
355
|
const childId = `child-${nonce}-${++itemCounter}`;
|
|
307
356
|
const steps: Frame[] = [{ type: 'userMessage', id: `${childId}-prompt`, content: [{ type: 'text', text: prompt, text_elements: [] }] }];
|
|
357
|
+
|
|
308
358
|
for (let step = 1; step <= FAKE_CHILD_STEPS; step++) {
|
|
309
359
|
steps.push({
|
|
310
360
|
type: 'commandExecution',
|
|
@@ -316,7 +366,9 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
316
366
|
exitCode: 0
|
|
317
367
|
});
|
|
318
368
|
}
|
|
369
|
+
|
|
319
370
|
childThreads.set(childId, steps);
|
|
371
|
+
|
|
320
372
|
const spawn = item('collabAgentToolCall', {
|
|
321
373
|
tool: 'spawnAgent',
|
|
322
374
|
prompt,
|
|
@@ -326,22 +378,28 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
326
378
|
agentsStates: {},
|
|
327
379
|
status: 'inProgress'
|
|
328
380
|
});
|
|
381
|
+
|
|
329
382
|
started(spawn);
|
|
330
383
|
const running = { ...spawn, receiverThreadIds: [childId], agentsStates: { [childId]: { status: 'running', message: null } } };
|
|
384
|
+
|
|
331
385
|
if (!finish) {
|
|
332
386
|
completed(running);
|
|
333
387
|
agentMessage('spawned');
|
|
334
388
|
turnCompleted('completed');
|
|
389
|
+
|
|
335
390
|
return;
|
|
336
391
|
}
|
|
392
|
+
|
|
337
393
|
completed({ ...running, status: 'completed', agentsStates: { [childId]: { status: 'completed', message: 'the docs are read' } } });
|
|
338
394
|
agentMessage('spawned');
|
|
339
395
|
const spawnedIn = turnId;
|
|
340
396
|
turnCompleted('completed');
|
|
341
397
|
// A snapshot of the call from while its agent still ran, delivered after the agent finished.
|
|
342
398
|
io.later(() => notify('item/completed', { item: running, threadId, turnId: spawnedIn, completedAtMs: Date.now() }));
|
|
399
|
+
|
|
343
400
|
return;
|
|
344
401
|
}
|
|
402
|
+
|
|
345
403
|
/*
|
|
346
404
|
* A spawn the way Codex 0.156.1 reports it: no spawnAgent call, a `subAgentActivity` that started under
|
|
347
405
|
* the call id, the agent's own thread streaming over the same connection, and a `completed` activity
|
|
@@ -352,14 +410,17 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
352
410
|
const childId = `child-${nonce}-${++itemCounter}`;
|
|
353
411
|
const childTurn = `turn-${nonce}-${++itemCounter}`;
|
|
354
412
|
childThreads.set(childId, [{ type: 'userMessage', id: `${childId}-prompt`, content: [{ type: 'text', text: prompt, text_elements: [] }] }]);
|
|
413
|
+
|
|
355
414
|
const activity = (id: string, kind: string): void => {
|
|
356
415
|
const entry = { type: 'subAgentActivity', id, kind, agentThreadId: childId, agentPath: '/root/survey' };
|
|
357
416
|
started(entry);
|
|
358
417
|
completed(entry);
|
|
359
418
|
};
|
|
419
|
+
|
|
360
420
|
const inChild = (method: string, params: Frame): void => {
|
|
361
421
|
notify(method, { threadId: childId, ...params });
|
|
362
422
|
};
|
|
423
|
+
|
|
363
424
|
activity(`call-spawn-${nonce}-${++itemCounter}`, 'started');
|
|
364
425
|
inChild('thread/status/changed', { status: { type: 'active', activeFlags: [] } });
|
|
365
426
|
inChild('turn/started', { turn: { id: childTurn, items: [], itemsView: 'notLoaded', status: 'inProgress', error: null } });
|
|
@@ -371,11 +432,14 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
371
432
|
agentMessage('spawned');
|
|
372
433
|
turnCompleted('completed');
|
|
373
434
|
io.later(() => activity(`subagent-completed-${childTurn}`, 'completed'));
|
|
435
|
+
|
|
374
436
|
return;
|
|
375
437
|
}
|
|
438
|
+
|
|
376
439
|
if (text.startsWith('ask:')) {
|
|
377
440
|
const question = text.slice(4).trim();
|
|
378
441
|
pendingQuestion = { rpcId: serverRequestId, questionId: 'color' };
|
|
442
|
+
|
|
379
443
|
out({
|
|
380
444
|
method: 'item/tool/requestUserInput',
|
|
381
445
|
id: serverRequestId++,
|
|
@@ -399,11 +463,14 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
399
463
|
]
|
|
400
464
|
}
|
|
401
465
|
});
|
|
466
|
+
|
|
402
467
|
return;
|
|
403
468
|
}
|
|
469
|
+
|
|
404
470
|
if (text.startsWith('async:')) {
|
|
405
471
|
const question = text.slice(6).trim();
|
|
406
472
|
pendingSteer = true;
|
|
473
|
+
|
|
407
474
|
const entry = item('agentMessage', {
|
|
408
475
|
text: `${question}\n- Red\n- Blue`,
|
|
409
476
|
phase: 'final_answer',
|
|
@@ -411,10 +478,13 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
411
478
|
delivery: 'async',
|
|
412
479
|
questions: [{ title: question, options: ['Red', 'Blue'] }]
|
|
413
480
|
});
|
|
481
|
+
|
|
414
482
|
started(entry);
|
|
415
483
|
completed(entry);
|
|
484
|
+
|
|
416
485
|
return;
|
|
417
486
|
}
|
|
487
|
+
|
|
418
488
|
agentMessage(`echo: ${text}${turnEffort ? ` (${turnEffort})` : ''}`);
|
|
419
489
|
turnCompleted('completed');
|
|
420
490
|
};
|
|
@@ -425,8 +495,10 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
425
495
|
const decision = result.decision;
|
|
426
496
|
const accepted = decision === 'accept' || decision === 'acceptForSession' || (typeof decision === 'object' && decision !== null);
|
|
427
497
|
const always = decision === 'acceptForSession' || (typeof decision === 'object' && decision !== null);
|
|
498
|
+
|
|
428
499
|
if (approval.kind === 'command') {
|
|
429
500
|
const command = String(approval.entry.command).replace('/bin/zsh -lc ', '');
|
|
501
|
+
|
|
430
502
|
completed({
|
|
431
503
|
...approval.entry,
|
|
432
504
|
status: accepted ? 'completed' : 'declined',
|
|
@@ -436,6 +508,7 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
436
508
|
} else {
|
|
437
509
|
completed({ ...approval.entry, status: accepted ? 'completed' : 'declined' });
|
|
438
510
|
}
|
|
511
|
+
|
|
439
512
|
agentMessage(accepted ? (always ? 'done, remembered' : 'done') : 'denied');
|
|
440
513
|
turnCompleted('completed');
|
|
441
514
|
};
|
|
@@ -451,6 +524,7 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
451
524
|
switch (method) {
|
|
452
525
|
case 'initialize':
|
|
453
526
|
out({ id, result: { userAgent: 'fake-codex/0.0.0', codexHome: io.cwd, platformFamily: 'unix', platformOs: 'test' } });
|
|
527
|
+
|
|
454
528
|
return;
|
|
455
529
|
case 'model/list':
|
|
456
530
|
out({
|
|
@@ -460,24 +534,31 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
460
534
|
nextCursor: null
|
|
461
535
|
}
|
|
462
536
|
});
|
|
537
|
+
|
|
463
538
|
return;
|
|
464
539
|
case 'thread/start':
|
|
465
540
|
case 'thread/resume': {
|
|
466
541
|
const resumed = method === 'thread/resume' ? String(params.threadId) : null;
|
|
542
|
+
|
|
467
543
|
if (resumed === 'gone') {
|
|
468
544
|
out({ id, error: { code: -32600, message: 'no thread gone' } });
|
|
545
|
+
|
|
469
546
|
return;
|
|
470
547
|
}
|
|
548
|
+
|
|
471
549
|
threadId = resumed ?? `fake-${Math.random().toString(36).slice(2, 8)}`;
|
|
550
|
+
|
|
472
551
|
// Codex 0.154 ignores developer instructions on a resume; only a start sets them.
|
|
473
552
|
if (resumed === null) {
|
|
474
553
|
threadInstructions.set(threadId, typeof params.developerInstructions === 'string' ? params.developerInstructions : null);
|
|
475
554
|
}
|
|
555
|
+
|
|
476
556
|
threadModel = typeof params.model === 'string' ? params.model : 'fake-model';
|
|
477
557
|
threadSandbox = typeof params.sandbox === 'string' ? params.sandbox : '';
|
|
478
558
|
threadApproval = typeof params.approvalPolicy === 'string' ? params.approvalPolicy : '';
|
|
479
559
|
threadName = threadId.startsWith('named-') ? 'Named before' : null;
|
|
480
560
|
const thread = { id: threadId, model: threadModel, cwd: params.cwd, turns: [], name: threadName };
|
|
561
|
+
|
|
481
562
|
out({
|
|
482
563
|
id,
|
|
483
564
|
result: {
|
|
@@ -489,51 +570,67 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
489
570
|
sandbox: { type: threadSandbox }
|
|
490
571
|
}
|
|
491
572
|
});
|
|
573
|
+
|
|
492
574
|
notify('thread/started', { thread });
|
|
575
|
+
|
|
493
576
|
return;
|
|
494
577
|
}
|
|
495
578
|
case 'thread/inject_items': {
|
|
496
579
|
if (options.injectItems === false) {
|
|
497
580
|
out({ id, error: { code: -32601, message: 'unknown method thread/inject_items' } });
|
|
581
|
+
|
|
498
582
|
return;
|
|
499
583
|
}
|
|
584
|
+
|
|
500
585
|
// The newest developer message is the one the model follows, so `note?` answers with it.
|
|
501
586
|
const items = Array.isArray(params.items) ? (params.items as Frame[]) : [];
|
|
587
|
+
|
|
502
588
|
for (const injected of items) {
|
|
503
589
|
const content = Array.isArray(injected.content) ? (injected.content[0] as { text?: unknown } | undefined) : undefined;
|
|
590
|
+
|
|
504
591
|
if (injected.role === 'developer' && typeof content?.text === 'string') {
|
|
505
592
|
threadInstructions.set(String(params.threadId), content.text);
|
|
506
593
|
}
|
|
507
594
|
}
|
|
595
|
+
|
|
508
596
|
out({ id, result: {} });
|
|
597
|
+
|
|
509
598
|
return;
|
|
510
599
|
}
|
|
511
600
|
case 'thread/fork': {
|
|
512
601
|
const source = String(params.threadId);
|
|
513
602
|
const turns = threadTurns.get(source) ?? [];
|
|
514
603
|
const last = typeof params.lastTurnId === 'string' ? turns.indexOf(params.lastTurnId) : turns.length - 1;
|
|
604
|
+
|
|
515
605
|
if (!threadTurns.has(source) || (typeof params.lastTurnId === 'string' && last < 0)) {
|
|
516
606
|
out({ id, error: { code: -32600, message: `no rollout found for thread id ${source}` } });
|
|
607
|
+
|
|
517
608
|
return;
|
|
518
609
|
}
|
|
610
|
+
|
|
519
611
|
fakeCodexForks.push(params);
|
|
520
612
|
const forked = `fork-${Math.random().toString(36).slice(2, 8)}`;
|
|
521
613
|
threadTurns.set(forked, turns.slice(0, last + 1));
|
|
522
614
|
threadInstructions.set(forked, threadInstructions.get(source) ?? null);
|
|
523
615
|
out({ id, result: { thread: { id: forked, forkedFromId: source, cwd: params.cwd, turns: [] }, model: params.model ?? 'fake-model' } });
|
|
616
|
+
|
|
524
617
|
return;
|
|
525
618
|
}
|
|
526
619
|
case 'thread/turns/list': {
|
|
527
620
|
const turns = threadTurns.get(String(params.threadId)) ?? [];
|
|
528
621
|
out({ id, result: { data: turns.map((turn) => ({ id: turn, items: [], status: 'completed', error: null })), nextCursor: null } });
|
|
622
|
+
|
|
529
623
|
return;
|
|
530
624
|
}
|
|
531
625
|
case 'thread/items/list': {
|
|
532
626
|
const steps = childThreads.get(String(params.threadId));
|
|
627
|
+
|
|
533
628
|
if (!steps) {
|
|
534
629
|
out({ id, error: { code: -32600, message: `no rollout found for thread id ${String(params.threadId)}` } });
|
|
630
|
+
|
|
535
631
|
return;
|
|
536
632
|
}
|
|
633
|
+
|
|
537
634
|
// The cursor is where the next page starts, counted from the end the list is sorted from.
|
|
538
635
|
const ordered = params.sortDirection === 'asc' ? steps : [...steps].reverse();
|
|
539
636
|
const start = typeof params.cursor === 'string' ? Number(params.cursor) : 0;
|
|
@@ -541,16 +638,19 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
541
638
|
const page = ordered.slice(start, start + limit);
|
|
542
639
|
const next = start + limit < ordered.length ? String(start + limit) : null;
|
|
543
640
|
out({ id, result: { data: page.map((entry) => ({ item: entry, turnId: 'child-turn' })), nextCursor: next, backwardsCursor: null } });
|
|
641
|
+
|
|
544
642
|
return;
|
|
545
643
|
}
|
|
546
644
|
case 'thread/name/set':
|
|
547
645
|
threadName = String(params.name);
|
|
548
646
|
out({ id, result: {} });
|
|
549
647
|
notify('thread/name/updated', { threadId, threadName });
|
|
648
|
+
|
|
550
649
|
return;
|
|
551
650
|
case 'turn/start':
|
|
552
651
|
out({ id, result: { turn: { id: `turn-${nonce}-${itemCounter + 1}`, items: [], status: 'inProgress', error: null } } });
|
|
553
652
|
handleTurnStart(params);
|
|
653
|
+
|
|
554
654
|
return;
|
|
555
655
|
case 'turn/steer': {
|
|
556
656
|
// The real app-server refuses a steer that does not name the turn it is meant for.
|
|
@@ -560,29 +660,37 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
560
660
|
// assertion rather than on a timeout.
|
|
561
661
|
pendingSteer = false;
|
|
562
662
|
turnCompleted('failed');
|
|
663
|
+
|
|
563
664
|
return;
|
|
564
665
|
}
|
|
666
|
+
|
|
565
667
|
out({ id, result: { turnId } });
|
|
668
|
+
|
|
566
669
|
if (pendingSteer) {
|
|
567
670
|
pendingSteer = false;
|
|
568
671
|
const input = Array.isArray(params.input) ? (params.input[0] as { text?: string } | undefined) : undefined;
|
|
569
672
|
agentMessage(`you chose ${input?.text ?? 'no answer'}`);
|
|
570
673
|
turnCompleted('completed');
|
|
571
674
|
}
|
|
675
|
+
|
|
572
676
|
return;
|
|
573
677
|
}
|
|
574
678
|
case 'turn/interrupt':
|
|
575
679
|
out({ id, result: {} });
|
|
680
|
+
|
|
576
681
|
if (slow) {
|
|
577
682
|
slow = false;
|
|
578
683
|
turnCompleted('interrupted');
|
|
579
684
|
}
|
|
685
|
+
|
|
580
686
|
return;
|
|
581
687
|
case 'thread/goal/get':
|
|
582
688
|
out({ id, result: { goal } });
|
|
689
|
+
|
|
583
690
|
return;
|
|
584
691
|
case 'thread/goal/set': {
|
|
585
692
|
const at = Math.floor(Date.now() / 1000);
|
|
693
|
+
|
|
586
694
|
if (typeof params.objective === 'string') {
|
|
587
695
|
goal = {
|
|
588
696
|
threadId,
|
|
@@ -594,26 +702,34 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
594
702
|
createdAt: at,
|
|
595
703
|
updatedAt: at
|
|
596
704
|
};
|
|
705
|
+
|
|
597
706
|
goalSteps = 0;
|
|
598
707
|
}
|
|
708
|
+
|
|
599
709
|
if (goal === null) {
|
|
600
710
|
out({ id, error: { code: -32600, message: 'no goal to update' } });
|
|
711
|
+
|
|
601
712
|
return;
|
|
602
713
|
}
|
|
714
|
+
|
|
603
715
|
if (typeof params.status === 'string') {
|
|
604
716
|
goal = { ...goal, status: params.status, updatedAt: at };
|
|
605
717
|
}
|
|
718
|
+
|
|
606
719
|
out({ id, result: { goal } });
|
|
607
720
|
goalChanged();
|
|
721
|
+
|
|
608
722
|
return;
|
|
609
723
|
}
|
|
610
724
|
case 'thread/goal/clear': {
|
|
611
725
|
const cleared = goal !== null;
|
|
612
726
|
goal = null;
|
|
613
727
|
out({ id, result: { cleared } });
|
|
728
|
+
|
|
614
729
|
if (cleared) {
|
|
615
730
|
notify('thread/goal/cleared', { threadId });
|
|
616
731
|
}
|
|
732
|
+
|
|
617
733
|
return;
|
|
618
734
|
}
|
|
619
735
|
case 'thread/compact/start':
|
|
@@ -622,6 +738,7 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
622
738
|
started(item('contextCompaction', {}));
|
|
623
739
|
completed(item('contextCompaction', {}));
|
|
624
740
|
turnCompleted('completed');
|
|
741
|
+
|
|
625
742
|
return;
|
|
626
743
|
default:
|
|
627
744
|
out({ id, error: { code: -32601, message: `unknown method ${method}` } });
|
|
@@ -632,16 +749,21 @@ export function fakeCodexWith(options: FakeCodexOptions): FakeCli {
|
|
|
632
749
|
onLine: (line) => {
|
|
633
750
|
const frame = JSON.parse(line) as Frame;
|
|
634
751
|
const params = (frame.params ?? {}) as Frame;
|
|
752
|
+
|
|
635
753
|
if (typeof frame.method === 'string' && frame.id !== undefined) {
|
|
636
754
|
handleRequest(frame.id, frame.method, params);
|
|
755
|
+
|
|
637
756
|
return;
|
|
638
757
|
}
|
|
758
|
+
|
|
639
759
|
if (frame.id === undefined || frame.result === undefined) {
|
|
640
760
|
return;
|
|
641
761
|
}
|
|
762
|
+
|
|
642
763
|
const result = frame.result as Frame;
|
|
643
764
|
const approval = pendingApproval;
|
|
644
765
|
const question = pendingQuestion;
|
|
766
|
+
|
|
645
767
|
if (approval && approval.rpcId === frame.id) {
|
|
646
768
|
handleApprovalResponse(approval, result);
|
|
647
769
|
} else if (question && question.rpcId === frame.id) {
|
|
@@ -13,6 +13,7 @@ export class ManualClock implements OutboxClock {
|
|
|
13
13
|
setTimeout(run: () => void, ms: number): unknown {
|
|
14
14
|
const id = this.nextId++;
|
|
15
15
|
this.timers.push({ at: this.time + ms, run, id });
|
|
16
|
+
|
|
16
17
|
return id;
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -24,6 +25,7 @@ export class ManualClock implements OutboxClock {
|
|
|
24
25
|
this.time += ms;
|
|
25
26
|
const due = this.timers.filter((timer) => timer.at <= this.time);
|
|
26
27
|
this.timers = this.timers.filter((timer) => timer.at > this.time);
|
|
28
|
+
|
|
27
29
|
for (const timer of due) {
|
|
28
30
|
timer.run();
|
|
29
31
|
}
|
package/src/testing/watch.ts
CHANGED
|
@@ -17,6 +17,7 @@ export class FakeDirectoryWatcher implements DirectoryWatcher {
|
|
|
17
17
|
if (this.closed) {
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
this.listener(event, filename);
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -41,18 +42,19 @@ export class FakeWatch implements WatchSeams {
|
|
|
41
42
|
readonly watch: WatchDirectory = (path, options, listener) => {
|
|
42
43
|
const watcher = new FakeDirectoryWatcher(path, options.recursive, listener);
|
|
43
44
|
this.watchers.push(watcher);
|
|
45
|
+
|
|
44
46
|
return watcher;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
readonly schedule = (callback: () => unknown, _ms: number): (() => void) => {
|
|
48
50
|
const id = this.nextId++;
|
|
49
51
|
this.due.set(id, callback);
|
|
52
|
+
|
|
50
53
|
return () => {
|
|
51
54
|
this.due.delete(id);
|
|
52
55
|
};
|
|
53
56
|
};
|
|
54
57
|
|
|
55
|
-
/* How many timers are waiting to run. */
|
|
56
58
|
get pending(): number {
|
|
57
59
|
return this.due.size;
|
|
58
60
|
}
|
|
@@ -64,9 +66,11 @@ export class FakeWatch implements WatchSeams {
|
|
|
64
66
|
/* The one open watcher on `path`, which a test that emits on it expects to exist. */
|
|
65
67
|
on(path: string): FakeDirectoryWatcher {
|
|
66
68
|
const [watcher, ...rest] = this.openOn(path);
|
|
69
|
+
|
|
67
70
|
if (!watcher || rest.length > 0) {
|
|
68
71
|
throw new Error(`Expected one open watcher on ${path}, found ${rest.length + (watcher ? 1 : 0)}`);
|
|
69
72
|
}
|
|
73
|
+
|
|
70
74
|
return watcher;
|
|
71
75
|
}
|
|
72
76
|
|
|
@@ -86,6 +90,7 @@ export class EventLog<T> {
|
|
|
86
90
|
this.events.push(event);
|
|
87
91
|
const ready = this.waiters.filter((waiter) => waiter.check(this.events));
|
|
88
92
|
this.waiters = this.waiters.filter((waiter) => !ready.includes(waiter));
|
|
93
|
+
|
|
89
94
|
for (const waiter of ready) {
|
|
90
95
|
waiter.resolve();
|
|
91
96
|
}
|
|
@@ -95,6 +100,7 @@ export class EventLog<T> {
|
|
|
95
100
|
if (check(this.events)) {
|
|
96
101
|
return Promise.resolve();
|
|
97
102
|
}
|
|
103
|
+
|
|
98
104
|
return new Promise((resolve) => {
|
|
99
105
|
this.waiters.push({ check, resolve });
|
|
100
106
|
});
|
package/src/title-file.ts
CHANGED
|
@@ -12,10 +12,13 @@ export function cleanTitle(raw: unknown): string | null {
|
|
|
12
12
|
if (typeof raw !== 'string') {
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
|
+
|
|
15
16
|
const text = raw.replace(CONTROL, ' ').replace(/\s+/g, ' ').trim();
|
|
17
|
+
|
|
16
18
|
if (text === '') {
|
|
17
19
|
return null;
|
|
18
20
|
}
|
|
21
|
+
|
|
19
22
|
return text.length <= SUGGESTED_TITLE_LIMIT ? text : `${clipText(text, SUGGESTED_TITLE_LIMIT - 1).trimEnd()}…`;
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -28,30 +31,39 @@ export function cleanTitle(raw: unknown): string | null {
|
|
|
28
31
|
export async function readLines(path: string, from: number, size: number, chunkBytes: number, onLine: (line: string) => void): Promise<number> {
|
|
29
32
|
const handle = await open(path, 'r');
|
|
30
33
|
let offset = from;
|
|
34
|
+
|
|
31
35
|
try {
|
|
32
36
|
let carry = Buffer.alloc(0);
|
|
33
37
|
let position = from;
|
|
38
|
+
|
|
34
39
|
while (position < size) {
|
|
35
40
|
const piece = Buffer.alloc(Math.min(chunkBytes, size - position));
|
|
36
41
|
const { bytesRead } = await handle.read(piece, 0, piece.length, position);
|
|
42
|
+
|
|
37
43
|
if (bytesRead === 0) {
|
|
38
44
|
break;
|
|
39
45
|
}
|
|
46
|
+
|
|
40
47
|
position += bytesRead;
|
|
41
48
|
const bytes = Buffer.concat([carry, piece.subarray(0, bytesRead)]);
|
|
42
49
|
const end = bytes.lastIndexOf(0x0a);
|
|
50
|
+
|
|
43
51
|
if (end < 0) {
|
|
44
52
|
carry = bytes;
|
|
53
|
+
|
|
45
54
|
continue;
|
|
46
55
|
}
|
|
56
|
+
|
|
47
57
|
for (const line of bytes.subarray(0, end).toString('utf8').split('\n')) {
|
|
48
58
|
onLine(line);
|
|
49
59
|
}
|
|
60
|
+
|
|
50
61
|
carry = bytes.subarray(end + 1);
|
|
51
62
|
offset = position - carry.length;
|
|
52
63
|
}
|
|
53
64
|
} finally {
|
|
54
65
|
await handle.close();
|
|
55
66
|
}
|
|
67
|
+
|
|
56
68
|
return offset;
|
|
57
69
|
}
|