@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
|
@@ -16,11 +16,13 @@ function textInput(text: string) {
|
|
|
16
16
|
export function parseSkillsList(result: unknown): ChatSkill[] {
|
|
17
17
|
const data = isRecord(result) && Array.isArray(result.data) ? result.data : [];
|
|
18
18
|
const skills = new Map<string, ChatSkill>();
|
|
19
|
+
|
|
19
20
|
for (const entry of data) {
|
|
20
21
|
for (const skill of isRecord(entry) && Array.isArray(entry.skills) ? entry.skills : []) {
|
|
21
22
|
if (!isRecord(skill) || typeof skill.name !== 'string' || skill.enabled === false || skills.has(skill.name)) {
|
|
22
23
|
continue;
|
|
23
24
|
}
|
|
25
|
+
|
|
24
26
|
skills.set(skill.name, {
|
|
25
27
|
name: skill.name,
|
|
26
28
|
description: typeof skill.description === 'string' ? skill.description : '',
|
|
@@ -28,6 +30,7 @@ export function parseSkillsList(result: unknown): ChatSkill[] {
|
|
|
28
30
|
});
|
|
29
31
|
}
|
|
30
32
|
}
|
|
33
|
+
|
|
31
34
|
return [...skills.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -39,20 +42,26 @@ const CLEAR_WORDS = new Set(['clear', 'stop', 'off', 'reset', 'none', 'cancel'])
|
|
|
39
42
|
/* A `/goal` message the way the CLIs read one: a bare `/goal` shows the goal, a control word acts on it, and anything else is a new objective. Null for any other message. */
|
|
40
43
|
export function parseGoalCommand(text: string): GoalCommand | null {
|
|
41
44
|
const match = /^\/goal(?:\s+([\s\S]*))?$/.exec(text.trim());
|
|
45
|
+
|
|
42
46
|
if (!match) {
|
|
43
47
|
return null;
|
|
44
48
|
}
|
|
49
|
+
|
|
45
50
|
const argument = (match[1] ?? '').trim();
|
|
46
51
|
const word = argument.toLowerCase();
|
|
52
|
+
|
|
47
53
|
if (word === '') {
|
|
48
54
|
return { kind: 'show' };
|
|
49
55
|
}
|
|
56
|
+
|
|
50
57
|
if (CLEAR_WORDS.has(word)) {
|
|
51
58
|
return { kind: 'clear' };
|
|
52
59
|
}
|
|
60
|
+
|
|
53
61
|
if (word === 'pause' || word === 'resume') {
|
|
54
62
|
return { kind: word };
|
|
55
63
|
}
|
|
64
|
+
|
|
56
65
|
return { kind: 'set', objective: argument };
|
|
57
66
|
}
|
|
58
67
|
|
|
@@ -111,6 +120,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
111
120
|
if (this.transport) {
|
|
112
121
|
return;
|
|
113
122
|
}
|
|
123
|
+
|
|
114
124
|
const transport = new CodexTransport({
|
|
115
125
|
command: this.launch.command,
|
|
116
126
|
cwd: this.launch.cwd,
|
|
@@ -120,7 +130,9 @@ export class CodexBackend implements ChatBackend {
|
|
|
120
130
|
onFrame: (frame) => this.handleFrame(transport, frame),
|
|
121
131
|
onExit: (exitCode, stderr) => this.handleExit(transport, exitCode, stderr)
|
|
122
132
|
});
|
|
133
|
+
|
|
123
134
|
this.transport = transport;
|
|
135
|
+
|
|
124
136
|
try {
|
|
125
137
|
await this.handshake(transport);
|
|
126
138
|
} catch (error) {
|
|
@@ -129,6 +141,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
129
141
|
this.transport = null;
|
|
130
142
|
void transport.dispose();
|
|
131
143
|
}
|
|
144
|
+
|
|
132
145
|
throw error;
|
|
133
146
|
}
|
|
134
147
|
}
|
|
@@ -137,14 +150,17 @@ export class CodexBackend implements ChatBackend {
|
|
|
137
150
|
await transport.request('initialize', { clientInfo: this.client, capabilities: { experimentalApi: true, requestAttestation: false } });
|
|
138
151
|
transport.notify('initialized', {});
|
|
139
152
|
const tier = codexServiceTier(this.launch.selection);
|
|
153
|
+
|
|
140
154
|
const params = {
|
|
141
155
|
cwd: this.launch.cwd,
|
|
142
156
|
model: this.launch.selection.model,
|
|
143
157
|
...(tier === null ? {} : { serviceTier: tier }),
|
|
144
158
|
...codexThreadOptions(this.launch.runtimeMode)
|
|
145
159
|
};
|
|
160
|
+
|
|
146
161
|
const instructions = this.launch.instructions === null ? {} : { developerInstructions: this.launch.instructions };
|
|
147
162
|
let result: unknown;
|
|
163
|
+
|
|
148
164
|
if (this.launch.resume) {
|
|
149
165
|
try {
|
|
150
166
|
result = await transport.request('thread/resume', { threadId: this.launch.resume, excludeTurns: true, ...params });
|
|
@@ -154,19 +170,24 @@ export class CodexBackend implements ChatBackend {
|
|
|
154
170
|
if (!(error instanceof Error) || error.name !== 'CodexRpcError') {
|
|
155
171
|
throw error;
|
|
156
172
|
}
|
|
173
|
+
|
|
157
174
|
this.emit({ type: 'note', level: 'warning', text: `Codex could not resume its thread (${errorText(error)}). Started a new one.` });
|
|
158
175
|
result = await transport.request('thread/start', { ...params, ...instructions });
|
|
159
176
|
}
|
|
160
177
|
} else {
|
|
161
178
|
result = await transport.request('thread/start', { ...params, ...instructions });
|
|
162
179
|
}
|
|
180
|
+
|
|
163
181
|
this.imageInputSupported = await this.readImageSupport(transport);
|
|
182
|
+
|
|
164
183
|
for (const event of this.protocol.threadReady(result)) {
|
|
165
184
|
if (event.type === 'session' && event.agentSessionId) {
|
|
166
185
|
this.threadId = event.agentSessionId;
|
|
167
186
|
}
|
|
187
|
+
|
|
168
188
|
this.emit(event);
|
|
169
189
|
}
|
|
190
|
+
|
|
170
191
|
await this.readGoal(transport).catch(() => {
|
|
171
192
|
// An app-server without goals, or with the feature turned off, has none to report.
|
|
172
193
|
});
|
|
@@ -178,6 +199,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
178
199
|
*/
|
|
179
200
|
private async resumeContext(transport: CodexTransport, threadId: string): Promise<string | null> {
|
|
180
201
|
const instructions = this.launch.resumeInstructions ?? null;
|
|
202
|
+
|
|
181
203
|
if (instructions !== null) {
|
|
182
204
|
try {
|
|
183
205
|
const items = [{ type: 'message', role: 'developer', content: [{ type: 'input_text', text: instructions }] }];
|
|
@@ -186,26 +208,32 @@ export class CodexBackend implements ChatBackend {
|
|
|
186
208
|
if (!(error instanceof Error) || error.name !== 'CodexRpcError') {
|
|
187
209
|
throw error;
|
|
188
210
|
}
|
|
211
|
+
|
|
189
212
|
return [instructions, this.launch.resumeNote].filter((part) => part !== null).join('\n\n');
|
|
190
213
|
}
|
|
191
214
|
}
|
|
215
|
+
|
|
192
216
|
return this.launch.resumeNote;
|
|
193
217
|
}
|
|
194
218
|
|
|
195
219
|
private async readGoal(transport: CodexTransport): Promise<ChatGoal | null> {
|
|
196
220
|
const result = await transport.request('thread/goal/get', { threadId: this.threadId });
|
|
197
221
|
const goal = codexGoal(isRecord(result) ? result.goal : null);
|
|
222
|
+
|
|
198
223
|
if (this.transport === transport) {
|
|
199
224
|
this.emit({ type: 'goal', goal });
|
|
200
225
|
}
|
|
226
|
+
|
|
201
227
|
return goal;
|
|
202
228
|
}
|
|
203
229
|
|
|
204
230
|
private turnInput(input: TurnInput): unknown[] {
|
|
205
231
|
const images = input.attachments.filter((attachment) => attachmentImageMime(attachment) !== null);
|
|
232
|
+
|
|
206
233
|
if (images.length > 0 && this.imageInputSupported === false) {
|
|
207
234
|
throw new Error(`${this.launch.modelName} does not support image input. Choose a model that accepts images.`);
|
|
208
235
|
}
|
|
236
|
+
|
|
209
237
|
for (const attachment of images) {
|
|
210
238
|
try {
|
|
211
239
|
accessSync(attachment.path, constants.R_OK);
|
|
@@ -213,28 +241,37 @@ export class CodexBackend implements ChatBackend {
|
|
|
213
241
|
throw new Error(`Could not read attached image "${attachment.name}". Attach it again and retry.`);
|
|
214
242
|
}
|
|
215
243
|
}
|
|
244
|
+
|
|
216
245
|
const parts: string[] = [];
|
|
246
|
+
|
|
217
247
|
if (this.contextPending !== null) {
|
|
218
248
|
parts.push(`${this.contextPending}\n\n`);
|
|
219
249
|
this.contextPending = null;
|
|
220
250
|
}
|
|
251
|
+
|
|
221
252
|
if (input.preamble !== null) {
|
|
222
253
|
parts.push(`${input.preamble}\n\n`);
|
|
223
254
|
}
|
|
255
|
+
|
|
224
256
|
parts.push(input.text);
|
|
225
257
|
const note = attachmentNote(input.attachments);
|
|
258
|
+
|
|
226
259
|
if (note !== '') {
|
|
227
260
|
parts.push(`\n\n${note}`);
|
|
228
261
|
}
|
|
262
|
+
|
|
229
263
|
return [...textInput(parts.join('')), ...images.map((attachment) => ({ type: 'localImage', path: attachment.path }))];
|
|
230
264
|
}
|
|
231
265
|
|
|
232
266
|
sendTurn(input: TurnInput): void {
|
|
233
267
|
const command = input.attachments.length === 0 ? parseGoalCommand(input.text) : null;
|
|
268
|
+
|
|
234
269
|
if (command !== null) {
|
|
235
270
|
void this.runGoalCommand(command, input);
|
|
271
|
+
|
|
236
272
|
return;
|
|
237
273
|
}
|
|
274
|
+
|
|
238
275
|
const nativeInput = this.turnInput(input);
|
|
239
276
|
this.turnUnnamed = true;
|
|
240
277
|
this.promptId = input.promptId ?? null;
|
|
@@ -244,6 +281,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
244
281
|
private turnStartParams(nativeInput: unknown[]): Record<string, unknown> {
|
|
245
282
|
const effort = this.launch.selection.options.effort;
|
|
246
283
|
const tier = codexServiceTier(this.launch.selection);
|
|
284
|
+
|
|
247
285
|
return {
|
|
248
286
|
threadId: this.threadId,
|
|
249
287
|
input: nativeInput,
|
|
@@ -261,52 +299,71 @@ export class CodexBackend implements ChatBackend {
|
|
|
261
299
|
*/
|
|
262
300
|
private async runGoalCommand(command: GoalCommand, input: TurnInput): Promise<void> {
|
|
263
301
|
const transport = this.transport;
|
|
302
|
+
|
|
264
303
|
if (!transport) {
|
|
265
304
|
this.emit({ type: 'failed', message: 'Codex is not running' });
|
|
305
|
+
|
|
266
306
|
return;
|
|
267
307
|
}
|
|
308
|
+
|
|
268
309
|
const threadId = this.threadId;
|
|
310
|
+
|
|
269
311
|
const setGoal = async (params: Record<string, unknown>): Promise<void> => {
|
|
270
312
|
const result = await transport.request('thread/goal/set', { threadId, origin: 'user', ...params });
|
|
313
|
+
|
|
271
314
|
if (this.transport === transport) {
|
|
272
315
|
this.emit({ type: 'goal', goal: codexGoal(isRecord(result) ? result.goal : null) });
|
|
273
316
|
}
|
|
274
317
|
};
|
|
318
|
+
|
|
275
319
|
try {
|
|
276
320
|
const current = await this.readGoal(transport);
|
|
321
|
+
|
|
277
322
|
if (command.kind === 'show') {
|
|
278
323
|
this.reply(input.promptId, current === null ? 'No goal is set.' : goalLine(current));
|
|
324
|
+
|
|
279
325
|
return;
|
|
280
326
|
}
|
|
327
|
+
|
|
281
328
|
if (command.kind === 'clear') {
|
|
282
329
|
const result = await transport.request('thread/goal/clear', { threadId, origin: 'user' });
|
|
283
330
|
this.emit({ type: 'goal', goal: null });
|
|
284
331
|
this.reply(input.promptId, isRecord(result) && result.cleared === true ? 'Goal cleared.' : 'No goal to clear.');
|
|
332
|
+
|
|
285
333
|
return;
|
|
286
334
|
}
|
|
335
|
+
|
|
287
336
|
if (current === null && command.kind !== 'set') {
|
|
288
337
|
this.reply(input.promptId, 'No goal is set. Set one with /goal and the objective.');
|
|
338
|
+
|
|
289
339
|
return;
|
|
290
340
|
}
|
|
341
|
+
|
|
291
342
|
if (command.kind === 'pause') {
|
|
292
343
|
await setGoal({ status: 'paused' });
|
|
293
344
|
this.reply(input.promptId, 'Goal paused. /goal resume takes it up again.');
|
|
345
|
+
|
|
294
346
|
return;
|
|
295
347
|
}
|
|
348
|
+
|
|
296
349
|
if (command.kind === 'set') {
|
|
297
350
|
// A new objective starts over, its tokens and time included.
|
|
298
351
|
if (current !== null) {
|
|
299
352
|
await transport.request('thread/goal/clear', { threadId, origin: 'user' });
|
|
300
353
|
}
|
|
354
|
+
|
|
301
355
|
await setGoal({ objective: command.objective, status: 'paused' });
|
|
302
356
|
}
|
|
357
|
+
|
|
303
358
|
const nativeInput = command.kind === 'set' ? this.turnInput({ ...input, text: command.objective }) : [];
|
|
304
359
|
this.turnUnnamed = true;
|
|
305
360
|
this.promptId = input.promptId ?? null;
|
|
306
361
|
await transport.request('turn/start', this.turnStartParams(nativeInput));
|
|
362
|
+
|
|
307
363
|
if (this.transport !== transport) {
|
|
308
364
|
return;
|
|
309
365
|
}
|
|
366
|
+
|
|
310
367
|
this.acceptPrompt(input.promptId);
|
|
311
368
|
await setGoal({ status: 'active' });
|
|
312
369
|
} catch (error) {
|
|
@@ -321,27 +378,34 @@ export class CodexBackend implements ChatBackend {
|
|
|
321
378
|
if (promptId !== undefined) {
|
|
322
379
|
this.emit({ type: 'turn.accepted', promptId });
|
|
323
380
|
}
|
|
381
|
+
|
|
324
382
|
this.emit({ type: 'text.done', ref: `goal-${randomUUID()}`, text });
|
|
325
383
|
this.emit({ type: 'turn.done', ...(promptId === undefined ? {} : { promptId }), state: 'done', costUsd: 0 });
|
|
326
384
|
}
|
|
327
385
|
|
|
328
386
|
async goal(action: ChatGoalAction): Promise<void> {
|
|
329
387
|
const transport = this.transport;
|
|
388
|
+
|
|
330
389
|
if (!transport) {
|
|
331
390
|
throw new Error('Codex is not running');
|
|
332
391
|
}
|
|
392
|
+
|
|
333
393
|
if (action === 'clear') {
|
|
334
394
|
await transport.request('thread/goal/clear', { threadId: this.threadId, origin: 'user' });
|
|
395
|
+
|
|
335
396
|
if (this.transport === transport) {
|
|
336
397
|
this.emit({ type: 'goal', goal: null });
|
|
337
398
|
}
|
|
399
|
+
|
|
338
400
|
return;
|
|
339
401
|
}
|
|
402
|
+
|
|
340
403
|
const result = await transport.request('thread/goal/set', {
|
|
341
404
|
threadId: this.threadId,
|
|
342
405
|
status: action === 'pause' ? 'paused' : 'active',
|
|
343
406
|
origin: 'user'
|
|
344
407
|
});
|
|
408
|
+
|
|
345
409
|
if (this.transport === transport) {
|
|
346
410
|
this.emit({ type: 'goal', goal: codexGoal(isRecord(result) ? result.goal : null) });
|
|
347
411
|
}
|
|
@@ -350,18 +414,23 @@ export class CodexBackend implements ChatBackend {
|
|
|
350
414
|
async steerTurn(input: TurnInput): Promise<boolean> {
|
|
351
415
|
const transport = this.transport;
|
|
352
416
|
const turnId = this.protocol.turnId;
|
|
417
|
+
|
|
353
418
|
if (!transport || !turnId || this.turnUnnamed) {
|
|
354
419
|
return false;
|
|
355
420
|
}
|
|
421
|
+
|
|
356
422
|
try {
|
|
357
423
|
await transport.request('turn/steer', { threadId: this.threadId, expectedTurnId: turnId, input: this.turnInput(input) });
|
|
424
|
+
|
|
358
425
|
return true;
|
|
359
426
|
} catch (error) {
|
|
360
427
|
const rejected = error instanceof Error && error.name === 'CodexRpcError';
|
|
428
|
+
|
|
361
429
|
// A compact or review turn takes no steer (codex 0.162: "cannot steer a compact turn"); the message waits for the next turn.
|
|
362
430
|
if (rejected && /no active turn|not.*active|turn.*mismatch|expected.*turn|method not found|cannot steer|not steerable/i.test(error.message)) {
|
|
363
431
|
return false;
|
|
364
432
|
}
|
|
433
|
+
|
|
365
434
|
throw error;
|
|
366
435
|
}
|
|
367
436
|
}
|
|
@@ -382,11 +451,14 @@ export class CodexBackend implements ChatBackend {
|
|
|
382
451
|
if (!this.transport) {
|
|
383
452
|
throw new Error('Codex is not running');
|
|
384
453
|
}
|
|
454
|
+
|
|
385
455
|
this.transport.respond(rpcId, result);
|
|
456
|
+
|
|
386
457
|
return true;
|
|
387
458
|
} catch (error) {
|
|
388
459
|
this.emit({ type: 'request.withdrawn', requestId });
|
|
389
460
|
this.fail(error);
|
|
461
|
+
|
|
390
462
|
return false;
|
|
391
463
|
}
|
|
392
464
|
}
|
|
@@ -394,27 +466,35 @@ export class CodexBackend implements ChatBackend {
|
|
|
394
466
|
private async readImageSupport(transport: CodexTransport): Promise<boolean | null> {
|
|
395
467
|
let cursor: string | null = null;
|
|
396
468
|
const seen = new Set<string>();
|
|
469
|
+
|
|
397
470
|
try {
|
|
398
471
|
do {
|
|
399
472
|
const result = await transport.request('model/list', { includeHidden: true, ...(cursor === null ? {} : { cursor }) });
|
|
473
|
+
|
|
400
474
|
if (!isRecord(result) || !Array.isArray(result.data)) {
|
|
401
475
|
return null;
|
|
402
476
|
}
|
|
477
|
+
|
|
403
478
|
const model = result.data.find((entry) => isRecord(entry) && entry.model === this.launch.selection.model);
|
|
479
|
+
|
|
404
480
|
if (isRecord(model)) {
|
|
405
481
|
return Array.isArray(model.inputModalities) ? model.inputModalities.includes('image') : null;
|
|
406
482
|
}
|
|
483
|
+
|
|
407
484
|
cursor = typeof result.nextCursor === 'string' ? result.nextCursor : null;
|
|
485
|
+
|
|
408
486
|
if (cursor !== null) {
|
|
409
487
|
if (seen.has(cursor)) {
|
|
410
488
|
return null;
|
|
411
489
|
}
|
|
490
|
+
|
|
412
491
|
seen.add(cursor);
|
|
413
492
|
}
|
|
414
493
|
} while (cursor !== null);
|
|
415
494
|
} catch {
|
|
416
495
|
// Older app-servers may not report modalities; their turn response remains authoritative.
|
|
417
496
|
}
|
|
497
|
+
|
|
418
498
|
return null;
|
|
419
499
|
}
|
|
420
500
|
|
|
@@ -428,43 +508,54 @@ export class CodexBackend implements ChatBackend {
|
|
|
428
508
|
if (!this.transport || this.threadId === '') {
|
|
429
509
|
return;
|
|
430
510
|
}
|
|
511
|
+
|
|
431
512
|
void this.transport.request('thread/name/set', { threadId: this.threadId, name: title }).catch(() => undefined);
|
|
432
513
|
}
|
|
433
514
|
|
|
434
515
|
/* Codex keeps its own skill index, so the running app-server is the authority on what it will run. */
|
|
435
516
|
async listSkills(): Promise<ChatSkill[]> {
|
|
436
517
|
const transport = this.transport;
|
|
518
|
+
|
|
437
519
|
if (!transport) {
|
|
438
520
|
return [];
|
|
439
521
|
}
|
|
522
|
+
|
|
440
523
|
return parseSkillsList(await transport.request('skills/list', { cwds: [this.launch.cwd] }));
|
|
441
524
|
}
|
|
442
525
|
|
|
443
526
|
listThreadItems(params: { threadId: string; cursor?: string; limit: number; sortDirection: 'asc' | 'desc' }): Promise<unknown> {
|
|
444
527
|
const transport = this.transport;
|
|
528
|
+
|
|
445
529
|
if (!transport) {
|
|
446
530
|
return Promise.reject(new Error('Codex is not running'));
|
|
447
531
|
}
|
|
532
|
+
|
|
448
533
|
return transport.request('thread/items/list', params);
|
|
449
534
|
}
|
|
450
535
|
|
|
451
536
|
interrupt(): void {
|
|
452
537
|
const turnId = this.protocol.turnId;
|
|
538
|
+
|
|
453
539
|
if (!this.transport) {
|
|
454
540
|
return;
|
|
455
541
|
}
|
|
542
|
+
|
|
456
543
|
// As Codex's own TUI does: paused first, so no goal turn opens after the one this stops.
|
|
457
544
|
if (this.goalNow?.status === 'active') {
|
|
458
545
|
void this.transport.request('thread/goal/set', { threadId: this.threadId, status: 'paused', origin: 'user' }).catch(() => undefined);
|
|
459
546
|
}
|
|
547
|
+
|
|
460
548
|
if (!turnId) {
|
|
461
549
|
this.interruptOwed = this.turnUnnamed;
|
|
550
|
+
|
|
462
551
|
// A child request can open a host turn after the root's native turn already ended.
|
|
463
552
|
if (!this.turnUnnamed) {
|
|
464
553
|
this.emit({ type: 'turn.done', state: 'aborted', costUsd: 0 });
|
|
465
554
|
}
|
|
555
|
+
|
|
466
556
|
return;
|
|
467
557
|
}
|
|
558
|
+
|
|
468
559
|
void this.transport.request('turn/interrupt', { threadId: this.threadId, turnId }).catch(() => {
|
|
469
560
|
// A turn that already ended has nothing to interrupt.
|
|
470
561
|
});
|
|
@@ -474,6 +565,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
474
565
|
if (!this.transport) {
|
|
475
566
|
return;
|
|
476
567
|
}
|
|
568
|
+
|
|
477
569
|
void this.transport.request('thread/backgroundTerminals/terminate', { threadId: this.threadId, processId: taskId }).catch(() => {
|
|
478
570
|
// A process that already ended has nothing to terminate; its item says so on its own.
|
|
479
571
|
});
|
|
@@ -483,10 +575,13 @@ export class CodexBackend implements ChatBackend {
|
|
|
483
575
|
if (!this.transport) {
|
|
484
576
|
return false;
|
|
485
577
|
}
|
|
578
|
+
|
|
486
579
|
const answer = this.protocol.approvalDecision(requestId, decision);
|
|
580
|
+
|
|
487
581
|
if (!answer) {
|
|
488
582
|
return false;
|
|
489
583
|
}
|
|
584
|
+
|
|
490
585
|
return this.respond(requestId, answer.rpcId, answer.result);
|
|
491
586
|
}
|
|
492
587
|
|
|
@@ -494,27 +589,36 @@ export class CodexBackend implements ChatBackend {
|
|
|
494
589
|
if (!this.transport) {
|
|
495
590
|
return false;
|
|
496
591
|
}
|
|
592
|
+
|
|
497
593
|
const answer = this.protocol.questionAnswer(requestId, answers);
|
|
594
|
+
|
|
498
595
|
if (!answer) {
|
|
499
596
|
return false;
|
|
500
597
|
}
|
|
598
|
+
|
|
501
599
|
if (answer.kind === 'respond') {
|
|
502
600
|
return this.respond(requestId, answer.rpcId, answer.result);
|
|
503
601
|
}
|
|
602
|
+
|
|
504
603
|
const turnId = this.protocol.turnId;
|
|
604
|
+
|
|
505
605
|
if (!turnId) {
|
|
506
606
|
// An optional question can outlive its turn; its answer resumes the same chat as plain text.
|
|
507
607
|
this.turnUnnamed = true;
|
|
508
608
|
this.promptId = null;
|
|
509
609
|
this.request('turn/start', this.turnStartParams(textInput(answer.text)));
|
|
610
|
+
|
|
510
611
|
return true;
|
|
511
612
|
}
|
|
613
|
+
|
|
512
614
|
this.request('turn/steer', { threadId: this.threadId, expectedTurnId: turnId, input: textInput(answer.text) });
|
|
615
|
+
|
|
513
616
|
return true;
|
|
514
617
|
}
|
|
515
618
|
|
|
516
619
|
dismissRequest(requestId: string): boolean {
|
|
517
620
|
const answer = this.protocol.dismissQuestionAnswer(requestId);
|
|
621
|
+
|
|
518
622
|
return answer?.kind === 'respond' ? this.respond(requestId, answer.rpcId, answer.result) : this.protocol.dismissQuestion(requestId);
|
|
519
623
|
}
|
|
520
624
|
|
|
@@ -522,10 +626,13 @@ export class CodexBackend implements ChatBackend {
|
|
|
522
626
|
if (this.protocol.dismissQuestion(requestId)) {
|
|
523
627
|
return true;
|
|
524
628
|
}
|
|
629
|
+
|
|
525
630
|
const answer = this.protocol.declineRequest(requestId);
|
|
631
|
+
|
|
526
632
|
if (!this.transport || !answer) {
|
|
527
633
|
return false;
|
|
528
634
|
}
|
|
635
|
+
|
|
529
636
|
return this.respond(requestId, answer.rpcId, answer.result);
|
|
530
637
|
}
|
|
531
638
|
|
|
@@ -536,16 +643,20 @@ export class CodexBackend implements ChatBackend {
|
|
|
536
643
|
dispose(): Promise<void> {
|
|
537
644
|
const transport = this.transport;
|
|
538
645
|
this.transport = null;
|
|
646
|
+
|
|
539
647
|
return Promise.all([transport?.dispose(), this.imageWrites]).then(() => undefined);
|
|
540
648
|
}
|
|
541
649
|
|
|
542
650
|
/* A request whose failure the person has to hear about, since it is the turn that cannot go on. */
|
|
543
651
|
private request(method: string, params: unknown, accepted?: () => void): void {
|
|
544
652
|
const transport = this.transport;
|
|
653
|
+
|
|
545
654
|
if (!transport) {
|
|
546
655
|
this.emit({ type: 'failed', message: 'Codex is not running' });
|
|
656
|
+
|
|
547
657
|
return;
|
|
548
658
|
}
|
|
659
|
+
|
|
549
660
|
void transport
|
|
550
661
|
.request(method, params)
|
|
551
662
|
.then(() => {
|
|
@@ -564,18 +675,24 @@ export class CodexBackend implements ChatBackend {
|
|
|
564
675
|
if (this.transport !== transport) {
|
|
565
676
|
return;
|
|
566
677
|
}
|
|
678
|
+
|
|
567
679
|
const events = this.protocol.handle(frame);
|
|
680
|
+
|
|
568
681
|
// A server request the protocol did not take still needs an answer, or Codex waits forever.
|
|
569
682
|
if (frame.id !== undefined && events.length === 0 && (typeof frame.id === 'number' || typeof frame.id === 'string')) {
|
|
570
683
|
transport.respondError(frame.id, -32601, `${this.client.title} does not handle ${String(frame.method)}`);
|
|
571
684
|
}
|
|
685
|
+
|
|
572
686
|
const nativeTurnId = frame.method === 'turn/started' && isRecord(frame.params) && frame.params.threadId === this.threadId ? this.protocol.turnId : null;
|
|
687
|
+
|
|
573
688
|
if (nativeTurnId !== null) {
|
|
574
689
|
if (this.turnUnnamed && this.promptId !== null) {
|
|
575
690
|
this.nativePrompt = { turnId: nativeTurnId, promptId: this.promptId };
|
|
576
691
|
}
|
|
692
|
+
|
|
577
693
|
this.acceptPrompt(this.promptId ?? undefined);
|
|
578
694
|
}
|
|
695
|
+
|
|
579
696
|
for (const event of events) {
|
|
580
697
|
if (event.type === 'turn.done' && this.nativePrompt !== null && event.native?.turnId === this.nativePrompt.turnId) {
|
|
581
698
|
const promptId = this.nativePrompt.promptId;
|
|
@@ -585,8 +702,10 @@ export class CodexBackend implements ChatBackend {
|
|
|
585
702
|
this.emit(event);
|
|
586
703
|
}
|
|
587
704
|
}
|
|
705
|
+
|
|
588
706
|
if (this.turnUnnamed && nativeTurnId !== null) {
|
|
589
707
|
this.turnUnnamed = false;
|
|
708
|
+
|
|
590
709
|
if (this.interruptOwed) {
|
|
591
710
|
this.interruptOwed = false;
|
|
592
711
|
this.interrupt();
|
|
@@ -598,6 +717,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
598
717
|
if (this.transport !== transport) {
|
|
599
718
|
return;
|
|
600
719
|
}
|
|
720
|
+
|
|
601
721
|
this.transport = null;
|
|
602
722
|
this.protocol.forgetPending();
|
|
603
723
|
this.emit({ type: 'exit', exitCode, ...(stderr === null ? {} : { stderr }) });
|
|
@@ -607,6 +727,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
607
727
|
if (event.type === 'goal') {
|
|
608
728
|
this.goalNow = event.goal;
|
|
609
729
|
}
|
|
730
|
+
|
|
610
731
|
// An older autonomous turn can finish while the next prompt still waits for its own turn/started.
|
|
611
732
|
if ((event.type === 'turn.done' && (!this.turnUnnamed || event.promptId !== undefined)) || event.type === 'failed' || event.type === 'exit') {
|
|
612
733
|
this.turnUnnamed = false;
|
|
@@ -614,6 +735,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
614
735
|
this.promptId = null;
|
|
615
736
|
this.nativePrompt = null;
|
|
616
737
|
}
|
|
738
|
+
|
|
617
739
|
if (event.type === 'image.generated' || this.imageWrites !== null) {
|
|
618
740
|
const pending = (this.imageWrites ?? Promise.resolve()).then(async () => {
|
|
619
741
|
if (event.type === 'image.generated') {
|
|
@@ -622,7 +744,9 @@ export class CodexBackend implements ChatBackend {
|
|
|
622
744
|
this.host.onEvent(event);
|
|
623
745
|
}
|
|
624
746
|
});
|
|
747
|
+
|
|
625
748
|
this.imageWrites = pending;
|
|
749
|
+
|
|
626
750
|
void pending
|
|
627
751
|
.finally(() => {
|
|
628
752
|
if (this.imageWrites === pending) {
|
|
@@ -630,24 +754,30 @@ export class CodexBackend implements ChatBackend {
|
|
|
630
754
|
}
|
|
631
755
|
})
|
|
632
756
|
.catch(() => undefined);
|
|
757
|
+
|
|
633
758
|
return;
|
|
634
759
|
}
|
|
760
|
+
|
|
635
761
|
this.host.onEvent(event);
|
|
636
762
|
}
|
|
637
763
|
|
|
638
764
|
private async storeImage(event: Extract<BackendEvent, { type: 'image.generated' }>): Promise<void> {
|
|
639
765
|
const input = { revisedPrompt: event.revisedPrompt, transparentBackground: event.transparentBackground };
|
|
640
766
|
let attachment;
|
|
767
|
+
|
|
641
768
|
try {
|
|
642
769
|
if (!this.launch.saveGeneratedImage) {
|
|
643
770
|
throw new Error('This machine cannot save generated images');
|
|
644
771
|
}
|
|
772
|
+
|
|
645
773
|
attachment = await this.launch.saveGeneratedImage(event.ref, event.upload);
|
|
646
774
|
} catch (error) {
|
|
647
775
|
this.host.onEvent({ type: 'tool.started', ref: event.ref, name: 'ImageGeneration', input, parentRef: null });
|
|
648
776
|
this.host.onEvent({ type: 'tool.done', ref: event.ref, output: errorText(error), state: 'error' });
|
|
777
|
+
|
|
649
778
|
return;
|
|
650
779
|
}
|
|
780
|
+
|
|
651
781
|
this.host.onEvent({
|
|
652
782
|
type: 'tool.started',
|
|
653
783
|
ref: event.ref,
|
|
@@ -662,6 +792,7 @@ export class CodexBackend implements ChatBackend {
|
|
|
662
792
|
},
|
|
663
793
|
parentRef: null
|
|
664
794
|
});
|
|
795
|
+
|
|
665
796
|
this.host.onEvent({ type: 'tool.done', ref: event.ref, output: null, state: 'done' });
|
|
666
797
|
}
|
|
667
798
|
}
|