@myagentroam/node 0.9.4 → 0.9.6
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 +1 -0
- package/dist/codex-app-server.js +4 -2
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +16 -11
- package/dist/native-jsonl-reader.d.ts +21 -0
- package/dist/native-jsonl-reader.js +89 -0
- package/dist/native-session-history.d.ts +20 -3
- package/dist/native-session-history.js +245 -38
- 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.js +6 -0
- package/dist/runner/codex-runner.d.ts +2 -1
- package/dist/runner/codex-runner.js +13 -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.js +1 -0
- package/dist/service/conversation-history-service.d.ts +6 -5
- package/dist/service/conversation-history-service.js +151 -55
- 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 +18 -0
- package/dist/service/native-session-watch-service.js +232 -34
- 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 +3 -0
- package/dist/service/session-message-service.d.ts +0 -1
- package/dist/service/session-message-service.js +15 -23
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- 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 +17 -0
- package/dist/service/workspace-queue-workbench-service.js +66 -6
- 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 +1 -0
- package/dist/util/runner-native-session-parsers.js +76 -12
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +65 -42
- package/package.json +2 -2
|
@@ -1,18 +1,29 @@
|
|
|
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
|
-
import { forEachJsonlRecord, listJsonlFiles, mapConcurrent, readJsonlWindow } from './native-jsonl-reader.js';
|
|
5
|
+
import { forEachJsonlRecord, listJsonlFiles, mapConcurrent, readJsonlBackwardWindow, readJsonlFileAnchor, readJsonlWindow } from './native-jsonl-reader.js';
|
|
6
6
|
import { nodeLog } from './operational.js';
|
|
7
7
|
import { stripClaudePlanTag } from './runner-command-engine.js';
|
|
8
8
|
const DISCOVERY_READ_BYTES = 32 * 1024;
|
|
9
|
-
const
|
|
9
|
+
const TRANSCRIPT_HEADER_READ_BYTES = 32 * 1024;
|
|
10
|
+
const TRANSCRIPT_READ_MAX_BYTES = 16 * 1024 * 1024;
|
|
11
|
+
const TRANSCRIPT_MIN_LINES = 256;
|
|
12
|
+
const TRANSCRIPT_MAX_LINES = 4096;
|
|
13
|
+
const TRANSCRIPT_LINES_PER_TURN = 32;
|
|
10
14
|
const CONTEXT_USAGE_READ_BYTES = 256 * 1024;
|
|
11
15
|
const CLAUDE_CONTEXT_USAGE_READ_BYTES = [CONTEXT_USAGE_READ_BYTES, 1024 * 1024, 4 * 1024 * 1024];
|
|
12
16
|
const TRANSCRIPT_INDEX_TTL_MS = 60 * 60_000;
|
|
17
|
+
const DISCOVERY_SNAPSHOT_TTL_MS = 15_000;
|
|
18
|
+
const TRANSCRIPT_DETAIL_CACHE_TTL_MS = 60_000;
|
|
19
|
+
const TRANSCRIPT_DETAIL_CACHE_MAX_ENTRIES = 16;
|
|
13
20
|
const transcriptIndex = new Map();
|
|
14
21
|
const transcriptIndexTimers = new Map();
|
|
22
|
+
const transcriptDetailCache = new Map();
|
|
23
|
+
const transcriptDetailReads = new Map();
|
|
15
24
|
const discoveryReads = new Map();
|
|
25
|
+
const discoverySnapshots = new Map();
|
|
26
|
+
const discoveryGenerations = new Map();
|
|
16
27
|
const DEFAULT_CLAUDE_CONTEXT_WINDOW_TOKENS = 200_000;
|
|
17
28
|
const CLAUDE_CONTEXT_WINDOW_ENV = 'MAR_CLAUDE_CONTEXT_WINDOW_TOKENS';
|
|
18
29
|
/**
|
|
@@ -26,76 +37,224 @@ export async function discoverNativeSessions(runner, workspacePath) {
|
|
|
26
37
|
}
|
|
27
38
|
/**
|
|
28
39
|
* Reads every supported transcript header before applying a Workspace filter.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
40
|
+
* A short Runner-level snapshot is shared across Workspaces and refreshes only
|
|
41
|
+
* files whose size or modification time changed. A fixed file-count cutoff
|
|
42
|
+
* would silently hide older sessions from a project with a large global history.
|
|
31
43
|
*/
|
|
32
44
|
export async function discoverAllNativeSessions(runner) {
|
|
33
|
-
const
|
|
34
|
-
|
|
45
|
+
const key = discoveryKey(runner);
|
|
46
|
+
const snapshot = discoverySnapshots.get(key);
|
|
47
|
+
if (snapshot !== undefined && snapshot.expiresAt > Date.now()) {
|
|
48
|
+
snapshot.cacheHits += 1;
|
|
49
|
+
return snapshot.sessions;
|
|
50
|
+
}
|
|
51
|
+
const current = discoveryReads.get(key);
|
|
52
|
+
if (current !== undefined) {
|
|
53
|
+
nodeLog('native.session.discovery.single-flight-joined', { runner });
|
|
35
54
|
return current;
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
}
|
|
56
|
+
const generation = discoveryGenerations.get(key) ?? 0;
|
|
57
|
+
const discovery = performNativeSessionDiscovery(runner, snapshot)
|
|
58
|
+
.then((next) => {
|
|
59
|
+
if ((discoveryGenerations.get(key) ?? 0) === generation)
|
|
60
|
+
discoverySnapshots.set(key, next);
|
|
61
|
+
return next.sessions;
|
|
62
|
+
})
|
|
63
|
+
.finally(() => {
|
|
64
|
+
if (discoveryReads.get(key) === discovery)
|
|
65
|
+
discoveryReads.delete(key);
|
|
66
|
+
});
|
|
67
|
+
discoveryReads.set(key, discovery);
|
|
38
68
|
return discovery;
|
|
39
69
|
}
|
|
40
|
-
async function performNativeSessionDiscovery(runner) {
|
|
70
|
+
async function performNativeSessionDiscovery(runner, previous) {
|
|
41
71
|
const startedAt = performance.now();
|
|
42
72
|
const found = new Map();
|
|
43
73
|
nodeLog('native.session.discovery.started', { runner });
|
|
44
|
-
const files = await listJsonlFiles(transcriptRoot(runner));
|
|
74
|
+
const files = (await listJsonlFiles(transcriptRoot(runner))).filter((path) => runner !== 'claude-code' || !path.split(/[\\/]/u).includes('subagents'));
|
|
45
75
|
nodeLog('native.session.discovery.files-listed', {
|
|
46
76
|
runner,
|
|
47
77
|
files: files.length,
|
|
48
78
|
durationMs: Math.round(performance.now() - startedAt)
|
|
49
79
|
});
|
|
50
|
-
|
|
51
|
-
|
|
80
|
+
let completed = 0;
|
|
81
|
+
let parsedCount = 0;
|
|
82
|
+
let reusedCount = 0;
|
|
83
|
+
let metadataFailures = 0;
|
|
84
|
+
let metadataWorkMs = 0;
|
|
85
|
+
let parseWorkMs = 0;
|
|
86
|
+
const discoveredFiles = await mapConcurrent(files, 32, async (path) => {
|
|
87
|
+
let fingerprint;
|
|
88
|
+
const metadataStartedAt = performance.now();
|
|
89
|
+
try {
|
|
90
|
+
const metadata = await stat(path);
|
|
91
|
+
fingerprint = `${metadata.size}:${metadata.mtimeMs}`;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
metadataFailures += 1;
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
metadataWorkMs += performance.now() - metadataStartedAt;
|
|
99
|
+
}
|
|
100
|
+
const cached = previous?.files.get(path);
|
|
101
|
+
let parsed;
|
|
102
|
+
if (cached?.fingerprint === fingerprint) {
|
|
103
|
+
parsed = cached.parsed;
|
|
104
|
+
reusedCount += 1;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const parseStartedAt = performance.now();
|
|
108
|
+
parsed = await readTranscript(path, runner, DISCOVERY_READ_BYTES);
|
|
109
|
+
parseWorkMs += performance.now() - parseStartedAt;
|
|
110
|
+
parsedCount += 1;
|
|
111
|
+
}
|
|
112
|
+
completed += 1;
|
|
113
|
+
if (completed % 250 === 0)
|
|
114
|
+
nodeLog('native.session.discovery.progress', {
|
|
115
|
+
runner,
|
|
116
|
+
completed,
|
|
117
|
+
files: files.length,
|
|
118
|
+
parsedFiles: parsedCount,
|
|
119
|
+
reusedFiles: reusedCount,
|
|
120
|
+
metadataFailures,
|
|
121
|
+
metadataWorkMs: Math.round(metadataWorkMs),
|
|
122
|
+
parseWorkMs: Math.round(parseWorkMs),
|
|
123
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
124
|
+
});
|
|
125
|
+
return { path, fingerprint, parsed };
|
|
126
|
+
});
|
|
127
|
+
const snapshotFiles = new Map();
|
|
128
|
+
for (const file of discoveredFiles) {
|
|
129
|
+
if (file === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
snapshotFiles.set(file.path, { fingerprint: file.fingerprint, parsed: file.parsed });
|
|
132
|
+
const parsed = file.parsed;
|
|
52
133
|
// Discovery only needs a session identity, cwd and an optional early
|
|
53
134
|
// title. Loading entire JSONL files here made opening a Workspace depend
|
|
54
135
|
// on the total history of every other project on the Node.
|
|
55
136
|
if (parsed === undefined)
|
|
56
137
|
continue;
|
|
57
138
|
const cwd = canonical(parsed.cwd);
|
|
58
|
-
setTranscriptIndex(indexKey(runner, cwd, parsed.externalSessionId),
|
|
139
|
+
setTranscriptIndex(indexKey(runner, cwd, parsed.externalSessionId), file.path);
|
|
59
140
|
found.set(`${cwd}\u0000${parsed.externalSessionId}`, parsed);
|
|
60
141
|
}
|
|
61
142
|
nodeLog('native.session.discovery.completed', {
|
|
62
143
|
runner,
|
|
63
144
|
files: files.length,
|
|
64
145
|
sessions: found.size,
|
|
146
|
+
parsedFiles: parsedCount,
|
|
147
|
+
reusedFiles: reusedCount,
|
|
148
|
+
unparsedFiles: [...snapshotFiles.values()].filter((file) => file.parsed === undefined).length,
|
|
149
|
+
metadataFailures,
|
|
150
|
+
metadataWorkMs: Math.round(metadataWorkMs),
|
|
151
|
+
parseWorkMs: Math.round(parseWorkMs),
|
|
152
|
+
cacheMode: previous === undefined ? 'cold' : 'refresh',
|
|
153
|
+
previousCacheHits: previous?.cacheHits ?? 0,
|
|
65
154
|
durationMs: Math.round(performance.now() - startedAt)
|
|
66
155
|
});
|
|
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
|
-
});
|
|
156
|
+
return {
|
|
157
|
+
expiresAt: Date.now() + DISCOVERY_SNAPSHOT_TTL_MS,
|
|
158
|
+
files: snapshotFiles,
|
|
159
|
+
sessions: [...found.values()],
|
|
160
|
+
cacheHits: 0
|
|
161
|
+
};
|
|
84
162
|
}
|
|
85
163
|
/** Reads one already-discovered external session again to follow appended JSONL records. */
|
|
86
|
-
export async function readNativeSession(runner, workspacePath, externalSessionId) {
|
|
164
|
+
export async function readNativeSession(runner, workspacePath, externalSessionId, page, limit = 30) {
|
|
87
165
|
const wanted = canonical(workspacePath);
|
|
88
166
|
const path = getTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
89
167
|
if (path === undefined)
|
|
90
168
|
return undefined;
|
|
91
|
-
const
|
|
169
|
+
const detail = await readTranscriptDetail(path, runner, page, page?.readLimit ?? limit);
|
|
170
|
+
const parsed = detail?.history;
|
|
92
171
|
if (parsed === undefined ||
|
|
93
172
|
parsed.externalSessionId !== externalSessionId ||
|
|
94
173
|
canonical(parsed.cwd) !== wanted) {
|
|
95
174
|
deleteTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
96
175
|
return undefined;
|
|
97
176
|
}
|
|
98
|
-
return parsed;
|
|
177
|
+
return detail?.cursorAccepted === false ? { ...parsed, cursorReset: true } : parsed;
|
|
178
|
+
}
|
|
179
|
+
async function readTranscriptDetail(path, runner, page, limit) {
|
|
180
|
+
let fingerprint;
|
|
181
|
+
try {
|
|
182
|
+
const metadata = await stat(path);
|
|
183
|
+
fingerprint = `${metadata.size}:${metadata.mtimeMs}`;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
const metadataSize = Number(fingerprint.slice(0, fingerprint.indexOf(':')));
|
|
189
|
+
const validPage = page !== undefined &&
|
|
190
|
+
page.snapshotEnd <= metadataSize &&
|
|
191
|
+
(await readJsonlFileAnchor(path, page.anchorBytes)) === page.fileAnchor
|
|
192
|
+
? page
|
|
193
|
+
: undefined;
|
|
194
|
+
const targetLines = Math.min(TRANSCRIPT_MAX_LINES, Math.max(TRANSCRIPT_MIN_LINES, Math.max(1, limit) * TRANSCRIPT_LINES_PER_TURN));
|
|
195
|
+
const pageKey = `${validPage === undefined
|
|
196
|
+
? 'latest'
|
|
197
|
+
: `${validPage.fileAnchor}:${validPage.snapshotEnd}:${validPage.nextEnd}`}:${targetLines}`;
|
|
198
|
+
const key = `${runner}\u0000${path}\u0000${pageKey}`;
|
|
199
|
+
const cacheFingerprint = validPage === undefined ? fingerprint : `${validPage.fileAnchor}:${validPage.snapshotEnd}`;
|
|
200
|
+
const cached = transcriptDetailCache.get(key);
|
|
201
|
+
if (cached?.fingerprint === cacheFingerprint && cached.expiresAt > Date.now()) {
|
|
202
|
+
cached.expiresAt = Date.now() + TRANSCRIPT_DETAIL_CACHE_TTL_MS;
|
|
203
|
+
transcriptDetailCache.delete(key);
|
|
204
|
+
transcriptDetailCache.set(key, cached);
|
|
205
|
+
return {
|
|
206
|
+
history: cached.parsed,
|
|
207
|
+
cursorAccepted: page === undefined || validPage !== undefined
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
transcriptDetailCache.delete(key);
|
|
211
|
+
const current = transcriptDetailReads.get(key);
|
|
212
|
+
if (current?.fingerprint === cacheFingerprint) {
|
|
213
|
+
nodeLog('native.session.history.single-flight-joined', { runner });
|
|
214
|
+
return {
|
|
215
|
+
history: await current.read,
|
|
216
|
+
cursorAccepted: page === undefined || validPage !== undefined
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const startedAt = performance.now();
|
|
220
|
+
const read = readTranscript(path, runner, TRANSCRIPT_READ_MAX_BYTES, validPage, targetLines)
|
|
221
|
+
.then((parsed) => parsed === undefined ? undefined : { ...parsed, windowReadLimit: page?.readLimit ?? limit })
|
|
222
|
+
.then((parsed) => {
|
|
223
|
+
if (transcriptDetailReads.get(key)?.read === read) {
|
|
224
|
+
transcriptDetailCache.set(key, {
|
|
225
|
+
fingerprint: cacheFingerprint,
|
|
226
|
+
parsed,
|
|
227
|
+
expiresAt: Date.now() + TRANSCRIPT_DETAIL_CACHE_TTL_MS
|
|
228
|
+
});
|
|
229
|
+
pruneTranscriptDetailCache();
|
|
230
|
+
}
|
|
231
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
232
|
+
if (durationMs >= 250)
|
|
233
|
+
nodeLog('native.session.history.detail-parse.completed', {
|
|
234
|
+
runner,
|
|
235
|
+
bytes: Number(fingerprint.slice(0, fingerprint.indexOf(':'))),
|
|
236
|
+
durationMs
|
|
237
|
+
});
|
|
238
|
+
return parsed;
|
|
239
|
+
})
|
|
240
|
+
.finally(() => {
|
|
241
|
+
if (transcriptDetailReads.get(key)?.read === read)
|
|
242
|
+
transcriptDetailReads.delete(key);
|
|
243
|
+
});
|
|
244
|
+
transcriptDetailReads.set(key, { fingerprint: cacheFingerprint, read });
|
|
245
|
+
return { history: await read, cursorAccepted: page === undefined || validPage !== undefined };
|
|
246
|
+
}
|
|
247
|
+
function pruneTranscriptDetailCache() {
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
for (const [key, cached] of transcriptDetailCache)
|
|
250
|
+
if (cached.expiresAt <= now)
|
|
251
|
+
transcriptDetailCache.delete(key);
|
|
252
|
+
while (transcriptDetailCache.size > TRANSCRIPT_DETAIL_CACHE_MAX_ENTRIES) {
|
|
253
|
+
const oldest = transcriptDetailCache.keys().next().value;
|
|
254
|
+
if (oldest === undefined)
|
|
255
|
+
return;
|
|
256
|
+
transcriptDetailCache.delete(oldest);
|
|
257
|
+
}
|
|
99
258
|
}
|
|
100
259
|
/**
|
|
101
260
|
* Codex records the most recent prompt token count alongside the model context
|
|
@@ -216,7 +375,7 @@ export async function removeNativeSession(runner, workspacePath, externalSession
|
|
|
216
375
|
}
|
|
217
376
|
if (path === undefined)
|
|
218
377
|
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
219
|
-
const parsed = await readTranscript(path, runner,
|
|
378
|
+
const parsed = await readTranscript(path, runner, DISCOVERY_READ_BYTES);
|
|
220
379
|
if (parsed === undefined ||
|
|
221
380
|
parsed.externalSessionId !== externalSessionId ||
|
|
222
381
|
canonical(parsed.cwd) !== wanted) {
|
|
@@ -232,26 +391,53 @@ export async function removeNativeSession(runner, workspacePath, externalSession
|
|
|
232
391
|
throw error;
|
|
233
392
|
}
|
|
234
393
|
deleteTranscriptIndex(indexKey(runner, wanted, externalSessionId));
|
|
394
|
+
const detailKeyPrefix = `${runner}\u0000${path}\u0000`;
|
|
395
|
+
for (const key of transcriptDetailCache.keys())
|
|
396
|
+
if (key.startsWith(detailKeyPrefix))
|
|
397
|
+
transcriptDetailCache.delete(key);
|
|
398
|
+
for (const key of transcriptDetailReads.keys())
|
|
399
|
+
if (key.startsWith(detailKeyPrefix))
|
|
400
|
+
transcriptDetailReads.delete(key);
|
|
401
|
+
invalidateNativeDiscovery(runner);
|
|
235
402
|
}
|
|
236
403
|
function transcriptRoot(runner) {
|
|
237
404
|
return runner === 'codex'
|
|
238
405
|
? join(homedir(), '.codex', 'sessions')
|
|
239
406
|
: join(homedir(), '.claude', 'projects');
|
|
240
407
|
}
|
|
241
|
-
|
|
242
|
-
|
|
408
|
+
function discoveryKey(runner) {
|
|
409
|
+
return `${runner}\u0000${transcriptRoot(runner)}`;
|
|
410
|
+
}
|
|
411
|
+
function invalidateNativeDiscovery(runner) {
|
|
412
|
+
const key = discoveryKey(runner);
|
|
413
|
+
discoveryGenerations.set(key, (discoveryGenerations.get(key) ?? 0) + 1);
|
|
414
|
+
discoverySnapshots.delete(key);
|
|
415
|
+
discoveryReads.delete(key);
|
|
416
|
+
}
|
|
417
|
+
async function readTranscript(path, runner, byteLimit, page, targetLines = Number.MAX_SAFE_INTEGER) {
|
|
418
|
+
const window = await readJsonlBackwardWindow(path, {
|
|
419
|
+
headBytes: Math.min(byteLimit, TRANSCRIPT_HEADER_READ_BYTES),
|
|
420
|
+
targetLines,
|
|
421
|
+
maxBytes: byteLimit,
|
|
422
|
+
...(page === undefined ? {} : { page })
|
|
423
|
+
});
|
|
243
424
|
if (window === undefined)
|
|
244
425
|
return undefined;
|
|
245
426
|
const { content, truncated } = window;
|
|
246
427
|
let cwd;
|
|
247
428
|
let id;
|
|
429
|
+
let derivedSession = false;
|
|
248
430
|
const items = [];
|
|
249
431
|
const toolIndexes = new Map();
|
|
250
432
|
const pendingToolResults = new Map();
|
|
251
433
|
const metaRecordIds = new Set();
|
|
252
434
|
forEachJsonlRecord(content, (value) => {
|
|
435
|
+
if (derivedTranscriptRecord(runner, value))
|
|
436
|
+
derivedSession = true;
|
|
253
437
|
cwd ??= workspacePath(value);
|
|
254
438
|
id ??= sessionId(value);
|
|
439
|
+
});
|
|
440
|
+
forEachJsonlRecord(truncated ? window.pageContent : content, (value) => {
|
|
255
441
|
const recordId = typeof value['uuid'] === 'string' ? value['uuid'] : undefined;
|
|
256
442
|
const parentId = typeof value['parentUuid'] === 'string' ? value['parentUuid'] : undefined;
|
|
257
443
|
if (value['isMeta'] === true && recordId !== undefined)
|
|
@@ -284,6 +470,8 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
284
470
|
items.push(item);
|
|
285
471
|
}
|
|
286
472
|
});
|
|
473
|
+
if (derivedSession)
|
|
474
|
+
return undefined;
|
|
287
475
|
for (const result of pendingToolResults.values())
|
|
288
476
|
items.push({
|
|
289
477
|
kind: 'tool_call',
|
|
@@ -315,9 +503,28 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
315
503
|
digest: createHash('sha256').update(content).digest('hex'),
|
|
316
504
|
items,
|
|
317
505
|
lastActivityAt: Math.max(0, ...items.map((item) => item.createdAt ?? 0)),
|
|
318
|
-
truncated
|
|
506
|
+
truncated,
|
|
507
|
+
windowStart: window.start,
|
|
508
|
+
windowEnd: window.end,
|
|
509
|
+
snapshotEnd: window.snapshotEnd,
|
|
510
|
+
fileAnchor: window.fileAnchor,
|
|
511
|
+
anchorBytes: window.anchorBytes
|
|
319
512
|
};
|
|
320
513
|
}
|
|
514
|
+
function derivedTranscriptRecord(runner, value) {
|
|
515
|
+
if (runner === 'claude-code')
|
|
516
|
+
return value['isSidechain'] === true;
|
|
517
|
+
if (runner !== 'codex' || value['type'] !== 'session_meta')
|
|
518
|
+
return false;
|
|
519
|
+
const payload = isRecord(value['payload']) ? value['payload'] : undefined;
|
|
520
|
+
if (payload === undefined)
|
|
521
|
+
return false;
|
|
522
|
+
if (payload['thread_source'] === 'subagent')
|
|
523
|
+
return true;
|
|
524
|
+
const source = payload['source'];
|
|
525
|
+
return (source === 'subagent' ||
|
|
526
|
+
(isRecord(source) && (source['kind'] === 'subagent' || isRecord(source['subagent']))));
|
|
527
|
+
}
|
|
321
528
|
function workspacePath(record) {
|
|
322
529
|
return (firstString(record, ['cwd', 'working_directory', 'workspacePath']) ??
|
|
323
530
|
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');
|
|
@@ -354,6 +354,9 @@ export class CodexManagedRunController {
|
|
|
354
354
|
access: payload.access,
|
|
355
355
|
collaborationMode: payload.collaborationMode,
|
|
356
356
|
serviceTier: payload.serviceTier,
|
|
357
|
+
...(typeof payload.personalInstructions === 'string'
|
|
358
|
+
? { personalInstructions: payload.personalInstructions }
|
|
359
|
+
: {}),
|
|
357
360
|
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, secretEnvironment)
|
|
358
361
|
}, attachments);
|
|
359
362
|
}
|
|
@@ -370,6 +373,9 @@ export class CodexManagedRunController {
|
|
|
370
373
|
...(typeof payload.model === 'string' ? { model: payload.model } : {}),
|
|
371
374
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
372
375
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
376
|
+
...(payload.personalInstructions === undefined
|
|
377
|
+
? {}
|
|
378
|
+
: { developerInstructions: payload.personalInstructions }),
|
|
373
379
|
...(payload.mcpServers === undefined ? {} : { mcpServers: payload.mcpServers })
|
|
374
380
|
};
|
|
375
381
|
const collaborationMode = payload.collaborationMode === 'plan' || payload.collaborationMode === 'default'
|
|
@@ -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>;
|
|
@@ -250,6 +250,17 @@ 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. A bounded tail is a
|
|
256
|
+
// valid recent-history page; use the official API only when JSONL is absent
|
|
257
|
+
// or cannot produce any visible history.
|
|
258
|
+
const native = await readers.native();
|
|
259
|
+
if (native !== undefined)
|
|
260
|
+
return { ...native, source: 'native' };
|
|
261
|
+
const official = await readers.official();
|
|
262
|
+
return official === undefined ? undefined : { ...official, source: 'official' };
|
|
263
|
+
}
|
|
253
264
|
async readExternalActivity(session) {
|
|
254
265
|
if (session.nativeControl !== 'EXTERNAL' || session.externalSessionId === null)
|
|
255
266
|
return undefined;
|
|
@@ -262,7 +273,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
262
273
|
}
|
|
263
274
|
}
|
|
264
275
|
acceptsTruncatedNativeHistory() {
|
|
265
|
-
return
|
|
276
|
+
return true;
|
|
266
277
|
}
|
|
267
278
|
async readManagedRunnerTitle(session, nativeSessionId) {
|
|
268
279
|
void session;
|
|
@@ -355,7 +366,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
355
366
|
}
|
|
356
367
|
const sessions = [];
|
|
357
368
|
for (const thread of threads) {
|
|
358
|
-
if (thread.source !== 'cli' && thread.source !== 'vscode')
|
|
369
|
+
if (thread.derived || (thread.source !== 'cli' && thread.source !== 'vscode'))
|
|
359
370
|
continue;
|
|
360
371
|
let cwd;
|
|
361
372
|
try {
|
|
@@ -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);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NodeCapabilities, RunnerDefaultConfiguration, RunnerProfile } from '@myagentroam/protocol';
|
|
2
2
|
import type { NodeAgentSession, NodeDatabase, NodeWorkspace } from '../database.js';
|
|
3
3
|
import { OpenCodeServerClient } from '../opencode-server.js';
|
|
4
|
-
import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerSessionPresentation, type RunnerSessionPresentationContext } from './abstract-runner.js';
|
|
4
|
+
import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerConversationHistoryPage, type RunnerConversationHistoryReaders, type RunnerSessionPresentation, type RunnerSessionPresentationContext } from './abstract-runner.js';
|
|
5
5
|
export interface OpenCodeManagedExecution {
|
|
6
6
|
readonly runId: string;
|
|
7
7
|
readonly sessionId: string;
|
|
@@ -20,6 +20,8 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
|
|
|
20
20
|
private readonly modelsByWorkspace;
|
|
21
21
|
private readonly contextLimitsByWorkspace;
|
|
22
22
|
private readonly profileTimersByWorkspace;
|
|
23
|
+
private readonly profileCache;
|
|
24
|
+
private readonly profileRefreshes;
|
|
23
25
|
readonly execution: Map<string, OpenCodeManagedExecution>;
|
|
24
26
|
readonly managedTurns: Map<string, string>;
|
|
25
27
|
private readonly environmentClients;
|
|
@@ -27,10 +29,14 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
|
|
|
27
29
|
constructor(client?: OpenCodeServerClient, clientFactory?: () => OpenCodeServerClient);
|
|
28
30
|
profile(capabilities: NodeCapabilities): RunnerProfile;
|
|
29
31
|
refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile>;
|
|
32
|
+
private fetchProfile;
|
|
33
|
+
private profileCacheKey;
|
|
34
|
+
private environmentFingerprint;
|
|
30
35
|
available(capabilities: NodeCapabilities): boolean;
|
|
31
36
|
protected profileForValidation(): RunnerProfile;
|
|
32
|
-
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace): boolean;
|
|
33
|
-
defaultConfiguration(workspace?: NodeWorkspace): RunnerDefaultConfiguration;
|
|
37
|
+
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): boolean;
|
|
38
|
+
defaultConfiguration(workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): RunnerDefaultConfiguration;
|
|
39
|
+
private cachedModels;
|
|
34
40
|
mcpConfiguration(raw: unknown, secrets: Readonly<Record<string, string>>): unknown;
|
|
35
41
|
discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
36
42
|
resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
|
|
@@ -42,6 +48,7 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
|
|
|
42
48
|
readonly turns: readonly import("../database.js").NodeConversationTurn[];
|
|
43
49
|
readonly nextCursor: string | null;
|
|
44
50
|
} | undefined>;
|
|
51
|
+
readConversationHistory(readers: RunnerConversationHistoryReaders): Promise<RunnerConversationHistoryPage | undefined>;
|
|
45
52
|
managedRunForNativeTurn(nativeTurnId: string): string | undefined;
|
|
46
53
|
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
47
54
|
renameNative(session: NodeAgentSession, input: {
|