@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.
- package/README.md +39 -1
- package/dist/events.d.ts +8 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +119 -47
- package/dist/events.js.map +1 -1
- package/dist/provider.d.ts +3 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +6 -3
- package/dist/provider.js.map +1 -1
- package/dist/rpc.d.ts +1 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +9 -1
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.d.ts +15 -7
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +242 -80
- package/dist/runtime.js.map +1 -1
- package/dist/supervisor.d.ts +7 -0
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +99 -43
- package/dist/supervisor.js.map +1 -1
- package/dist/transcript/budget.d.ts +8 -0
- package/dist/transcript/budget.d.ts.map +1 -0
- package/dist/transcript/budget.js +9 -0
- package/dist/transcript/budget.js.map +1 -0
- package/dist/transcript/cursor.d.ts +23 -0
- package/dist/transcript/cursor.d.ts.map +1 -0
- package/dist/transcript/cursor.js +57 -0
- package/dist/transcript/cursor.js.map +1 -0
- package/dist/transcript/error.d.ts +9 -0
- package/dist/transcript/error.d.ts.map +1 -0
- package/dist/transcript/error.js +9 -0
- package/dist/transcript/error.js.map +1 -0
- package/dist/transcript/opened-file.d.ts +10 -0
- package/dist/transcript/opened-file.d.ts.map +1 -0
- package/dist/transcript/opened-file.js +61 -0
- package/dist/transcript/opened-file.js.map +1 -0
- package/dist/transcript/path.d.ts +28 -0
- package/dist/transcript/path.d.ts.map +1 -0
- package/dist/transcript/path.js +422 -0
- package/dist/transcript/path.js.map +1 -0
- package/dist/transcript/projection.d.ts +6 -0
- package/dist/transcript/projection.d.ts.map +1 -0
- package/dist/transcript/projection.js +170 -0
- package/dist/transcript/projection.js.map +1 -0
- package/dist/transcript/reader.d.ts +7 -0
- package/dist/transcript/reader.d.ts.map +1 -0
- package/dist/transcript/reader.js +421 -0
- package/dist/transcript/reader.js.map +1 -0
- package/dist/turn-manager.d.ts +34 -56
- package/dist/turn-manager.d.ts.map +1 -1
- package/dist/turn-manager.js +369 -303
- package/dist/turn-manager.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- 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,
|
|
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
|
-
|
|
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<
|
|
69
|
+
channelInput(input: InboundTurnInput): Promise<RuntimeAdmission>;
|
|
63
70
|
waitIdle(): Promise<void>;
|
|
64
|
-
completionInput(input: AgentRuntimeTextInput): Promise<
|
|
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
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EAEZ,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
176
|
-
|
|
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.
|
|
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({
|
|
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(
|
|
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.
|
|
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
|
-
|
|
234
|
-
this.
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
await turnManager.stop();
|
|
384
|
+
const managerStop = turnManager?.stop() ?? Promise.resolve();
|
|
385
|
+
void managerStop.catch(() => undefined);
|
|
258
386
|
const client = this.client;
|
|
259
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|