mcp-codex-worker 0.1.0 → 0.1.2
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/src/app.js +155 -83
- package/dist/src/app.js.map +1 -1
- package/dist/src/mcp/task-markdown.d.ts +4 -0
- package/dist/src/mcp/task-markdown.js +107 -0
- package/dist/src/mcp/task-markdown.js.map +1 -0
- package/dist/src/mcp/tool-banners.d.ts +3 -0
- package/dist/src/mcp/tool-banners.js +44 -0
- package/dist/src/mcp/tool-banners.js.map +1 -0
- package/dist/src/mcp/tool-definitions.d.ts +6 -9
- package/dist/src/mcp/tool-definitions.js +80 -78
- package/dist/src/mcp/tool-definitions.js.map +1 -1
- package/dist/src/services/app-server-client.d.ts +1 -0
- package/dist/src/services/app-server-client.js +5 -0
- package/dist/src/services/app-server-client.js.map +1 -1
- package/dist/src/services/codex-runtime.d.ts +5 -3
- package/dist/src/services/codex-runtime.js +18 -0
- package/dist/src/services/codex-runtime.js.map +1 -1
- package/dist/src/services/model-catalog.js +1 -1
- package/dist/src/services/model-catalog.js.map +1 -1
- package/package.json +1 -2
- package/src/app.ts +187 -85
- package/src/mcp/task-markdown.ts +136 -0
- package/src/mcp/tool-banners.ts +53 -0
- package/src/mcp/tool-definitions.ts +86 -81
- package/src/services/app-server-client.ts +6 -0
- package/src/services/codex-runtime.ts +19 -1
- package/src/services/model-catalog.ts +1 -1
package/src/app.ts
CHANGED
|
@@ -11,6 +11,12 @@ import {
|
|
|
11
11
|
type TurnSteerInput,
|
|
12
12
|
type WaitInput,
|
|
13
13
|
} from './mcp/tool-definitions.js';
|
|
14
|
+
import { buildRequestBanner, buildThreadBanner } from './mcp/tool-banners.js';
|
|
15
|
+
import {
|
|
16
|
+
renderRequestListMarkdown,
|
|
17
|
+
renderThreadListMarkdown,
|
|
18
|
+
renderThreadMarkdown,
|
|
19
|
+
} from './mcp/task-markdown.js';
|
|
14
20
|
import { OPERATION_POLL_INTERVAL_MS, REQUEST_TIMEOUT_MS } from './config/defaults.js';
|
|
15
21
|
import { CodexRuntime } from './services/codex-runtime.js';
|
|
16
22
|
|
|
@@ -33,9 +39,13 @@ export class CodexWorkerApp {
|
|
|
33
39
|
async listTools(): Promise<ToolDefinition[]> {
|
|
34
40
|
try {
|
|
35
41
|
const modelIds = await this.runtime.listVisibleModelIds();
|
|
36
|
-
return createToolDefinitions(
|
|
42
|
+
return createToolDefinitions({
|
|
43
|
+
modelIds,
|
|
44
|
+
threadBanner: buildThreadBanner(this.runtime),
|
|
45
|
+
requestBanner: buildRequestBanner(this.runtime),
|
|
46
|
+
});
|
|
37
47
|
} catch {
|
|
38
|
-
return createToolDefinitions([]);
|
|
48
|
+
return createToolDefinitions({ modelIds: [], threadBanner: '', requestBanner: '' });
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
|
|
@@ -44,8 +54,8 @@ export class CodexWorkerApp {
|
|
|
44
54
|
{
|
|
45
55
|
uri: 'codex://threads',
|
|
46
56
|
name: 'Codex Threads',
|
|
47
|
-
description: '
|
|
48
|
-
mimeType: '
|
|
57
|
+
description: 'All tracked threads with status',
|
|
58
|
+
mimeType: 'text/markdown',
|
|
49
59
|
},
|
|
50
60
|
{
|
|
51
61
|
uri: 'codex://models',
|
|
@@ -72,8 +82,8 @@ export class CodexWorkerApp {
|
|
|
72
82
|
{
|
|
73
83
|
uri: `codex://thread/${threadId}`,
|
|
74
84
|
name: `Thread ${threadId}`,
|
|
75
|
-
description: 'Thread
|
|
76
|
-
mimeType: '
|
|
85
|
+
description: 'Thread status and guidance',
|
|
86
|
+
mimeType: 'text/markdown',
|
|
77
87
|
},
|
|
78
88
|
{
|
|
79
89
|
uri: `codex://thread/${threadId}/events`,
|
|
@@ -98,18 +108,18 @@ export class CodexWorkerApp {
|
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
110
|
return {
|
|
101
|
-
mimeType: '
|
|
102
|
-
text:
|
|
111
|
+
mimeType: 'text/markdown',
|
|
112
|
+
text: renderThreadListMarkdown(
|
|
113
|
+
response.data ?? [],
|
|
114
|
+
this.runtime.getPendingServerRequests(false),
|
|
115
|
+
),
|
|
103
116
|
};
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
if (uri === 'codex://models') {
|
|
107
|
-
const response = {
|
|
108
|
-
data: await this.runtime.listModels(false),
|
|
109
|
-
};
|
|
110
120
|
return {
|
|
111
121
|
mimeType: 'application/json',
|
|
112
|
-
text: JSON.stringify(
|
|
122
|
+
text: JSON.stringify({ data: await this.runtime.listModels(false) }, null, 2),
|
|
113
123
|
};
|
|
114
124
|
}
|
|
115
125
|
|
|
@@ -153,8 +163,12 @@ export class CodexWorkerApp {
|
|
|
153
163
|
includeTurns: true,
|
|
154
164
|
});
|
|
155
165
|
return {
|
|
156
|
-
mimeType: '
|
|
157
|
-
text:
|
|
166
|
+
mimeType: 'text/markdown',
|
|
167
|
+
text: renderThreadMarkdown(
|
|
168
|
+
response,
|
|
169
|
+
this.runtime.getOperationsForThread(threadId),
|
|
170
|
+
this.runtime.getPendingServerRequests(false),
|
|
171
|
+
),
|
|
158
172
|
};
|
|
159
173
|
}
|
|
160
174
|
|
|
@@ -172,47 +186,29 @@ export class CodexWorkerApp {
|
|
|
172
186
|
}
|
|
173
187
|
const validated = tool.validate(args ?? {});
|
|
174
188
|
switch (name) {
|
|
175
|
-
case 'thread-start':
|
|
189
|
+
case 'codex-thread-start':
|
|
176
190
|
return this.handleThreadStart(validated as ThreadStartInput);
|
|
177
|
-
case 'thread-resume':
|
|
191
|
+
case 'codex-thread-resume':
|
|
178
192
|
return this.handleThreadResume(validated as ThreadResumeInput);
|
|
179
|
-
case 'thread-read':
|
|
193
|
+
case 'codex-thread-read':
|
|
180
194
|
return this.handleThreadRead(validated as ThreadReadInput);
|
|
181
|
-
case 'thread-list':
|
|
195
|
+
case 'codex-thread-list':
|
|
182
196
|
return this.handleThreadList(validated as ThreadListInput);
|
|
183
|
-
case 'turn-start':
|
|
197
|
+
case 'codex-turn-start':
|
|
184
198
|
return this.handleTurnStart(validated as TurnStartInput);
|
|
185
|
-
case 'turn-steer':
|
|
199
|
+
case 'codex-turn-steer':
|
|
186
200
|
return this.handleTurnSteer(validated as TurnSteerInput);
|
|
187
|
-
case 'turn-interrupt':
|
|
201
|
+
case 'codex-turn-interrupt':
|
|
188
202
|
return this.handleTurnInterrupt(validated as TurnInterruptInput);
|
|
189
|
-
case '
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
}, null, 2);
|
|
193
|
-
case 'account-read':
|
|
194
|
-
return JSON.stringify(await this.runtime.request('account/read', {}), null, 2);
|
|
195
|
-
case 'account-rate-limits-read':
|
|
196
|
-
return JSON.stringify(await this.runtime.request('account/rateLimits/read', {}), null, 2);
|
|
197
|
-
case 'skills-list':
|
|
198
|
-
return JSON.stringify(await this.runtime.request('skills/list', {}), null, 2);
|
|
199
|
-
case 'app-list':
|
|
200
|
-
return JSON.stringify(await this.runtime.request('app/list', {}), null, 2);
|
|
201
|
-
case 'request-list':
|
|
202
|
-
return JSON.stringify({
|
|
203
|
-
requests: this.runtime.getPendingServerRequests(
|
|
203
|
+
case 'codex-request-list':
|
|
204
|
+
return renderRequestListMarkdown(
|
|
205
|
+
this.runtime.getPendingServerRequests(
|
|
204
206
|
Boolean((validated as { include_resolved?: boolean }).include_resolved),
|
|
205
207
|
),
|
|
206
|
-
|
|
207
|
-
case 'request-
|
|
208
|
-
return JSON.stringify({
|
|
209
|
-
request: this.runtime.getPendingServerRequest(
|
|
210
|
-
(validated as { request_id: string | number }).request_id,
|
|
211
|
-
) ?? null,
|
|
212
|
-
}, null, 2);
|
|
213
|
-
case 'request-respond':
|
|
208
|
+
);
|
|
209
|
+
case 'codex-request-respond':
|
|
214
210
|
return this.handleRequestRespond(validated as RequestRespondInput);
|
|
215
|
-
case 'wait':
|
|
211
|
+
case 'codex-wait':
|
|
216
212
|
return this.handleWait(validated as WaitInput);
|
|
217
213
|
default:
|
|
218
214
|
throw new Error(`Unhandled tool: ${name}`);
|
|
@@ -230,11 +226,18 @@ export class CodexWorkerApp {
|
|
|
230
226
|
if (threadId) {
|
|
231
227
|
this.runtime.rememberThreadId(threadId);
|
|
232
228
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
229
|
+
|
|
230
|
+
const remapNote = built.remappedFrom ? ` (remapped from ${built.remappedFrom})` : '';
|
|
231
|
+
return [
|
|
232
|
+
`**Thread started**`,
|
|
233
|
+
`thread_id: \`${threadId ?? 'unknown'}\``,
|
|
234
|
+
`model: \`${built.params.model as string}\`${remapNote}`,
|
|
235
|
+
'',
|
|
236
|
+
'**What to do next:**',
|
|
237
|
+
`- Use \`codex-turn-start\` with \`thread_id: "${threadId}"\` to send the agent its task.`,
|
|
238
|
+
'- After starting a turn, use `codex-wait` with `operation_id` to block until it finishes or asks for approval.',
|
|
239
|
+
'- Check `codex-request-list` after starting turns -- agents often need approval for commands or file changes.',
|
|
240
|
+
].join('\n');
|
|
238
241
|
}
|
|
239
242
|
|
|
240
243
|
private async handleThreadResume(input: ThreadResumeInput): Promise<string> {
|
|
@@ -244,13 +247,17 @@ export class CodexWorkerApp {
|
|
|
244
247
|
cwd: input.cwd,
|
|
245
248
|
developerInstructions: input.developer_instructions,
|
|
246
249
|
});
|
|
247
|
-
|
|
250
|
+
await this.runtime.request('thread/resume', built.params);
|
|
248
251
|
this.runtime.rememberThreadId(input.thread_id);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
|
|
253
|
+
return [
|
|
254
|
+
`**Thread resumed**`,
|
|
255
|
+
`thread_id: \`${input.thread_id}\``,
|
|
256
|
+
'',
|
|
257
|
+
'**What to do next:**',
|
|
258
|
+
'- Use `codex-turn-start` to send a new task to this thread.',
|
|
259
|
+
'- The thread\'s previous context is loaded -- the agent remembers prior conversation.',
|
|
260
|
+
].join('\n');
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
private async handleThreadRead(input: ThreadReadInput): Promise<string> {
|
|
@@ -259,7 +266,11 @@ export class CodexWorkerApp {
|
|
|
259
266
|
includeTurns: input.include_turns ?? true,
|
|
260
267
|
});
|
|
261
268
|
this.runtime.rememberThreadId(input.thread_id);
|
|
262
|
-
return
|
|
269
|
+
return renderThreadMarkdown(
|
|
270
|
+
response,
|
|
271
|
+
this.runtime.getOperationsForThread(input.thread_id),
|
|
272
|
+
this.runtime.getPendingServerRequests(false),
|
|
273
|
+
);
|
|
263
274
|
}
|
|
264
275
|
|
|
265
276
|
private async handleThreadList(input: ThreadListInput): Promise<string> {
|
|
@@ -274,7 +285,10 @@ export class CodexWorkerApp {
|
|
|
274
285
|
this.runtime.rememberThreadId(row.id);
|
|
275
286
|
}
|
|
276
287
|
}
|
|
277
|
-
return
|
|
288
|
+
return renderThreadListMarkdown(
|
|
289
|
+
response.data ?? [],
|
|
290
|
+
this.runtime.getPendingServerRequests(false),
|
|
291
|
+
);
|
|
278
292
|
}
|
|
279
293
|
|
|
280
294
|
private async handleTurnStart(input: TurnStartInput): Promise<string> {
|
|
@@ -287,11 +301,48 @@ export class CodexWorkerApp {
|
|
|
287
301
|
const bridged = await this.runtime.requestWithBridge('turn/start', built.params, {
|
|
288
302
|
threadId: input.thread_id,
|
|
289
303
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
304
|
+
|
|
305
|
+
const remapNote = built.remappedFrom ? ` (remapped from ${built.remappedFrom})` : '';
|
|
306
|
+
const header = [
|
|
307
|
+
`thread_id: \`${input.thread_id}\``,
|
|
308
|
+
`operation_id: \`${bridged.operationId}\`${remapNote}`,
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
if (bridged.status === 'pending_request') {
|
|
312
|
+
const reqCount = bridged.pendingRequestIds?.length ?? 0;
|
|
313
|
+
return [
|
|
314
|
+
`**Turn started -- approval needed**`,
|
|
315
|
+
...header,
|
|
316
|
+
`pending_requests: ${reqCount}`,
|
|
317
|
+
'',
|
|
318
|
+
'**ACTION REQUIRED:**',
|
|
319
|
+
'The agent needs approval before it can proceed. Use `codex-request-list` to see what it needs, then `codex-request-respond` to approve or decline.',
|
|
320
|
+
'',
|
|
321
|
+
'**After approving:**',
|
|
322
|
+
`Use \`codex-wait\` with \`operation_id: "${bridged.operationId}"\` to block until the turn completes.`,
|
|
323
|
+
].join('\n');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (bridged.status === 'completed') {
|
|
327
|
+
return [
|
|
328
|
+
`**Turn completed**`,
|
|
329
|
+
...header,
|
|
330
|
+
'',
|
|
331
|
+
'**What to do next:**',
|
|
332
|
+
`- Use \`codex-thread-read\` with \`thread_id: "${input.thread_id}"\` to inspect the agent's output and file changes.`,
|
|
333
|
+
'- Use `codex-turn-start` to send follow-up instructions.',
|
|
334
|
+
].join('\n');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return [
|
|
338
|
+
`**Turn started -- agent working**`,
|
|
339
|
+
...header,
|
|
340
|
+
'',
|
|
341
|
+
'**What to do next:**',
|
|
342
|
+
`- Use \`codex-wait\` with \`operation_id: "${bridged.operationId}"\` to block until the agent finishes or asks for approval.`,
|
|
343
|
+
'- Check `codex-request-list` for any pending approvals.',
|
|
344
|
+
'- For parallel work, start turns on other threads now.',
|
|
345
|
+
].join('\n');
|
|
295
346
|
}
|
|
296
347
|
|
|
297
348
|
private async handleTurnSteer(input: TurnSteerInput): Promise<string> {
|
|
@@ -303,18 +354,30 @@ export class CodexWorkerApp {
|
|
|
303
354
|
const bridged = await this.runtime.requestWithBridge('turn/steer', params, {
|
|
304
355
|
threadId: input.thread_id,
|
|
305
356
|
});
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
357
|
+
|
|
358
|
+
return [
|
|
359
|
+
`**Turn steered**`,
|
|
360
|
+
`thread_id: \`${input.thread_id}\``,
|
|
361
|
+
`operation_id: \`${bridged.operationId}\``,
|
|
362
|
+
'',
|
|
363
|
+
'The agent is adjusting course with your new instructions.',
|
|
364
|
+
`Use \`codex-wait\` with \`operation_id: "${bridged.operationId}"\` to block until it finishes.`,
|
|
365
|
+
].join('\n');
|
|
310
366
|
}
|
|
311
367
|
|
|
312
368
|
private async handleTurnInterrupt(input: TurnInterruptInput): Promise<string> {
|
|
313
|
-
|
|
369
|
+
await this.runtime.request('turn/interrupt', {
|
|
314
370
|
threadId: input.thread_id,
|
|
315
371
|
turnId: input.turn_id,
|
|
316
372
|
});
|
|
317
|
-
|
|
373
|
+
|
|
374
|
+
return [
|
|
375
|
+
`**Turn interrupted**`,
|
|
376
|
+
`thread_id: \`${input.thread_id}\``,
|
|
377
|
+
`turn_id: \`${input.turn_id}\``,
|
|
378
|
+
'',
|
|
379
|
+
'The turn has been stopped. Use `codex-turn-start` to begin a new turn with corrected instructions.',
|
|
380
|
+
].join('\n');
|
|
318
381
|
}
|
|
319
382
|
|
|
320
383
|
private async handleRequestRespond(input: RequestRespondInput): Promise<string> {
|
|
@@ -325,12 +388,16 @@ export class CodexWorkerApp {
|
|
|
325
388
|
|
|
326
389
|
const payload = this.buildServerRequestPayload(pending.method, input);
|
|
327
390
|
await this.runtime.respondToServerRequest(input.request_id, payload);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
391
|
+
|
|
392
|
+
return [
|
|
393
|
+
`**Request responded**`,
|
|
394
|
+
`request_id: \`${String(input.request_id)}\``,
|
|
395
|
+
`method: \`${pending.method}\``,
|
|
396
|
+
'',
|
|
397
|
+
'**What to do next:**',
|
|
398
|
+
'- The agent will continue working. Use `codex-wait` to block until it finishes or needs another approval.',
|
|
399
|
+
'- Check `codex-request-list` for any remaining pending requests.',
|
|
400
|
+
].join('\n');
|
|
334
401
|
}
|
|
335
402
|
|
|
336
403
|
private async handleWait(input: WaitInput): Promise<string> {
|
|
@@ -339,7 +406,41 @@ export class CodexWorkerApp {
|
|
|
339
406
|
|
|
340
407
|
if (input.operation_id) {
|
|
341
408
|
const operation = await this.runtime.waitForOperation(input.operation_id, timeoutMs, pollIntervalMs);
|
|
342
|
-
|
|
409
|
+
const threadId = operation.threadId ?? 'unknown';
|
|
410
|
+
|
|
411
|
+
if (operation.status === 'failed') {
|
|
412
|
+
return [
|
|
413
|
+
`**Operation failed**`,
|
|
414
|
+
`operation_id: \`${operation.operationId}\``,
|
|
415
|
+
`error: ${operation.error ?? 'unknown error'}`,
|
|
416
|
+
'',
|
|
417
|
+
'**What to do next:**',
|
|
418
|
+
`- Use \`codex-thread-read\` with \`thread_id: "${threadId}"\` to inspect what happened.`,
|
|
419
|
+
'- Fix the issue and use `codex-turn-start` to retry.',
|
|
420
|
+
].join('\n');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (operation.pendingRequestIds.length > 0) {
|
|
424
|
+
return [
|
|
425
|
+
`**Operation paused -- approval needed**`,
|
|
426
|
+
`operation_id: \`${operation.operationId}\``,
|
|
427
|
+
`pending_requests: ${operation.pendingRequestIds.length}`,
|
|
428
|
+
'',
|
|
429
|
+
'**ACTION REQUIRED:**',
|
|
430
|
+
'Use `codex-request-list` to see what the agent needs, then `codex-request-respond` to approve.',
|
|
431
|
+
`After approving, call \`codex-wait\` again with \`operation_id: "${operation.operationId}"\`.`,
|
|
432
|
+
].join('\n');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return [
|
|
436
|
+
`**Operation completed**`,
|
|
437
|
+
`operation_id: \`${operation.operationId}\``,
|
|
438
|
+
`thread_id: \`${threadId}\``,
|
|
439
|
+
'',
|
|
440
|
+
'**What to do next:**',
|
|
441
|
+
`- Use \`codex-thread-read\` with \`thread_id: "${threadId}"\` to inspect the agent's output.`,
|
|
442
|
+
'- Use `codex-turn-start` to send follow-up instructions.',
|
|
443
|
+
].join('\n');
|
|
343
444
|
}
|
|
344
445
|
|
|
345
446
|
if (input.thread_id) {
|
|
@@ -355,21 +456,22 @@ export class CodexWorkerApp {
|
|
|
355
456
|
};
|
|
356
457
|
const statusType = response.thread?.status?.type;
|
|
357
458
|
if (statusType !== 'active') {
|
|
358
|
-
return
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
459
|
+
return [
|
|
460
|
+
`**Thread idle**`,
|
|
461
|
+
`thread_id: \`${input.thread_id}\``,
|
|
462
|
+
`status: \`${statusType ?? 'unknown'}\``,
|
|
463
|
+
'',
|
|
464
|
+
'**What to do next:**',
|
|
465
|
+
`- Use \`codex-thread-read\` with \`thread_id: "${input.thread_id}"\` to review the agent's work.`,
|
|
466
|
+
'- Use `codex-turn-start` to send another task.',
|
|
467
|
+
].join('\n');
|
|
363
468
|
}
|
|
364
469
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
365
470
|
}
|
|
366
471
|
throw new Error(`Timed out waiting for thread ${input.thread_id}`);
|
|
367
472
|
}
|
|
368
473
|
|
|
369
|
-
return
|
|
370
|
-
requests: this.runtime.getPendingServerRequests(false),
|
|
371
|
-
operations: [],
|
|
372
|
-
}, null, 2);
|
|
474
|
+
return renderRequestListMarkdown(this.runtime.getPendingServerRequests(false));
|
|
373
475
|
}
|
|
374
476
|
|
|
375
477
|
private buildServerRequestPayload(method: string, input: RequestRespondInput): unknown {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { PendingServerRequest, RuntimeOperation } from '../types/codex.js';
|
|
2
|
+
|
|
3
|
+
interface ThreadData {
|
|
4
|
+
id: string | undefined;
|
|
5
|
+
status: { type?: string } | undefined;
|
|
6
|
+
model: string | undefined;
|
|
7
|
+
createdAt: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function extractThread(raw: unknown): ThreadData {
|
|
11
|
+
const obj = (raw ?? {}) as Record<string, unknown>;
|
|
12
|
+
const thread = (obj.thread ?? obj) as Record<string, unknown>;
|
|
13
|
+
return {
|
|
14
|
+
id: typeof thread.id === 'string' ? thread.id : undefined,
|
|
15
|
+
status: thread.status as ThreadData['status'],
|
|
16
|
+
model: typeof thread.model === 'string' ? thread.model : undefined,
|
|
17
|
+
createdAt: typeof thread.createdAt === 'string' ? thread.createdAt : undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function requestsForThread(requests: PendingServerRequest[], threadId: string | undefined): PendingServerRequest[] {
|
|
22
|
+
if (!threadId) return requests;
|
|
23
|
+
return requests.filter((r) => {
|
|
24
|
+
const params = r.params as Record<string, unknown> | undefined;
|
|
25
|
+
return params?.threadId === threadId;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderThreadMarkdown(
|
|
30
|
+
raw: unknown,
|
|
31
|
+
operations: RuntimeOperation[],
|
|
32
|
+
pendingRequests: PendingServerRequest[],
|
|
33
|
+
): string {
|
|
34
|
+
const thread = extractThread(raw);
|
|
35
|
+
const threadId = thread.id ?? 'unknown';
|
|
36
|
+
const statusType = thread.status?.type ?? 'unknown';
|
|
37
|
+
const threadRequests = requestsForThread(pendingRequests, thread.id);
|
|
38
|
+
|
|
39
|
+
const lines: string[] = [
|
|
40
|
+
`# Thread: ${threadId}`,
|
|
41
|
+
'',
|
|
42
|
+
'| Field | Value |',
|
|
43
|
+
'|---|---|',
|
|
44
|
+
`| **Status** | \`${statusType}\` |`,
|
|
45
|
+
...(thread.model ? [`| **Model** | \`${thread.model}\` |`] : []),
|
|
46
|
+
...(thread.createdAt ? [`| **Created** | ${thread.createdAt} |`] : []),
|
|
47
|
+
'',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
if (operations.length > 0) {
|
|
51
|
+
lines.push('## Operations', '');
|
|
52
|
+
for (const op of operations) {
|
|
53
|
+
const time = op.completedAt ?? op.startedAt;
|
|
54
|
+
lines.push(`- \`${op.operationId}\` [${op.status}] ${op.method} -- ${time}`);
|
|
55
|
+
}
|
|
56
|
+
lines.push('');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (threadRequests.length > 0) {
|
|
60
|
+
lines.push(`## Pending Requests (${threadRequests.length})`, '');
|
|
61
|
+
for (const req of threadRequests) {
|
|
62
|
+
lines.push(`### ${String(req.id)} (${req.method})`);
|
|
63
|
+
lines.push(`Use \`codex-request-respond\` with \`request_id: "${String(req.id)}"\` and \`decision: "accept"\`.`);
|
|
64
|
+
lines.push('');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
lines.push('## What to do next', '');
|
|
69
|
+
|
|
70
|
+
if (threadRequests.length > 0) {
|
|
71
|
+
lines.push(`**ACTION REQUIRED** -- ${threadRequests.length} pending request(s) need your response. Use \`codex-request-list\` to see details, then \`codex-request-respond\` to approve or decline.`);
|
|
72
|
+
} else if (statusType === 'active') {
|
|
73
|
+
lines.push(`The agent is still working. Use \`codex-wait\` with \`thread_id: "${threadId}"\` to block until it finishes or needs approval.`);
|
|
74
|
+
} else if (statusType === 'idle' || statusType === 'completed') {
|
|
75
|
+
lines.push(`Thread is idle. Use \`codex-turn-start\` to send a new task, or \`codex-thread-read\` with \`include_turns: true\` for full conversation history.`);
|
|
76
|
+
} else {
|
|
77
|
+
lines.push(`Use \`codex-thread-resume\` to reload this thread before starting a turn.`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return lines.join('\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function renderThreadListMarkdown(
|
|
84
|
+
rawThreads: unknown[],
|
|
85
|
+
pendingRequests: PendingServerRequest[],
|
|
86
|
+
): string {
|
|
87
|
+
const threads = rawThreads.map(extractThread);
|
|
88
|
+
|
|
89
|
+
const lines: string[] = [
|
|
90
|
+
`# Threads (${threads.length} total)`,
|
|
91
|
+
'',
|
|
92
|
+
'| ID | Status |',
|
|
93
|
+
'|---|---|',
|
|
94
|
+
...threads.map((t) => `| ${t.id ?? 'unknown'} | ${t.status?.type ?? 'unknown'} |`),
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const pending = pendingRequests.filter((r) => r.status === 'pending');
|
|
98
|
+
if (pending.length > 0) {
|
|
99
|
+
lines.push('', `## Pending Requests (${pending.length})`, '');
|
|
100
|
+
for (const req of pending) {
|
|
101
|
+
const params = req.params as Record<string, unknown> | undefined;
|
|
102
|
+
const tid = typeof params?.threadId === 'string' ? params.threadId : '?';
|
|
103
|
+
lines.push(`- ${tid} / ${String(req.id)}: ${req.method}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function renderRequestListMarkdown(
|
|
111
|
+
requests: PendingServerRequest[],
|
|
112
|
+
): string {
|
|
113
|
+
const pending = requests.filter((r) => r.status === 'pending');
|
|
114
|
+
|
|
115
|
+
if (pending.length === 0) {
|
|
116
|
+
return [
|
|
117
|
+
'**No pending requests**',
|
|
118
|
+
'',
|
|
119
|
+
'All agents are running without needing approval.',
|
|
120
|
+
].join('\n');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const lines: string[] = [
|
|
124
|
+
`# Pending Requests (${pending.length})`,
|
|
125
|
+
'',
|
|
126
|
+
'| Request ID | Method | Created |',
|
|
127
|
+
'|---|---|---|',
|
|
128
|
+
...pending.map((r) => `| ${String(r.id)} | ${r.method} | ${r.createdAt} |`),
|
|
129
|
+
'',
|
|
130
|
+
'## How to respond',
|
|
131
|
+
'',
|
|
132
|
+
'Use `codex-request-respond` with the `request_id` and `decision: "accept"` to approve, or `decision: "decline"` to reject.',
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
return lines.join('\n');
|
|
136
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CodexRuntime } from '../services/codex-runtime.js';
|
|
2
|
+
|
|
3
|
+
export function buildThreadBanner(runtime: CodexRuntime): string {
|
|
4
|
+
const threadIds = runtime.listKnownThreadIds();
|
|
5
|
+
if (threadIds.length === 0) {
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const operations = runtime.getAllOperations();
|
|
10
|
+
const requests = runtime.getPendingServerRequests(false);
|
|
11
|
+
const active = operations.filter((op) => op.status === 'running').length;
|
|
12
|
+
const pending = requests.length;
|
|
13
|
+
|
|
14
|
+
const lines = [
|
|
15
|
+
'---',
|
|
16
|
+
`AGENT STATUS: ${active} active | ${pending} pending requests`,
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
for (const threadId of threadIds.slice(-3)) {
|
|
20
|
+
const threadOps = operations.filter((op) => op.threadId === threadId);
|
|
21
|
+
const threadReqs = requests.filter((r) => {
|
|
22
|
+
const params = r.params as Record<string, unknown> | undefined;
|
|
23
|
+
return params?.threadId === threadId;
|
|
24
|
+
});
|
|
25
|
+
const status = threadOps.some((op) => op.status === 'running') ? 'active' : 'idle';
|
|
26
|
+
const reqNote = threadReqs.length > 0 ? ` -- ${threadReqs.length} pending requests` : '';
|
|
27
|
+
lines.push(`- ${threadId} [${status}]${reqNote}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
lines.push('Read codex://threads for full details.');
|
|
31
|
+
return lines.join('\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildRequestBanner(runtime: CodexRuntime): string {
|
|
35
|
+
const requests = runtime.getPendingServerRequests(false);
|
|
36
|
+
if (requests.length === 0) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lines = [
|
|
41
|
+
'---',
|
|
42
|
+
`ACTION REQUIRED -- ${requests.length} request(s) waiting for your response:`,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
for (const req of requests.slice(0, 5)) {
|
|
46
|
+
const params = req.params as Record<string, unknown> | undefined;
|
|
47
|
+
const threadId = typeof params?.threadId === 'string' ? params.threadId : '?';
|
|
48
|
+
lines.push(`- ${String(req.id)} (${threadId}): ${req.method}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
lines.push('Use codex-request-respond { "request_id": "<id>", "decision": "accept" }');
|
|
52
|
+
return lines.join('\n');
|
|
53
|
+
}
|