@myagentroam/node 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codex-app-server.d.ts +24 -0
- package/dist/codex-app-server.js +100 -5
- package/dist/connector.js +6 -2
- package/dist/database.d.ts +2 -0
- package/dist/runner/codex/managed-run-controller.d.ts +9 -0
- package/dist/runner/codex/managed-run-controller.js +57 -17
- package/dist/runner/codex-runner.d.ts +1 -0
- package/dist/runner/codex-runner.js +12 -1
- package/dist/runtime-state.d.ts +2 -0
- package/dist/runtime-state.js +11 -7
- package/dist/service/conversation-history-service.d.ts +8 -0
- package/dist/service/conversation-history-service.js +181 -9
- package/dist/service/conversation-segment-service.js +7 -1
- package/dist/service/native-session-watch-service.d.ts +8 -2
- package/dist/service/native-session-watch-service.js +28 -10
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-event-service.js +16 -5
- package/dist/service/session-lifecycle-service.js +4 -12
- package/dist/service/session-message-service.d.ts +1 -0
- package/dist/service/session-message-service.js +107 -78
- package/dist/service/workspace-change-service.js +1 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +12 -2
- package/dist/service/workspace-queue-workbench-service.js +7 -2
- package/dist/util/runner-native-session-parsers.d.ts +2 -1
- package/dist/util/runner-native-session-parsers.js +27 -16
- package/dist/workspace.js +33 -8
- package/package.json +2 -2
|
@@ -51,6 +51,18 @@ export interface CodexRunConfiguration {
|
|
|
51
51
|
readonly collaborationMode?: 'default' | 'plan';
|
|
52
52
|
readonly mcpServers?: unknown;
|
|
53
53
|
}
|
|
54
|
+
interface CodexRpcErrorPayload {
|
|
55
|
+
readonly code?: unknown;
|
|
56
|
+
readonly message?: unknown;
|
|
57
|
+
readonly data?: unknown;
|
|
58
|
+
}
|
|
59
|
+
export declare class CodexAppServerRpcError extends Error {
|
|
60
|
+
readonly code: unknown;
|
|
61
|
+
readonly rpcMessage: string;
|
|
62
|
+
readonly data: unknown;
|
|
63
|
+
constructor(payload: CodexRpcErrorPayload);
|
|
64
|
+
}
|
|
65
|
+
export declare function isCodexRolloutMissingError(error: unknown): boolean;
|
|
54
66
|
/** Public App Server turn inputs supported by the Workbench Composer. */
|
|
55
67
|
export type CodexComposerInput = {
|
|
56
68
|
readonly type: 'text';
|
|
@@ -71,15 +83,26 @@ export declare class CodexAppServerClient {
|
|
|
71
83
|
private socketPath;
|
|
72
84
|
private nextId;
|
|
73
85
|
private readonly pending;
|
|
86
|
+
private readonly pendingDrainWaiters;
|
|
74
87
|
private readonly notifications;
|
|
75
88
|
private environment;
|
|
89
|
+
private starting;
|
|
90
|
+
private restarting;
|
|
91
|
+
private lifecycleRevision;
|
|
76
92
|
constructor(options?: CodexAppServerOptions);
|
|
77
93
|
configureEnvironment(environment: Readonly<Record<string, string>>): void;
|
|
78
94
|
clearEnvironment(): void;
|
|
79
95
|
start(): Promise<void>;
|
|
96
|
+
restart(): Promise<void>;
|
|
97
|
+
private startManaged;
|
|
98
|
+
private restartManaged;
|
|
80
99
|
stop(): void;
|
|
100
|
+
private stopManaged;
|
|
81
101
|
onNotification(listener: (notification: JsonRpcNotification) => void): () => void;
|
|
82
102
|
request(method: string, params: unknown): Promise<unknown>;
|
|
103
|
+
private requestDirect;
|
|
104
|
+
private waitForPendingRequests;
|
|
105
|
+
private resolvePendingDrain;
|
|
83
106
|
notify(method: string, params: unknown): void;
|
|
84
107
|
respond(requestId: string | number, result: unknown): void;
|
|
85
108
|
startThread(cwd: string, configuration?: CodexRunConfiguration): Promise<unknown>;
|
|
@@ -106,3 +129,4 @@ export declare class CodexAppServerClient {
|
|
|
106
129
|
export declare function probeCodex(command?: string): CodexProbe;
|
|
107
130
|
export declare function probeCodexAsync(command?: string): Promise<CodexProbe>;
|
|
108
131
|
export declare function isSupportedCodexVersion(version: string): boolean;
|
|
132
|
+
export {};
|
package/dist/codex-app-server.js
CHANGED
|
@@ -36,6 +36,25 @@ export function codexSessionControl(input) {
|
|
|
36
36
|
capabilities: ['history.read', 'turn.start']
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
export class CodexAppServerRpcError extends Error {
|
|
40
|
+
code;
|
|
41
|
+
rpcMessage;
|
|
42
|
+
data;
|
|
43
|
+
constructor(payload) {
|
|
44
|
+
const rpcMessage = typeof payload.message === 'string' ? payload.message : 'Codex App Server request failed';
|
|
45
|
+
super(`CODEX_APP_SERVER_RPC_ERROR:${JSON.stringify(payload)}`);
|
|
46
|
+
this.name = 'CodexAppServerRpcError';
|
|
47
|
+
this.code = payload.code;
|
|
48
|
+
this.rpcMessage = rpcMessage;
|
|
49
|
+
this.data = payload.data;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function isCodexRolloutMissingError(error) {
|
|
53
|
+
return (error instanceof CodexAppServerRpcError &&
|
|
54
|
+
error.code === -32600 &&
|
|
55
|
+
(/^no rollout found for thread id\b/i.test(error.rpcMessage) ||
|
|
56
|
+
/^thread not loaded:\s*/i.test(error.rpcMessage)));
|
|
57
|
+
}
|
|
39
58
|
/**
|
|
40
59
|
* Local JSON-RPC client for a Node-supervised Codex App Server. Linux uses a
|
|
41
60
|
* private Unix socket by default; Windows uses stdio. Neither is exposed to
|
|
@@ -48,8 +67,12 @@ export class CodexAppServerClient {
|
|
|
48
67
|
socketPath;
|
|
49
68
|
nextId = 1;
|
|
50
69
|
pending = new Map();
|
|
70
|
+
pendingDrainWaiters = new Set();
|
|
51
71
|
notifications = new Set();
|
|
52
72
|
environment = {};
|
|
73
|
+
starting;
|
|
74
|
+
restarting;
|
|
75
|
+
lifecycleRevision = 0;
|
|
53
76
|
constructor(options = {}) {
|
|
54
77
|
this.options = options;
|
|
55
78
|
}
|
|
@@ -63,9 +86,35 @@ export class CodexAppServerClient {
|
|
|
63
86
|
throw new Error('CODEX_APP_SERVER_ALREADY_STARTED');
|
|
64
87
|
this.environment = {};
|
|
65
88
|
}
|
|
66
|
-
|
|
89
|
+
start() {
|
|
90
|
+
if (this.restarting !== undefined)
|
|
91
|
+
return this.restarting;
|
|
92
|
+
if (this.starting !== undefined)
|
|
93
|
+
return this.starting;
|
|
67
94
|
if (this.child !== undefined)
|
|
68
|
-
return;
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
const starting = this.startManaged();
|
|
97
|
+
this.starting = starting;
|
|
98
|
+
const clearStarting = () => {
|
|
99
|
+
if (this.starting === starting)
|
|
100
|
+
this.starting = undefined;
|
|
101
|
+
};
|
|
102
|
+
void starting.then(clearStarting, clearStarting);
|
|
103
|
+
return starting;
|
|
104
|
+
}
|
|
105
|
+
restart() {
|
|
106
|
+
if (this.restarting !== undefined)
|
|
107
|
+
return this.restarting;
|
|
108
|
+
const restarting = this.restartManaged(this.lifecycleRevision);
|
|
109
|
+
this.restarting = restarting;
|
|
110
|
+
const clearRestarting = () => {
|
|
111
|
+
if (this.restarting === restarting)
|
|
112
|
+
this.restarting = undefined;
|
|
113
|
+
};
|
|
114
|
+
void restarting.then(clearRestarting, clearRestarting);
|
|
115
|
+
return restarting;
|
|
116
|
+
}
|
|
117
|
+
async startManaged() {
|
|
69
118
|
const command = this.options.command ?? 'codex';
|
|
70
119
|
const mode = this.transportMode();
|
|
71
120
|
this.socketPath =
|
|
@@ -105,18 +154,39 @@ export class CodexAppServerClient {
|
|
|
105
154
|
this.transport = socket ?? stdioTransport(child);
|
|
106
155
|
const lines = createInterface({ input: socket ?? child.stdout });
|
|
107
156
|
lines.on('line', (line) => this.handleLine(line));
|
|
108
|
-
|
|
157
|
+
const managedChild = child;
|
|
158
|
+
child.once('error', (error) => {
|
|
159
|
+
if (this.child !== managedChild)
|
|
160
|
+
return;
|
|
161
|
+
this.failAll(new Error(`CODEX_APP_SERVER_START_FAILED:${error.message}`));
|
|
162
|
+
});
|
|
109
163
|
child.once('exit', () => {
|
|
164
|
+
if (this.child !== managedChild)
|
|
165
|
+
return;
|
|
110
166
|
this.child = undefined;
|
|
111
167
|
this.failAll(new Error('CODEX_APP_SERVER_EXITED'));
|
|
112
168
|
});
|
|
113
|
-
await this.
|
|
169
|
+
await this.requestDirect('initialize', {
|
|
114
170
|
clientInfo: { name: 'codex_vscode', title: 'MyAgentRoam', version: '0.1.4' },
|
|
115
171
|
capabilities: { experimentalApi: true }
|
|
116
172
|
});
|
|
117
173
|
this.notify('initialized', {});
|
|
118
174
|
}
|
|
175
|
+
async restartManaged(lifecycleRevision) {
|
|
176
|
+
await Promise.resolve();
|
|
177
|
+
if (this.starting !== undefined)
|
|
178
|
+
await this.starting;
|
|
179
|
+
await this.waitForPendingRequests();
|
|
180
|
+
if (this.lifecycleRevision !== lifecycleRevision)
|
|
181
|
+
throw new Error('CODEX_APP_SERVER_STOPPED');
|
|
182
|
+
this.stopManaged();
|
|
183
|
+
await this.startManaged();
|
|
184
|
+
}
|
|
119
185
|
stop() {
|
|
186
|
+
this.lifecycleRevision += 1;
|
|
187
|
+
this.stopManaged();
|
|
188
|
+
}
|
|
189
|
+
stopManaged() {
|
|
120
190
|
this.transport?.destroy();
|
|
121
191
|
this.transport = undefined;
|
|
122
192
|
this.child?.kill();
|
|
@@ -131,6 +201,13 @@ export class CodexAppServerClient {
|
|
|
131
201
|
return () => this.notifications.delete(listener);
|
|
132
202
|
}
|
|
133
203
|
async request(method, params) {
|
|
204
|
+
if (this.restarting !== undefined)
|
|
205
|
+
await this.restarting;
|
|
206
|
+
else if (this.starting !== undefined)
|
|
207
|
+
await this.starting;
|
|
208
|
+
return this.requestDirect(method, params);
|
|
209
|
+
}
|
|
210
|
+
requestDirect(method, params) {
|
|
134
211
|
const transport = this.transport;
|
|
135
212
|
if (transport === undefined || !transport.writable)
|
|
136
213
|
throw new Error('CODEX_APP_SERVER_UNAVAILABLE');
|
|
@@ -139,6 +216,7 @@ export class CodexAppServerClient {
|
|
|
139
216
|
const result = new Promise((resolve, reject) => {
|
|
140
217
|
const timer = setTimeout(() => {
|
|
141
218
|
this.pending.delete(id);
|
|
219
|
+
this.resolvePendingDrain();
|
|
142
220
|
reject(new Error('CODEX_APP_SERVER_TIMEOUT'));
|
|
143
221
|
}, timeoutMs);
|
|
144
222
|
this.pending.set(id, { resolve, reject, timer });
|
|
@@ -146,6 +224,18 @@ export class CodexAppServerClient {
|
|
|
146
224
|
transport.write(`${JSON.stringify({ method, id, params })}\n`);
|
|
147
225
|
return result;
|
|
148
226
|
}
|
|
227
|
+
waitForPendingRequests() {
|
|
228
|
+
if (this.pending.size === 0)
|
|
229
|
+
return Promise.resolve();
|
|
230
|
+
return new Promise((resolve) => this.pendingDrainWaiters.add(resolve));
|
|
231
|
+
}
|
|
232
|
+
resolvePendingDrain() {
|
|
233
|
+
if (this.pending.size !== 0)
|
|
234
|
+
return;
|
|
235
|
+
for (const resolve of this.pendingDrainWaiters)
|
|
236
|
+
resolve();
|
|
237
|
+
this.pendingDrainWaiters.clear();
|
|
238
|
+
}
|
|
149
239
|
notify(method, params) {
|
|
150
240
|
const transport = this.transport;
|
|
151
241
|
if (transport === undefined || !transport.writable)
|
|
@@ -233,8 +323,9 @@ export class CodexAppServerClient {
|
|
|
233
323
|
return;
|
|
234
324
|
clearTimeout(pending.timer);
|
|
235
325
|
this.pending.delete(message.id);
|
|
326
|
+
this.resolvePendingDrain();
|
|
236
327
|
if (message.error !== undefined) {
|
|
237
|
-
pending.reject(new
|
|
328
|
+
pending.reject(new CodexAppServerRpcError(isRecord(message.error) ? message.error : { message: String(message.error) }));
|
|
238
329
|
}
|
|
239
330
|
else {
|
|
240
331
|
pending.resolve(message.result);
|
|
@@ -252,6 +343,7 @@ export class CodexAppServerClient {
|
|
|
252
343
|
pending.reject(error);
|
|
253
344
|
this.pending.delete(id);
|
|
254
345
|
}
|
|
346
|
+
this.resolvePendingDrain();
|
|
255
347
|
}
|
|
256
348
|
transportMode() {
|
|
257
349
|
const requested = this.options.transport ?? 'auto';
|
|
@@ -371,6 +463,9 @@ function defaultSpawn(command, args, options) {
|
|
|
371
463
|
...(options.env === undefined ? {} : { env: options.env })
|
|
372
464
|
});
|
|
373
465
|
}
|
|
466
|
+
function isRecord(value) {
|
|
467
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
468
|
+
}
|
|
374
469
|
function stdioTransport(child) {
|
|
375
470
|
return {
|
|
376
471
|
get writable() {
|
package/dist/connector.js
CHANGED
|
@@ -43,7 +43,7 @@ import { SessionQueryService } from './service/session-query-service.js';
|
|
|
43
43
|
import { WorkspaceQueueWorkbenchService } from './service/workspace-queue-workbench-service.js';
|
|
44
44
|
import { SessionIdentityService } from './service/session-identity-service.js';
|
|
45
45
|
import { NativeSessionProjectionService } from './service/native-session-projection-service.js';
|
|
46
|
-
import { ConversationHistoryService, hasManagedActiveRun } from './service/conversation-history-service.js';
|
|
46
|
+
import { ConversationHistoryService, convergeConversationWatchPage, hasManagedActiveRun } from './service/conversation-history-service.js';
|
|
47
47
|
import { ConversationSegmentService, conversationSegmentIdentityForItem, conversationSegments } from './service/conversation-segment-service.js';
|
|
48
48
|
import { RunnerInteractionService } from './service/runner-interaction-service.js';
|
|
49
49
|
import { SessionLifecycleService } from './service/session-lifecycle-service.js';
|
|
@@ -197,9 +197,13 @@ export class NodeConnector {
|
|
|
197
197
|
refreshActivity: (session) => this.conversationHistoryService.refreshExternalActivity(session),
|
|
198
198
|
present: (session) => this.sessionPresentationService.present(session),
|
|
199
199
|
readPage: (session, limit) => this.conversationHistoryService.read(session, { limit }),
|
|
200
|
+
convergePage: convergeConversationWatchPage,
|
|
200
201
|
projectInitialPage: (session, page, _unit, limit) => this.conversationSegmentService.projectInitial(session, page, limit),
|
|
201
202
|
emitSession: (session) => this.emitWorkbenchEvent('session', { session }),
|
|
202
|
-
|
|
203
|
+
emitPage: (session, page) => this.emitWorkbenchEvent('conversation', {
|
|
204
|
+
sessionId: session.id,
|
|
205
|
+
snapshot: this.conversationSegmentService.projectInitial(session, page, 10)
|
|
206
|
+
})
|
|
203
207
|
});
|
|
204
208
|
constructor(options = {}) {
|
|
205
209
|
this.config = options.config;
|
package/dist/database.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface NodeAgentRun {
|
|
|
67
67
|
readonly recoveryState: RunRecoveryState;
|
|
68
68
|
readonly version: number;
|
|
69
69
|
readonly clientMessageId: string;
|
|
70
|
+
/** Server-authenticated user that submitted this Run. Internal lifecycle metadata only. */
|
|
71
|
+
readonly initiatedByUserId?: number;
|
|
70
72
|
readonly createdAt: number;
|
|
71
73
|
readonly updatedAt: number;
|
|
72
74
|
}
|
|
@@ -6,6 +6,15 @@ import { CodexRunner } from '../codex-runner.js';
|
|
|
6
6
|
type CodexImageInput = Extract<CodexComposerInput, {
|
|
7
7
|
readonly type: 'localImage';
|
|
8
8
|
}>;
|
|
9
|
+
interface CodexNotificationRun {
|
|
10
|
+
readonly threadId: string;
|
|
11
|
+
readonly turnId?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function resolveCodexNotificationRun<T extends CodexNotificationRun>(runs: ReadonlyMap<string, T>, input: {
|
|
14
|
+
readonly threadId?: string;
|
|
15
|
+
readonly turnId?: string;
|
|
16
|
+
readonly allowUnboundFallback?: boolean;
|
|
17
|
+
}): readonly [string, T] | undefined;
|
|
9
18
|
export interface CodexManagedRunHost<TAttachment> {
|
|
10
19
|
readonly available: () => boolean;
|
|
11
20
|
readonly intercepted: () => boolean;
|
|
@@ -3,6 +3,25 @@ import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
|
3
3
|
import { safeErrorCode } from '../../util/safe-error.js';
|
|
4
4
|
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
5
5
|
import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
|
|
6
|
+
export function resolveCodexNotificationRun(runs, input) {
|
|
7
|
+
const entries = [...runs.entries()];
|
|
8
|
+
if (input.turnId !== undefined) {
|
|
9
|
+
const exact = entries.find(([, value]) => value.turnId === input.turnId &&
|
|
10
|
+
(input.threadId === undefined || value.threadId === input.threadId));
|
|
11
|
+
if (exact !== undefined)
|
|
12
|
+
return exact;
|
|
13
|
+
if (input.allowUnboundFallback !== true)
|
|
14
|
+
return undefined;
|
|
15
|
+
// `turn/started` can arrive before the `turn/start` response binds its native Turn ID. Only
|
|
16
|
+
// an unbound Run on the same thread is a valid fallback; an older bound Run is never valid.
|
|
17
|
+
return entries
|
|
18
|
+
.reverse()
|
|
19
|
+
.find(([, value]) => value.threadId === input.threadId && value.turnId === undefined);
|
|
20
|
+
}
|
|
21
|
+
if (input.threadId === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
return entries.reverse().find(([, value]) => value.threadId === input.threadId);
|
|
24
|
+
}
|
|
6
25
|
export class CodexManagedRunController {
|
|
7
26
|
runner;
|
|
8
27
|
host;
|
|
@@ -104,8 +123,11 @@ export class CodexManagedRunController {
|
|
|
104
123
|
const threadId = typeof params.threadId === 'string' ? params.threadId : undefined;
|
|
105
124
|
if (turnId === undefined && threadId === undefined)
|
|
106
125
|
return;
|
|
107
|
-
const run =
|
|
108
|
-
(
|
|
126
|
+
const run = resolveCodexNotificationRun(this.runner.execution.runs, {
|
|
127
|
+
...(turnId === undefined ? {} : { turnId }),
|
|
128
|
+
...(threadId === undefined ? {} : { threadId }),
|
|
129
|
+
...(notification.method === 'turn/started' ? { allowUnboundFallback: true } : {})
|
|
130
|
+
});
|
|
109
131
|
if (run === undefined)
|
|
110
132
|
return;
|
|
111
133
|
const [runId] = run;
|
|
@@ -350,28 +372,46 @@ export class CodexManagedRunController {
|
|
|
350
372
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
351
373
|
...(payload.mcpServers === undefined ? {} : { mcpServers: payload.mcpServers })
|
|
352
374
|
};
|
|
353
|
-
const
|
|
354
|
-
?
|
|
355
|
-
:
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
375
|
+
const collaborationMode = payload.collaborationMode === 'plan' || payload.collaborationMode === 'default'
|
|
376
|
+
? payload.collaborationMode
|
|
377
|
+
: undefined;
|
|
378
|
+
const turnConfiguration = {
|
|
379
|
+
...configuration,
|
|
380
|
+
...(collaborationMode === undefined ? {} : { collaborationMode }),
|
|
381
|
+
...(payload.serviceTier === 'fast' ? { serviceTier: 'fast' } : {})
|
|
382
|
+
};
|
|
383
|
+
const materializedAttachments = await this.host.materialize(runId, attachments);
|
|
384
|
+
let threadId;
|
|
385
|
+
let turnResult;
|
|
386
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
387
|
+
try {
|
|
388
|
+
const threadResult = typeof payload.externalSessionId === 'string'
|
|
389
|
+
? await this.runner.resumeThread(payload.externalSessionId, cwd, configuration)
|
|
390
|
+
: await this.runner.startThread(cwd, configuration);
|
|
391
|
+
if (this.host.completeCancelled(runId, cwd)) {
|
|
392
|
+
this.runner.releaseEnvironment(runId);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
threadId = extractId(threadResult, 'thread');
|
|
396
|
+
this.runner.execution.runs.set(runId, { sessionId, threadId, cwd });
|
|
397
|
+
turnResult = await this.runner.startTurn(threadId, input, cwd, turnConfiguration, materializedAttachments);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
this.runner.execution.runs.delete(runId);
|
|
402
|
+
if (attempt === 0 && (await this.runner.recoverMissingRollout(runId, error)))
|
|
403
|
+
continue;
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
359
406
|
}
|
|
360
|
-
|
|
407
|
+
if (threadId === undefined || turnResult === undefined)
|
|
408
|
+
throw new Error('CODEX_RUN_FAILED');
|
|
361
409
|
let publicSessionId = sessionId;
|
|
362
410
|
if (typeof payload.externalSessionId !== 'string') {
|
|
363
411
|
this.host.promoteSession(sessionId, threadId);
|
|
364
412
|
publicSessionId = threadId;
|
|
365
413
|
this.host.send('session.external-id', { sessionId, externalSessionId: threadId });
|
|
366
414
|
}
|
|
367
|
-
this.runner.execution.runs.set(runId, { sessionId: publicSessionId, threadId, cwd });
|
|
368
|
-
const turnResult = await this.runner.startTurn(threadId, input, cwd, {
|
|
369
|
-
...configuration,
|
|
370
|
-
...(payload.collaborationMode === 'plan' || payload.collaborationMode === 'default'
|
|
371
|
-
? { collaborationMode: payload.collaborationMode }
|
|
372
|
-
: {}),
|
|
373
|
-
...(payload.serviceTier === 'fast' ? { serviceTier: 'fast' } : {})
|
|
374
|
-
}, await this.host.materialize(runId, attachments));
|
|
375
415
|
const turnId = extractId(turnResult, 'turn');
|
|
376
416
|
this.runner.execution.runs.set(runId, { sessionId: publicSessionId, threadId, turnId, cwd });
|
|
377
417
|
this.runner.execution.managedTurns.set(turnId, { sessionId: publicSessionId, runId });
|
|
@@ -58,6 +58,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
58
58
|
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
59
59
|
onNotification(listener: (notification: JsonRpcNotification) => void): () => void;
|
|
60
60
|
start(): Promise<void>;
|
|
61
|
+
recoverMissingRollout(runId: string, error: unknown): Promise<boolean>;
|
|
61
62
|
prepareEnvironment(environment: Readonly<Record<string, string>>, runId: string): void;
|
|
62
63
|
releaseEnvironment(runId: string): void;
|
|
63
64
|
stop(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CodexAppServerClient } from '../codex-app-server.js';
|
|
1
|
+
import { CodexAppServerClient, isCodexRolloutMissingError } from '../codex-app-server.js';
|
|
2
2
|
import { AbstractRunner } from './abstract-runner.js';
|
|
3
3
|
import { declaredRunnerProfiles } from '../runner-profiles.js';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
@@ -451,6 +451,17 @@ export class CodexRunner extends AbstractRunner {
|
|
|
451
451
|
start() {
|
|
452
452
|
return this.client.start();
|
|
453
453
|
}
|
|
454
|
+
async recoverMissingRollout(runId, error) {
|
|
455
|
+
if (!isCodexRolloutMissingError(error))
|
|
456
|
+
return false;
|
|
457
|
+
if ([...this.environmentRuns].some((activeRunId) => activeRunId !== runId))
|
|
458
|
+
return false;
|
|
459
|
+
if ([...this.execution.runs.keys()].some((activeRunId) => activeRunId !== runId))
|
|
460
|
+
return false;
|
|
461
|
+
nodeLog('runner.codex.rollout.recovering', { runId });
|
|
462
|
+
await this.client.restart();
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
454
465
|
prepareEnvironment(environment, runId) {
|
|
455
466
|
if (Object.keys(environment).length === 0 && this.environmentFingerprint === undefined) {
|
|
456
467
|
this.environmentRuns.add(runId);
|
package/dist/runtime-state.d.ts
CHANGED
|
@@ -86,9 +86,11 @@ export declare class NodeRuntimeState {
|
|
|
86
86
|
*/
|
|
87
87
|
setRunnerTitleFromNative(id: string, title: string): NodeAgentSession;
|
|
88
88
|
createMessageRun(input: {
|
|
89
|
+
readonly runId?: string;
|
|
89
90
|
readonly sessionId: string;
|
|
90
91
|
readonly clientMessageId: string;
|
|
91
92
|
readonly content: string;
|
|
93
|
+
readonly initiatedByUserId?: number;
|
|
92
94
|
readonly attachments?: readonly NodeMessageAttachmentInput[];
|
|
93
95
|
}): {
|
|
94
96
|
readonly run: NodeAgentRun;
|
package/dist/runtime-state.js
CHANGED
|
@@ -255,7 +255,7 @@ export class NodeRuntimeState {
|
|
|
255
255
|
const session = this.requireSession(input.sessionId);
|
|
256
256
|
const now = this.now();
|
|
257
257
|
const run = {
|
|
258
|
-
id: randomUUID(),
|
|
258
|
+
id: input.runId ?? randomUUID(),
|
|
259
259
|
sessionId: session.id,
|
|
260
260
|
nodeId: this.nodeId(),
|
|
261
261
|
workspaceId: session.workspaceId,
|
|
@@ -264,6 +264,9 @@ export class NodeRuntimeState {
|
|
|
264
264
|
recoveryState: 'ACTIVE',
|
|
265
265
|
version: 0,
|
|
266
266
|
clientMessageId: input.clientMessageId,
|
|
267
|
+
...(input.initiatedByUserId === undefined
|
|
268
|
+
? {}
|
|
269
|
+
: { initiatedByUserId: input.initiatedByUserId }),
|
|
267
270
|
createdAt: now,
|
|
268
271
|
updatedAt: now
|
|
269
272
|
};
|
|
@@ -471,15 +474,16 @@ export class NodeRuntimeState {
|
|
|
471
474
|
}
|
|
472
475
|
listConversationTurns(input) {
|
|
473
476
|
this.requireSession(input.sessionId);
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
//
|
|
477
|
+
// A managed Turn keeps the user's acceptance time as its stable timeline
|
|
478
|
+
// position. Runner start/completion timestamps must not move it behind a
|
|
479
|
+
// message that was accepted later. Native Turns without a user timestamp
|
|
480
|
+
// fall back to their own lifecycle time.
|
|
478
481
|
const all = [...(this.turns.get(input.sessionId) ?? [])]
|
|
479
482
|
.map((turn, index) => ({ turn, index }))
|
|
480
483
|
.sort((a, b) => {
|
|
481
|
-
const timestamp = (value) => value.startedAt ??
|
|
482
|
-
value.
|
|
484
|
+
const timestamp = (value) => value.items.find((item) => item.id === value.userItemId)?.startedAt ??
|
|
485
|
+
value.startedAt ??
|
|
486
|
+
value.completedAt ??
|
|
483
487
|
0;
|
|
484
488
|
return timestamp(a.turn) - timestamp(b.turn) || a.index - b.index;
|
|
485
489
|
})
|
|
@@ -5,6 +5,10 @@ import type { RunnerRegistry } from '../runner/runner-registry.js';
|
|
|
5
5
|
import { type ConversationHistoryPage } from '../util/runner-native-session-parsers.js';
|
|
6
6
|
import type { RunAttachmentService } from './run-attachment-service.js';
|
|
7
7
|
export declare function hasManagedActiveRun(runtime: NodeRuntimeState, session: NodeAgentSession): boolean;
|
|
8
|
+
export declare function convergeConversationWatchPage(previous: ConversationHistoryPage | undefined, previousObserved: ConversationHistoryPage | undefined, next: ConversationHistoryPage, previousAuthority: unknown): {
|
|
9
|
+
readonly page: ConversationHistoryPage;
|
|
10
|
+
readonly authority: ConversationHistoryPage['source'];
|
|
11
|
+
};
|
|
8
12
|
interface ConversationHistoryServiceOptions {
|
|
9
13
|
readonly runtime: NodeRuntimeState;
|
|
10
14
|
readonly runners: RunnerRegistry;
|
|
@@ -16,11 +20,15 @@ interface ConversationHistoryServiceOptions {
|
|
|
16
20
|
}
|
|
17
21
|
export declare class ConversationHistoryService {
|
|
18
22
|
private readonly options;
|
|
23
|
+
private readonly activeOfficialReads;
|
|
19
24
|
constructor(options: ConversationHistoryServiceOptions);
|
|
20
25
|
read(session: NodeAgentSession, input: {
|
|
21
26
|
readonly cursor?: string;
|
|
22
27
|
readonly limit?: number;
|
|
23
28
|
}): Promise<ConversationHistoryPage>;
|
|
29
|
+
private readManagedActive;
|
|
30
|
+
private prefetchActiveOfficial;
|
|
31
|
+
private takeActiveOfficialRead;
|
|
24
32
|
refreshExternalActivity(session: NodeAgentSession): Promise<void>;
|
|
25
33
|
readNative(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeSessionHistory | undefined>;
|
|
26
34
|
private attachNativeImages;
|