@myagentroam/node 0.9.3 → 0.9.5
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/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +9 -0
- package/dist/codex-app-server.d.ts +25 -0
- package/dist/codex-app-server.js +104 -7
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +21 -12
- package/dist/database.d.ts +2 -0
- package/dist/native-session-history.d.ts +3 -2
- package/dist/native-session-history.js +199 -30
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +1 -0
- package/dist/runner/abstract-runner.d.ts +11 -2
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +3 -0
- package/dist/runner/claude-code-runner.d.ts +2 -1
- package/dist/runner/claude-code-runner.js +4 -0
- package/dist/runner/codex/managed-run-controller.d.ts +9 -0
- package/dist/runner/codex/managed-run-controller.js +63 -17
- package/dist/runner/codex-runner.d.ts +3 -1
- package/dist/runner/codex-runner.js +23 -2
- package/dist/runner/opencode/managed-run-controller.js +4 -0
- package/dist/runner/opencode-runner.d.ts +10 -3
- package/dist/runner/opencode-runner.js +87 -14
- package/dist/runner/runner-registry.js +16 -1
- package/dist/runtime-command-detector.js +1 -6
- package/dist/runtime-state.d.ts +2 -0
- package/dist/runtime-state.js +12 -7
- package/dist/service/conversation-history-service.d.ts +10 -1
- package/dist/service/conversation-history-service.js +272 -42
- package/dist/service/conversation-segment-service.js +7 -1
- package/dist/service/external-session-resume-service.d.ts +2 -0
- package/dist/service/external-session-resume-service.js +45 -34
- package/dist/service/native-session-projection-service.d.ts +1 -0
- package/dist/service/native-session-projection-service.js +4 -0
- package/dist/service/native-session-watch-service.d.ts +26 -2
- package/dist/service/native-session-watch-service.js +258 -42
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-event-service.js +16 -5
- package/dist/service/run-workbench-service.d.ts +2 -4
- package/dist/service/run-workbench-service.js +2 -7
- package/dist/service/session-command-service.js +1 -1
- package/dist/service/session-identity-service.d.ts +1 -1
- package/dist/service/session-identity-service.js +1 -1
- package/dist/service/session-lifecycle-service.js +7 -12
- package/dist/service/session-message-service.d.ts +1 -1
- package/dist/service/session-message-service.js +104 -83
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-change-service.js +1 -1
- package/dist/service/workspace-domain-state.d.ts +3 -0
- package/dist/service/workspace-domain-state.js +1 -0
- package/dist/service/workspace-file-service.d.ts +1 -0
- package/dist/service/workspace-file-service.js +14 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +29 -2
- package/dist/service/workspace-queue-workbench-service.js +73 -8
- package/dist/service/workspace-watch-service.d.ts +7 -2
- package/dist/service/workspace-watch-service.js +32 -6
- package/dist/service/workspace-workbench-service.d.ts +14 -0
- package/dist/service/workspace-workbench-service.js +215 -16
- package/dist/util/personal-instructions.d.ts +2 -0
- package/dist/util/personal-instructions.js +31 -0
- package/dist/util/runner-native-session-parsers.d.ts +3 -1
- package/dist/util/runner-native-session-parsers.js +35 -17
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +98 -50
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFile, readdir, unlink } from 'node:fs/promises';
|
|
2
|
+
import { readFile, readdir, stat, unlink } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, join, resolve } from 'node:path';
|
|
5
5
|
import { forEachJsonlRecord, listJsonlFiles, mapConcurrent, readJsonlWindow } from './native-jsonl-reader.js';
|
|
@@ -10,9 +10,16 @@ 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
12
|
const TRANSCRIPT_INDEX_TTL_MS = 60 * 60_000;
|
|
13
|
+
const DISCOVERY_SNAPSHOT_TTL_MS = 15_000;
|
|
14
|
+
const TRANSCRIPT_DETAIL_CACHE_TTL_MS = 60_000;
|
|
15
|
+
const TRANSCRIPT_DETAIL_CACHE_MAX_ENTRIES = 16;
|
|
13
16
|
const transcriptIndex = new Map();
|
|
14
17
|
const transcriptIndexTimers = new Map();
|
|
18
|
+
const transcriptDetailCache = new Map();
|
|
19
|
+
const transcriptDetailReads = new Map();
|
|
15
20
|
const discoveryReads = new Map();
|
|
21
|
+
const discoverySnapshots = new Map();
|
|
22
|
+
const discoveryGenerations = new Map();
|
|
16
23
|
const DEFAULT_CLAUDE_CONTEXT_WINDOW_TOKENS = 200_000;
|
|
17
24
|
const CLAUDE_CONTEXT_WINDOW_ENV = 'MAR_CLAUDE_CONTEXT_WINDOW_TOKENS';
|
|
18
25
|
/**
|
|
@@ -26,61 +33,128 @@ export async function discoverNativeSessions(runner, workspacePath) {
|
|
|
26
33
|
}
|
|
27
34
|
/**
|
|
28
35
|
* Reads every supported transcript header before applying a Workspace filter.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
36
|
+
* A short Runner-level snapshot is shared across Workspaces and refreshes only
|
|
37
|
+
* files whose size or modification time changed. A fixed file-count cutoff
|
|
38
|
+
* would silently hide older sessions from a project with a large global history.
|
|
31
39
|
*/
|
|
32
40
|
export async function discoverAllNativeSessions(runner) {
|
|
33
|
-
const
|
|
34
|
-
|
|
41
|
+
const key = discoveryKey(runner);
|
|
42
|
+
const snapshot = discoverySnapshots.get(key);
|
|
43
|
+
if (snapshot !== undefined && snapshot.expiresAt > Date.now()) {
|
|
44
|
+
snapshot.cacheHits += 1;
|
|
45
|
+
return snapshot.sessions;
|
|
46
|
+
}
|
|
47
|
+
const current = discoveryReads.get(key);
|
|
48
|
+
if (current !== undefined) {
|
|
49
|
+
nodeLog('native.session.discovery.single-flight-joined', { runner });
|
|
35
50
|
return current;
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
}
|
|
52
|
+
const generation = discoveryGenerations.get(key) ?? 0;
|
|
53
|
+
const discovery = performNativeSessionDiscovery(runner, snapshot)
|
|
54
|
+
.then((next) => {
|
|
55
|
+
if ((discoveryGenerations.get(key) ?? 0) === generation)
|
|
56
|
+
discoverySnapshots.set(key, next);
|
|
57
|
+
return next.sessions;
|
|
58
|
+
})
|
|
59
|
+
.finally(() => {
|
|
60
|
+
if (discoveryReads.get(key) === discovery)
|
|
61
|
+
discoveryReads.delete(key);
|
|
62
|
+
});
|
|
63
|
+
discoveryReads.set(key, discovery);
|
|
38
64
|
return discovery;
|
|
39
65
|
}
|
|
40
|
-
async function performNativeSessionDiscovery(runner) {
|
|
66
|
+
async function performNativeSessionDiscovery(runner, previous) {
|
|
41
67
|
const startedAt = performance.now();
|
|
42
68
|
const found = new Map();
|
|
43
69
|
nodeLog('native.session.discovery.started', { runner });
|
|
44
|
-
const files = await listJsonlFiles(transcriptRoot(runner));
|
|
70
|
+
const files = (await listJsonlFiles(transcriptRoot(runner))).filter((path) => runner !== 'claude-code' || !path.split(/[\\/]/u).includes('subagents'));
|
|
45
71
|
nodeLog('native.session.discovery.files-listed', {
|
|
46
72
|
runner,
|
|
47
73
|
files: files.length,
|
|
48
74
|
durationMs: Math.round(performance.now() - startedAt)
|
|
49
75
|
});
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
let completed = 0;
|
|
77
|
+
let parsedCount = 0;
|
|
78
|
+
let reusedCount = 0;
|
|
79
|
+
let metadataFailures = 0;
|
|
80
|
+
let metadataWorkMs = 0;
|
|
81
|
+
let parseWorkMs = 0;
|
|
82
|
+
const discoveredFiles = await mapConcurrent(files, 32, async (path) => {
|
|
83
|
+
let fingerprint;
|
|
84
|
+
const metadataStartedAt = performance.now();
|
|
85
|
+
try {
|
|
86
|
+
const metadata = await stat(path);
|
|
87
|
+
fingerprint = `${metadata.size}:${metadata.mtimeMs}`;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
metadataFailures += 1;
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
metadataWorkMs += performance.now() - metadataStartedAt;
|
|
95
|
+
}
|
|
96
|
+
const cached = previous?.files.get(path);
|
|
97
|
+
let parsed;
|
|
98
|
+
if (cached?.fingerprint === fingerprint) {
|
|
99
|
+
parsed = cached.parsed;
|
|
100
|
+
reusedCount += 1;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const parseStartedAt = performance.now();
|
|
104
|
+
parsed = await readTranscript(path, runner, DISCOVERY_READ_BYTES);
|
|
105
|
+
parseWorkMs += performance.now() - parseStartedAt;
|
|
106
|
+
parsedCount += 1;
|
|
107
|
+
}
|
|
108
|
+
completed += 1;
|
|
109
|
+
if (completed % 250 === 0)
|
|
110
|
+
nodeLog('native.session.discovery.progress', {
|
|
111
|
+
runner,
|
|
112
|
+
completed,
|
|
113
|
+
files: files.length,
|
|
114
|
+
parsedFiles: parsedCount,
|
|
115
|
+
reusedFiles: reusedCount,
|
|
116
|
+
metadataFailures,
|
|
117
|
+
metadataWorkMs: Math.round(metadataWorkMs),
|
|
118
|
+
parseWorkMs: Math.round(parseWorkMs),
|
|
119
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
120
|
+
});
|
|
121
|
+
return { path, fingerprint, parsed };
|
|
122
|
+
});
|
|
123
|
+
const snapshotFiles = new Map();
|
|
124
|
+
for (const file of discoveredFiles) {
|
|
125
|
+
if (file === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
snapshotFiles.set(file.path, { fingerprint: file.fingerprint, parsed: file.parsed });
|
|
128
|
+
const parsed = file.parsed;
|
|
52
129
|
// Discovery only needs a session identity, cwd and an optional early
|
|
53
130
|
// title. Loading entire JSONL files here made opening a Workspace depend
|
|
54
131
|
// on the total history of every other project on the Node.
|
|
55
132
|
if (parsed === undefined)
|
|
56
133
|
continue;
|
|
57
134
|
const cwd = canonical(parsed.cwd);
|
|
58
|
-
setTranscriptIndex(indexKey(runner, cwd, parsed.externalSessionId),
|
|
135
|
+
setTranscriptIndex(indexKey(runner, cwd, parsed.externalSessionId), file.path);
|
|
59
136
|
found.set(`${cwd}\u0000${parsed.externalSessionId}`, parsed);
|
|
60
137
|
}
|
|
61
138
|
nodeLog('native.session.discovery.completed', {
|
|
62
139
|
runner,
|
|
63
140
|
files: files.length,
|
|
64
141
|
sessions: found.size,
|
|
142
|
+
parsedFiles: parsedCount,
|
|
143
|
+
reusedFiles: reusedCount,
|
|
144
|
+
unparsedFiles: [...snapshotFiles.values()].filter((file) => file.parsed === undefined).length,
|
|
145
|
+
metadataFailures,
|
|
146
|
+
metadataWorkMs: Math.round(metadataWorkMs),
|
|
147
|
+
parseWorkMs: Math.round(parseWorkMs),
|
|
148
|
+
cacheMode: previous === undefined ? 'cold' : 'refresh',
|
|
149
|
+
previousCacheHits: previous?.cacheHits ?? 0,
|
|
65
150
|
durationMs: Math.round(performance.now() - startedAt)
|
|
66
151
|
});
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const value = await readTranscript(path, runner, DISCOVERY_READ_BYTES);
|
|
74
|
-
completed += 1;
|
|
75
|
-
if (completed % 250 === 0)
|
|
76
|
-
nodeLog('native.session.discovery.progress', {
|
|
77
|
-
runner,
|
|
78
|
-
completed,
|
|
79
|
-
files: files.length,
|
|
80
|
-
durationMs: Math.round(performance.now() - startedAt)
|
|
81
|
-
});
|
|
82
|
-
return value;
|
|
83
|
-
});
|
|
152
|
+
return {
|
|
153
|
+
expiresAt: Date.now() + DISCOVERY_SNAPSHOT_TTL_MS,
|
|
154
|
+
files: snapshotFiles,
|
|
155
|
+
sessions: [...found.values()],
|
|
156
|
+
cacheHits: 0
|
|
157
|
+
};
|
|
84
158
|
}
|
|
85
159
|
/** Reads one already-discovered external session again to follow appended JSONL records. */
|
|
86
160
|
export async function readNativeSession(runner, workspacePath, externalSessionId) {
|
|
@@ -88,7 +162,7 @@ export async function readNativeSession(runner, workspacePath, externalSessionId
|
|
|
88
162
|
const path = getTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
89
163
|
if (path === undefined)
|
|
90
164
|
return undefined;
|
|
91
|
-
const parsed = await
|
|
165
|
+
const parsed = await readTranscriptDetail(path, runner);
|
|
92
166
|
if (parsed === undefined ||
|
|
93
167
|
parsed.externalSessionId !== externalSessionId ||
|
|
94
168
|
canonical(parsed.cwd) !== wanted) {
|
|
@@ -97,6 +171,70 @@ export async function readNativeSession(runner, workspacePath, externalSessionId
|
|
|
97
171
|
}
|
|
98
172
|
return parsed;
|
|
99
173
|
}
|
|
174
|
+
async function readTranscriptDetail(path, runner) {
|
|
175
|
+
const key = `${runner}\u0000${path}`;
|
|
176
|
+
let fingerprint;
|
|
177
|
+
try {
|
|
178
|
+
const metadata = await stat(path);
|
|
179
|
+
fingerprint = `${metadata.size}:${metadata.mtimeMs}`;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
transcriptDetailCache.delete(key);
|
|
183
|
+
transcriptDetailReads.delete(key);
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
const cached = transcriptDetailCache.get(key);
|
|
187
|
+
if (cached?.fingerprint === fingerprint && cached.expiresAt > Date.now()) {
|
|
188
|
+
cached.expiresAt = Date.now() + TRANSCRIPT_DETAIL_CACHE_TTL_MS;
|
|
189
|
+
transcriptDetailCache.delete(key);
|
|
190
|
+
transcriptDetailCache.set(key, cached);
|
|
191
|
+
return cached.parsed;
|
|
192
|
+
}
|
|
193
|
+
transcriptDetailCache.delete(key);
|
|
194
|
+
const current = transcriptDetailReads.get(key);
|
|
195
|
+
if (current?.fingerprint === fingerprint) {
|
|
196
|
+
nodeLog('native.session.history.single-flight-joined', { runner });
|
|
197
|
+
return current.read;
|
|
198
|
+
}
|
|
199
|
+
const startedAt = performance.now();
|
|
200
|
+
const read = readTranscript(path, runner, TRANSCRIPT_READ_BYTES)
|
|
201
|
+
.then((parsed) => {
|
|
202
|
+
if (transcriptDetailReads.get(key)?.read === read) {
|
|
203
|
+
transcriptDetailCache.set(key, {
|
|
204
|
+
fingerprint,
|
|
205
|
+
parsed,
|
|
206
|
+
expiresAt: Date.now() + TRANSCRIPT_DETAIL_CACHE_TTL_MS
|
|
207
|
+
});
|
|
208
|
+
pruneTranscriptDetailCache();
|
|
209
|
+
}
|
|
210
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
211
|
+
if (durationMs >= 250)
|
|
212
|
+
nodeLog('native.session.history.detail-parse.completed', {
|
|
213
|
+
runner,
|
|
214
|
+
bytes: Number(fingerprint.slice(0, fingerprint.indexOf(':'))),
|
|
215
|
+
durationMs
|
|
216
|
+
});
|
|
217
|
+
return parsed;
|
|
218
|
+
})
|
|
219
|
+
.finally(() => {
|
|
220
|
+
if (transcriptDetailReads.get(key)?.read === read)
|
|
221
|
+
transcriptDetailReads.delete(key);
|
|
222
|
+
});
|
|
223
|
+
transcriptDetailReads.set(key, { fingerprint, read });
|
|
224
|
+
return read;
|
|
225
|
+
}
|
|
226
|
+
function pruneTranscriptDetailCache() {
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
for (const [key, cached] of transcriptDetailCache)
|
|
229
|
+
if (cached.expiresAt <= now)
|
|
230
|
+
transcriptDetailCache.delete(key);
|
|
231
|
+
while (transcriptDetailCache.size > TRANSCRIPT_DETAIL_CACHE_MAX_ENTRIES) {
|
|
232
|
+
const oldest = transcriptDetailCache.keys().next().value;
|
|
233
|
+
if (oldest === undefined)
|
|
234
|
+
return;
|
|
235
|
+
transcriptDetailCache.delete(oldest);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
100
238
|
/**
|
|
101
239
|
* Codex records the most recent prompt token count alongside the model context
|
|
102
240
|
* window in `event_msg/token_count`. Read only the tail of an already
|
|
@@ -232,12 +370,24 @@ export async function removeNativeSession(runner, workspacePath, externalSession
|
|
|
232
370
|
throw error;
|
|
233
371
|
}
|
|
234
372
|
deleteTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
373
|
+
transcriptDetailCache.delete(`${runner}\u0000${path}`);
|
|
374
|
+
transcriptDetailReads.delete(`${runner}\u0000${path}`);
|
|
375
|
+
invalidateNativeDiscovery(runner);
|
|
235
376
|
}
|
|
236
377
|
function transcriptRoot(runner) {
|
|
237
378
|
return runner === 'codex'
|
|
238
379
|
? join(homedir(), '.codex', 'sessions')
|
|
239
380
|
: join(homedir(), '.claude', 'projects');
|
|
240
381
|
}
|
|
382
|
+
function discoveryKey(runner) {
|
|
383
|
+
return `${runner}\u0000${transcriptRoot(runner)}`;
|
|
384
|
+
}
|
|
385
|
+
function invalidateNativeDiscovery(runner) {
|
|
386
|
+
const key = discoveryKey(runner);
|
|
387
|
+
discoveryGenerations.set(key, (discoveryGenerations.get(key) ?? 0) + 1);
|
|
388
|
+
discoverySnapshots.delete(key);
|
|
389
|
+
discoveryReads.delete(key);
|
|
390
|
+
}
|
|
241
391
|
async function readTranscript(path, runner, byteLimit) {
|
|
242
392
|
const window = await readJsonlWindow(path, { headBytes: byteLimit, tailBytes: byteLimit });
|
|
243
393
|
if (window === undefined)
|
|
@@ -245,11 +395,14 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
245
395
|
const { content, truncated } = window;
|
|
246
396
|
let cwd;
|
|
247
397
|
let id;
|
|
398
|
+
let derivedSession = false;
|
|
248
399
|
const items = [];
|
|
249
400
|
const toolIndexes = new Map();
|
|
250
401
|
const pendingToolResults = new Map();
|
|
251
402
|
const metaRecordIds = new Set();
|
|
252
403
|
forEachJsonlRecord(content, (value) => {
|
|
404
|
+
if (derivedTranscriptRecord(runner, value))
|
|
405
|
+
derivedSession = true;
|
|
253
406
|
cwd ??= workspacePath(value);
|
|
254
407
|
id ??= sessionId(value);
|
|
255
408
|
const recordId = typeof value['uuid'] === 'string' ? value['uuid'] : undefined;
|
|
@@ -284,6 +437,8 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
284
437
|
items.push(item);
|
|
285
438
|
}
|
|
286
439
|
});
|
|
440
|
+
if (derivedSession)
|
|
441
|
+
return undefined;
|
|
287
442
|
for (const result of pendingToolResults.values())
|
|
288
443
|
items.push({
|
|
289
444
|
kind: 'tool_call',
|
|
@@ -318,6 +473,20 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
318
473
|
truncated
|
|
319
474
|
};
|
|
320
475
|
}
|
|
476
|
+
function derivedTranscriptRecord(runner, value) {
|
|
477
|
+
if (runner === 'claude-code')
|
|
478
|
+
return value['isSidechain'] === true;
|
|
479
|
+
if (runner !== 'codex' || value['type'] !== 'session_meta')
|
|
480
|
+
return false;
|
|
481
|
+
const payload = isRecord(value['payload']) ? value['payload'] : undefined;
|
|
482
|
+
if (payload === undefined)
|
|
483
|
+
return false;
|
|
484
|
+
if (payload['thread_source'] === 'subagent')
|
|
485
|
+
return true;
|
|
486
|
+
const source = payload['source'];
|
|
487
|
+
return (source === 'subagent' ||
|
|
488
|
+
(isRecord(source) && (source['kind'] === 'subagent' || isRecord(source['subagent']))));
|
|
489
|
+
}
|
|
321
490
|
function workspacePath(record) {
|
|
322
491
|
return (firstString(record, ['cwd', 'working_directory', 'workspacePath']) ??
|
|
323
492
|
nestedString(record, ['payload', 'cwd']) ??
|
|
@@ -36,6 +36,7 @@ export declare class OpenCodeServerClient {
|
|
|
36
36
|
readonly model: string;
|
|
37
37
|
readonly variant?: string;
|
|
38
38
|
readonly agent?: 'plan' | 'build';
|
|
39
|
+
readonly system?: string;
|
|
39
40
|
readonly attachments?: readonly {
|
|
40
41
|
readonly name: string;
|
|
41
42
|
readonly mime: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
|
package/dist/opencode-server.js
CHANGED
|
@@ -169,6 +169,7 @@ export class OpenCodeServerClient {
|
|
|
169
169
|
? {}
|
|
170
170
|
: { variant: input.variant }),
|
|
171
171
|
...(input.agent === undefined ? {} : { agent: input.agent }),
|
|
172
|
+
...(input.system === undefined ? {} : { system: input.system }),
|
|
172
173
|
parts: [
|
|
173
174
|
{ type: 'text', text: input.text },
|
|
174
175
|
...(input.attachments ?? []).map((attachment) => ({
|
|
@@ -50,7 +50,8 @@ export declare abstract class AbstractRunner<TName extends RegisteredRunnerName
|
|
|
50
50
|
readOfficialConversation(session: NodeAgentSession, input: {
|
|
51
51
|
readonly cursor?: string;
|
|
52
52
|
readonly limit?: number;
|
|
53
|
-
}, managedTurn: (nativeTurnId: string) => NodeConversationTurn | undefined): Promise<
|
|
53
|
+
}, managedTurn: (nativeTurnId: string) => NodeConversationTurn | undefined): Promise<RunnerConversationPage | undefined>;
|
|
54
|
+
readConversationHistory(readers: RunnerConversationHistoryReaders): Promise<RunnerConversationHistoryPage | undefined>;
|
|
54
55
|
readExternalActivity(session: NodeAgentSession): Promise<SessionActivityState | undefined>;
|
|
55
56
|
acceptsTruncatedNativeHistory(): boolean;
|
|
56
57
|
nativeImageRoots(session: NodeAgentSession): readonly string[];
|
|
@@ -134,10 +135,18 @@ export interface RunnerProjectionTitleInput {
|
|
|
134
135
|
readonly title?: string;
|
|
135
136
|
readonly titleOrigin?: 'OFFICIAL' | 'NATIVE';
|
|
136
137
|
}
|
|
137
|
-
export interface
|
|
138
|
+
export interface RunnerConversationPage {
|
|
138
139
|
readonly turns: readonly NodeConversationTurn[];
|
|
139
140
|
readonly nextCursor: string | null;
|
|
140
141
|
}
|
|
142
|
+
export type RunnerConversationHistorySource = 'native' | 'official';
|
|
143
|
+
export interface RunnerConversationHistoryReaders {
|
|
144
|
+
readonly native: () => Promise<RunnerConversationPage | undefined>;
|
|
145
|
+
readonly official: () => Promise<RunnerConversationPage | undefined>;
|
|
146
|
+
}
|
|
147
|
+
export interface RunnerConversationHistoryPage extends RunnerConversationPage {
|
|
148
|
+
readonly source: RunnerConversationHistorySource;
|
|
149
|
+
}
|
|
141
150
|
export interface RunnerSessionPresentationContext {
|
|
142
151
|
readonly managedActive: boolean;
|
|
143
152
|
readonly externalActivity: SessionActivityState | undefined;
|
|
@@ -99,6 +99,10 @@ export class AbstractRunner {
|
|
|
99
99
|
void [session, input, managedTurn];
|
|
100
100
|
return undefined;
|
|
101
101
|
}
|
|
102
|
+
async readConversationHistory(readers) {
|
|
103
|
+
const page = await readers.native();
|
|
104
|
+
return page === undefined ? undefined : { ...page, source: 'native' };
|
|
105
|
+
}
|
|
102
106
|
async readExternalActivity(session) {
|
|
103
107
|
void session;
|
|
104
108
|
return undefined;
|
|
@@ -148,6 +148,9 @@ export class ClaudeManagedRunController {
|
|
|
148
148
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
149
149
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
150
150
|
environment: secretEnvironment,
|
|
151
|
+
...(typeof payload.personalInstructions === 'string'
|
|
152
|
+
? { personalInstructions: payload.personalInstructions }
|
|
153
|
+
: {}),
|
|
151
154
|
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, secretEnvironment),
|
|
152
155
|
...(attachments.length === 0
|
|
153
156
|
? {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ClaudeAgentSdkAdapter, type ClaudeChannelRunHandle, type ClaudeQueryInput, type ClaudeRunHandle } from '../claude-agent-sdk.js';
|
|
2
|
-
import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerSessionPresentationContext, type RunnerSessionPresentation } from './abstract-runner.js';
|
|
2
|
+
import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerConversationHistoryPage, type RunnerConversationHistoryReaders, type RunnerSessionPresentationContext, type RunnerSessionPresentation } from './abstract-runner.js';
|
|
3
3
|
import type { NodeCapabilities, RunnerDefaultConfiguration, RunnerProfile } from '@myagentroam/protocol';
|
|
4
4
|
import { ClaudeChannelGate } from '../claude-channel.js';
|
|
5
5
|
import type { ChannelDelivery } from '../claude-channel.js';
|
|
@@ -21,6 +21,7 @@ export declare class ClaudeCodeRunner extends AbstractRunner<'claude-code'> {
|
|
|
21
21
|
resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
|
|
22
22
|
discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
23
23
|
readContextUsage(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeClaudeContextUsage | undefined>;
|
|
24
|
+
readConversationHistory(readers: RunnerConversationHistoryReaders): Promise<RunnerConversationHistoryPage | undefined>;
|
|
24
25
|
forkNative(session: NodeAgentSession, boundary: string | null, title: string): Promise<string | null>;
|
|
25
26
|
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
26
27
|
prepareMessageInput(input: string, collaborationMode: 'default' | 'plan'): string;
|
|
@@ -135,6 +135,10 @@ export class ClaudeCodeRunner extends AbstractRunner {
|
|
|
135
135
|
? Promise.resolve(undefined)
|
|
136
136
|
: readClaudeNativeContextUsage(session.cwd, session.externalSessionId);
|
|
137
137
|
}
|
|
138
|
+
async readConversationHistory(readers) {
|
|
139
|
+
const native = await readers.native();
|
|
140
|
+
return native === undefined ? undefined : { ...native, source: 'native' };
|
|
141
|
+
}
|
|
138
142
|
forkNative(session, boundary, title) {
|
|
139
143
|
if (session.externalSessionId === null)
|
|
140
144
|
throw new Error('SESSION_REWIND_UNAVAILABLE');
|
|
@@ -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;
|
|
@@ -332,6 +354,9 @@ export class CodexManagedRunController {
|
|
|
332
354
|
access: payload.access,
|
|
333
355
|
collaborationMode: payload.collaborationMode,
|
|
334
356
|
serviceTier: payload.serviceTier,
|
|
357
|
+
...(typeof payload.personalInstructions === 'string'
|
|
358
|
+
? { personalInstructions: payload.personalInstructions }
|
|
359
|
+
: {}),
|
|
335
360
|
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, secretEnvironment)
|
|
336
361
|
}, attachments);
|
|
337
362
|
}
|
|
@@ -348,30 +373,51 @@ export class CodexManagedRunController {
|
|
|
348
373
|
...(typeof payload.model === 'string' ? { model: payload.model } : {}),
|
|
349
374
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
350
375
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
376
|
+
...(payload.personalInstructions === undefined
|
|
377
|
+
? {}
|
|
378
|
+
: { developerInstructions: payload.personalInstructions }),
|
|
351
379
|
...(payload.mcpServers === undefined ? {} : { mcpServers: payload.mcpServers })
|
|
352
380
|
};
|
|
353
|
-
const
|
|
354
|
-
?
|
|
355
|
-
:
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
381
|
+
const collaborationMode = payload.collaborationMode === 'plan' || payload.collaborationMode === 'default'
|
|
382
|
+
? payload.collaborationMode
|
|
383
|
+
: undefined;
|
|
384
|
+
const turnConfiguration = {
|
|
385
|
+
...configuration,
|
|
386
|
+
...(collaborationMode === undefined ? {} : { collaborationMode }),
|
|
387
|
+
...(payload.serviceTier === 'fast' ? { serviceTier: 'fast' } : {})
|
|
388
|
+
};
|
|
389
|
+
const materializedAttachments = await this.host.materialize(runId, attachments);
|
|
390
|
+
let threadId;
|
|
391
|
+
let turnResult;
|
|
392
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
393
|
+
try {
|
|
394
|
+
const threadResult = typeof payload.externalSessionId === 'string'
|
|
395
|
+
? await this.runner.resumeThread(payload.externalSessionId, cwd, configuration)
|
|
396
|
+
: await this.runner.startThread(cwd, configuration);
|
|
397
|
+
if (this.host.completeCancelled(runId, cwd)) {
|
|
398
|
+
this.runner.releaseEnvironment(runId);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
threadId = extractId(threadResult, 'thread');
|
|
402
|
+
this.runner.execution.runs.set(runId, { sessionId, threadId, cwd });
|
|
403
|
+
turnResult = await this.runner.startTurn(threadId, input, cwd, turnConfiguration, materializedAttachments);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
this.runner.execution.runs.delete(runId);
|
|
408
|
+
if (attempt === 0 && (await this.runner.recoverMissingRollout(runId, error)))
|
|
409
|
+
continue;
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
359
412
|
}
|
|
360
|
-
|
|
413
|
+
if (threadId === undefined || turnResult === undefined)
|
|
414
|
+
throw new Error('CODEX_RUN_FAILED');
|
|
361
415
|
let publicSessionId = sessionId;
|
|
362
416
|
if (typeof payload.externalSessionId !== 'string') {
|
|
363
417
|
this.host.promoteSession(sessionId, threadId);
|
|
364
418
|
publicSessionId = threadId;
|
|
365
419
|
this.host.send('session.external-id', { sessionId, externalSessionId: threadId });
|
|
366
420
|
}
|
|
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
421
|
const turnId = extractId(turnResult, 'turn');
|
|
376
422
|
this.runner.execution.runs.set(runId, { sessionId: publicSessionId, threadId, turnId, cwd });
|
|
377
423
|
this.runner.execution.managedTurns.set(turnId, { sessionId: publicSessionId, runId });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CodexAppServerClient, type CodexComposerInput, type CodexRunConfiguration, type JsonRpcNotification } from '../codex-app-server.js';
|
|
2
|
-
import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerSessionPresentationContext, type RunnerSessionPresentation, type RunnerProjectionTitleInput } from './abstract-runner.js';
|
|
2
|
+
import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerConversationHistoryPage, type RunnerConversationHistoryReaders, type RunnerSessionPresentationContext, type RunnerSessionPresentation, type RunnerProjectionTitleInput } from './abstract-runner.js';
|
|
3
3
|
import type { NodeCapabilities, RunnerDefaultConfiguration, RunnerProfile } from '@myagentroam/protocol';
|
|
4
4
|
import type { NodeAgentSession, NodeDatabase } from '../database.js';
|
|
5
5
|
import type { NodeWorkspace } from '../database.js';
|
|
@@ -43,6 +43,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
43
43
|
readonly turns: readonly import("../database.js").NodeConversationTurn[];
|
|
44
44
|
readonly nextCursor: string | null;
|
|
45
45
|
} | undefined>;
|
|
46
|
+
readConversationHistory(readers: RunnerConversationHistoryReaders): Promise<RunnerConversationHistoryPage | undefined>;
|
|
46
47
|
readExternalActivity(session: NodeAgentSession): Promise<"UNAVAILABLE" | "MANAGED_ACTIVE" | "EXTERNAL_ACTIVE" | "IDLE" | undefined>;
|
|
47
48
|
acceptsTruncatedNativeHistory(): boolean;
|
|
48
49
|
readManagedRunnerTitle(session: NodeAgentSession, nativeSessionId: string): Promise<string | undefined>;
|
|
@@ -58,6 +59,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
58
59
|
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
59
60
|
onNotification(listener: (notification: JsonRpcNotification) => void): () => void;
|
|
60
61
|
start(): Promise<void>;
|
|
62
|
+
recoverMissingRollout(runId: string, error: unknown): Promise<boolean>;
|
|
61
63
|
prepareEnvironment(environment: Readonly<Record<string, string>>, runId: string): void;
|
|
62
64
|
releaseEnvironment(runId: string): void;
|
|
63
65
|
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';
|
|
@@ -250,6 +250,16 @@ export class CodexRunner extends AbstractRunner {
|
|
|
250
250
|
return undefined;
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
|
+
async readConversationHistory(readers) {
|
|
254
|
+
// Codex JSONL is the low-latency durable record for normal history reads,
|
|
255
|
+
// while thread/read can take seconds on large threads. Keep the official
|
|
256
|
+
// API as a fallback because a transcript can be missing or bounded/truncated.
|
|
257
|
+
const native = await readers.native();
|
|
258
|
+
if (native !== undefined)
|
|
259
|
+
return { ...native, source: 'native' };
|
|
260
|
+
const official = await readers.official();
|
|
261
|
+
return official === undefined ? undefined : { ...official, source: 'official' };
|
|
262
|
+
}
|
|
253
263
|
async readExternalActivity(session) {
|
|
254
264
|
if (session.nativeControl !== 'EXTERNAL' || session.externalSessionId === null)
|
|
255
265
|
return undefined;
|
|
@@ -355,7 +365,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
355
365
|
}
|
|
356
366
|
const sessions = [];
|
|
357
367
|
for (const thread of threads) {
|
|
358
|
-
if (thread.source !== 'cli' && thread.source !== 'vscode')
|
|
368
|
+
if (thread.derived || (thread.source !== 'cli' && thread.source !== 'vscode'))
|
|
359
369
|
continue;
|
|
360
370
|
let cwd;
|
|
361
371
|
try {
|
|
@@ -451,6 +461,17 @@ export class CodexRunner extends AbstractRunner {
|
|
|
451
461
|
start() {
|
|
452
462
|
return this.client.start();
|
|
453
463
|
}
|
|
464
|
+
async recoverMissingRollout(runId, error) {
|
|
465
|
+
if (!isCodexRolloutMissingError(error))
|
|
466
|
+
return false;
|
|
467
|
+
if ([...this.environmentRuns].some((activeRunId) => activeRunId !== runId))
|
|
468
|
+
return false;
|
|
469
|
+
if ([...this.execution.runs.keys()].some((activeRunId) => activeRunId !== runId))
|
|
470
|
+
return false;
|
|
471
|
+
nodeLog('runner.codex.rollout.recovering', { runId });
|
|
472
|
+
await this.client.restart();
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
454
475
|
prepareEnvironment(environment, runId) {
|
|
455
476
|
if (Object.keys(environment).length === 0 && this.environmentFingerprint === undefined) {
|
|
456
477
|
this.environmentRuns.add(runId);
|
|
@@ -128,6 +128,9 @@ export class OpenCodeManagedRunController {
|
|
|
128
128
|
attachments,
|
|
129
129
|
access: typeof payload.access === 'string' ? payload.access : 'default',
|
|
130
130
|
agent: payload.collaborationMode === 'plan' ? 'plan' : 'build',
|
|
131
|
+
...(typeof payload.personalInstructions === 'string'
|
|
132
|
+
? { personalInstructions: payload.personalInstructions }
|
|
133
|
+
: {}),
|
|
131
134
|
mcp
|
|
132
135
|
});
|
|
133
136
|
}
|
|
@@ -251,6 +254,7 @@ export class OpenCodeManagedRunController {
|
|
|
251
254
|
model: input.model,
|
|
252
255
|
variant: input.effort,
|
|
253
256
|
agent: input.agent,
|
|
257
|
+
...(input.personalInstructions === undefined ? {} : { system: input.personalInstructions }),
|
|
254
258
|
attachments: input.attachments
|
|
255
259
|
});
|
|
256
260
|
this.promptAdmitted.add(input.runId);
|