mcp-codex-worker 0.1.19 → 0.1.21

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/src/app.ts CHANGED
@@ -1,28 +1,13 @@
1
1
  import {
2
2
  createToolDefinitions,
3
- type RequestRespondInput,
4
- type ThreadListInput,
5
- type ThreadReadInput,
6
- type ThreadResumeInput,
7
- type ThreadStartInput,
8
3
  type ToolDefinition,
9
- type TurnInterruptInput,
10
- type TurnStartInput,
11
- type TurnSteerInput,
12
- type WaitInput,
13
4
  type SpawnTaskInput,
14
5
  type WaitTaskInput,
15
6
  type RespondTaskInput,
16
7
  type MessageTaskInput,
17
8
  type CancelTaskInput,
18
9
  } from './mcp/tool-definitions.js';
19
- import { buildRequestBanner, buildThreadBanner } from './mcp/tool-banners.js';
20
- import {
21
- renderRequestListMarkdown,
22
- renderThreadListMarkdown,
23
- renderThreadMarkdown,
24
- } from './mcp/task-markdown.js';
25
- import { OPERATION_BRIDGE_TIMEOUT_MS, OPERATION_POLL_INTERVAL_MS, REQUEST_TIMEOUT_MS } from './config/defaults.js';
10
+ import { OPERATION_BRIDGE_TIMEOUT_MS, OPERATION_POLL_INTERVAL_MS } from './config/defaults.js';
26
11
  import { CodexRuntime } from './services/codex-runtime.js';
27
12
  import { TaskManager } from './task/task-manager.js';
28
13
  import type { TaskState } from './task/task-state.js';
@@ -38,6 +23,7 @@ import {
38
23
  renderVerboseLog,
39
24
  } from './mcp/resource-renderers.js';
40
25
  import { validateResponseAgainstCapabilities } from './execution/provider-capabilities.js';
26
+ // Persistence utilities — re-exported for external use; not called directly in this module.
41
27
  import { saveState, loadState, persistenceDir, applyRecovery } from './task/task-persistence.js';
42
28
 
