@excitedjs/agent-runtime-codex 0.3.5-beta.168 → 0.4.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.
Files changed (56) hide show
  1. package/README.md +39 -1
  2. package/dist/events.d.ts +8 -0
  3. package/dist/events.d.ts.map +1 -1
  4. package/dist/events.js +119 -47
  5. package/dist/events.js.map +1 -1
  6. package/dist/provider.d.ts +3 -0
  7. package/dist/provider.d.ts.map +1 -1
  8. package/dist/provider.js +6 -3
  9. package/dist/provider.js.map +1 -1
  10. package/dist/rpc.d.ts +1 -0
  11. package/dist/rpc.d.ts.map +1 -1
  12. package/dist/rpc.js +9 -1
  13. package/dist/rpc.js.map +1 -1
  14. package/dist/runtime.d.ts +15 -7
  15. package/dist/runtime.d.ts.map +1 -1
  16. package/dist/runtime.js +242 -80
  17. package/dist/runtime.js.map +1 -1
  18. package/dist/supervisor.d.ts +7 -0
  19. package/dist/supervisor.d.ts.map +1 -1
  20. package/dist/supervisor.js +99 -43
  21. package/dist/supervisor.js.map +1 -1
  22. package/dist/transcript/budget.d.ts +8 -0
  23. package/dist/transcript/budget.d.ts.map +1 -0
  24. package/dist/transcript/budget.js +9 -0
  25. package/dist/transcript/budget.js.map +1 -0
  26. package/dist/transcript/cursor.d.ts +23 -0
  27. package/dist/transcript/cursor.d.ts.map +1 -0
  28. package/dist/transcript/cursor.js +57 -0
  29. package/dist/transcript/cursor.js.map +1 -0
  30. package/dist/transcript/error.d.ts +9 -0
  31. package/dist/transcript/error.d.ts.map +1 -0
  32. package/dist/transcript/error.js +9 -0
  33. package/dist/transcript/error.js.map +1 -0
  34. package/dist/transcript/opened-file.d.ts +10 -0
  35. package/dist/transcript/opened-file.d.ts.map +1 -0
  36. package/dist/transcript/opened-file.js +61 -0
  37. package/dist/transcript/opened-file.js.map +1 -0
  38. package/dist/transcript/path.d.ts +28 -0
  39. package/dist/transcript/path.d.ts.map +1 -0
  40. package/dist/transcript/path.js +422 -0
  41. package/dist/transcript/path.js.map +1 -0
  42. package/dist/transcript/projection.d.ts +6 -0
  43. package/dist/transcript/projection.d.ts.map +1 -0
  44. package/dist/transcript/projection.js +170 -0
  45. package/dist/transcript/projection.js.map +1 -0
  46. package/dist/transcript/reader.d.ts +7 -0
  47. package/dist/transcript/reader.d.ts.map +1 -0
  48. package/dist/transcript/reader.js +421 -0
  49. package/dist/transcript/reader.js.map +1 -0
  50. package/dist/turn-manager.d.ts +34 -56
  51. package/dist/turn-manager.d.ts.map +1 -1
  52. package/dist/turn-manager.js +369 -303
  53. package/dist/turn-manager.js.map +1 -1
  54. package/dist/types.d.ts +7 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/package.json +5 -4
package/dist/runtime.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CodexProcess, type CodexProcessOptions } from './supervisor.js';
2
2
  import { CodexWsClient } from './rpc.js';
