@myagentroam/node 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +9 -0
- package/dist/codex-app-server.d.ts +1 -0
- package/dist/codex-app-server.js +4 -2
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +16 -11
- package/dist/native-jsonl-reader.d.ts +21 -0
- package/dist/native-jsonl-reader.js +89 -0
- package/dist/native-session-history.d.ts +20 -3
- package/dist/native-session-history.js +245 -38
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +1 -0
- package/dist/runner/abstract-runner.d.ts +11 -2
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +3 -0
- package/dist/runner/claude-code-runner.d.ts +2 -1
- package/dist/runner/claude-code-runner.js +4 -0
- package/dist/runner/codex/managed-run-controller.js +6 -0
- package/dist/runner/codex-runner.d.ts +2 -1
- package/dist/runner/codex-runner.js +13 -2
- package/dist/runner/opencode/managed-run-controller.js +4 -0
- package/dist/runner/opencode-runner.d.ts +10 -3
- package/dist/runner/opencode-runner.js +87 -14
- package/dist/runner/runner-registry.js +16 -1
- package/dist/runtime-command-detector.js +1 -6
- package/dist/runtime-state.js +1 -0
- package/dist/service/conversation-history-service.d.ts +6 -5
- package/dist/service/conversation-history-service.js +151 -55
- package/dist/service/external-session-resume-service.d.ts +2 -0
- package/dist/service/external-session-resume-service.js +45 -34
- package/dist/service/native-session-projection-service.d.ts +1 -0
- package/dist/service/native-session-projection-service.js +4 -0
- package/dist/service/native-session-watch-service.d.ts +18 -0
- package/dist/service/native-session-watch-service.js +232 -34
- package/dist/service/run-workbench-service.d.ts +2 -4
- package/dist/service/run-workbench-service.js +2 -7
- package/dist/service/session-command-service.js +1 -1
- package/dist/service/session-identity-service.d.ts +1 -1
- package/dist/service/session-identity-service.js +1 -1
- package/dist/service/session-lifecycle-service.js +3 -0
- package/dist/service/session-message-service.d.ts +0 -1
- package/dist/service/session-message-service.js +15 -23
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-domain-state.d.ts +3 -0
- package/dist/service/workspace-domain-state.js +1 -0
- package/dist/service/workspace-file-service.d.ts +1 -0
- package/dist/service/workspace-file-service.js +14 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +17 -0
- package/dist/service/workspace-queue-workbench-service.js +66 -6
- package/dist/service/workspace-watch-service.d.ts +7 -2
- package/dist/service/workspace-watch-service.js +32 -6
- package/dist/service/workspace-workbench-service.d.ts +14 -0
- package/dist/service/workspace-workbench-service.js +215 -16
- package/dist/util/personal-instructions.d.ts +2 -0
- package/dist/util/personal-instructions.js +31 -0
- package/dist/util/runner-native-session-parsers.d.ts +1 -0
- package/dist/util/runner-native-session-parsers.js +76 -12
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +65 -42
- package/package.json +2 -2
|
@@ -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
|
-
readNative(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeSessionHistory | undefined>;
|
|
34
|
+
readNative(session: NodeAgentSession, cursor?: string, limit?: number): 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,68 @@ 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, input.cursor, input.limit);
|
|
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 &&
|
|
178
|
+
(!runner.acceptsTruncatedNativeHistory() || history.items.length === 0)) {
|
|
179
|
+
incompleteNative = true;
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const runtimeTurns = this.options.runtime.getAgentSession(session.id) === undefined
|
|
183
|
+
? []
|
|
184
|
+
: this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 })
|
|
185
|
+
.turns;
|
|
186
|
+
const native = nativeConversationPage(session, history, history.cursorReset === true
|
|
187
|
+
? input.limit === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { limit: input.limit }
|
|
190
|
+
: input, runtimeTurns, (images) => this.options.attachments.registerNative(session, images));
|
|
191
|
+
return native;
|
|
192
|
+
},
|
|
193
|
+
official: () => this.readOfficial(session, input)
|
|
194
|
+
});
|
|
195
|
+
return { page, incompleteNative };
|
|
196
|
+
}
|
|
161
197
|
async readManagedActive(session, input, cursor, snapshotSequence, readAt) {
|
|
162
198
|
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
163
199
|
const runtimeTurns = this.options.runtime.listConversationTurns({
|
|
@@ -166,26 +202,30 @@ export class ConversationHistoryService {
|
|
|
166
202
|
}).turns;
|
|
167
203
|
const beforeAt = cursor?.beforeAt ?? oldestTurnActivityAt(runtimeTurns);
|
|
168
204
|
if (cursor?.stage === 'OFFICIAL') {
|
|
169
|
-
const prefetched = this.
|
|
170
|
-
const
|
|
171
|
-
this.
|
|
205
|
+
const prefetched = this.takeActiveHistoryRead(session.id, limit, cursor.cursor);
|
|
206
|
+
const selected = await (prefetched ??
|
|
207
|
+
this.readRunnerHistory(session, {
|
|
172
208
|
...(cursor.cursor === null ? {} : { cursor: cursor.cursor }),
|
|
173
209
|
limit
|
|
174
210
|
}));
|
|
175
|
-
|
|
211
|
+
const history = selected.page;
|
|
212
|
+
if (history === undefined)
|
|
176
213
|
return this.page(null, [], { snapshotSequence, source: 'runtime', readAt });
|
|
177
|
-
const
|
|
178
|
-
|
|
214
|
+
const hydratedTurns = history.source === 'official'
|
|
215
|
+
? await this.attachNativeImages(session, history.turns)
|
|
216
|
+
: history.turns;
|
|
217
|
+
const turns = hydratedTurns.filter((turn) => latestTurnActivityAt(turn) < beforeAt);
|
|
218
|
+
return this.page(history.nextCursor === null
|
|
179
219
|
? null
|
|
180
220
|
: encodeActiveHistoryCursor({
|
|
181
221
|
sessionId: session.id,
|
|
182
222
|
stage: 'OFFICIAL',
|
|
183
223
|
beforeAt,
|
|
184
|
-
cursor:
|
|
185
|
-
}), turns, { snapshotSequence, source:
|
|
224
|
+
cursor: history.nextCursor
|
|
225
|
+
}), turns, { snapshotSequence, source: history.source, readAt });
|
|
186
226
|
}
|
|
187
227
|
if (cursor === undefined && session.externalSessionId !== null)
|
|
188
|
-
this.
|
|
228
|
+
this.prefetchActiveHistory(session, limit);
|
|
189
229
|
const runtimePage = this.options.runtime.listConversationTurns({
|
|
190
230
|
sessionId: session.id,
|
|
191
231
|
...(cursor?.cursor === null || cursor === undefined ? {} : { cursor: cursor.cursor }),
|
|
@@ -213,29 +253,32 @@ export class ConversationHistoryService {
|
|
|
213
253
|
deferredOlderHistory: true
|
|
214
254
|
});
|
|
215
255
|
}
|
|
216
|
-
|
|
256
|
+
prefetchActiveHistory(session, limit) {
|
|
217
257
|
const now = Date.now();
|
|
218
|
-
for (const [sessionId, read] of this.
|
|
258
|
+
for (const [sessionId, read] of this.activeHistoryReads)
|
|
219
259
|
if (read.createdAt + 60_000 <= now)
|
|
220
|
-
this.
|
|
221
|
-
const current = this.
|
|
260
|
+
this.activeHistoryReads.delete(sessionId);
|
|
261
|
+
const current = this.activeHistoryReads.get(session.id);
|
|
222
262
|
if (current !== undefined && current.limit === limit)
|
|
223
263
|
return;
|
|
224
|
-
this.
|
|
264
|
+
this.activeHistoryReads.set(session.id, {
|
|
225
265
|
createdAt: now,
|
|
226
266
|
limit,
|
|
227
267
|
// The active Run fast path must stay independent from an optional
|
|
228
268
|
// background reader failure until the user explicitly requests older history.
|
|
229
|
-
page: this.
|
|
269
|
+
page: this.readRunnerHistory(session, { limit }).catch(() => ({
|
|
270
|
+
page: undefined,
|
|
271
|
+
incompleteNative: false
|
|
272
|
+
}))
|
|
230
273
|
});
|
|
231
274
|
}
|
|
232
|
-
|
|
275
|
+
takeActiveHistoryRead(sessionId, limit, cursor) {
|
|
233
276
|
if (cursor !== null)
|
|
234
277
|
return undefined;
|
|
235
|
-
const read = this.
|
|
278
|
+
const read = this.activeHistoryReads.get(sessionId);
|
|
236
279
|
if (read === undefined || read.limit !== limit || read.createdAt + 60_000 <= Date.now())
|
|
237
280
|
return undefined;
|
|
238
|
-
this.
|
|
281
|
+
this.activeHistoryReads.delete(sessionId);
|
|
239
282
|
return read.page;
|
|
240
283
|
}
|
|
241
284
|
async refreshExternalActivity(session) {
|
|
@@ -248,17 +291,26 @@ export class ConversationHistoryService {
|
|
|
248
291
|
if (activity !== undefined)
|
|
249
292
|
this.options.setExternalActivity(session.id, activity);
|
|
250
293
|
}
|
|
251
|
-
async readNative(session) {
|
|
294
|
+
async readNative(session, cursor, limit = 30) {
|
|
252
295
|
if (session.externalSessionId === null)
|
|
253
296
|
return undefined;
|
|
254
|
-
const
|
|
297
|
+
const page = nativeHistoryPageCursor(cursor, session.id);
|
|
298
|
+
const current = await readNativeSession(session.runner, session.cwd, session.externalSessionId, page, limit);
|
|
255
299
|
if (current !== undefined)
|
|
256
300
|
return current;
|
|
257
301
|
await discoverNativeSessions(session.runner, session.cwd);
|
|
258
|
-
return readNativeSession(session.runner, session.cwd, session.externalSessionId);
|
|
302
|
+
return readNativeSession(session.runner, session.cwd, session.externalSessionId, page, limit);
|
|
259
303
|
}
|
|
260
304
|
async attachNativeImages(session, turns) {
|
|
305
|
+
const startedAt = performance.now();
|
|
261
306
|
const history = await this.readNative(session);
|
|
307
|
+
const readDurationMs = Math.round(performance.now() - startedAt);
|
|
308
|
+
if (readDurationMs >= 250)
|
|
309
|
+
nodeLog('native.session.history.attachment-source-read.completed', {
|
|
310
|
+
runner: session.runner,
|
|
311
|
+
sessionId: session.id,
|
|
312
|
+
durationMs: readDurationMs
|
|
313
|
+
});
|
|
262
314
|
if (history === undefined)
|
|
263
315
|
return turns;
|
|
264
316
|
const attachments = new Map();
|
|
@@ -285,7 +337,19 @@ export class ConversationHistoryService {
|
|
|
285
337
|
const runner = this.options.runners.require(session.runner);
|
|
286
338
|
if (session.externalSessionId === null || !runner.available(this.options.capabilities()))
|
|
287
339
|
return undefined;
|
|
288
|
-
|
|
340
|
+
const startedAt = performance.now();
|
|
341
|
+
try {
|
|
342
|
+
return await runner.readOfficialConversation(session, input, (nativeTurnId) => this.managedRunnerTurn(session, nativeTurnId));
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
346
|
+
if (durationMs >= 250)
|
|
347
|
+
nodeLog('native.session.history.official-read.completed', {
|
|
348
|
+
runner: session.runner,
|
|
349
|
+
sessionId: session.id,
|
|
350
|
+
durationMs
|
|
351
|
+
});
|
|
352
|
+
}
|
|
289
353
|
}
|
|
290
354
|
managedRunnerTurn(session, nativeTurnId) {
|
|
291
355
|
const runId = this.options.runners
|
|
@@ -306,6 +370,38 @@ export class ConversationHistoryService {
|
|
|
306
370
|
};
|
|
307
371
|
}
|
|
308
372
|
}
|
|
373
|
+
function nativeHistoryPageCursor(cursor, sessionId) {
|
|
374
|
+
if (cursor === undefined)
|
|
375
|
+
return undefined;
|
|
376
|
+
try {
|
|
377
|
+
const value = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
378
|
+
return value.sessionId === sessionId &&
|
|
379
|
+
Number.isInteger(value.snapshotEnd) &&
|
|
380
|
+
Number.isInteger(value.nextEnd) &&
|
|
381
|
+
Number.isInteger(value.anchorBytes) &&
|
|
382
|
+
value.snapshotEnd >= 0 &&
|
|
383
|
+
value.nextEnd >= 0 &&
|
|
384
|
+
value.nextEnd <= value.snapshotEnd &&
|
|
385
|
+
value.anchorBytes >= 0 &&
|
|
386
|
+
value.anchorBytes <= value.snapshotEnd &&
|
|
387
|
+
(value.readLimit === undefined ||
|
|
388
|
+
(Number.isInteger(value.readLimit) &&
|
|
389
|
+
value.readLimit >= 1 &&
|
|
390
|
+
value.readLimit <= 500)) &&
|
|
391
|
+
typeof value.fileAnchor === 'string'
|
|
392
|
+
? {
|
|
393
|
+
snapshotEnd: value.snapshotEnd,
|
|
394
|
+
nextEnd: value.nextEnd,
|
|
395
|
+
fileAnchor: value.fileAnchor,
|
|
396
|
+
anchorBytes: value.anchorBytes,
|
|
397
|
+
...(value.readLimit === undefined ? {} : { readLimit: value.readLimit })
|
|
398
|
+
}
|
|
399
|
+
: undefined;
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
309
405
|
const ACTIVE_HISTORY_CURSOR_PREFIX = 'mar-active-history:1:';
|
|
310
406
|
function encodeActiveHistoryCursor(cursor) {
|
|
311
407
|
return `${ACTIVE_HISTORY_CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString('base64url')}`;
|
|
@@ -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;
|