43
29
  export interface ResourceDefinition {
@@ -72,45 +58,11 @@ export class CodexWorkerApp {
72
58
  }
73
59
 
74
60
  async listTools(): Promise<ToolDefinition[]> {
75
- try {
76
- const modelIds = await this.runtime.listVisibleModelIds();
77
- return createToolDefinitions({
78
- modelIds,
79
- threadBanner: buildThreadBanner(this.runtime),
80
- requestBanner: buildRequestBanner(this.runtime),
81
- });
82
- } catch {
83
- return createToolDefinitions({ modelIds: [], threadBanner: '', requestBanner: '' });
84
- }
61
+ return createToolDefinitions();
85
62
  }
86
63
 
87
64
  listResources(): ResourceDefinition[] {
88
- const base: ResourceDefinition[] = [
89
- {
90
- uri: 'codex://threads',
91
- name: 'Codex Threads',
92
- description: 'All tracked threads with status',
93
- mimeType: 'text/markdown',
94
- },
95
- {
96
- uri: 'codex://models',
97
- name: 'Codex Models',
98
- description: 'Visible models from `model/list`',
99
- mimeType: 'application/json',
100
- },
101
- {
102
- uri: 'codex://account',
103
- name: 'Codex Account',
104
- description: 'Account data from `account/read`',
105
- mimeType: 'application/json',
106
- },
107
- {
108
- uri: 'codex://requests',
109
- name: 'Codex Requests',
110
- description: 'Pending server requests from app-server',
111
- mimeType: 'application/json',
112
- },
113
- ];
65
+ const base: ResourceDefinition[] = [];
114
66
 
115
67
  // Task tracking resources
116
68
  base.push(
@@ -160,23 +112,6 @@ export class CodexWorkerApp {
160
112
  }
161
113
  }
162
114
 
163
- for (const threadId of this.runtime.listKnownThreadIds()) {
164
- base.push(
165
- {
166
- uri: `codex://thread/${threadId}`,
167
- name: `Thread ${threadId}`,
168
- description: 'Thread status and guidance',
169
- mimeType: 'text/markdown',
170
- },
171
- {
172
- uri: `codex://thread/${threadId}/events`,
173
- name: `Thread ${threadId} events`,
174
- description: 'Observed notifications for this thread',
175
- mimeType: 'application/json',
176
- },
177
- );
178
- }
179
-
180
115
  return base;
181
116
  }
182
117
 
@@ -223,81 +158,6 @@ export class CodexWorkerApp {
223
158
  return { mimeType: 'text/markdown', text: renderTaskDetail(task) };
224
159
  }
225
160
 
226
- // ---- Legacy codex:// resources ----
227
- if (uri === 'codex://threads') {
228
- const response = await this.runtime.request('thread/list', { limit: 50 }) as {
229
- data?: Array<{ id?: string }>;
230
- };
231
- for (const row of response.data ?? []) {
232
- if (typeof row.id === 'string') {
233
- this.runtime.rememberThreadId(row.id);
234
- }
235
- }
236
- return {
237
- mimeType: 'text/markdown',
238
- text: renderThreadListMarkdown(
239
- response.data ?? [],
240
- this.runtime.getPendingServerRequests(false),
241
- ),
242
- };
243
- }
244
-
245
- if (uri === 'codex://models') {
246
- return {
247
- mimeType: 'application/json',
248
- text: JSON.stringify({ data: await this.runtime.listModels(false) }, null, 2),
249
- };
250
- }
251
-
252
- if (uri === 'codex://account') {
253
- const [account, limits] = await Promise.all([
254
- this.runtime.request('account/read', {}),
255
- this.runtime.request('account/rateLimits/read', {}),
256
- ]);
257
- return {
258
- mimeType: 'application/json',
259
- text: JSON.stringify({ account, rate_limits: limits }, null, 2),
260
- };
261
- }
262
-
263
- if (uri === 'codex://requests') {
264
- return {
265
- mimeType: 'application/json',
266
- text: JSON.stringify({
267
- requests: this.runtime.getPendingServerRequests(false),
268
- }, null, 2),
269
- };
270
- }
271
-
272
- const threadEventsMatch = /^codex:\/\/thread\/([^/]+)\/events$/.exec(uri);
273
- if (threadEventsMatch) {
274
- const threadId = decodeURIComponent(threadEventsMatch[1]!);
275
- return {
276
- mimeType: 'application/json',
277
- text: JSON.stringify({
278
- thread_id: threadId,
279
- events: await this.runtime.getThreadEvents(threadId),
280
- }, null, 2),
281
- };
282
- }
283
-
284
- const threadMatch = /^codex:\/\/thread\/([^/]+)$/.exec(uri);
285
- if (threadMatch) {
286
- const threadId = decodeURIComponent(threadMatch[1]!);
287
- const response = await this.runtime.request('thread/read', {
288
- threadId,
289
- includeTurns: true,
290
- });
291
- return {
292
- mimeType: 'text/markdown',
293
- text: renderThreadMarkdown(
294
- response,
295
- this.runtime.getOperationsForThread(threadId),
296
- this.runtime.getPendingServerRequests(false),
297
- ),
298
- };
299
- }
300
-
301
161
  return {
302
162
  mimeType: 'application/json',
303
163
  text: JSON.stringify({ error: `Unknown resource URI: ${uri}` }, null, 2),
@@ -312,30 +172,6 @@ export class CodexWorkerApp {
312
172
  }
313
173
  const validated = tool.validate(args ?? {});
314
174
  switch (name) {
315
- case 'codex-thread-start':
316
- return this.handleThreadStart(validated as ThreadStartInput);
317
- case 'codex-thread-resume':
318
- return this.handleThreadResume(validated as ThreadResumeInput);
319
- case 'codex-thread-read':
320
- return this.handleThreadRead(validated as ThreadReadInput);
321
- case 'codex-thread-list':
322
- return this.handleThreadList(validated as ThreadListInput);
323
- case 'codex-turn-start':
324
- return this.handleTurnStart(validated as TurnStartInput);
325
- case 'codex-turn-steer':
326
- return this.handleTurnSteer(validated as TurnSteerInput);
327
- case 'codex-turn-interrupt':
328
- return this.handleTurnInterrupt(validated as TurnInterruptInput);
329
- case 'codex-request-list':
330
- return renderRequestListMarkdown(
331
- this.runtime.getPendingServerRequests(
332
- Boolean((validated as { include_resolved?: boolean }).include_resolved),
333
- ),
334
- );
335
- case 'codex-request-respond':
336
- return this.handleRequestRespond(validated as RequestRespondInput);
337
- case 'codex-wait':
338
- return this.handleWait(validated as WaitInput);
339
175
  case 'spawn-task':
340
176
  return this.handleSpawnTask(validated as SpawnTaskInput);
341
177
  case 'wait-task':
@@ -351,270 +187,8 @@ export class CodexWorkerApp {
351
187
  }
352
188
  }
353
189
 
354
- private async handleThreadStart(input: ThreadStartInput): Promise<string> {
355
- const built = await this.runtime.buildThreadStartParams({
356
- model: input.model,
357
- cwd: input.cwd,
358
- developerInstructions: input.developer_instructions,
359
- });
360
- const response = await this.runtime.request('thread/start', built.params);
361
- const threadId = (response as { thread?: { id?: string } }).thread?.id;
362
- if (threadId) {
363
- this.runtime.rememberThreadId(threadId);
364
- }
365
-
366
- const remapNote = built.remappedFrom ? ` (remapped from ${built.remappedFrom})` : '';
367
- const modelLine = built.params.model
368
- ? `model: \`${built.params.model as string}\`${remapNote}`
369
- : `model: server default${remapNote}`;
370
- return [
371
- `**Thread started**`,
372
- `thread_id: \`${threadId ?? 'unknown'}\``,
373
- modelLine,
374
- '',
375
- '**What to do next:**',
376
- `- Use \`codex-turn-start\` with \`thread_id: "${threadId}"\` to send the agent its task.`,
377
- '- After starting a turn, use `codex-wait` with `operation_id` to block until it finishes or asks for approval.',
378
- '- Check `codex-request-list` after starting turns -- agents often need approval for commands or file changes.',
379
- ].join('\n');
380
- }
381
-
382
- private async handleThreadResume(input: ThreadResumeInput): Promise<string> {
383
- const built = await this.runtime.buildThreadResumeParams({
384
- threadId: input.thread_id,
385
- model: input.model,
386
- cwd: input.cwd,
387
- developerInstructions: input.developer_instructions,
388
- });
389
- await this.runtime.request('thread/resume', built.params);
390
- this.runtime.rememberThreadId(input.thread_id);
391
-
392
- return [
393
- `**Thread resumed**`,
394
- `thread_id: \`${input.thread_id}\``,
395
- '',
396
- '**What to do next:**',
397
- '- Use `codex-turn-start` to send a new task to this thread.',
398
- '- The thread\'s previous context is loaded -- the agent remembers prior conversation.',
399
- ].join('\n');
400
- }
401
-
402
- private async handleThreadRead(input: ThreadReadInput): Promise<string> {
403
- const response = await this.runtime.request('thread/read', {
404
- threadId: input.thread_id,
405
- includeTurns: input.include_turns ?? true,
406
- });
407
- this.runtime.rememberThreadId(input.thread_id);
408
- return renderThreadMarkdown(
409
- response,
410
- this.runtime.getOperationsForThread(input.thread_id),
411
- this.runtime.getPendingServerRequests(false),
412
- );
413
- }
414
-
415
- private async handleThreadList(input: ThreadListInput): Promise<string> {
416
- const response = await this.runtime.request('thread/list', {
417
- limit: input.limit,
418
- cursor: input.cursor ?? null,
419
- }) as {
420
- data?: Array<{ id?: string }>;
421
- };
422
- for (const row of response.data ?? []) {
423
- if (typeof row.id === 'string') {
424
- this.runtime.rememberThreadId(row.id);
425
- }
426
- }
427
- return renderThreadListMarkdown(
428
- response.data ?? [],
429
- this.runtime.getPendingServerRequests(false),
430
- );
431
- }
432
-
433
- private async handleTurnStart(input: TurnStartInput): Promise<string> {
434
- const built = await this.runtime.buildTurnStartParams({
435
- threadId: input.thread_id,
436
- userInput: input.user_input,
437
- model: input.model,
438
- });
439
- await this.runtime.ensureThreadLoaded(input.thread_id, input.model);
440
- const bridged = await this.runtime.requestWithBridge('turn/start', built.params, {
441
- threadId: input.thread_id,
442
- });
443
-
444
- const remapNote = built.remappedFrom ? ` (remapped from ${built.remappedFrom})` : '';
445
- const header = [
446
- `thread_id: \`${input.thread_id}\``,
447
- `operation_id: \`${bridged.operationId}\`${remapNote}`,
448
- ];
449
-
450
- if (bridged.status === 'pending_request') {
451
- const reqCount = bridged.pendingRequestIds?.length ?? 0;
452
- return [
453
- `**Turn started -- approval needed**`,
454
- ...header,
455
- `pending_requests: ${reqCount}`,
456
- '',
457
- '**ACTION REQUIRED:**',
458
- '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.',
459
- '',
460
- '**After approving:**',
461
- `Use \`codex-wait\` with \`operation_id: "${bridged.operationId}"\` to block until the turn completes.`,
462
- ].join('\n');
463
- }
464
-
465
- if (bridged.status === 'completed') {
466
- return [
467
- `**Turn completed**`,
468
- ...header,
469
- '',
470
- '**What to do next:**',
471
- `- Use \`codex-thread-read\` with \`thread_id: "${input.thread_id}"\` to inspect the agent's output and file changes.`,
472
- '- Use `codex-turn-start` to send follow-up instructions.',
473
- ].join('\n');
474
- }
475
-
476
- return [
477
- `**Turn started -- agent working**`,
478
- ...header,
479
- '',
480
- '**What to do next:**',
481
- `- Use \`codex-wait\` with \`operation_id: "${bridged.operationId}"\` to block until the agent finishes or asks for approval.`,
482
- '- Check `codex-request-list` for any pending approvals.',
483
- '- For parallel work, start turns on other threads now.',
484
- ].join('\n');
485
- }
486
-
487
- private async handleTurnSteer(input: TurnSteerInput): Promise<string> {
488
- const params = this.runtime.buildTurnSteerParams({
489
- threadId: input.thread_id,
490
- expectedTurnId: input.expected_turn_id,
491
- userInput: input.user_input,
492
- });
493
- const bridged = await this.runtime.requestWithBridge('turn/steer', params, {
494
- threadId: input.thread_id,
495
- });
496
-
497
- return [
498
- `**Turn steered**`,
499
- `thread_id: \`${input.thread_id}\``,
500
- `operation_id: \`${bridged.operationId}\``,
501
- '',
502
- 'The agent is adjusting course with your new instructions.',
503
- `Use \`codex-wait\` with \`operation_id: "${bridged.operationId}"\` to block until it finishes.`,
504
- ].join('\n');
505
- }
506
-
507
- private async handleTurnInterrupt(input: TurnInterruptInput): Promise<string> {
508
- await this.runtime.request('turn/interrupt', {
509
- threadId: input.thread_id,
510
- turnId: input.turn_id,
511
- });
512
-
513
- return [
514
- `**Turn interrupted**`,
515
- `thread_id: \`${input.thread_id}\``,
516
- `turn_id: \`${input.turn_id}\``,
517
- '',
518
- 'The turn has been stopped. Use `codex-turn-start` to begin a new turn with corrected instructions.',
519
- ].join('\n');
520
- }
521
-
522
- private async handleRequestRespond(input: RequestRespondInput): Promise<string> {
523
- const pending = this.runtime.getPendingServerRequest(input.request_id);
524
- if (!pending) {
525
- throw new Error(`Pending server request not found: ${String(input.request_id)}`);
526
- }
527
-
528
- const payload = this.buildServerRequestPayload(pending.method, input);
529
- await this.runtime.respondToServerRequest(input.request_id, payload);
530
-
531
- return [
532
- `**Request responded**`,
533
- `request_id: \`${String(input.request_id)}\``,
534
- `method: \`${pending.method}\``,
535
- '',
536
- '**What to do next:**',
537
- '- The agent will continue working. Use `codex-wait` to block until it finishes or needs another approval.',
538
- '- Check `codex-request-list` for any remaining pending requests.',
539
- ].join('\n');
540
- }
541
-
542
- private async handleWait(input: WaitInput): Promise<string> {
543
- const timeoutMs = input.timeout_ms ?? REQUEST_TIMEOUT_MS;
544
- const pollIntervalMs = input.poll_interval_ms ?? OPERATION_POLL_INTERVAL_MS;
545
-
546
- if (input.operation_id) {
547
- const operation = await this.runtime.waitForOperation(input.operation_id, timeoutMs, pollIntervalMs);
548
- const threadId = operation.threadId ?? 'unknown';
549
-
550
- if (operation.status === 'failed') {
551
- return [
552
- `**Operation failed**`,
553
- `operation_id: \`${operation.operationId}\``,
554
- `error: ${operation.error ?? 'unknown error'}`,
555
- '',
556
- '**What to do next:**',
557
- `- Use \`codex-thread-read\` with \`thread_id: "${threadId}"\` to inspect what happened.`,
558
- '- Fix the issue and use `codex-turn-start` to retry.',
559
- ].join('\n');
560
- }
561
-
562
- if (operation.pendingRequestIds.length > 0) {
563
- return [
564
- `**Operation paused -- approval needed**`,
565
- `operation_id: \`${operation.operationId}\``,
566
- `pending_requests: ${operation.pendingRequestIds.length}`,
567
- '',
568
- '**ACTION REQUIRED:**',
569
- 'Use `codex-request-list` to see what the agent needs, then `codex-request-respond` to approve.',
570
- `After approving, call \`codex-wait\` again with \`operation_id: "${operation.operationId}"\`.`,
571
- ].join('\n');
572
- }
573
-
574
- return [
575
- `**Operation completed**`,
576
- `operation_id: \`${operation.operationId}\``,
577
- `thread_id: \`${threadId}\``,
578
- '',
579
- '**What to do next:**',
580
- `- Use \`codex-thread-read\` with \`thread_id: "${threadId}"\` to inspect the agent's output.`,
581
- '- Use `codex-turn-start` to send follow-up instructions.',
582
- ].join('\n');
583
- }
584
-
585
- if (input.thread_id) {
586
- const start = Date.now();
587
- while (Date.now() - start <= timeoutMs) {
588
- const response = await this.runtime.request('thread/read', {
589
- threadId: input.thread_id,
590
- includeTurns: false,
591
- }) as {
592
- thread?: {
593
- status?: { type?: string };
594
- };
595
- };
596
- const statusType = response.thread?.status?.type;
597
- if (statusType !== 'active') {
598
- return [
599
- `**Thread idle**`,
600
- `thread_id: \`${input.thread_id}\``,
601
- `status: \`${statusType ?? 'unknown'}\``,
602
- '',
603
- '**What to do next:**',
604
- `- Use \`codex-thread-read\` with \`thread_id: "${input.thread_id}"\` to review the agent's work.`,
605
- '- Use `codex-turn-start` to send another task.',
606
- ].join('\n');
607
- }
608
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
609
- }
610
- throw new Error(`Timed out waiting for thread ${input.thread_id}`);
611
- }
612
-
613
- return renderRequestListMarkdown(this.runtime.getPendingServerRequests(false));
614
- }
615
-
616
190
  // ---------------------------------------------------------------------------
617
- // New unified task tool handlers
191
+ // Unified task tool handlers
618
192
  // ---------------------------------------------------------------------------
619
193
 
620
194
  private async handleSpawnTask(input: SpawnTaskInput): Promise<string> {
@@ -909,47 +483,4 @@ export class CodexWorkerApp {
909
483
  ...(notFound.length > 0 ? { not_found: notFound } : {}),
910
484
  }, null, 2);
911
485
  }
912
-
913
- private buildServerRequestPayload(method: string, input: RequestRespondInput): unknown {
914
- if (input.payload) {
915
- return input.payload;
916
- }
917
-
918
- switch (method) {
919
- case 'item/commandExecution/requestApproval':
920
- case 'item/fileChange/requestApproval':
921
- return {
922
- decision: input.decision ?? 'accept',
923
- };
924
- case 'item/tool/requestUserInput':
925
- return {
926
- answers: input.answers ?? {},
927
- };
928
- case 'mcpServer/elicitation/request':
929
- return {
930
- action: input.action ?? 'accept',
931
- content: input.content ?? null,
932
- _meta: input.meta ?? null,
933
- };
934
- case 'item/permissions/requestApproval':
935
- return {
936
- permissions: input.permissions ?? {},
937
- scope: input.scope ?? 'session',
938
- };
939
- case 'item/tool/call':
940
- return {
941
- contentItems: [],
942
- success: true,
943
- };
944
- case 'applyPatchApproval':
945
- case 'execCommandApproval':
946
- return {
947
- decision: input.decision ?? 'approved',
948
- };
949
- default:
950
- throw new Error(
951
- `No default response payload for request method ${method}. Provide payload explicitly.`,
952
- );
953
- }
954
- }
955
486
  }