3
- import type { AgentRuntime, AgentRuntimeCapabilities, AgentRuntimeIdentity, AgentRuntimeLastResult, AgentRuntimePathContext, AgentRuntimeSkillSource, AgentRuntimeStateCallbacks, AgentRuntimeStatus, AgentRuntimeTextInput, AgentRuntimeTurnResult, DreamuxLogger, InboundTurnInput, TurnSettledSignal } from '@excitedjs/dreamux-types';
3
+ import type { AgentRuntime, AgentRuntimeCapabilities, AgentRuntimeIdentity, AgentRuntimePathContext, AgentRuntimeSkillSource, AgentRuntimeStateCallbacks, AgentRuntimeStatus, AgentRuntimeTextInput, DreamuxLogger, InboundTurnInput, RuntimeAdmission, RuntimeActivitySink } from '@excitedjs/dreamux-types';
4
4
  export interface CodexRuntimeDeps {
5
5
  cwd: string;
6
6
  systemPromptReplace?: string;
@@ -22,8 +22,9 @@ export interface CodexRuntimeDeps {
22
22
  extraEnv?: Record<string, string>;
23
23
  restartBackoffBaseMs?: number;
24
24
  restartBackoffMaxMs?: number;
25
- onTurnSettled?: (settled: TurnSettledSignal) => void;
25
+ validateTranscriptPath?: (path: string, threadId: string) => Promise<string>;
26
26
  logger?: DreamuxLogger;
27
+ activitySink: RuntimeActivitySink;
27
28
  }
28
29
  export declare class CodexRuntime implements AgentRuntime {
29
30
  readonly identity: AgentRuntimeIdentity;
@@ -33,14 +34,18 @@ export declare class CodexRuntime implements AgentRuntime {
33
34
  private client;
34
35
  private turnManager;
35
36
  private threadId;
37
+ private transcriptLocator;
36
38
  private threadResumed;
37
39
  private status;
38
40
  private readonly log;
39
41
  private stopping;
40
42
  private restarting;
43
+ private generation;
44
+ private startupTask;
45
+ private restartTask;
46
+ private stopTask;
41
47
  private restartAttempts;
42
48
  private restartTimer;
43
- private lastResult;
44
49
  private readonly state;
45
50
  private readonly paths;
46
51
  constructor(identity: AgentRuntimeIdentity, deps: CodexRuntimeDeps);
@@ -49,20 +54,23 @@ export declare class CodexRuntime implements AgentRuntime {
49
54
  getCapabilities(): AgentRuntimeCapabilities;
50
55
  getCheckpoint(): {
51
56
  id: string;
57
+ transcript_locator?: string | null;
52
58
  } | null;
53
59
  wasCheckpointResumed(): boolean;
54
- getLast(): Promise<AgentRuntimeLastResult | null>;
55
60
  getContext(): Promise<null>;
56
61
  resume(): Promise<void>;
57
62
  start(): Promise<void>;
63
+ private startRuntime;
58
64
  private startCodexRuntime;
59
65
  private applySkillExtraRoots;
60
66
  private resolveThread;
67
+ private validateThreadPath;
61
68
  private threadInstructionParams;
62
- channelInput(input: InboundTurnInput): Promise<AgentRuntimeTurnResult>;
69
+ channelInput(input: InboundTurnInput): Promise<RuntimeAdmission>;
63
70
  waitIdle(): Promise<void>;
64
- completionInput(input: AgentRuntimeTextInput): Promise<AgentRuntimeTurnResult>;
71
+ completionInput(input: AgentRuntimeTextInput): Promise<RuntimeAdmission>;
65
72
  stop(): Promise<void>;
73
+ private stopRuntime;
66
74
  private cleanupOnFailure;
67
75
  private teardownCodexRuntime;
68
76
  private handleChildExit;
@@ -72,7 +80,7 @@ export declare class CodexRuntime implements AgentRuntime {
72
80
  private restartDelayMs;
73
81
  private clearRestartTimer;
74
82
  private markReady;
75
- private recordCollectedTurn;
76
83
  private setStatus;
84
+ private assertGeneration;
77
85
  }
78
86
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
package/dist/runtime.js CHANGED
@@ -3,13 +3,13 @@ import { CodexProcess, } from './supervisor.js';
3
3
  import { CodexWsClient } from './rpc.js';
4
4
  import { performInitializeHandshake } from './handshake.js';
5
5
  import { TurnManager, } from './turn-manager.js';
6
- import { extractAssistantText, } from './events.js';
7
6
  import { renderChannelInput } from '@excitedjs/dreamux-utils';
8
7
  import { createFailFastApprovalHandler } from './approval.js';
9
8
  import { BUILTIN_CODEX_PROVIDER_REF } from './provider-ref.js';
10
9
  import { CODEX_AGENT_RUNTIME_CAPABILITIES } from './provider.js';
11
10
  import { codexProcessEnv, renderCodexSystemPromptAppend, } from './runtime-support.js';
12
11
  import { applyCodexSkillExtraRoots } from './skill-roots.js';
12
+ import { resolveCodexTranscriptRoots, validateCodexThreadPath, } from './transcript/path.js';
13
13
  const DEFAULT_RESTART_BACKOFF_BASE_MS = 1000;
14
14
  const DEFAULT_RESTART_BACKOFF_MAX_MS = 30_000;
15
15
  export class CodexRuntime {
@@ -20,14 +20,18 @@ export class CodexRuntime {
20
20
  client = null;
21
21
  turnManager = null;
22
22
  threadId = null;
23
+ transcriptLocator = null;
23
24
  threadResumed = false;
24
25
  status = 'declared';
25
26
  log;
26
27
  stopping = false;
27
28
  restarting = false;
29
+ generation = 0;
30
+ startupTask = null;
31
+ restartTask = null;
32
+ stopTask = null;
28
33
  restartAttempts = 0;
29
34
  restartTimer = null;
30
- lastResult = null;
31
35
  state;
32
36
  paths;
33
37
  constructor(identity, deps) {
@@ -44,7 +48,9 @@ export class CodexRuntime {
44
48
  else
45
49
  console.error(prefix, msg);
46
50
  };
47
- this.threadId = identity.checkpoint_id ?? null;
51
+ this.threadId = identity.checkpoint?.id ?? null;
52
+ this.transcriptLocator =
53
+ identity.checkpoint?.transcript_locator ?? null;
48
54
  this.state = deps.state;
49
55
  this.paths = deps.paths;
50
56
  }
@@ -58,49 +64,91 @@ export class CodexRuntime {
58
64
  return CODEX_AGENT_RUNTIME_CAPABILITIES;
59
65
  }
60
66
  getCheckpoint() {
61
- return this.threadId === null ? null : { id: this.threadId };
67
+ if (this.threadId === null)
68
+ return null;
69
+ return {
70
+ id: this.threadId,
71
+ transcript_locator: this.transcriptLocator,
72
+ };
62
73
  }
63
74
  wasCheckpointResumed() {
64
75
  return this.threadResumed;
65
76
  }
66
- async getLast() {
67
- return this.lastResult;
68
- }
69
77
  async getContext() {
70
78
  return null;
71
79
  }
72
80
  async resume() {
73
81
  await this.start();
74
82
  }
75
- async start() {
76
- this.stopping = false;
83
+ start() {
84
+ if (this.startupTask !== null)
85
+ return this.startupTask;
86
+ if (this.stopping || this.stopTask !== null || this.status === 'stopped') {
87
+ return Promise.reject(new Error('codex runtime is stopped'));
88
+ }
89
+ if (this.status === 'ready')
90
+ return Promise.resolve();
77
91
  this.restarting = false;
92
+ this.generation += 1;
78
93
  this.clearRestartTimer();
94
+ const generation = this.generation;
95
+ const task = this.startRuntime(generation);
96
+ this.startupTask = task;
97
+ void task.finally(() => {
98
+ if (this.startupTask === task)
99
+ this.startupTask = null;
100
+ }).catch(() => undefined);
101
+ return task;
102
+ }
103
+ async startRuntime(generation) {
79
104
  this.setStatus('starting');
80
105
  await this.state.setStatus('starting', {
81
106
  last_started_at: Date.now(),
82
107
  });
83
108
  try {
84
- await this.startCodexRuntime();
85
- await this.markReady();
109
+ await this.startCodexRuntime(generation);
110
+ this.assertGeneration(generation);
111
+ await this.markReady(generation);
86
112
  }
87
113
  catch (err) {
88
114
  const msg = err instanceof Error ? err.message : String(err);
89
115
  this.log('error', `start failed: ${msg}`, err);
90
- this.setStatus('degraded');
91
- await this.state.setStatus('degraded', {
92
- last_error: msg,
93
- });
94
- await this.cleanupOnFailure();
116
+ const failures = [err];
117
+ if (!this.stopping && generation === this.generation) {
118
+ this.setStatus('degraded');
119
+ try {
120
+ await this.state.setStatus('degraded', {
121
+ last_error: msg,
122
+ });
123
+ }
124
+ catch (stateError) {
125
+ failures.push(stateError);
126
+ }
127
+ }
128
+ try {
129
+ await this.cleanupOnFailure();
130
+ }
131
+ catch (cleanupError) {
132
+ failures.push(cleanupError);
133
+ }
134
+ if (failures.length > 1) {
135
+ const failureDetails = failures.slice(1).map((failure) => failure instanceof Error ? failure.message : String(failure)).join('; ');
136
+ throw new AggregateError(failures, `codex runtime start failed and cleanup did not fully converge: ${failureDetails}`);
137
+ }
95
138
  throw err;
96
139
  }
97
140
  }
98
- async startCodexRuntime() {
141
+ async startCodexRuntime(generation) {
142
+ this.assertGeneration(generation);
143
+ if (this.process !== null) {
144
+ throw new Error('codex runtime retains an unterminated process from a prior attempt');
145
+ }
99
146
  const cwd = this.deps.cwd;
100
147
  const socketPath = this.deps.allocateSocketPath(this.dispatcherId);
101
148
  const extraArgs = this.deps.resolveExtraArgs?.() ?? [];
102
149
  if (this.deps.codexHomeDoctor !== undefined) {
103
150
  await this.deps.codexHomeDoctor({ runtimeId: this.dispatcherId, cwd });
151
+ this.assertGeneration(generation);
104
152
  }
105
153
  const codexLogDir = join(this.paths.logsDir(), 'codex-app-server');
106
154
  const factory = this.deps.codexProcessFactory ?? ((o) => new CodexProcess(o));
@@ -120,6 +168,7 @@ export class CodexRuntime {
120
168
  this.handleChildExit(exit);
121
169
  });
122
170
  await process.start();
171
+ this.assertGeneration(generation);
123
172
  const clientFactory = this.deps.codexClientFactory ?? ((sock) => new CodexWsClient({ socketPath: sock }));
124
173
  const client = clientFactory(socketPath);
125
174
  this.client = client;
@@ -129,6 +178,7 @@ export class CodexRuntime {
129
178
  this.handleClientClose(reason);
130
179
  });
131
180
  await client.ready();
181
+ this.assertGeneration(generation);
132
182
  const approvalHandler = createFailFastApprovalHandler({
133
183
  onReject: async (req) => {
134
184
  this.log('warn', `rejected Codex approval request '${req.method}'; Feishu outbound is MCP reply-only`);
@@ -140,16 +190,18 @@ export class CodexRuntime {
140
190
  ? { timeoutMs: this.deps.handshakeTimeoutMs }
141
191
  : {}),
142
192
  });
193
+ this.assertGeneration(generation);
143
194
  this.log('info', `codex initialized: ${initResponse.userAgent} (home=${initResponse.codexHome}, ${initResponse.platformOs})`);
144
195
  await this.applySkillExtraRoots();
145
- await this.resolveThread();
196
+ this.assertGeneration(generation);
197
+ await this.resolveThread(generation);
198
+ this.assertGeneration(generation);
146
199
  this.turnManager = new TurnManager({
147
200
  dispatcherId: this.dispatcherId,
148
201
  getThreadId: () => this.threadId,
149
202
  client: this.client,
150
- onTurnCompleted: (turn) => this.recordCollectedTurn(turn),
151
- onTurnSettled: this.deps.onTurnSettled,
152
203
  log: this.log,
204
+ activitySink: this.deps.activitySink,
153
205
  });
154
206
  }
155
207
  async applySkillExtraRoots() {
@@ -161,47 +213,88 @@ export class CodexRuntime {
161
213
  log: this.log,
162
214
  });
163
215
  }
164
- async resolveThread() {
216
+ async resolveThread(generation) {
165
217
  if (this.client === null)
166
218
  throw new Error('client not initialized');
167
219
  this.threadResumed = false;
168
220
  const threadInstructions = this.threadInstructionParams();
169
- const existing = this.threadId ?? this.identity.checkpoint_id ?? null;
221
+ const existing = this.threadId ?? this.identity.checkpoint?.id ?? null;
170
222
  if (existing === null) {
171
223
  const params = {
172
224
  ...threadInstructions,
173
225
  };
174
226
  const res = await this.client.request('thread/start', params);
175
- this.threadId = res.thread.id;
176
- await this.state.setCheckpoint({ id: this.threadId });
227
+ this.assertGeneration(generation);
228
+ const candidateThreadId = res.thread.id;
229
+ const transcript = await this.validateThreadPath(res.thread.path, candidateThreadId);
230
+ await this.state.setCheckpoint({
231
+ id: candidateThreadId,
232
+ transcript_locator: transcript.path,
233
+ });
234
+ this.assertGeneration(generation);
235
+ this.threadId = candidateThreadId;
236
+ this.transcriptLocator = transcript.path;
177
237
  this.log('info', `started fresh thread ${this.threadId}`);
178
238
  return;
179
239
  }
240
+ let resumed;
180
241
  try {
181
242
  const params = {
182
243
  threadId: existing,
183
244
  ...threadInstructions,
184
245
  };
185
- await this.client.request('thread/resume', params);
186
- this.threadId = existing;
187
- this.threadResumed = true;
188
- this.log('info', `resumed thread ${this.threadId}`);
246
+ resumed = await this.client.request('thread/resume', params);
189
247
  }
190
248
  catch (err) {
249
+ this.assertGeneration(generation);
191
250
  const msg = err instanceof Error ? err.message : String(err);
192
251
  this.log('warn', `thread/resume failed for ${existing}: ${msg}; starting fresh thread`);
193
252
  const res = await this.client.request('thread/start', { ...threadInstructions });
194
- this.threadId = res.thread.id;
253
+ this.assertGeneration(generation);
254
+ const replacementThreadId = res.thread.id;
255
+ const transcript = await this.validateThreadPath(res.thread.path, replacementThreadId);
256
+ const replacement = {
257
+ id: replacementThreadId,
258
+ transcript_locator: transcript.path,
259
+ };
195
260
  if (this.state.recordLostCheckpoint !== undefined) {
196
- await this.state.recordLostCheckpoint({ id: existing }, { id: this.threadId }, `thread/resume failed: ${msg}`);
261
+ await this.state.recordLostCheckpoint({
262
+ id: existing,
263
+ transcript_locator: this.transcriptLocator,
264
+ }, replacement, `thread/resume failed: ${msg}`);
197
265
  }
198
266
  else {
199
- await this.state.setCheckpoint({ id: this.threadId });
267
+ await this.state.setCheckpoint(replacement);
200
268
  await this.state.setStatus('degraded', {
201
269
  last_error: `thread/resume failed: ${msg}`,
202
270
  });
203
271
  }
272
+ this.assertGeneration(generation);
273
+ this.threadId = replacementThreadId;
274
+ this.transcriptLocator = transcript.path;
275
+ return;
276
+ }
277
+ this.assertGeneration(generation);
278
+ const resumedThreadId = resumed.thread.id;
279
+ const transcript = await this.validateThreadPath(resumed.thread.path, resumedThreadId);
280
+ await this.state.setCheckpoint({
281
+ id: resumedThreadId,
282
+ transcript_locator: transcript.path,
283
+ });
284
+ this.assertGeneration(generation);
285
+ this.threadId = resumedThreadId;
286
+ this.transcriptLocator = transcript.path;
287
+ this.threadResumed = true;
288
+ this.log('info', `resumed thread ${this.threadId}`);
289
+ }
290
+ validateThreadPath(path, threadId) {
291
+ if (path === null || path === undefined) {
292
+ throw new Error('Codex thread response omitted the native transcript path');
293
+ }
294
+ if (this.deps.validateTranscriptPath !== undefined) {
295
+ return this.deps.validateTranscriptPath(path, threadId).then((validatedPath) => ({ path: validatedPath }));
204
296
  }
297
+ return resolveCodexTranscriptRoots(codexProcessEnv(this.deps.injectEnv, this.deps.extraEnv)).then((roots) => validateCodexThreadPath(path, threadId, roots));
205
298
  }
206
299
  threadInstructionParams() {
207
300
  const params = {};
@@ -217,6 +310,9 @@ export class CodexRuntime {
217
310
  return params;
218
311
  }
219
312
  async channelInput(input) {
313
+ if (this.stopping || this.status === 'stopped') {
314
+ return { status: 'stopped' };
315
+ }
220
316
  if (this.turnManager === null) {
221
317
  return { status: 'failed', error: new Error('turn manager not initialized') };
222
318
  }
@@ -226,18 +322,47 @@ export class CodexRuntime {
226
322
  return this.turnManager?.waitIdle() ?? Promise.resolve();
227
323
  }
228
324
  async completionInput(input) {
229
- if (this.turnManager === null)
325
+ if (this.stopping || this.status === 'stopped') {
230
326
  return { status: 'stopped' };
327
+ }
328
+ if (this.turnManager === null) {
329
+ return { status: 'failed', error: new Error('turn manager not initialized') };
330
+ }
231
331
  return this.turnManager.submitTextInput(input);
232
332
  }
233
- async stop() {
234
- this.stopping = true;
333
+ stop() {
334
+ if (this.status === 'stopped')
335
+ return Promise.resolve();
336
+ if (this.stopTask !== null)
337
+ return this.stopTask;
338
+ if (!this.stopping) {
339
+ this.stopping = true;
340
+ this.generation += 1;
341
+ }
235
342
  this.clearRestartTimer();
343
+ const task = this.stopRuntime();
344
+ this.stopTask = task;
345
+ void task.catch(() => {
346
+ if (this.stopTask === task)
347
+ this.stopTask = null;
348
+ });
349
+ return task;
350
+ }
351
+ async stopRuntime() {
236
352
  this.setStatus('stopping');
237
- await this.state.setStatus('stopping');
238
- await this.teardownCodexRuntime();
239
- this.setStatus('stopped');
353
+ // Teardown must be initiated before any status/start/restart join. Closing
354
+ // the client is what rejects stuck ready/RPC admissions; the generation
355
+ // fence prevents pre-authority startup work from publishing later.
356
+ const teardown = this.teardownCodexRuntime();
357
+ void teardown.catch(() => undefined);
358
+ const stoppingState = Promise.resolve().then(() => this.state.setStatus('stopping'));
359
+ const [stateResult, teardownResult] = await Promise.allSettled([
360
+ stoppingState,
361
+ teardown,
362
+ ]);
363
+ throwSettledFailures([stateResult, teardownResult], 'codex runtime stop did not converge');
240
364
  await this.state.setStatus('stopped');
365
+ this.setStatus('stopped');
241
366
  }
242
367
  async cleanupOnFailure() {
243
368
  this.clearRestartTimer();
@@ -245,31 +370,48 @@ export class CodexRuntime {
245
370
  this.stopping = true;
246
371
  try {
247
372
  await this.teardownCodexRuntime();
248
- }
249
- finally {
250
373
  this.stopping = wasStopping;
251
374
  }
375
+ catch (error) {
376
+ // Retain the stop fence together with the process authority. Only a
377
+ // successful stop retry may permit any later lifecycle transition.
378
+ this.stopping = true;
379
+ throw error;
380
+ }
252
381
  }
253
382
  async teardownCodexRuntime() {
254
383
  const turnManager = this.turnManager;
255
- this.turnManager = null;
256
- if (turnManager !== null)
257
- await turnManager.stop();
384
+ const managerStop = turnManager?.stop() ?? Promise.resolve();
385
+ void managerStop.catch(() => undefined);
258
386
  const client = this.client;
259
- this.client = null;
387
+ let clientClose = Promise.resolve();
260
388
  if (client !== null) {
261
389
  try {
262
390
  client.close();
263
391
  }
264
- catch {
265
- /* */
392
+ catch (error) {
393
+ clientClose = Promise.reject(error);
394
+ void clientClose.catch(() => undefined);
266
395
  }
267
396
  }
268
397
  const process = this.process;
269
- this.process = null;
270
- if (process !== null) {
271
- await process.reap();
398
+ const processReap = process?.reap() ?? Promise.resolve();
399
+ void processReap.catch(() => undefined);
400
+ const results = await Promise.allSettled([
401
+ managerStop,
402
+ clientClose,
403
+ processReap,
404
+ ]);
405
+ if (results[0]?.status === 'fulfilled' && this.turnManager === turnManager) {
406
+ this.turnManager = null;
407
+ }
408
+ if (results[1]?.status === 'fulfilled' && this.client === client) {
409
+ this.client = null;
272
410
  }
411
+ if (results[2]?.status === 'fulfilled' && this.process === process) {
412
+ this.process = null;
413
+ }
414
+ throwSettledFailures(results, 'codex runtime teardown did not converge');
273
415
  }
274
416
  handleChildExit(exit) {
275
417
  const details = exit.signal !== null ? `signal=${exit.signal}` : `code=${exit.code ?? 'null'}`;
@@ -291,39 +433,53 @@ export class CodexRuntime {
291
433
  .catch((err) => this.log('warn', 'failed to persist degraded status', err));
292
434
  this.restartTimer = setTimeout(() => {
293
435
  this.restartTimer = null;
294
- void this.restartCodexRuntime(reason);
436
+ const task = this.restartCodexRuntime(reason, this.generation);
437
+ this.restartTask = task;
438
+ void task.finally(() => {
439
+ if (this.restartTask === task)
440
+ this.restartTask = null;
441
+ }).catch(() => undefined);
295
442
  }, delay);
296
443
  }
297
- async restartCodexRuntime(reason) {
298
- if (this.stopping)
444
+ async restartCodexRuntime(reason, generation) {
445
+ if (this.stopping || generation !== this.generation)
299
446
  return;
300
447
  this.restarting = true;
301
448
  let retryReason = null;
302
- this.setStatus('starting');
303
- await this.state.setStatus('starting', {
304
- last_started_at: Date.now(),
305
- });
306
449
  try {
450
+ this.setStatus('starting');
451
+ await this.state.setStatus('starting', {
452
+ last_started_at: Date.now(),
453
+ });
307
454
  await this.teardownCodexRuntime();
308
- if (this.stopping)
309
- return;
310
- await this.startCodexRuntime();
311
- if (this.stopping) {
312
- await this.teardownCodexRuntime();
313
- return;
314
- }
455
+ this.assertGeneration(generation);
456
+ await this.startCodexRuntime(generation);
457
+ this.assertGeneration(generation);
315
458
  this.restartAttempts = 0;
316
- await this.markReady();
459
+ await this.markReady(generation);
317
460
  this.log('info', `restarted codex app-server after: ${reason}`);
318
461
  }
319
462
  catch (err) {
320
- const msg = err instanceof Error ? err.message : String(err);
463
+ const failures = [err];
464
+ try {
465
+ await this.teardownCodexRuntime();
466
+ }
467
+ catch (cleanupError) {
468
+ failures.push(cleanupError);
469
+ }
470
+ if (this.stopping || generation !== this.generation)
471
+ return;
472
+ const msg = failures.map((failure) => failure instanceof Error ? failure.message : String(failure)).join('; ');
321
473
  this.log('error', `restart failed: ${msg}`, err);
322
474
  this.setStatus('degraded');
323
- await this.state.setStatus('degraded', {
324
- last_error: msg,
325
- });
326
- await this.teardownCodexRuntime();
475
+ try {
476
+ await this.state.setStatus('degraded', {
477
+ last_error: msg,
478
+ });
479
+ }
480
+ catch (stateError) {
481
+ this.log('warn', 'failed to persist restart failure', stateError);
482
+ }
327
483
  retryReason = `codex app-server restart failed: ${msg}`;
328
484
  }
329
485
  finally {
@@ -343,26 +499,32 @@ export class CodexRuntime {
343
499
  clearTimeout(this.restartTimer);
344
500
  this.restartTimer = null;
345
501
  }
346
- async markReady() {
502
+ async markReady(generation) {
503
+ this.assertGeneration(generation);
347
504
  this.setStatus('ready');
348
505
  await this.state.setStatus('ready', {
349
506
  last_ready_at: Date.now(),
350
507
  last_error: null,
351
508
  });
352
- }
353
- recordCollectedTurn(turn) {
354
- const resultText = extractAssistantText(turn);
355
- if (resultText !== null) {
356
- this.lastResult = { text: resultText };
357
- }
358
- this.deps.onTurnSettled?.({
359
- turnId: turn.turnId,
360
- status: 'completed',
361
- result: { text: resultText },
362
- });
509
+ this.assertGeneration(generation);
363
510
  }
364
511
  setStatus(s) {
365
512
  this.status = s;
366
513
  }
514
+ assertGeneration(generation) {
515
+ if (this.stopping || generation !== this.generation) {
516
+ throw new Error('codex runtime is stopping');
517
+ }
518
+ }
519
+ }
520
+ function throwSettledFailures(results, message) {
521
+ const failures = results
522
+ .filter((result) => result.status === 'rejected')
523
+ .map((result) => result.reason);
524
+ if (failures.length === 0)
525
+ return;
526
+ if (failures.length === 1)
527
+ throw failures[0];
528
+ throw new AggregateError(failures, message);
367
529
  }
368
530
  //# sourceMappingURL=runtime.js.map