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.
@@ -1,7 +1,5 @@
1
1
  import { z } from 'zod';
2
2
 
3
- const jsonSchema = { type: 'object' as const };
4
-
5
3
  const threadStartSchema = z.object({
6
4
  model: z.string().optional(),
7
5
  cwd: z.string().optional(),
@@ -46,10 +44,6 @@ const requestListSchema = z.object({
46
44
  include_resolved: z.boolean().optional(),
47
45
  });
48
46
 
49
- const requestReadSchema = z.object({
50
- request_id: z.union([z.string(), z.number()]),
51
- });
52
-
53
47
  const requestRespondSchema = z.object({
54
48
  request_id: z.union([z.string(), z.number()]),
55
49
  payload: z.record(z.string(), z.unknown()).optional(),
@@ -91,20 +85,32 @@ function objectSchema(
91
85
  };
92
86
  }
93
87
 
94
- export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
88
+ export interface CreateToolDefinitionsInput {
89
+ modelIds: string[];
90
+ threadBanner: string;
91
+ requestBanner: string;
92
+ }
93
+
94
+ export function createToolDefinitions(input: CreateToolDefinitionsInput): ToolDefinition[] {
95
+ const { modelIds, threadBanner, requestBanner } = input;
95
96
  return [
96
97
  {
97
- name: 'thread-start',
98
+ name: 'codex-thread-start',
98
99
  description: [
99
- 'Create a new Codex conversation thread.',
100
- 'Each thread is an independent agent workspace. Launch multiple threads in parallel to work on different tasks concurrently.',
101
- 'Returns thread_id for use with turn-start.',
102
- ].join(' '),
100
+ 'Launch a new Codex agent as a background thread.',
101
+ '',
102
+ 'PARALLEL EXECUTION: Launch multiple threads simultaneously — each thread is an independent agent workspace with its own context and conversation history.',
103
+ 'This is the primary way to dispatch coding and testing work to Codex.',
104
+ '',
105
+ 'After creating a thread, use codex-turn-start to send the task prompt.',
106
+ 'Use codex-wait to block until the agent finishes or asks for permission.',
107
+ 'Check codex-request-list after starting turns — agents often need approval for commands or file changes.',
108
+ ].join('\n'),
103
109
  inputSchema: objectSchema({
104
110
  model: {
105
111
  type: 'string',
106
112
  ...(modelIds.length > 0 ? { enum: modelIds } : {}),
107
- description: 'Model to use for this thread. If omitted, Codex uses the account default.',
113
+ description: 'Model to use. Defaults to gpt-5.4. Available models are listed in the enum.',
108
114
  },
109
115
  cwd: {
110
116
  type: 'string',
@@ -112,14 +118,14 @@ export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
112
118
  },
113
119
  developer_instructions: {
114
120
  type: 'string',
115
- description: 'System-level instructions injected before user messages. Use for constraints, coding style, or scope boundaries.',
121
+ description: 'System-level instructions injected before user messages. Use for constraints, coding style, acceptance criteria, or scope boundaries. Be specific — include file paths, function names, and expected behavior.',
116
122
  },
117
123
  }),
118
124
  validate: (value) => threadStartSchema.parse(value),
119
125
  },
120
126
  {
121
- name: 'thread-resume',
122
- description: 'Resume an existing Codex thread that was previously started. Reloads context and reconnects the agent to the conversation.',
127
+ name: 'codex-thread-resume',
128
+ description: 'Resume a previously started Codex thread. Reloads context and reconnects the agent. Use to continue work on an existing thread after a pause, or to send follow-up instructions to a completed thread.',
123
129
  inputSchema: objectSchema({
124
130
  thread_id: { type: 'string', minLength: 1, description: 'ID of the thread to resume.' },
125
131
  model: {
@@ -133,17 +139,20 @@ export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
133
139
  validate: (value) => threadResumeSchema.parse(value),
134
140
  },
135
141
  {
136
- name: 'thread-read',
137
- description: 'Read a thread and its conversation history. Use include_turns=true to get full turn details.',
142
+ name: 'codex-thread-read',
143
+ description: [
144
+ 'Read thread status and conversation history. Use to check what an agent has done, inspect its output, or verify task completion. Set include_turns=true for full turn details including tool calls and file changes.',
145
+ threadBanner,
146
+ ].filter(Boolean).join('\n\n'),
138
147
  inputSchema: objectSchema({
139
148
  thread_id: { type: 'string', minLength: 1, description: 'Thread to read.' },
140
- include_turns: { type: 'boolean', description: 'Include full turn history. Defaults to true.' },
149
+ include_turns: { type: 'boolean', description: 'Include full turn history with tool calls and outputs. Defaults to true.' },
141
150
  }, ['thread_id']),
142
151
  validate: (value) => threadReadSchema.parse(value),
143
152
  },
144
153
  {
145
- name: 'thread-list',
146
- description: 'List recent Codex threads. Use to discover existing conversations before starting new ones.',
154
+ name: 'codex-thread-list',
155
+ description: 'List recent Codex threads across all sessions. Use to discover existing threads before starting new ones, or to find a thread ID you need to resume.',
147
156
  inputSchema: objectSchema({
148
157
  limit: { type: 'integer', minimum: 1, maximum: 100, description: 'Max threads to return. Default 50.' },
149
158
  cursor: { type: 'string', description: 'Pagination cursor from a previous response.' },
@@ -151,15 +160,23 @@ export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
151
160
  validate: (value) => threadListSchema.parse(value),
152
161
  },
153
162
  {
154
- name: 'turn-start',
163
+ name: 'codex-turn-start',
155
164
  description: [
156
- 'Send a user message to an active thread, starting a new agent turn.',
157
- 'The agent will execute autonomously use wait or turn-steer to monitor or redirect.',
158
- 'For parallel work, start turns on multiple threads simultaneously.',
159
- ].join(' '),
165
+ [
166
+ 'Send a task to an active Codex thread, starting an autonomous agent turn.',
167
+ '',
168
+ 'The agent executes independently — it reads files, writes code, runs commands, and commits changes.',
169
+ 'Use codex-wait to block until the turn completes or a pending request appears.',
170
+ 'Use codex-turn-steer to redirect the agent mid-execution if it goes off track.',
171
+ '',
172
+ 'IMPORTANT: After starting a turn, check codex-request-list — the agent frequently needs approval for shell commands or file changes before it can proceed.',
173
+ 'For parallel work, start turns on multiple threads simultaneously.',
174
+ ].join('\n'),
175
+ threadBanner,
176
+ ].filter(Boolean).join('\n\n'),
160
177
  inputSchema: objectSchema({
161
- thread_id: { type: 'string', minLength: 1, description: 'Thread to send the message to.' },
162
- user_input: { type: 'string', minLength: 1, description: 'The user message or task instruction.' },
178
+ thread_id: { type: 'string', minLength: 1, description: 'Thread to send the task to.' },
179
+ user_input: { type: 'string', minLength: 1, description: 'The task instruction. Be specific: include file paths, function names, acceptance criteria, and constraints.' },
163
180
  model: {
164
181
  type: 'string',
165
182
  ...(modelIds.length > 0 ? { enum: modelIds } : {}),
@@ -169,8 +186,11 @@ export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
169
186
  validate: (value) => turnStartSchema.parse(value),
170
187
  },
171
188
  {
172
- name: 'turn-steer',
173
- description: 'Redirect an in-progress turn with new instructions. The agent adjusts its approach without losing prior context.',
189
+ name: 'codex-turn-steer',
190
+ description: [
191
+ 'Redirect an in-progress turn with new instructions. The agent adjusts course without losing prior context. Use when you see the agent heading in the wrong direction via codex-thread-read or codex-request-list.',
192
+ threadBanner,
193
+ ].filter(Boolean).join('\n\n'),
174
194
  inputSchema: objectSchema({
175
195
  thread_id: { type: 'string', minLength: 1, description: 'Thread containing the active turn.' },
176
196
  expected_turn_id: { type: 'string', minLength: 1, description: 'Turn ID to steer. Must be the currently active turn.' },
@@ -179,8 +199,8 @@ export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
179
199
  validate: (value) => turnSteerSchema.parse(value),
180
200
  },
181
201
  {
182
- name: 'turn-interrupt',
183
- description: 'Stop an active turn immediately. Use when the agent is heading in the wrong direction or a task should be cancelled.',
202
+ name: 'codex-turn-interrupt',
203
+ description: 'Stop an active turn immediately. Use when the agent is heading in the wrong direction and steering is not enough, or when you need to cancel work in progress.',
184
204
  inputSchema: objectSchema({
185
205
  thread_id: { type: 'string', minLength: 1, description: 'Thread containing the turn.' },
186
206
  turn_id: { type: 'string', minLength: 1, description: 'Turn ID to interrupt.' },
@@ -188,58 +208,38 @@ export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
188
208
  validate: (value) => turnInterruptSchema.parse(value),
189
209
  },
190
210
  {
191
- name: 'model-list',
192
- description: 'List all available models for the authenticated Codex account.',
193
- inputSchema: jsonSchema,
194
- validate: (value) => value ?? {},
195
- },
196
- {
197
- name: 'account-read',
198
- description: 'Read the authenticated Codex account details username, plan, and capabilities.',
199
- inputSchema: jsonSchema,
200
- validate: (value) => value ?? {},
201
- },
202
- {
203
- name: 'account-rate-limits-read',
204
- description: 'Read current rate limit status for the Codex account. Check before launching many parallel threads.',
205
- inputSchema: jsonSchema,
206
- validate: (value) => value ?? {},
207
- },
208
- {
209
- name: 'skills-list',
210
- description: 'List registered Codex skills available in this session.',
211
- inputSchema: jsonSchema,
212
- validate: (value) => value ?? {},
213
- },
214
- {
215
- name: 'app-list',
216
- description: 'List Codex apps available in this session.',
217
- inputSchema: jsonSchema,
218
- validate: (value) => value ?? {},
219
- },
220
- {
221
- name: 'request-list',
222
- description: 'List pending Codex server requests awaiting approval (command execution, file changes, permissions). Check this after starting turns — agents often need permission to proceed.',
211
+ name: 'codex-request-list',
212
+ description: [
213
+ [
214
+ 'List pending approval requests from Codex agents (command execution, file changes, permissions).',
215
+ '',
216
+ 'CRITICAL: Check this after every codex-turn-start — agents frequently pause and wait for approval before executing shell commands or writing files.',
217
+ 'If an agent appears stuck, it is almost certainly waiting for a request to be approved.',
218
+ 'Use codex-request-respond to approve or decline each request.',
219
+ ].join('\n'),
220
+ requestBanner,
221
+ ].filter(Boolean).join('\n\n'),
223
222
  inputSchema: objectSchema({
224
223
  include_resolved: { type: 'boolean', description: 'Include already-resolved requests. Default false.' },
225
224
  }),
226
225
  validate: (value) => requestListSchema.parse(value),
227
226
  },
228
227
  {
229
- name: 'request-read',
230
- description: 'Read details of a specific pending server request. Use to understand what the agent is asking before responding.',
231
- inputSchema: objectSchema({
232
- request_id: { type: ['string', 'number'], description: 'ID of the pending request.' },
233
- }, ['request_id']),
234
- validate: (value) => requestReadSchema.parse(value),
235
- },
236
- {
237
- name: 'request-respond',
228
+ name: 'codex-request-respond',
238
229
  description: [
239
- 'Respond to a pending Codex server request (approve commands, grant permissions, answer questions).',
240
- 'The response shape depends on the request method. For command/file approvals use decision="accept".',
241
- 'For permission grants use scope and permissions. The tool auto-builds the right payload shape for common methods.',
242
- ].join(' '),
230
+ [
231
+ 'Approve or decline a pending Codex agent request (command execution, file changes, permissions, user input).',
232
+ '',
233
+ 'Common patterns:',
234
+ '- Approve command/file change: decision="accept"',
235
+ '- Decline: decision="decline"',
236
+ '- Grant permissions: scope="session", permissions={...}',
237
+ '- Answer agent question: answers={ "key": { "answers": ["value"] } }',
238
+ '',
239
+ 'The tool auto-builds the correct payload shape based on the request method.',
240
+ ].join('\n'),
241
+ requestBanner,
242
+ ].filter(Boolean).join('\n\n'),
243
243
  inputSchema: objectSchema({
244
244
  request_id: { type: ['string', 'number'], description: 'ID of the request to respond to.' },
245
245
  payload: { type: 'object', description: 'Raw response payload. Overrides all other fields if provided.' },
@@ -254,10 +254,16 @@ export function createToolDefinitions(modelIds: string[]): ToolDefinition[] {
254
254
  validate: (value) => requestRespondSchema.parse(value),
255
255
  },
256
256
  {
257
- name: 'wait',
258
- description: 'Block until a Codex operation completes or a pending request appears. Use after turn-start to wait for the agent to finish or ask for approval. Provide either operation_id or thread_id.',
257
+ name: 'codex-wait',
258
+ description: [
259
+ 'Block until a Codex turn completes or a pending approval request appears.',
260
+ '',
261
+ 'Use after codex-turn-start to wait for the agent to finish or ask for permission.',
262
+ 'Provide operation_id (from turn-start response) for precise tracking, or thread_id to poll thread status.',
263
+ 'When it returns with a pending request, use codex-request-list + codex-request-respond to unblock the agent.',
264
+ ].join('\n'),
259
265
  inputSchema: objectSchema({
260
- operation_id: { type: 'string', description: 'Operation ID to wait on (from a turn-start response).' },
266
+ operation_id: { type: 'string', description: 'Operation ID to wait on (from a codex-turn-start response).' },
261
267
  thread_id: { type: 'string', description: 'Thread ID to wait on — polls until thread status is no longer active.' },
262
268
  timeout_ms: { type: 'integer', minimum: 1, maximum: 300000, description: 'Max wait time in ms. Default 120,000 (2 minutes).' },
263
269
  poll_interval_ms: { type: 'integer', minimum: 1, maximum: 5000, description: 'Poll interval in ms. Default 250.' },
@@ -275,6 +281,5 @@ export type TurnStartInput = z.infer<typeof turnStartSchema>;
275
281
  export type TurnSteerInput = z.infer<typeof turnSteerSchema>;
276
282
  export type TurnInterruptInput = z.infer<typeof turnInterruptSchema>;
277
283
  export type RequestListInput = z.infer<typeof requestListSchema>;
278
- export type RequestReadInput = z.infer<typeof requestReadSchema>;
279
284
  export type RequestRespondInput = z.infer<typeof requestRespondSchema>;
280
285
  export type WaitInput = z.infer<typeof waitSchema>;
@@ -352,6 +352,12 @@ export class AppServerClient extends EventEmitter {
352
352
  }));
353
353
  }
354
354
 
355
+ listOperationsForThread(threadId: string): RuntimeOperation[] {
356
+ return [...this.operations.values()]
357
+ .filter((op) => op.threadId === threadId)
358
+ .map((op) => ({ ...op, pendingRequestIds: [...op.pendingRequestIds] }));
359
+ }
360
+
355
361
  listServerRequests(includeResolved = false): PendingServerRequest[] {
356
362
  return [...this.serverRequests.values()]
357
363
  .filter((request) => includeResolved || request.status === 'pending')
@@ -8,7 +8,7 @@ import {
8
8
  CODEX_APP_SERVER_COMMAND_ENV,
9
9
  REQUEST_TIMEOUT_MS,
10
10
  } from '../config/defaults.js';
11
- import type { PendingServerRequest } from '../types/codex.js';
11
+ import type { PendingServerRequest, RuntimeOperation } from '../types/codex.js';
12
12
  import { AppServerClient, type BridgedOperationResult } from './app-server-client.js';
13
13
  import { appendFleetDeveloperInstructions } from './fleet-mode.js';
14
14
  import {
@@ -294,6 +294,7 @@ export class CodexRuntime {
294
294
  cwd: input.cwd ?? process.cwd(),
295
295
  approvalPolicy: 'on-request',
296
296
  sandbox: 'workspace-write',
297
+ reasoningEffort: 'high',
297
298
  developerInstructions: appendFleetDeveloperInstructions(input.developerInstructions),
298
299
  experimentalRawEvents: false,
299
300
  persistExtendedHistory: false,
@@ -319,6 +320,7 @@ export class CodexRuntime {
319
320
  cwd: input.cwd ?? process.cwd(),
320
321
  approvalPolicy: 'on-request',
321
322
  sandbox: 'workspace-write',
323
+ reasoningEffort: 'high',
322
324
  developerInstructions: appendFleetDeveloperInstructions(input.developerInstructions),
323
325
  persistExtendedHistory: false,
324
326
  };
@@ -364,6 +366,22 @@ export class CodexRuntime {
364
366
  };
365
367
  }
366
368
 
369
+ getOperationsForThread(threadId: string): RuntimeOperation[] {
370
+ try {
371
+ return this.getCurrentClient().listOperationsForThread(threadId);
372
+ } catch {
373
+ return [];
374
+ }
375
+ }
376
+
377
+ getAllOperations(): RuntimeOperation[] {
378
+ try {
379
+ return this.getCurrentClient().listOperations();
380
+ } catch {
381
+ return [];
382
+ }
383
+ }
384
+
367
385
  async getThreadEvents(threadId: string): Promise<unknown[]> {
368
386
  return this.getCurrentClient().getThreadEvents(threadId);
369
387
  }
@@ -52,7 +52,7 @@ export function resolveModel(
52
52
  catalog: ModelCatalog,
53
53
  requestedModel?: string | undefined,
54
54
  ): ModelResolution {
55
- const requested = requestedModel ?? catalog.defaultModelId;
55
+ const requested = requestedModel ?? catalog.defaultModelId ?? 'gpt-5.4';
56
56
  if (!requested) {
57
57
  throw new Error('No models available from model/list.');
58
58
  }