@myagentroam/node 0.9.4 → 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 +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-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.js +6 -0
- package/dist/runner/codex-runner.d.ts +2 -1
- package/dist/runner/codex-runner.js +11 -1
- 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 +5 -4
- package/dist/service/conversation-history-service.js +110 -52
- 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 +8 -1
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +65 -42
- package/package.json +2 -2
|
@@ -23,6 +23,7 @@ export interface ClaudeQueryInput {
|
|
|
23
23
|
readonly onChannelReply?: (content: string) => void;
|
|
24
24
|
readonly environment?: Readonly<Record<string, string>>;
|
|
25
25
|
readonly mcpServers?: NonNullable<Options['mcpServers']>;
|
|
26
|
+
readonly personalInstructions?: string;
|
|
26
27
|
}
|
|
27
28
|
/** Image blocks accepted by the Claude Agent SDK message input. */
|
|
28
29
|
export interface ClaudeImageAttachment {
|
package/dist/claude-agent-sdk.js
CHANGED
|
@@ -64,6 +64,15 @@ export class ClaudeAgentSdkAdapter {
|
|
|
64
64
|
// This is the SDK's documented API-side public summary mode. We never
|
|
65
65
|
// enable or forward raw thinking deltas to the Workbench.
|
|
66
66
|
settings: { showThinkingSummaries: true },
|
|
67
|
+
...(input.personalInstructions === undefined
|
|
68
|
+
? {}
|
|
69
|
+
: {
|
|
70
|
+
systemPrompt: {
|
|
71
|
+
type: 'preset',
|
|
72
|
+
preset: 'claude_code',
|
|
73
|
+
append: input.personalInstructions
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
67
76
|
abortController,
|
|
68
77
|
canUseTool: input.onPermission,
|
|
69
78
|
...(input.mcpServers !== undefined || channelEnabled
|
|
@@ -50,6 +50,7 @@ export interface CodexRunConfiguration {
|
|
|
50
50
|
readonly serviceTier?: 'fast';
|
|
51
51
|
readonly collaborationMode?: 'default' | 'plan';
|
|
52
52
|
readonly mcpServers?: unknown;
|
|
53
|
+
readonly developerInstructions?: string;
|
|
53
54
|
}
|
|
54
55
|
interface CodexRpcErrorPayload {
|
|
55
56
|
readonly code?: unknown;
|
package/dist/codex-app-server.js
CHANGED
|
@@ -358,6 +358,9 @@ export class CodexAppServerClient {
|
|
|
358
358
|
function codexThreadConfiguration(cwd, configuration) {
|
|
359
359
|
return {
|
|
360
360
|
...modelOption(configuration.model),
|
|
361
|
+
...(configuration.developerInstructions === undefined
|
|
362
|
+
? {}
|
|
363
|
+
: { developerInstructions: configuration.developerInstructions }),
|
|
361
364
|
...(configuration.mcpServers === undefined
|
|
362
365
|
? {}
|
|
363
366
|
: { config: { mcp_servers: configuration.mcpServers } }),
|
|
@@ -376,8 +379,7 @@ function codexTurnConfiguration(cwd, configuration) {
|
|
|
376
379
|
mode: configuration.collaborationMode,
|
|
377
380
|
settings: {
|
|
378
381
|
model: configuration.model ?? 'gpt-5.6-sol',
|
|
379
|
-
reasoningEffort: configuration.effort ?? null
|
|
380
|
-
developerInstructions: null
|
|
382
|
+
reasoningEffort: configuration.effort ?? null
|
|
381
383
|
}
|
|
382
384
|
}
|
|
383
385
|
}),
|
package/dist/connector.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { NodeConnectorOptions } from './connector/node-connector-options.js
|
|
|
3
3
|
export { nodeConnectEndpoint, validatedCapabilities } from './runner/codex/conversation-parser.js';
|
|
4
4
|
export declare function nextReconnectDelay(attempt: number): number;
|
|
5
5
|
export declare class NodeConnector {
|
|
6
|
+
private readonly nodeGeneration;
|
|
6
7
|
private readonly controlChannel;
|
|
7
8
|
private readonly controlMessages;
|
|
8
9
|
private readonly nodeRequests;
|
package/dist/connector.js
CHANGED
|
@@ -86,6 +86,7 @@ function upgradeMetadata(metadata, status) {
|
|
|
86
86
|
return next;
|
|
87
87
|
}
|
|
88
88
|
export class NodeConnector {
|
|
89
|
+
nodeGeneration = crypto.randomUUID().replace(/-/g, '');
|
|
89
90
|
controlChannel;
|
|
90
91
|
controlMessages;
|
|
91
92
|
nodeRequests;
|
|
@@ -153,7 +154,7 @@ export class NodeConnector {
|
|
|
153
154
|
discover: (workspaceId) => this.discoverWorkspaceSessions(workspaceId),
|
|
154
155
|
listPage: (workspaceId) => this.executeNodeOperation('session.list', { workspaceId, limit: 20 }),
|
|
155
156
|
active: (workspaceId) => this.workspaceWatchService.topicActive(workspaceId, 'sessions'),
|
|
156
|
-
emitRevision: (workspaceId
|
|
157
|
+
emitRevision: (workspaceId) => void this.workspaceWorkbenchService?.refreshTopic(workspaceId, 'sessions'),
|
|
157
158
|
onPending: (workspaceId, waitMs) => nodeLog('native.session.discovery.pending', {
|
|
158
159
|
nodeId: this.config?.nodeId,
|
|
159
160
|
workspaceId,
|
|
@@ -192,8 +193,9 @@ export class NodeConnector {
|
|
|
192
193
|
});
|
|
193
194
|
nativeSessionWatchService = new NativeSessionWatchService({
|
|
194
195
|
resolve: async (sessionId) => this.runtime.getAgentSession(sessionId) ??
|
|
195
|
-
this.nativeSessionProjectionService.
|
|
196
|
+
this.nativeSessionProjectionService.resolveCurrent(sessionId),
|
|
196
197
|
managedActive: (session) => hasManagedActiveRun(this.runtime, session),
|
|
198
|
+
suspended: (sessionId) => this.externalSessionResumeService.transitioning(sessionId),
|
|
197
199
|
refreshActivity: (session) => this.conversationHistoryService.refreshExternalActivity(session),
|
|
198
200
|
present: (session) => this.sessionPresentationService.present(session),
|
|
199
201
|
readPage: (session, limit) => this.conversationHistoryService.read(session, { limit }),
|
|
@@ -203,7 +205,10 @@ export class NodeConnector {
|
|
|
203
205
|
emitPage: (session, page) => this.emitWorkbenchEvent('conversation', {
|
|
204
206
|
sessionId: session.id,
|
|
205
207
|
snapshot: this.conversationSegmentService.projectInitial(session, page, 10)
|
|
206
|
-
})
|
|
208
|
+
}),
|
|
209
|
+
nodeId: () => this.config?.nodeId ?? 'runtime-node',
|
|
210
|
+
nodeGeneration: () => this.nodeGeneration,
|
|
211
|
+
emitWatchEvent: (payload) => this.send('watch.event', payload)
|
|
207
212
|
});
|
|
208
213
|
constructor(options = {}) {
|
|
209
214
|
this.config = options.config;
|
|
@@ -396,10 +401,8 @@ export class NodeConnector {
|
|
|
396
401
|
readImage: async (runId, imageIndex) => this.runAttachmentService.read(runId, imageIndex) ??
|
|
397
402
|
(await this.runAttachmentService.readNative(runId, imageIndex)),
|
|
398
403
|
respondUserInput: (runId, requestId, answers) => this.runnerInteractionService.respondUserInput(runId, requestId, answers),
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
cleanupAttachments: (runId) => this.runAttachmentService.cleanup(runId),
|
|
402
|
-
emitQueue: (workspaceId, sessionId) => this.runEventBridge.emitQueue(workspaceId, sessionId),
|
|
404
|
+
cancelQueued: (runId) => this.workspaceQueueWorkbenchService.cancelQueued(runId),
|
|
405
|
+
pauseQueue: (sessionId) => this.workspaceQueueWorkbenchService.pauseForInterrupt(sessionId),
|
|
403
406
|
emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
404
407
|
control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
|
|
405
408
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId)
|
|
@@ -447,7 +450,7 @@ export class NodeConnector {
|
|
|
447
450
|
migrateRunnerState: (previousId, nextId) => this.runEventBridge.migrateRunnerSessionIdentity(previousId, nextId),
|
|
448
451
|
emitSession: (session) => this.emitWorkbenchEvent('session', { session }),
|
|
449
452
|
emitRun: (run) => this.emitWorkbenchEvent('run', { run }),
|
|
450
|
-
emitQueue: (
|
|
453
|
+
emitQueue: (sessionId) => this.workspaceQueueWorkbenchService.publish(sessionId),
|
|
451
454
|
emitTurn: (turn) => this.emitConversationTurn(turn)
|
|
452
455
|
});
|
|
453
456
|
this.sessionLifecycleService = new SessionLifecycleService({
|
|
@@ -501,7 +504,6 @@ export class NodeConnector {
|
|
|
501
504
|
emitSession: (session) => this.emitWorkbenchEvent('session', {
|
|
502
505
|
session: this.sessionPresentationService.present(session)
|
|
503
506
|
}),
|
|
504
|
-
emitQueue: (workspaceId, sessionId) => this.runEventBridge.emitQueue(workspaceId, sessionId),
|
|
505
507
|
channelToken: (sessionId) => {
|
|
506
508
|
const session = this.runtime.getAgentSession(sessionId);
|
|
507
509
|
return session === undefined
|
|
@@ -650,7 +652,7 @@ export class NodeConnector {
|
|
|
650
652
|
watches: this.workspaceWatchService,
|
|
651
653
|
sessions: this.workspaceSessionService,
|
|
652
654
|
changes: this.workspaceChangeService,
|
|
653
|
-
emit: (workspaceId, topic
|
|
655
|
+
emit: (workspaceId, topic) => void this.workspaceWorkbenchService?.refreshTopic(workspaceId, topic),
|
|
654
656
|
send: (type, payload, replyTo) => this.send(type, payload, replyTo)
|
|
655
657
|
});
|
|
656
658
|
this.runEventBridge = new NodeRunEventBridge({
|
|
@@ -711,7 +713,10 @@ export class NodeConnector {
|
|
|
711
713
|
uploads: this.workspaceUploadService,
|
|
712
714
|
changes: this.workspaceChangeService,
|
|
713
715
|
listSessions: (workspaceId) => this.executeNodeOperation('session.list', { workspaceId, limit: 20 }),
|
|
714
|
-
invalidate: (workspaceId) => this.workspaceCoordinator.invalidate(workspaceId)
|
|
716
|
+
invalidate: (workspaceId) => this.workspaceCoordinator.invalidate(workspaceId),
|
|
717
|
+
nodeId: () => this.config?.nodeId ?? 'runtime-node',
|
|
718
|
+
nodeGeneration: () => this.nodeGeneration,
|
|
719
|
+
emitWatchEvent: (payload) => this.send('watch.event', payload)
|
|
715
720
|
});
|
|
716
721
|
this.operationRouter.registerAll(this.runnerService.operations());
|
|
717
722
|
this.operationRouter.registerAll(this.terminalService.operations());
|
|
@@ -78,8 +78,9 @@ export interface NativeClaudeContextUsage {
|
|
|
78
78
|
export declare function discoverNativeSessions(runner: RunnerName, workspacePath: string): Promise<readonly NativeSessionHistory[]>;
|
|
79
79
|
/**
|
|
80
80
|
* Reads every supported transcript header before applying a Workspace filter.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
81
|
+
* A short Runner-level snapshot is shared across Workspaces and refreshes only
|
|
82
|
+
* files whose size or modification time changed. A fixed file-count cutoff
|
|
83
|
+
* would silently hide older sessions from a project with a large global history.
|
|
83
84
|
*/
|
|
84
85
|
export declare function discoverAllNativeSessions(runner: RunnerName): Promise<readonly NativeSessionHistory[]>;
|
|
85
86
|
/** Reads one already-discovered external session again to follow appended JSONL records. */
|
|
@@ -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');
|
|
@@ -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>;
|