@myagentroam/node 0.9.2 → 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 +18 -15
- package/dist/control-outbound-scheduler.d.ts +4 -1
- package/dist/control-outbound-scheduler.js +39 -6
- package/dist/database.d.ts +2 -19
- package/dist/database.js +1 -62
- package/dist/event-buffer.d.ts +2 -5
- package/dist/event-buffer.js +3 -12
- package/dist/native-session-history.js +33 -12
- package/dist/runner/claude/managed-run-controller.d.ts +0 -1
- package/dist/runner/claude/managed-run-controller.js +17 -12
- package/dist/runner/codex/conversation-parser.d.ts +1 -1
- package/dist/runner/codex/conversation-parser.js +3 -0
- package/dist/runner/codex/managed-run-controller.d.ts +11 -2
- package/dist/runner/codex/managed-run-controller.js +79 -24
- package/dist/runner/codex-runner.d.ts +3 -0
- package/dist/runner/codex-runner.js +42 -3
- package/dist/runner/opencode/conversation-parser.js +1 -1
- package/dist/runner/opencode/managed-run-controller.d.ts +7 -2
- package/dist/runner/opencode/managed-run-controller.js +72 -14
- package/dist/runner/opencode-runner.d.ts +3 -0
- package/dist/runner/opencode-runner.js +25 -0
- package/dist/runner-profiles.js +10 -6
- package/dist/runtime-state.d.ts +7 -12
- package/dist/runtime-state.js +43 -53
- package/dist/service/attachment-domain-state.d.ts +2 -0
- package/dist/service/attachment-domain-state.js +2 -0
- 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 +17 -10
- package/dist/service/external-session-resume-service.js +9 -0
- package/dist/service/fake-managed-run-service.d.ts +0 -1
- package/dist/service/fake-managed-run-service.js +0 -2
- package/dist/service/native-session-projection-service.js +5 -0
- 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-connection-lifecycle-service.d.ts +0 -2
- package/dist/service/node-connection-lifecycle-service.js +0 -1
- package/dist/service/node-control-channel.js +1 -1
- package/dist/service/node-control-message-service.d.ts +0 -1
- package/dist/service/node-control-message-service.js +9 -6
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/node-workspace-coordinator.d.ts +1 -4
- package/dist/service/node-workspace-coordinator.js +2 -12
- package/dist/service/run-attachment-service.js +30 -6
- package/dist/service/run-event-service.d.ts +1 -0
- package/dist/service/run-event-service.js +80 -0
- package/dist/service/run-workbench-service.d.ts +1 -1
- package/dist/service/run-workbench-service.js +1 -5
- package/dist/service/runner-interaction-service.d.ts +1 -2
- package/dist/service/runner-interaction-service.js +1 -4
- package/dist/service/session-context-service.d.ts +1 -0
- package/dist/service/session-context-service.js +14 -0
- package/dist/service/session-domain-state.d.ts +1 -0
- package/dist/service/session-domain-state.js +1 -0
- 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 +109 -85
- package/dist/service/skill-directory-service.d.ts +3 -1
- package/dist/service/skill-directory-service.js +28 -4
- package/dist/service/skill-node-operation-service.d.ts +1 -1
- package/dist/service/skill-node-operation-service.js +4 -2
- package/dist/service/workbench-event-service.d.ts +1 -13
- package/dist/service/workbench-event-service.js +2 -36
- package/dist/service/workbench-manifest-service.d.ts +0 -1
- package/dist/service/workbench-manifest-service.js +0 -1
- package/dist/service/workspace-change-service.js +1 -1
- package/dist/service/workspace-domain-state.d.ts +1 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +13 -2
- package/dist/service/workspace-queue-workbench-service.js +8 -2
- package/dist/service/workspace-upload-service.d.ts +2 -0
- package/dist/service/workspace-upload-service.js +28 -6
- package/dist/service/workspace-workbench-service.d.ts +0 -3
- package/dist/service/workspace-workbench-service.js +0 -17
- package/dist/util/runner-error.d.ts +6 -0
- package/dist/util/runner-error.js +52 -0
- package/dist/util/runner-native-session-parsers.d.ts +2 -1
- package/dist/util/runner-native-session-parsers.js +63 -15
- package/dist/workspace.js +33 -8
- package/package.json +2 -2
package/dist/database.js
CHANGED
|
@@ -552,11 +552,6 @@ export class NodeDatabase {
|
|
|
552
552
|
this.raw
|
|
553
553
|
.prepare('UPDATE legacy_agent_sessions SET last_activity_at = ?, updated_at = ? WHERE id = ?')
|
|
554
554
|
.run(value.createdAt, value.createdAt, value.sessionId);
|
|
555
|
-
this.recordWorkspaceActivity(value.workspaceId, 'run.created', {
|
|
556
|
-
runId: value.id,
|
|
557
|
-
sessionId: value.sessionId,
|
|
558
|
-
runner: value.runner
|
|
559
|
-
});
|
|
560
555
|
this.raw.exec('COMMIT');
|
|
561
556
|
return { run: value, created: true };
|
|
562
557
|
}
|
|
@@ -632,14 +627,6 @@ export class NodeDatabase {
|
|
|
632
627
|
.get(input.runId, input.sequence);
|
|
633
628
|
const value = event(stored);
|
|
634
629
|
this.projectRunEvent(value, input.status);
|
|
635
|
-
const run = this.getRun(input.runId);
|
|
636
|
-
if (run !== undefined) {
|
|
637
|
-
this.recordWorkspaceActivity(run.workspaceId, 'run.updated', {
|
|
638
|
-
runId: run.id,
|
|
639
|
-
eventType: input.eventType,
|
|
640
|
-
status: input.status ?? null
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
630
|
return value;
|
|
644
631
|
}
|
|
645
632
|
listRunEvents(runId, after = 0) {
|
|
@@ -709,25 +696,6 @@ export class NodeDatabase {
|
|
|
709
696
|
: null
|
|
710
697
|
};
|
|
711
698
|
}
|
|
712
|
-
listWorkspaceActivity(input) {
|
|
713
|
-
if (this.getWorkspace(input.workspaceId) === undefined)
|
|
714
|
-
throw new Error('WORKSPACE_NOT_FOUND');
|
|
715
|
-
const limit = pageLimit(input.limit);
|
|
716
|
-
const cursor = decodeCursor(input.cursor, 'workspace-activity', input.workspaceId);
|
|
717
|
-
const rows = this.raw
|
|
718
|
-
.prepare(`SELECT id, workspace_id, event_type, payload, created_at FROM workspace_activity_events
|
|
719
|
-
WHERE workspace_id = ? AND (? IS NULL OR id > ?)
|
|
720
|
-
ORDER BY id ASC LIMIT ?`)
|
|
721
|
-
.all(input.workspaceId, cursor === undefined ? null : cursor.value, cursor?.value ?? 0, limit + 1);
|
|
722
|
-
const page = rows.slice(0, limit);
|
|
723
|
-
const last = page.at(-1);
|
|
724
|
-
return {
|
|
725
|
-
events: page.map((row) => workspaceActivityEvent(row)),
|
|
726
|
-
nextCursor: rows.length > limit && last !== undefined
|
|
727
|
-
? encodeCursor('workspace-activity', input.workspaceId, numberValue(last, 'id'), '')
|
|
728
|
-
: null
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
699
|
createApproval(input) {
|
|
732
700
|
if (this.getRun(input.runId) === undefined)
|
|
733
701
|
throw new Error('RUN_NOT_FOUND');
|
|
@@ -828,18 +796,6 @@ export class NodeDatabase {
|
|
|
828
796
|
.get(runId);
|
|
829
797
|
return row['sequence'] === null ? 0 : numberValue(row, 'sequence');
|
|
830
798
|
}
|
|
831
|
-
saveSnapshot(runId, snapshot) {
|
|
832
|
-
this.raw
|
|
833
|
-
.prepare(`INSERT INTO run_snapshots (run_id, snapshot, created_at) VALUES (?, ?, ?)
|
|
834
|
-
ON CONFLICT(run_id) DO UPDATE SET snapshot = excluded.snapshot, created_at = excluded.created_at`)
|
|
835
|
-
.run(runId, JSON.stringify(snapshot), this.now());
|
|
836
|
-
}
|
|
837
|
-
getSnapshot(runId) {
|
|
838
|
-
const row = this.raw
|
|
839
|
-
.prepare('SELECT snapshot FROM run_snapshots WHERE run_id = ?')
|
|
840
|
-
.get(runId);
|
|
841
|
-
return row === undefined ? undefined : JSON.parse(stringValue(row, 'snapshot'));
|
|
842
|
-
}
|
|
843
799
|
markActiveRunsSuspect() {
|
|
844
800
|
this.raw
|
|
845
801
|
.prepare("UPDATE agent_runs SET recovery_state = 'SUSPECT', recovery_updated_at = ? WHERE status NOT IN ('SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED')")
|
|
@@ -877,12 +833,6 @@ export class NodeDatabase {
|
|
|
877
833
|
.prepare("UPDATE workspace_leases SET state = 'RELEASED', updated_at = ? WHERE run_id = ? AND state IN ('ACTIVE', 'SUSPECT')")
|
|
878
834
|
.run(this.now(), runId);
|
|
879
835
|
}
|
|
880
|
-
recordWorkspaceActivity(workspaceId, eventType, payload) {
|
|
881
|
-
this.raw
|
|
882
|
-
.prepare(`INSERT INTO workspace_activity_events (workspace_id, event_type, payload, created_at)
|
|
883
|
-
VALUES (?, ?, ?, ?)`)
|
|
884
|
-
.run(workspaceId, eventType, JSON.stringify(payload), this.now());
|
|
885
|
-
}
|
|
886
836
|
recordConversationItemChange(sessionId, itemId, revision) {
|
|
887
837
|
this.raw
|
|
888
838
|
.prepare(`INSERT INTO conversation_item_events (session_id, item_id, revision, created_at)
|
|
@@ -1206,10 +1156,8 @@ function initializeEphemeralRuntimeTables(database) {
|
|
|
1206
1156
|
'conversation_turns',
|
|
1207
1157
|
'conversation_items',
|
|
1208
1158
|
'conversation_item_events',
|
|
1209
|
-
'workspace_activity_events',
|
|
1210
1159
|
'approvals',
|
|
1211
|
-
'workspace_leases'
|
|
1212
|
-
'run_snapshots'
|
|
1160
|
+
'workspace_leases'
|
|
1213
1161
|
]);
|
|
1214
1162
|
for (const raw of NODE_SCHEMA_V1.split(';')) {
|
|
1215
1163
|
const statement = raw.trim();
|
|
@@ -1341,15 +1289,6 @@ function conversationItem(row) {
|
|
|
1341
1289
|
completedAt: nullableNumber(row, 'completed_at')
|
|
1342
1290
|
};
|
|
1343
1291
|
}
|
|
1344
|
-
function workspaceActivityEvent(row) {
|
|
1345
|
-
return {
|
|
1346
|
-
id: numberValue(row, 'id'),
|
|
1347
|
-
workspaceId: stringValue(row, 'workspace_id'),
|
|
1348
|
-
eventType: stringValue(row, 'event_type'),
|
|
1349
|
-
payload: JSON.parse(stringValue(row, 'payload')),
|
|
1350
|
-
createdAt: numberValue(row, 'created_at')
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1353
1292
|
function pageLimit(value) {
|
|
1354
1293
|
if (value === undefined)
|
|
1355
1294
|
return 100;
|
package/dist/event-buffer.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { type NodeEnvelope, type WorkbenchEvent } from '@myagentroam/protocol';
|
|
2
|
-
/**
|
|
3
|
-
* Keeps exactly the page-level business frames that have not received a Server
|
|
4
|
-
* ACK. Node-global sequences make a reconnect replay harmless.
|
|
5
|
-
*/
|
|
2
|
+
/** Keeps live page-level frames only until the current Server connection ACKs them. */
|
|
6
3
|
export declare class ReliableWorkbenchEventBuffer {
|
|
7
4
|
private readonly pending;
|
|
8
5
|
queue(event: WorkbenchEvent): NodeEnvelope;
|
|
9
6
|
acknowledge(replyTo: string | undefined): boolean;
|
|
10
|
-
|
|
7
|
+
clear(): void;
|
|
11
8
|
get size(): number;
|
|
12
9
|
}
|
package/dist/event-buffer.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { createEnvelope } from '@myagentroam/protocol';
|
|
2
|
-
/**
|
|
3
|
-
* Keeps exactly the page-level business frames that have not received a Server
|
|
4
|
-
* ACK. Node-global sequences make a reconnect replay harmless.
|
|
5
|
-
*/
|
|
2
|
+
/** Keeps live page-level frames only until the current Server connection ACKs them. */
|
|
6
3
|
export class ReliableWorkbenchEventBuffer {
|
|
7
4
|
pending = new Map();
|
|
8
5
|
queue(event) {
|
|
@@ -13,14 +10,8 @@ export class ReliableWorkbenchEventBuffer {
|
|
|
13
10
|
acknowledge(replyTo) {
|
|
14
11
|
return replyTo !== undefined && this.pending.delete(replyTo);
|
|
15
12
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const a = left.payload;
|
|
19
|
-
const b = right.payload;
|
|
20
|
-
return a.sequence - b.sequence;
|
|
21
|
-
});
|
|
22
|
-
for (const envelope of ordered)
|
|
23
|
-
send(envelope);
|
|
13
|
+
clear() {
|
|
14
|
+
this.pending.clear();
|
|
24
15
|
}
|
|
25
16
|
get size() {
|
|
26
17
|
return this.pending.size;
|
|
@@ -9,7 +9,9 @@ const DISCOVERY_READ_BYTES = 32 * 1024;
|
|
|
9
9
|
const TRANSCRIPT_READ_BYTES = 4 * 1024 * 1024;
|
|
10
10
|
const CONTEXT_USAGE_READ_BYTES = 256 * 1024;
|
|
11
11
|
const CLAUDE_CONTEXT_USAGE_READ_BYTES = [CONTEXT_USAGE_READ_BYTES, 1024 * 1024, 4 * 1024 * 1024];
|
|
12
|
+
const TRANSCRIPT_INDEX_TTL_MS = 60 * 60_000;
|
|
12
13
|
const transcriptIndex = new Map();
|
|
14
|
+
const transcriptIndexTimers = new Map();
|
|
13
15
|
const discoveryReads = new Map();
|
|
14
16
|
const DEFAULT_CLAUDE_CONTEXT_WINDOW_TOKENS = 200_000;
|
|
15
17
|
const CLAUDE_CONTEXT_WINDOW_ENV = 'MAR_CLAUDE_CONTEXT_WINDOW_TOKENS';
|
|
@@ -53,7 +55,7 @@ async function performNativeSessionDiscovery(runner) {
|
|
|
53
55
|
if (parsed === undefined)
|
|
54
56
|
continue;
|
|
55
57
|
const cwd = canonical(parsed.cwd);
|
|
56
|
-
|
|
58
|
+
setTranscriptIndex(indexKey(runner, cwd, parsed.externalSessionId), files[index]);
|
|
57
59
|
found.set(`${cwd}\u0000${parsed.externalSessionId}`, parsed);
|
|
58
60
|
}
|
|
59
61
|
nodeLog('native.session.discovery.completed', {
|
|
@@ -83,14 +85,14 @@ async function readDiscoveryHeaders(files, runner) {
|
|
|
83
85
|
/** Reads one already-discovered external session again to follow appended JSONL records. */
|
|
84
86
|
export async function readNativeSession(runner, workspacePath, externalSessionId) {
|
|
85
87
|
const wanted = canonical(workspacePath);
|
|
86
|
-
const path =
|
|
88
|
+
const path = getTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
87
89
|
if (path === undefined)
|
|
88
90
|
return undefined;
|
|
89
91
|
const parsed = await readTranscript(path, runner, TRANSCRIPT_READ_BYTES);
|
|
90
92
|
if (parsed === undefined ||
|
|
91
93
|
parsed.externalSessionId !== externalSessionId ||
|
|
92
94
|
canonical(parsed.cwd) !== wanted) {
|
|
93
|
-
|
|
95
|
+
deleteTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
94
96
|
return undefined;
|
|
95
97
|
}
|
|
96
98
|
return parsed;
|
|
@@ -103,10 +105,10 @@ export async function readNativeSession(runner, workspacePath, externalSessionId
|
|
|
103
105
|
*/
|
|
104
106
|
export async function readCodexNativeContextUsage(workspacePath, externalSessionId) {
|
|
105
107
|
const wanted = canonical(workspacePath);
|
|
106
|
-
let path =
|
|
108
|
+
let path = getTranscriptIndex(indexKey('codex', wanted, externalSessionId));
|
|
107
109
|
if (path === undefined) {
|
|
108
110
|
await discoverNativeSessions('codex', workspacePath);
|
|
109
|
-
path =
|
|
111
|
+
path = getTranscriptIndex(indexKey('codex', wanted, externalSessionId));
|
|
110
112
|
}
|
|
111
113
|
if (path === undefined)
|
|
112
114
|
return undefined;
|
|
@@ -124,11 +126,11 @@ export async function readCodexNativeContextUsage(workspacePath, externalSession
|
|
|
124
126
|
/** Reads the latest actual Claude prompt occupancy without starting a CLI/SDK control stream. */
|
|
125
127
|
export async function readClaudeNativeContextUsage(workspacePath, externalSessionId, options = {}) {
|
|
126
128
|
const wanted = canonical(workspacePath);
|
|
127
|
-
let path =
|
|
129
|
+
let path = getTranscriptIndex(indexKey('claude-code', wanted, externalSessionId));
|
|
128
130
|
path ??= await locateClaudeTranscript(wanted, externalSessionId);
|
|
129
131
|
if (path === undefined) {
|
|
130
132
|
await discoverNativeSessions('claude-code', workspacePath);
|
|
131
|
-
path =
|
|
133
|
+
path = getTranscriptIndex(indexKey('claude-code', wanted, externalSessionId));
|
|
132
134
|
}
|
|
133
135
|
if (path === undefined)
|
|
134
136
|
return undefined;
|
|
@@ -178,7 +180,7 @@ async function locateClaudeTranscript(wantedCwd, externalSessionId) {
|
|
|
178
180
|
const match = parsed.find(({ history }) => history?.externalSessionId === externalSessionId && canonical(history.cwd) === wantedCwd);
|
|
179
181
|
if (match === undefined)
|
|
180
182
|
return undefined;
|
|
181
|
-
|
|
183
|
+
setTranscriptIndex(indexKey('claude-code', wantedCwd, externalSessionId), match.path);
|
|
182
184
|
return match.path;
|
|
183
185
|
}
|
|
184
186
|
export async function claudeContextWindowTokens(options = {}) {
|
|
@@ -207,10 +209,10 @@ export async function claudeContextWindowTokens(options = {}) {
|
|
|
207
209
|
*/
|
|
208
210
|
export async function removeNativeSession(runner, workspacePath, externalSessionId) {
|
|
209
211
|
const wanted = canonical(workspacePath);
|
|
210
|
-
let path =
|
|
212
|
+
let path = getTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
211
213
|
if (path === undefined) {
|
|
212
214
|
await discoverNativeSessions(runner, workspacePath);
|
|
213
|
-
path =
|
|
215
|
+
path = getTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
214
216
|
}
|
|
215
217
|
if (path === undefined)
|
|
216
218
|
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
@@ -218,7 +220,7 @@ export async function removeNativeSession(runner, workspacePath, externalSession
|
|
|
218
220
|
if (parsed === undefined ||
|
|
219
221
|
parsed.externalSessionId !== externalSessionId ||
|
|
220
222
|
canonical(parsed.cwd) !== wanted) {
|
|
221
|
-
|
|
223
|
+
deleteTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
222
224
|
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
223
225
|
}
|
|
224
226
|
try {
|
|
@@ -229,7 +231,7 @@ export async function removeNativeSession(runner, workspacePath, externalSession
|
|
|
229
231
|
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
230
232
|
throw error;
|
|
231
233
|
}
|
|
232
|
-
|
|
234
|
+
deleteTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
233
235
|
}
|
|
234
236
|
function transcriptRoot(runner) {
|
|
235
237
|
return runner === 'codex'
|
|
@@ -886,6 +888,25 @@ function canonical(value) {
|
|
|
886
888
|
function indexKey(runner, cwd, externalSessionId) {
|
|
887
889
|
return `${runner}\u0000${cwd}\u0000${externalSessionId}`;
|
|
888
890
|
}
|
|
891
|
+
function setTranscriptIndex(key, path) {
|
|
892
|
+
const previousTimer = transcriptIndexTimers.get(key);
|
|
893
|
+
if (previousTimer !== undefined)
|
|
894
|
+
clearTimeout(previousTimer);
|
|
895
|
+
transcriptIndex.set(key, path);
|
|
896
|
+
const timer = setTimeout(() => deleteTranscriptIndex(key), TRANSCRIPT_INDEX_TTL_MS);
|
|
897
|
+
timer.unref();
|
|
898
|
+
transcriptIndexTimers.set(key, timer);
|
|
899
|
+
}
|
|
900
|
+
function getTranscriptIndex(key) {
|
|
901
|
+
return transcriptIndex.get(key);
|
|
902
|
+
}
|
|
903
|
+
function deleteTranscriptIndex(key) {
|
|
904
|
+
const timer = transcriptIndexTimers.get(key);
|
|
905
|
+
if (timer !== undefined)
|
|
906
|
+
clearTimeout(timer);
|
|
907
|
+
transcriptIndexTimers.delete(key);
|
|
908
|
+
transcriptIndex.delete(key);
|
|
909
|
+
}
|
|
889
910
|
function isRecord(value) {
|
|
890
911
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
891
912
|
}
|
|
@@ -26,7 +26,6 @@ export interface ClaudeManagedRunHost<TAttachment extends ClaudeRunnerImageAttac
|
|
|
26
26
|
hasActiveSessionLease(sessionId: string): boolean;
|
|
27
27
|
session(sessionId: string): NodeAgentSession | undefined;
|
|
28
28
|
completeCancelled(runId: string, cwd: string): boolean;
|
|
29
|
-
captureSnapshot(runId: string, cwd: string): Promise<void>;
|
|
30
29
|
requestUserInput(runId: string, input: Record<string, unknown>, toolUseId: string): Promise<ReturnType<typeof denyPermission>>;
|
|
31
30
|
requestApproval(runId: string, toolName: string, input: Record<string, unknown>): Promise<ReturnType<typeof denyPermission>>;
|
|
32
31
|
rememberNative(nativeSessionId: string, sessionId: string): void;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createEnvelope } from '@myagentroam/protocol';
|
|
2
2
|
import { ClaudeChannelGate } from '../../claude-channel.js';
|
|
3
3
|
import { boundedConversationItemId, claudePlanText, compactRunnerText, compactRunnerValue, extractClaudeText, extractClaudeThinkingSummaries, extractClaudeToolResults, extractClaudeToolUses, isClaudeCommandTool } from '../codex/conversation-parser.js';
|
|
4
|
-
import { isTerminalRunStatus } from '../../util/node-operation-parsers.js';
|
|
5
4
|
import { claudeContentBlocks } from '../../util/node-operation-parsers.js';
|
|
6
5
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
7
6
|
import { safeErrorCode } from '../../util/safe-error.js';
|
|
7
|
+
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
8
8
|
export class ClaudeManagedRunController {
|
|
9
9
|
runner;
|
|
10
10
|
host;
|
|
@@ -116,16 +116,25 @@ export class ClaudeManagedRunController {
|
|
|
116
116
|
let terminal = false;
|
|
117
117
|
let handle;
|
|
118
118
|
const toolKinds = new Map();
|
|
119
|
-
const finish = (status, eventType, eventPayload) => {
|
|
119
|
+
const finish = (status, eventType, eventPayload, failure, fallbackCode = 'CLAUDE_RUN_FAILED') => {
|
|
120
120
|
if (terminal)
|
|
121
121
|
return;
|
|
122
122
|
terminal = true;
|
|
123
|
-
|
|
123
|
+
const projected = status === 'FAILED'
|
|
124
|
+
? runnerErrorProjection(failure ?? eventPayload, fallbackCode)
|
|
125
|
+
: undefined;
|
|
126
|
+
if (projected !== undefined) {
|
|
127
|
+
this.host.emitItem(runId, {
|
|
128
|
+
itemId: boundedConversationItemId('runner-error', runId),
|
|
129
|
+
kind: 'error',
|
|
130
|
+
status: 'FAILED',
|
|
131
|
+
payload: { ...projected, recoverable: true, retryAfterMs: null }
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
this.host.emit(runId, eventType, projected ?? eventPayload, status);
|
|
124
135
|
this.runner.execution.runs.delete(runId);
|
|
125
136
|
handle?.cancel();
|
|
126
137
|
this.host.active.delete(runId);
|
|
127
|
-
if (isTerminalRunStatus(status))
|
|
128
|
-
void this.host.captureSnapshot(runId, cwd);
|
|
129
138
|
};
|
|
130
139
|
try {
|
|
131
140
|
const gate = new ClaudeChannelGate(sessionId);
|
|
@@ -314,7 +323,7 @@ export class ClaudeManagedRunController {
|
|
|
314
323
|
// result completes this AgentRun even though that iterable can
|
|
315
324
|
// remain open waiting for another user turn; waiting only for
|
|
316
325
|
// stream closure would leave the WorkspaceLease permanently held.
|
|
317
|
-
finish(subtype === 'success' && record.is_error !== true ? 'SUCCEEDED' : 'FAILED', subtype === 'success' && record.is_error !== true ? 'run.completed' : 'run.failed', { subtype });
|
|
326
|
+
finish(subtype === 'success' && record.is_error !== true ? 'SUCCEEDED' : 'FAILED', subtype === 'success' && record.is_error !== true ? 'run.completed' : 'run.failed', { subtype }, record);
|
|
318
327
|
if (completedNativeSessionId !== undefined)
|
|
319
328
|
void this.host.syncTitle(sessionId, completedNativeSessionId, cwd);
|
|
320
329
|
}
|
|
@@ -324,14 +333,10 @@ export class ClaudeManagedRunController {
|
|
|
324
333
|
this.host.setChannelToken(sessionId, gate.token);
|
|
325
334
|
this.host.send('session.channel.ready', { sessionId, token: gate.token });
|
|
326
335
|
this.host.emit(runId, 'run.running', {}, 'RUNNING');
|
|
327
|
-
void handle.completed.then(() => finish('SUCCEEDED', 'run.completed', { subtype: 'stream_closed' }), (error) => finish('FAILED', 'run.failed', {
|
|
328
|
-
code: safeErrorCode(error, 'CLAUDE_RUN_FAILED')
|
|
329
|
-
}));
|
|
336
|
+
void handle.completed.then(() => finish('SUCCEEDED', 'run.completed', { subtype: 'stream_closed' }), (error) => finish('FAILED', 'run.failed', { code: safeErrorCode(error, 'CLAUDE_RUN_FAILED') }, error));
|
|
330
337
|
}
|
|
331
338
|
catch (error) {
|
|
332
|
-
finish('FAILED', 'run.failed', {
|
|
333
|
-
code: safeErrorCode(error, 'CLAUDE_RUN_START_FAILED')
|
|
334
|
-
});
|
|
339
|
+
finish('FAILED', 'run.failed', { code: safeErrorCode(error, 'CLAUDE_RUN_START_FAILED') }, error, 'CLAUDE_RUN_START_FAILED');
|
|
335
340
|
}
|
|
336
341
|
}
|
|
337
342
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NodeAgentSession, NodeConversationItem, NodeConversationTurn, NodeMessageAttachmentInput } from '../../database.js';
|
|
2
2
|
export type PublicConversationItem = {
|
|
3
3
|
readonly itemId: string;
|
|
4
|
-
readonly kind: 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'plan' | 'context_compaction' | 'file_change' | 'unknown';
|
|
4
|
+
readonly kind: 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'plan' | 'context_compaction' | 'file_change' | 'error' | 'unknown';
|
|
5
5
|
readonly status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'DECLINED';
|
|
6
6
|
readonly payload: Record<string, unknown>;
|
|
7
7
|
readonly merge?: boolean;
|
|
@@ -83,6 +83,7 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
|
83
83
|
const consumedOfficialIds = new Set();
|
|
84
84
|
const officialUser = officialItems.find((item) => item.kind === 'user_message');
|
|
85
85
|
const officialAssistants = officialItems.filter((item) => item.kind === 'assistant_message');
|
|
86
|
+
const officialContextCompactions = officialItems.filter((item) => item.kind === 'context_compaction');
|
|
86
87
|
const officialItemMarker = `:official:${value.id}:`;
|
|
87
88
|
const officialByNativeId = new Map(officialItems.flatMap((item) => {
|
|
88
89
|
const markerIndex = item.id.lastIndexOf(officialItemMarker);
|
|
@@ -95,6 +96,8 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
|
95
96
|
let replacement;
|
|
96
97
|
if (managedItem.kind === 'user_message')
|
|
97
98
|
replacement = officialUser;
|
|
99
|
+
else if (managedItem.kind === 'context_compaction')
|
|
100
|
+
replacement = officialContextCompactions.find((item) => !consumedOfficialIds.has(item.id));
|
|
98
101
|
else {
|
|
99
102
|
const nativeId = conversationItemNativeId(managedItem.id);
|
|
100
103
|
if (nativeId !== undefined)
|
|
@@ -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;
|
|
@@ -44,14 +53,13 @@ export interface CodexManagedRunHost<TAttachment> {
|
|
|
44
53
|
}): void;
|
|
45
54
|
emitItem(runId: string, item: {
|
|
46
55
|
readonly itemId: string;
|
|
47
|
-
readonly kind: 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'plan' | 'context_compaction' | 'file_change' | 'unknown';
|
|
56
|
+
readonly kind: 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'plan' | 'context_compaction' | 'file_change' | 'error' | 'unknown';
|
|
48
57
|
readonly status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'DECLINED';
|
|
49
58
|
readonly payload: Record<string, unknown>;
|
|
50
59
|
readonly merge?: boolean;
|
|
51
60
|
readonly append?: 'sections' | 'outputPreview' | 'outputSummary';
|
|
52
61
|
}): void;
|
|
53
62
|
appendImages(runId: string, images: readonly NodeMessageAttachmentInput[]): readonly Record<string, unknown>[];
|
|
54
|
-
captureSnapshot(runId: string, cwd: string): Promise<void>;
|
|
55
63
|
syncTitle(sessionId: string, nativeSessionId: string, cwd: string): Promise<void>;
|
|
56
64
|
commandState(sessionId: string, commandId: string): RunnerCommandState | undefined;
|
|
57
65
|
setCommandState(sessionId: string, state: RunnerCommandState): void;
|
|
@@ -76,5 +84,6 @@ export declare class CodexManagedRunController<TAttachment> {
|
|
|
76
84
|
private requestUserInput;
|
|
77
85
|
start(envelope: NodeEnvelope): void;
|
|
78
86
|
private run;
|
|
87
|
+
private emitError;
|
|
79
88
|
}
|
|
80
89
|
export {};
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import { extractId } from '../../util/runner-native-session-parsers.js';
|
|
2
2
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
3
3
|
import { safeErrorCode } from '../../util/safe-error.js';
|
|
4
|
+
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
4
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
|
+
}
|
|
5
25
|
export class CodexManagedRunController {
|
|
6
26
|
runner;
|
|
7
27
|
host;
|
|
@@ -84,7 +104,8 @@ export class CodexManagedRunController {
|
|
|
84
104
|
};
|
|
85
105
|
}
|
|
86
106
|
catch (error) {
|
|
87
|
-
this.
|
|
107
|
+
this.emitError(runId, error, 'CODEX_COMPACT_FAILED');
|
|
108
|
+
this.host.emit(runId, 'run.failed', runnerErrorProjection(error, 'CODEX_COMPACT_FAILED'), 'FAILED');
|
|
88
109
|
this.runner.execution.runs.delete(runId);
|
|
89
110
|
this.runner.releaseEnvironment(runId);
|
|
90
111
|
this.host.removeActive(runId);
|
|
@@ -102,8 +123,11 @@ export class CodexManagedRunController {
|
|
|
102
123
|
const threadId = typeof params.threadId === 'string' ? params.threadId : undefined;
|
|
103
124
|
if (turnId === undefined && threadId === undefined)
|
|
104
125
|
return;
|
|
105
|
-
const run =
|
|
106
|
-
(
|
|
126
|
+
const run = resolveCodexNotificationRun(this.runner.execution.runs, {
|
|
127
|
+
...(turnId === undefined ? {} : { turnId }),
|
|
128
|
+
...(threadId === undefined ? {} : { threadId }),
|
|
129
|
+
...(notification.method === 'turn/started' ? { allowUnboundFallback: true } : {})
|
|
130
|
+
});
|
|
107
131
|
if (run === undefined)
|
|
108
132
|
return;
|
|
109
133
|
const [runId] = run;
|
|
@@ -184,16 +208,18 @@ export class CodexManagedRunController {
|
|
|
184
208
|
if (notification.method === 'turn/completed') {
|
|
185
209
|
const status = turn?.status;
|
|
186
210
|
const terminal = status === 'completed' ? 'SUCCEEDED' : status === 'interrupted' ? 'INTERRUPTED' : 'FAILED';
|
|
187
|
-
|
|
211
|
+
if (terminal === 'FAILED')
|
|
212
|
+
this.emitError(runId, turn?.error ?? turn, 'CODEX_RUN_FAILED');
|
|
213
|
+
const failure = terminal === 'FAILED'
|
|
214
|
+
? runnerErrorProjection(turn?.error ?? turn, 'CODEX_RUN_FAILED')
|
|
215
|
+
: { status: status ?? 'unknown' };
|
|
216
|
+
this.host.emit(runId, terminal === 'FAILED' ? 'run.failed' : 'run.completed', failure, terminal);
|
|
188
217
|
const active = this.runner.execution.runs.get(runId);
|
|
189
218
|
const titleRefresh = active === undefined ||
|
|
190
219
|
active.refreshTitle === false ||
|
|
191
220
|
this.host.session(active.sessionId)?.titleSource !== 'AUTO'
|
|
192
221
|
? undefined
|
|
193
222
|
: this.host.syncTitle(active.sessionId, active.threadId, active.cwd);
|
|
194
|
-
if (active !== undefined) {
|
|
195
|
-
void this.host.captureSnapshot(runId, active.cwd);
|
|
196
|
-
}
|
|
197
223
|
this.runner.execution.runs.delete(runId);
|
|
198
224
|
if (titleRefresh === undefined)
|
|
199
225
|
this.runner.releaseEnvironment(runId);
|
|
@@ -284,7 +310,8 @@ export class CodexManagedRunController {
|
|
|
284
310
|
secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
|
|
285
311
|
}
|
|
286
312
|
catch (error) {
|
|
287
|
-
this.
|
|
313
|
+
this.emitError(runId, error, 'CODEX_RUN_FAILED');
|
|
314
|
+
this.host.emit(runId, 'run.rejected', runnerErrorProjection(error, 'MESSAGE_ATTACHMENTS_INVALID'), 'FAILED');
|
|
288
315
|
return;
|
|
289
316
|
}
|
|
290
317
|
this.host.adopt({
|
|
@@ -345,28 +372,46 @@ export class CodexManagedRunController {
|
|
|
345
372
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
346
373
|
...(payload.mcpServers === undefined ? {} : { mcpServers: payload.mcpServers })
|
|
347
374
|
};
|
|
348
|
-
const
|
|
349
|
-
?
|
|
350
|
-
:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
}
|
|
354
406
|
}
|
|
355
|
-
|
|
407
|
+
if (threadId === undefined || turnResult === undefined)
|
|
408
|
+
throw new Error('CODEX_RUN_FAILED');
|
|
356
409
|
let publicSessionId = sessionId;
|
|
357
410
|
if (typeof payload.externalSessionId !== 'string') {
|
|
358
411
|
this.host.promoteSession(sessionId, threadId);
|
|
359
412
|
publicSessionId = threadId;
|
|
360
413
|
this.host.send('session.external-id', { sessionId, externalSessionId: threadId });
|
|
361
414
|
}
|
|
362
|
-
this.runner.execution.runs.set(runId, { sessionId: publicSessionId, threadId, cwd });
|
|
363
|
-
const turnResult = await this.runner.startTurn(threadId, input, cwd, {
|
|
364
|
-
...configuration,
|
|
365
|
-
...(payload.collaborationMode === 'plan' || payload.collaborationMode === 'default'
|
|
366
|
-
? { collaborationMode: payload.collaborationMode }
|
|
367
|
-
: {}),
|
|
368
|
-
...(payload.serviceTier === 'fast' ? { serviceTier: 'fast' } : {})
|
|
369
|
-
}, await this.host.materialize(runId, attachments));
|
|
370
415
|
const turnId = extractId(turnResult, 'turn');
|
|
371
416
|
this.runner.execution.runs.set(runId, { sessionId: publicSessionId, threadId, turnId, cwd });
|
|
372
417
|
this.runner.execution.managedTurns.set(turnId, { sessionId: publicSessionId, runId });
|
|
@@ -382,10 +427,20 @@ export class CodexManagedRunController {
|
|
|
382
427
|
this.runner.releaseEnvironment(runId);
|
|
383
428
|
return;
|
|
384
429
|
}
|
|
385
|
-
this.
|
|
430
|
+
this.emitError(runId, error, 'CODEX_RUN_FAILED');
|
|
431
|
+
this.host.emit(runId, 'run.failed', runnerErrorProjection(error, 'CODEX_RUN_FAILED'), 'FAILED');
|
|
386
432
|
this.runner.execution.runs.delete(runId);
|
|
387
433
|
this.runner.releaseEnvironment(runId);
|
|
388
434
|
this.host.removeActive(runId);
|
|
389
435
|
}
|
|
390
436
|
}
|
|
437
|
+
emitError(runId, error, fallbackCode) {
|
|
438
|
+
const projected = runnerErrorProjection(error, fallbackCode);
|
|
439
|
+
this.host.emitItem(runId, {
|
|
440
|
+
itemId: boundedConversationItemId('runner-error', runId),
|
|
441
|
+
kind: 'error',
|
|
442
|
+
status: 'FAILED',
|
|
443
|
+
payload: { ...projected, recoverable: true, retryAfterMs: null }
|
|
444
|
+
});
|
|
445
|
+
}
|
|
391
446
|
}
|
|
@@ -13,6 +13,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
13
13
|
private environmentFingerprint;
|
|
14
14
|
private readonly environmentRuns;
|
|
15
15
|
private readonly profileCache;
|
|
16
|
+
private readonly profileCacheTimers;
|
|
16
17
|
private readonly profileRefreshes;
|
|
17
18
|
private readonly profileClients;
|
|
18
19
|
private readonly profileKeyByEnvironment;
|
|
@@ -23,6 +24,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
23
24
|
refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile>;
|
|
24
25
|
private refreshCodexProfile;
|
|
25
26
|
private selectCachedProfile;
|
|
27
|
+
private cacheProfile;
|
|
26
28
|
private fetchCodexProfile;
|
|
27
29
|
available(capabilities: NodeCapabilities): boolean;
|
|
28
30
|
executeGlobalCommand(commandId: string, input: string): unknown;
|
|
@@ -56,6 +58,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
56
58
|
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
57
59
|
onNotification(listener: (notification: JsonRpcNotification) => void): () => void;
|
|
58
60
|
start(): Promise<void>;
|
|
61
|
+
recoverMissingRollout(runId: string, error: unknown): Promise<boolean>;
|
|
59
62
|
prepareEnvironment(environment: Readonly<Record<string, string>>, runId: string): void;
|
|
60
63
|
releaseEnvironment(runId: string): void;
|
|
61
64
|
stop(): void;
|