@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.
Files changed (61) hide show
  1. package/dist/claude-agent-sdk.d.ts +1 -0
  2. package/dist/claude-agent-sdk.js +9 -0
  3. package/dist/codex-app-server.d.ts +1 -0
  4. package/dist/codex-app-server.js +4 -2
  5. package/dist/connector.d.ts +1 -0
  6. package/dist/connector.js +16 -11
  7. package/dist/native-jsonl-reader.d.ts +21 -0
  8. package/dist/native-jsonl-reader.js +89 -0
  9. package/dist/native-session-history.d.ts +20 -3
  10. package/dist/native-session-history.js +245 -38
  11. package/dist/opencode-server.d.ts +1 -0
  12. package/dist/opencode-server.js +1 -0
  13. package/dist/runner/abstract-runner.d.ts +11 -2
  14. package/dist/runner/abstract-runner.js +4 -0
  15. package/dist/runner/claude/managed-run-controller.js +3 -0
  16. package/dist/runner/claude-code-runner.d.ts +2 -1
  17. package/dist/runner/claude-code-runner.js +4 -0
  18. package/dist/runner/codex/managed-run-controller.js +6 -0
  19. package/dist/runner/codex-runner.d.ts +2 -1
  20. package/dist/runner/codex-runner.js +13 -2
  21. package/dist/runner/opencode/managed-run-controller.js +4 -0
  22. package/dist/runner/opencode-runner.d.ts +10 -3
  23. package/dist/runner/opencode-runner.js +87 -14
  24. package/dist/runner/runner-registry.js +16 -1
  25. package/dist/runtime-command-detector.js +1 -6
  26. package/dist/runtime-state.js +1 -0
  27. package/dist/service/conversation-history-service.d.ts +6 -5
  28. package/dist/service/conversation-history-service.js +151 -55
  29. package/dist/service/external-session-resume-service.d.ts +2 -0
  30. package/dist/service/external-session-resume-service.js +45 -34
  31. package/dist/service/native-session-projection-service.d.ts +1 -0
  32. package/dist/service/native-session-projection-service.js +4 -0
  33. package/dist/service/native-session-watch-service.d.ts +18 -0
  34. package/dist/service/native-session-watch-service.js +232 -34
  35. package/dist/service/run-workbench-service.d.ts +2 -4
  36. package/dist/service/run-workbench-service.js +2 -7
  37. package/dist/service/session-command-service.js +1 -1
  38. package/dist/service/session-identity-service.d.ts +1 -1
  39. package/dist/service/session-identity-service.js +1 -1
  40. package/dist/service/session-lifecycle-service.js +3 -0
  41. package/dist/service/session-message-service.d.ts +0 -1
  42. package/dist/service/session-message-service.js +15 -23
  43. package/dist/service/skill-directory-service.d.ts +1 -0
  44. package/dist/service/skill-directory-service.js +40 -6
  45. package/dist/service/workspace-domain-state.d.ts +3 -0
  46. package/dist/service/workspace-domain-state.js +1 -0
  47. package/dist/service/workspace-file-service.d.ts +1 -0
  48. package/dist/service/workspace-file-service.js +14 -1
  49. package/dist/service/workspace-queue-workbench-service.d.ts +17 -0
  50. package/dist/service/workspace-queue-workbench-service.js +66 -6
  51. package/dist/service/workspace-watch-service.d.ts +7 -2
  52. package/dist/service/workspace-watch-service.js +32 -6
  53. package/dist/service/workspace-workbench-service.d.ts +14 -0
  54. package/dist/service/workspace-workbench-service.js +215 -16
  55. package/dist/util/personal-instructions.d.ts +2 -0
  56. package/dist/util/personal-instructions.js +31 -0
  57. package/dist/util/runner-native-session-parsers.d.ts +1 -0
  58. package/dist/util/runner-native-session-parsers.js +76 -12
  59. package/dist/workspace.d.ts +14 -8
  60. package/dist/workspace.js +65 -42
  61. 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 TRANSCRIPT_READ_BYTES = 4 * 1024 * 1024;
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
- * The caller owns in-process caching; a fixed file-count cutoff would silently
30
- * hide older sessions from a project with a large global history.
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 current = discoveryReads.get(runner);
34
- if (current !== undefined)
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
- const discovery = performNativeSessionDiscovery(runner).finally(() => discoveryReads.delete(runner));
37
- discoveryReads.set(runner, discovery);
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
- const parsedFiles = await readDiscoveryHeaders(files, runner);
51
- for (const [index, parsed] of parsedFiles.entries()) {
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), files[index]);
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 [...found.values()];
68
- }
69
- async function readDiscoveryHeaders(files, runner) {
70
- let completed = 0;
71
- const startedAt = performance.now();
72
- return mapConcurrent(files, 32, async (path) => {
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 parsed = await readTranscript(path, runner, TRANSCRIPT_READ_BYTES);
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, TRANSCRIPT_READ_BYTES);
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
- async function readTranscript(path, runner, byteLimit) {
242
- const window = await readJsonlWindow(path, { headBytes: byteLimit, tailBytes: byteLimit });
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';
@@ -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<RunnerOfficialConversationPage | undefined>;
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 RunnerOfficialConversationPage {
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 false;
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: {