@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
|
@@ -250,6 +250,16 @@ export class CodexRunner extends AbstractRunner {
|
|
|
250
250
|
return undefined;
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
|
+
async readConversationHistory(readers) {
|
|
254
|
+
// Codex JSONL is the low-latency durable record for normal history reads,
|
|
255
|
+
// while thread/read can take seconds on large threads. Keep the official
|
|
256
|
+
// API as a fallback because a transcript can be missing or bounded/truncated.
|
|
257
|
+
const native = await readers.native();
|
|
258
|
+
if (native !== undefined)
|
|
259
|
+
return { ...native, source: 'native' };
|
|
260
|
+
const official = await readers.official();
|
|
261
|
+
return official === undefined ? undefined : { ...official, source: 'official' };
|
|
262
|
+
}
|
|
253
263
|
async readExternalActivity(session) {
|
|
254
264
|
if (session.nativeControl !== 'EXTERNAL' || session.externalSessionId === null)
|
|
255
265
|
return undefined;
|
|
@@ -355,7 +365,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
355
365
|
}
|
|
356
366
|
const sessions = [];
|
|
357
367
|
for (const thread of threads) {
|
|
358
|
-
if (thread.source !== 'cli' && thread.source !== 'vscode')
|
|
368
|
+
if (thread.derived || (thread.source !== 'cli' && thread.source !== 'vscode'))
|
|
359
369
|
continue;
|
|
360
370
|
let cwd;
|
|
361
371
|
try {
|
|
@@ -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: {
|
|
@@ -5,6 +5,7 @@ import { AbstractRunner } from './abstract-runner.js';
|
|
|
5
5
|
import { isWithinWorkspace } from './codex/conversation-parser.js';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { openCodeConversationPage } from './opencode/conversation-parser.js';
|
|
8
|
+
import { nodeLog } from '../operational.js';
|
|
8
9
|
const PROFILE_ENTRY_TTL_MS = 30 * 60_000;
|
|
9
10
|
export class OpenCodeRunner extends AbstractRunner {
|
|
10
11
|
client;
|
|
@@ -14,6 +15,8 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
14
15
|
modelsByWorkspace = new Map();
|
|
15
16
|
contextLimitsByWorkspace = new Map();
|
|
16
17
|
profileTimersByWorkspace = new Map();
|
|
18
|
+
profileCache = new Map();
|
|
19
|
+
profileRefreshes = new Map();
|
|
17
20
|
execution = new Map();
|
|
18
21
|
managedTurns = new Map();
|
|
19
22
|
environmentClients = new Map();
|
|
@@ -35,25 +38,79 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
35
38
|
if (capabilities.openCode?.available !== true || workspace === undefined) {
|
|
36
39
|
return this.profile(capabilities);
|
|
37
40
|
}
|
|
41
|
+
const cacheKey = this.profileCacheKey(workspace.path, environment);
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
for (const [key, entry] of this.profileCache)
|
|
44
|
+
if (entry.expiresAt <= now)
|
|
45
|
+
this.profileCache.delete(key);
|
|
46
|
+
const cached = this.profileCache.get(cacheKey);
|
|
47
|
+
if (cached !== undefined) {
|
|
48
|
+
nodeLog('runner.opencode.profile.cache-hit', {
|
|
49
|
+
workspaceId: workspace.id,
|
|
50
|
+
models: cached.models.length,
|
|
51
|
+
remainingTtlMs: cached.expiresAt - now
|
|
52
|
+
});
|
|
53
|
+
this.modelsByWorkspace.set(workspace.path, cached.models);
|
|
54
|
+
this.contextLimitsByWorkspace.set(workspace.path, cached.contextLimits);
|
|
55
|
+
this.scheduleProfileExpiry(workspace.path);
|
|
56
|
+
return { ...this.profile(capabilities), models: cached.models };
|
|
57
|
+
}
|
|
58
|
+
const current = this.profileRefreshes.get(cacheKey);
|
|
59
|
+
if (current !== undefined) {
|
|
60
|
+
nodeLog('runner.opencode.profile.single-flight-joined', { workspaceId: workspace.id });
|
|
61
|
+
return current;
|
|
62
|
+
}
|
|
63
|
+
const refresh = this.fetchProfile(capabilities, workspace, environment, cacheKey).finally(() => {
|
|
64
|
+
if (this.profileRefreshes.get(cacheKey) === refresh)
|
|
65
|
+
this.profileRefreshes.delete(cacheKey);
|
|
66
|
+
});
|
|
67
|
+
this.profileRefreshes.set(cacheKey, refresh);
|
|
68
|
+
return refresh;
|
|
69
|
+
}
|
|
70
|
+
async fetchProfile(capabilities, workspace, environment, cacheKey) {
|
|
38
71
|
const client = this.managedClient(environment);
|
|
72
|
+
const startedAt = performance.now();
|
|
39
73
|
try {
|
|
40
74
|
const catalog = await client.providers(workspace.path);
|
|
41
75
|
const models = openCodeProfileModels(catalog);
|
|
76
|
+
const contextLimits = openCodeContextLimits(catalog);
|
|
42
77
|
this.modelsByWorkspace.set(workspace.path, models);
|
|
43
|
-
this.contextLimitsByWorkspace.set(workspace.path,
|
|
78
|
+
this.contextLimitsByWorkspace.set(workspace.path, contextLimits);
|
|
79
|
+
this.profileCache.set(cacheKey, {
|
|
80
|
+
expiresAt: Date.now() + PROFILE_ENTRY_TTL_MS,
|
|
81
|
+
workspacePath: workspace.path,
|
|
82
|
+
environmentFingerprint: this.environmentFingerprint(environment),
|
|
83
|
+
models,
|
|
84
|
+
contextLimits
|
|
85
|
+
});
|
|
44
86
|
this.scheduleProfileExpiry(workspace.path);
|
|
87
|
+
nodeLog('runner.opencode.profile.providers.completed', {
|
|
88
|
+
workspaceId: workspace.id,
|
|
89
|
+
models: models.length,
|
|
90
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
91
|
+
});
|
|
45
92
|
return { ...this.profile(capabilities), models };
|
|
46
93
|
}
|
|
47
94
|
catch {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
95
|
+
nodeLog('runner.opencode.profile.providers.failed', {
|
|
96
|
+
workspaceId: workspace.id,
|
|
97
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
98
|
+
});
|
|
51
99
|
}
|
|
52
100
|
finally {
|
|
53
101
|
this.releaseManagedClient(client);
|
|
54
102
|
}
|
|
55
103
|
return this.profile(capabilities);
|
|
56
104
|
}
|
|
105
|
+
profileCacheKey(workspacePath, environment) {
|
|
106
|
+
return createHash('sha256')
|
|
107
|
+
.update(JSON.stringify({ workspacePath, environment: this.environmentFingerprint(environment) }))
|
|
108
|
+
.digest('hex');
|
|
109
|
+
}
|
|
110
|
+
environmentFingerprint(environment) {
|
|
111
|
+
const entries = Object.entries(environment).sort(([left], [right]) => left.localeCompare(right));
|
|
112
|
+
return createHash('sha256').update(JSON.stringify(entries)).digest('hex');
|
|
113
|
+
}
|
|
57
114
|
available(capabilities) {
|
|
58
115
|
return capabilities.openCode?.available === true;
|
|
59
116
|
}
|
|
@@ -68,22 +125,18 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
68
125
|
openCode: { available: true }
|
|
69
126
|
});
|
|
70
127
|
}
|
|
71
|
-
supportsConfiguration(configuration, workspace) {
|
|
128
|
+
supportsConfiguration(configuration, workspace, environment = {}) {
|
|
72
129
|
if (configuration.runner !== this.name)
|
|
73
130
|
return false;
|
|
74
|
-
const models = workspace
|
|
75
|
-
? [...this.modelsByWorkspace.values()].flat()
|
|
76
|
-
: (this.modelsByWorkspace.get(workspace.path) ?? []);
|
|
131
|
+
const models = this.cachedModels(workspace, environment);
|
|
77
132
|
const model = models.find((candidate) => candidate.id === configuration.model);
|
|
78
133
|
return (model !== undefined &&
|
|
79
134
|
(model.supportedEfforts.length === 0 ||
|
|
80
135
|
model.supportedEfforts.includes(configuration.effort)) &&
|
|
81
136
|
this.profileForValidation().accessOptions.some((candidate) => candidate.id === configuration.access));
|
|
82
137
|
}
|
|
83
|
-
defaultConfiguration(workspace) {
|
|
84
|
-
const model = workspace
|
|
85
|
-
? this.modelsByWorkspace.values().next().value?.[0]
|
|
86
|
-
: this.modelsByWorkspace.get(workspace.path)?.[0];
|
|
138
|
+
defaultConfiguration(workspace, environment = {}) {
|
|
139
|
+
const model = this.cachedModels(workspace, environment)[0];
|
|
87
140
|
return {
|
|
88
141
|
runner: this.name,
|
|
89
142
|
model: model?.id ?? '',
|
|
@@ -91,6 +144,15 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
91
144
|
access: 'default'
|
|
92
145
|
};
|
|
93
146
|
}
|
|
147
|
+
cachedModels(workspace, environment) {
|
|
148
|
+
const fingerprint = this.environmentFingerprint(environment);
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
return [...this.profileCache.values()]
|
|
151
|
+
.filter((entry) => entry.expiresAt > now &&
|
|
152
|
+
entry.environmentFingerprint === fingerprint &&
|
|
153
|
+
(workspace === undefined || entry.workspacePath === workspace.path))
|
|
154
|
+
.flatMap((entry) => entry.models);
|
|
155
|
+
}
|
|
94
156
|
mcpConfiguration(raw, secrets) {
|
|
95
157
|
const servers = super.mcpConfiguration(raw, secrets);
|
|
96
158
|
return Object.fromEntries(servers.map((server) => [
|
|
@@ -121,7 +183,9 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
121
183
|
}
|
|
122
184
|
for (const value of sessions) {
|
|
123
185
|
const session = openCodeSession(value);
|
|
124
|
-
if (session === undefined ||
|
|
186
|
+
if (session === undefined ||
|
|
187
|
+
session.parentId !== undefined ||
|
|
188
|
+
!isWithinWorkspace(session.directory, target.path))
|
|
125
189
|
continue;
|
|
126
190
|
result.push(context.createProjection({
|
|
127
191
|
workspace: target,
|
|
@@ -177,6 +241,10 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
177
241
|
return undefined;
|
|
178
242
|
}
|
|
179
243
|
}
|
|
244
|
+
async readConversationHistory(readers) {
|
|
245
|
+
const official = await readers.official();
|
|
246
|
+
return official === undefined ? undefined : { ...official, source: 'official' };
|
|
247
|
+
}
|
|
180
248
|
managedRunForNativeTurn(nativeTurnId) {
|
|
181
249
|
return this.managedTurns.get(nativeTurnId);
|
|
182
250
|
}
|
|
@@ -286,6 +354,8 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
286
354
|
for (const timer of this.profileTimersByWorkspace.values())
|
|
287
355
|
clearTimeout(timer);
|
|
288
356
|
this.profileTimersByWorkspace.clear();
|
|
357
|
+
this.profileCache.clear();
|
|
358
|
+
this.profileRefreshes.clear();
|
|
289
359
|
this.modelsByWorkspace.clear();
|
|
290
360
|
this.contextLimitsByWorkspace.clear();
|
|
291
361
|
const stopping = [
|
|
@@ -370,6 +440,9 @@ function openCodeSession(value) {
|
|
|
370
440
|
id: record.id,
|
|
371
441
|
directory: record.directory,
|
|
372
442
|
title: record.title,
|
|
373
|
-
updatedAt: typeof time?.updated === 'number' ? time.updated : 0
|
|
443
|
+
updatedAt: typeof time?.updated === 'number' ? time.updated : 0,
|
|
444
|
+
...(typeof record.parentID === 'string' && record.parentID.length > 0
|
|
445
|
+
? { parentId: record.parentID }
|
|
446
|
+
: {})
|
|
374
447
|
};
|
|
375
448
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nodeLog } from '../operational.js';
|
|
1
2
|
export class RunnerRegistry {
|
|
2
3
|
runners = new Map();
|
|
3
4
|
constructor(runners = []) {
|
|
@@ -35,7 +36,21 @@ export class RunnerRegistry {
|
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
async refreshProfiles(capabilities, workspace, environment = {}) {
|
|
38
|
-
const profiles = await Promise.all(this.advertised().map((runner) =>
|
|
39
|
+
const profiles = await Promise.all(this.advertised().map(async (runner) => {
|
|
40
|
+
const startedAt = performance.now();
|
|
41
|
+
try {
|
|
42
|
+
return await runner.refreshProfile(capabilities, workspace, environment);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
46
|
+
if (durationMs >= 100)
|
|
47
|
+
nodeLog('runner.profile.refresh.completed', {
|
|
48
|
+
runner: runner.name,
|
|
49
|
+
workspaceId: workspace?.id,
|
|
50
|
+
durationMs
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
39
54
|
return profiles.flatMap((profile) => (profile === undefined ? [] : [profile]));
|
|
40
55
|
}
|
|
41
56
|
supportsConfiguration(configuration, workspace, environment) {
|
|
@@ -50,18 +50,13 @@ export function normalizeVersion(id, value) {
|
|
|
50
50
|
}
|
|
51
51
|
async function executeVersion(command, args) {
|
|
52
52
|
const executable = process.platform === 'win32' ? process.env['ComSpec'] || 'cmd.exe' : command;
|
|
53
|
-
const commandArgs = process.platform === 'win32'
|
|
54
|
-
? ['/d', '/s', '/c', [command, ...args].map(quoteWindowsArgument).join(' ')]
|
|
55
|
-
: [...args];
|
|
53
|
+
const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', [command, ...args].join(' ')] : [...args];
|
|
56
54
|
return execFileAsync(executable, commandArgs, {
|
|
57
55
|
timeout: 5_000,
|
|
58
56
|
maxBuffer: 64 * 1024,
|
|
59
57
|
windowsHide: true
|
|
60
58
|
});
|
|
61
59
|
}
|
|
62
|
-
function quoteWindowsArgument(value) {
|
|
63
|
-
return `"${value.replaceAll('"', '""')}"`;
|
|
64
|
-
}
|
|
65
60
|
async function resolveCommandPath(command) {
|
|
66
61
|
try {
|
|
67
62
|
const resolver = process.platform === 'win32' ? 'where.exe' : 'which';
|
package/dist/runtime-state.js
CHANGED
|
@@ -20,22 +20,23 @@ interface ConversationHistoryServiceOptions {
|
|
|
20
20
|
}
|
|
21
21
|
export declare class ConversationHistoryService {
|
|
22
22
|
private readonly options;
|
|
23
|
-
private readonly
|
|
23
|
+
private readonly activeHistoryReads;
|
|
24
24
|
constructor(options: ConversationHistoryServiceOptions);
|
|
25
25
|
read(session: NodeAgentSession, input: {
|
|
26
26
|
readonly cursor?: string;
|
|
27
27
|
readonly limit?: number;
|
|
28
28
|
}): Promise<ConversationHistoryPage>;
|
|
29
|
+
private readRunnerHistory;
|
|
29
30
|
private readManagedActive;
|
|
30
|
-
private
|
|
31
|
-
private
|
|
31
|
+
private prefetchActiveHistory;
|
|
32
|
+
private takeActiveHistoryRead;
|
|
32
33
|
refreshExternalActivity(session: NodeAgentSession): Promise<void>;
|
|
33
34
|
readNative(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeSessionHistory | undefined>;
|
|
34
35
|
private attachNativeImages;
|
|
35
36
|
readOfficial(session: NodeAgentSession, input: {
|
|
36
37
|
readonly cursor?: string;
|
|
37
38
|
readonly limit?: number;
|
|
38
|
-
}): Promise<import("../runner/abstract-runner.js").
|
|
39
|
+
}): Promise<import("../runner/abstract-runner.js").RunnerConversationPage | undefined>;
|
|
39
40
|
private managedRunnerTurn;
|
|
40
41
|
private page;
|
|
41
42
|
}
|
|
@@ -2,6 +2,7 @@ import { discoverNativeSessions, readNativeSession } from '../native-session-his
|
|
|
2
2
|
import { coalesceImageGenerationItems } from '../runner/codex/conversation-parser.js';
|
|
3
3
|
import { nativeConversationPage } from '../util/runner-native-session-parsers.js';
|
|
4
4
|
import { isPlainRecord } from '../util/node-operation-parsers.js';
|
|
5
|
+
import { nodeLog } from '../operational.js';
|
|
5
6
|
export function hasManagedActiveRun(runtime, session) {
|
|
6
7
|
return (session.nativeControl === 'MAR_MANAGED' &&
|
|
7
8
|
runtime
|
|
@@ -47,7 +48,7 @@ function isConversationHistorySource(value) {
|
|
|
47
48
|
}
|
|
48
49
|
export class ConversationHistoryService {
|
|
49
50
|
options;
|
|
50
|
-
|
|
51
|
+
activeHistoryReads = new Map();
|
|
51
52
|
constructor(options) {
|
|
52
53
|
this.options = options;
|
|
53
54
|
}
|
|
@@ -57,7 +58,14 @@ export class ConversationHistoryService {
|
|
|
57
58
|
const activeCursor = decodeActiveHistoryCursor(input.cursor, session.id);
|
|
58
59
|
if (activeCursor !== undefined || this.options.hasManagedActiveRun(session))
|
|
59
60
|
return this.readManagedActive(session, input, activeCursor, snapshotSequence, readAt);
|
|
60
|
-
|
|
61
|
+
const selected = await this.readRunnerHistory(session, input);
|
|
62
|
+
if (selected.page?.source === 'native')
|
|
63
|
+
return this.page(selected.page.nextCursor, selected.page.turns, {
|
|
64
|
+
snapshotSequence,
|
|
65
|
+
source: 'native',
|
|
66
|
+
readAt
|
|
67
|
+
});
|
|
68
|
+
let official = selected.page?.source === 'official' ? selected.page : undefined;
|
|
61
69
|
if (official !== undefined) {
|
|
62
70
|
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
63
71
|
const runtimeTurns = this.options.runtime.getAgentSession(session.id) === undefined
|
|
@@ -75,7 +83,7 @@ export class ConversationHistoryService {
|
|
|
75
83
|
const reserved = Math.min(missingLocalTurns.length, Math.max(0, limit - 1));
|
|
76
84
|
const narrowed = await this.readOfficial(session, { ...input, limit: limit - reserved });
|
|
77
85
|
if (narrowed !== undefined)
|
|
78
|
-
official = narrowed;
|
|
86
|
+
official = { ...narrowed, source: 'official' };
|
|
79
87
|
}
|
|
80
88
|
const officialTurns = await this.attachNativeImages(session, official.turns);
|
|
81
89
|
const officialRunIds = new Set(officialTurns.flatMap((turn) => (turn.runId === null ? [] : [turn.runId])));
|
|
@@ -124,40 +132,63 @@ export class ConversationHistoryService {
|
|
|
124
132
|
readAt
|
|
125
133
|
});
|
|
126
134
|
}
|
|
127
|
-
if (session.externalSessionId !== null &&
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
source: 'native',
|
|
140
|
-
readAt
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
const runtimePage = this.options.runtime.listConversationTurns({
|
|
144
|
-
sessionId: session.id,
|
|
145
|
-
...input
|
|
146
|
-
});
|
|
147
|
-
if (runtimePage.turns.length === 0)
|
|
148
|
-
throw new Error('NATIVE_TRANSCRIPT_INCOMPLETE');
|
|
149
|
-
return this.page(runtimePage.nextCursor, runtimePage.turns, {
|
|
150
|
-
snapshotSequence,
|
|
151
|
-
source: 'runtime',
|
|
152
|
-
readAt
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
if (session.nativeControl === 'EXTERNAL')
|
|
156
|
-
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
135
|
+
if (session.externalSessionId !== null && selected.incompleteNative) {
|
|
136
|
+
const runtimePage = this.options.runtime.listConversationTurns({
|
|
137
|
+
sessionId: session.id,
|
|
138
|
+
...input
|
|
139
|
+
});
|
|
140
|
+
if (runtimePage.turns.length === 0)
|
|
141
|
+
throw new Error('NATIVE_TRANSCRIPT_INCOMPLETE');
|
|
142
|
+
return this.page(runtimePage.nextCursor, runtimePage.turns, {
|
|
143
|
+
snapshotSequence,
|
|
144
|
+
source: 'runtime',
|
|
145
|
+
readAt
|
|
146
|
+
});
|
|
157
147
|
}
|
|
148
|
+
if (session.externalSessionId !== null && session.nativeControl === 'EXTERNAL')
|
|
149
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
158
150
|
const page = this.options.runtime.listConversationTurns({ sessionId: session.id, ...input });
|
|
159
151
|
return this.page(page.nextCursor, page.turns, { snapshotSequence, source: 'runtime', readAt });
|
|
160
152
|
}
|
|
153
|
+
async readRunnerHistory(session, input) {
|
|
154
|
+
const runner = this.options.runners.require(session.runner);
|
|
155
|
+
let incompleteNative = false;
|
|
156
|
+
const page = await runner.readConversationHistory({
|
|
157
|
+
native: async () => {
|
|
158
|
+
const startedAt = performance.now();
|
|
159
|
+
let history;
|
|
160
|
+
try {
|
|
161
|
+
history = await this.readNative(session);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
168
|
+
if (durationMs >= 250)
|
|
169
|
+
nodeLog('native.session.history.native-read.completed', {
|
|
170
|
+
runner: session.runner,
|
|
171
|
+
sessionId: session.id,
|
|
172
|
+
durationMs
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (history === undefined)
|
|
176
|
+
return undefined;
|
|
177
|
+
if (history.truncated === true && !runner.acceptsTruncatedNativeHistory()) {
|
|
178
|
+
incompleteNative = true;
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
const runtimeTurns = this.options.runtime.getAgentSession(session.id) === undefined
|
|
182
|
+
? []
|
|
183
|
+
: this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 })
|
|
184
|
+
.turns;
|
|
185
|
+
const native = nativeConversationPage(session, history, input, runtimeTurns, (images) => this.options.attachments.registerNative(session, images));
|
|
186
|
+
return native;
|
|
187
|
+
},
|
|
188
|
+
official: () => this.readOfficial(session, input)
|
|
189
|
+
});
|
|
190
|
+
return { page, incompleteNative };
|
|
191
|
+
}
|
|
161
192
|
async readManagedActive(session, input, cursor, snapshotSequence, readAt) {
|
|
162
193
|
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
163
194
|
const runtimeTurns = this.options.runtime.listConversationTurns({
|
|
@@ -166,26 +197,30 @@ export class ConversationHistoryService {
|
|
|
166
197
|
}).turns;
|
|
167
198
|
const beforeAt = cursor?.beforeAt ?? oldestTurnActivityAt(runtimeTurns);
|
|
168
199
|
if (cursor?.stage === 'OFFICIAL') {
|
|
169
|
-
const prefetched = this.
|
|
170
|
-
const
|
|
171
|
-
this.
|
|
200
|
+
const prefetched = this.takeActiveHistoryRead(session.id, limit, cursor.cursor);
|
|
201
|
+
const selected = await (prefetched ??
|
|
202
|
+
this.readRunnerHistory(session, {
|
|
172
203
|
...(cursor.cursor === null ? {} : { cursor: cursor.cursor }),
|
|
173
204
|
limit
|
|
174
205
|
}));
|
|
175
|
-
|
|
206
|
+
const history = selected.page;
|
|
207
|
+
if (history === undefined)
|
|
176
208
|
return this.page(null, [], { snapshotSequence, source: 'runtime', readAt });
|
|
177
|
-
const
|
|
178
|
-
|
|
209
|
+
const hydratedTurns = history.source === 'official'
|
|
210
|
+
? await this.attachNativeImages(session, history.turns)
|
|
211
|
+
: history.turns;
|
|
212
|
+
const turns = hydratedTurns.filter((turn) => latestTurnActivityAt(turn) < beforeAt);
|
|
213
|
+
return this.page(history.nextCursor === null
|
|
179
214
|
? null
|
|
180
215
|
: encodeActiveHistoryCursor({
|
|
181
216
|
sessionId: session.id,
|
|
182
217
|
stage: 'OFFICIAL',
|
|
183
218
|
beforeAt,
|
|
184
|
-
cursor:
|
|
185
|
-
}), turns, { snapshotSequence, source:
|
|
219
|
+
cursor: history.nextCursor
|
|
220
|
+
}), turns, { snapshotSequence, source: history.source, readAt });
|
|
186
221
|
}
|
|
187
222
|
if (cursor === undefined && session.externalSessionId !== null)
|
|
188
|
-
this.
|
|
223
|
+
this.prefetchActiveHistory(session, limit);
|
|
189
224
|
const runtimePage = this.options.runtime.listConversationTurns({
|
|
190
225
|
sessionId: session.id,
|
|
191
226
|
...(cursor?.cursor === null || cursor === undefined ? {} : { cursor: cursor.cursor }),
|
|
@@ -213,29 +248,32 @@ export class ConversationHistoryService {
|
|
|
213
248
|
deferredOlderHistory: true
|
|
214
249
|
});
|
|
215
250
|
}
|
|
216
|
-
|
|
251
|
+
prefetchActiveHistory(session, limit) {
|
|
217
252
|
const now = Date.now();
|
|
218
|
-
for (const [sessionId, read] of this.
|
|
253
|
+
for (const [sessionId, read] of this.activeHistoryReads)
|
|
219
254
|
if (read.createdAt + 60_000 <= now)
|
|
220
|
-
this.
|
|
221
|
-
const current = this.
|
|
255
|
+
this.activeHistoryReads.delete(sessionId);
|
|
256
|
+
const current = this.activeHistoryReads.get(session.id);
|
|
222
257
|
if (current !== undefined && current.limit === limit)
|
|
223
258
|
return;
|
|
224
|
-
this.
|
|
259
|
+
this.activeHistoryReads.set(session.id, {
|
|
225
260
|
createdAt: now,
|
|
226
261
|
limit,
|
|
227
262
|
// The active Run fast path must stay independent from an optional
|
|
228
263
|
// background reader failure until the user explicitly requests older history.
|
|
229
|
-
page: this.
|
|
264
|
+
page: this.readRunnerHistory(session, { limit }).catch(() => ({
|
|
265
|
+
page: undefined,
|
|
266
|
+
incompleteNative: false
|
|
267
|
+
}))
|
|
230
268
|
});
|
|
231
269
|
}
|
|
232
|
-
|
|
270
|
+
takeActiveHistoryRead(sessionId, limit, cursor) {
|
|
233
271
|
if (cursor !== null)
|
|
234
272
|
return undefined;
|
|
235
|
-
const read = this.
|
|
273
|
+
const read = this.activeHistoryReads.get(sessionId);
|
|
236
274
|
if (read === undefined || read.limit !== limit || read.createdAt + 60_000 <= Date.now())
|
|
237
275
|
return undefined;
|
|
238
|
-
this.
|
|
276
|
+
this.activeHistoryReads.delete(sessionId);
|
|
239
277
|
return read.page;
|
|
240
278
|
}
|
|
241
279
|
async refreshExternalActivity(session) {
|
|
@@ -258,7 +296,15 @@ export class ConversationHistoryService {
|
|
|
258
296
|
return readNativeSession(session.runner, session.cwd, session.externalSessionId);
|
|
259
297
|
}
|
|
260
298
|
async attachNativeImages(session, turns) {
|
|
299
|
+
const startedAt = performance.now();
|
|
261
300
|
const history = await this.readNative(session);
|
|
301
|
+
const readDurationMs = Math.round(performance.now() - startedAt);
|
|
302
|
+
if (readDurationMs >= 250)
|
|
303
|
+
nodeLog('native.session.history.attachment-source-read.completed', {
|
|
304
|
+
runner: session.runner,
|
|
305
|
+
sessionId: session.id,
|
|
306
|
+
durationMs: readDurationMs
|
|
307
|
+
});
|
|
262
308
|
if (history === undefined)
|
|
263
309
|
return turns;
|
|
264
310
|
const attachments = new Map();
|
|
@@ -285,7 +331,19 @@ export class ConversationHistoryService {
|
|
|
285
331
|
const runner = this.options.runners.require(session.runner);
|
|
286
332
|
if (session.externalSessionId === null || !runner.available(this.options.capabilities()))
|
|
287
333
|
return undefined;
|
|
288
|
-
|
|
334
|
+
const startedAt = performance.now();
|
|
335
|
+
try {
|
|
336
|
+
return await runner.readOfficialConversation(session, input, (nativeTurnId) => this.managedRunnerTurn(session, nativeTurnId));
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
340
|
+
if (durationMs >= 250)
|
|
341
|
+
nodeLog('native.session.history.official-read.completed', {
|
|
342
|
+
runner: session.runner,
|
|
343
|
+
sessionId: session.id,
|
|
344
|
+
durationMs
|
|
345
|
+
});
|
|
346
|
+
}
|
|
289
347
|
}
|
|
290
348
|
managedRunnerTurn(session, nativeTurnId) {
|
|
291
349
|
const runId = this.options.runners
|
|
@@ -18,7 +18,9 @@ interface ExternalSessionResumeServiceOptions {
|
|
|
18
18
|
}
|
|
19
19
|
export declare class ExternalSessionResumeService {
|
|
20
20
|
private readonly options;
|
|
21
|
+
private readonly transitions;
|
|
21
22
|
constructor(options: ExternalSessionResumeServiceOptions);
|
|
23
|
+
transitioning(sessionId: string): boolean;
|
|
22
24
|
resume(external: NodeAgentSession, force?: boolean): Promise<NodeAgentSession | undefined>;
|
|
23
25
|
failure(sessionId: string): {
|
|
24
26
|
readonly code: string;
|