@myagentroam/node 0.9.3 → 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 +25 -0
- package/dist/codex-app-server.js +104 -7
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +21 -12
- package/dist/database.d.ts +2 -0
- 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.d.ts +9 -0
- package/dist/runner/codex/managed-run-controller.js +63 -17
- package/dist/runner/codex-runner.d.ts +3 -1
- package/dist/runner/codex-runner.js +23 -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.d.ts +2 -0
- package/dist/runtime-state.js +12 -7
- package/dist/service/conversation-history-service.d.ts +10 -1
- package/dist/service/conversation-history-service.js +272 -42
- package/dist/service/conversation-segment-service.js +7 -1
- 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 +26 -2
- package/dist/service/native-session-watch-service.js +258 -42
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-event-service.js +16 -5
- 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 +7 -12
- package/dist/service/session-message-service.d.ts +1 -1
- package/dist/service/session-message-service.js +104 -83
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-change-service.js +1 -1
- 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 +29 -2
- package/dist/service/workspace-queue-workbench-service.js +73 -8
- 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 +3 -1
- package/dist/util/runner-native-session-parsers.js +35 -17
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +98 -50
- package/package.json +2 -2
|
@@ -2,41 +2,88 @@ 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
|
|
8
9
|
.listRunsForSession(session.id)
|
|
9
10
|
.some((run) => run.status === 'STARTING' || run.status === 'RUNNING' || run.status === 'CANCELLING'));
|
|
10
11
|
}
|
|
12
|
+
export function convergeConversationWatchPage(previous, previousObserved, next, previousAuthority) {
|
|
13
|
+
const authority = isConversationHistorySource(previousAuthority)
|
|
14
|
+
? previousAuthority
|
|
15
|
+
: previous?.source;
|
|
16
|
+
if (previous === undefined ||
|
|
17
|
+
authority === undefined ||
|
|
18
|
+
sourceRank(next.source) >= sourceRank(authority))
|
|
19
|
+
return { page: next, authority: next.source };
|
|
20
|
+
if (previous.turns.length === 0)
|
|
21
|
+
return { page: next, authority };
|
|
22
|
+
const boundary = next.turns.findLastIndex((turn) => previous.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
23
|
+
const observedBoundary = boundary >= 0 || previousObserved === undefined
|
|
24
|
+
? -1
|
|
25
|
+
: next.turns.findLastIndex((turn) => previousObserved.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
26
|
+
const safeBoundary = Math.max(boundary, observedBoundary);
|
|
27
|
+
if (safeBoundary < 0)
|
|
28
|
+
return { page: previous, authority };
|
|
29
|
+
const additions = next.turns
|
|
30
|
+
.slice(safeBoundary + 1)
|
|
31
|
+
.filter((turn) => !previous.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
32
|
+
if (additions.length === 0)
|
|
33
|
+
return { page: previous, authority };
|
|
34
|
+
return {
|
|
35
|
+
page: {
|
|
36
|
+
...previous,
|
|
37
|
+
source: next.source,
|
|
38
|
+
snapshotSequence: Math.max(previous.snapshotSequence, next.snapshotSequence),
|
|
39
|
+
readAt: next.readAt,
|
|
40
|
+
latestVisibleAt: next.latestVisibleAt ?? previous.latestVisibleAt,
|
|
41
|
+
turns: [...previous.turns, ...additions]
|
|
42
|
+
},
|
|
43
|
+
authority
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function isConversationHistorySource(value) {
|
|
47
|
+
return value === 'official' || value === 'native' || value === 'runtime';
|
|
48
|
+
}
|
|
11
49
|
export class ConversationHistoryService {
|
|
12
50
|
options;
|
|
51
|
+
activeHistoryReads = new Map();
|
|
13
52
|
constructor(options) {
|
|
14
53
|
this.options = options;
|
|
15
54
|
}
|
|
16
55
|
async read(session, input) {
|
|
17
56
|
const snapshotSequence = this.options.snapshotSequence();
|
|
18
57
|
const readAt = Date.now();
|
|
19
|
-
|
|
58
|
+
const activeCursor = decodeActiveHistoryCursor(input.cursor, session.id);
|
|
59
|
+
if (activeCursor !== undefined || this.options.hasManagedActiveRun(session))
|
|
60
|
+
return this.readManagedActive(session, input, activeCursor, snapshotSequence, readAt);
|
|
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;
|
|
20
69
|
if (official !== undefined) {
|
|
21
70
|
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
22
71
|
const runtimeTurns = this.options.runtime.getAgentSession(session.id) === undefined
|
|
23
72
|
? []
|
|
24
73
|
: this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 }).turns;
|
|
25
74
|
const initialOfficialRunIds = new Set(official.turns.flatMap((turn) => (turn.runId === null ? [] : [turn.runId])));
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
? absentLocalTurns.filter((turn) => Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0) >= officialFloor)
|
|
32
|
-
: absentLocalTurns.slice(-1);
|
|
75
|
+
const lastRepresentedRuntimeIndex = runtimeTurns.findLastIndex((turn) => turn.runId !== null && initialOfficialRunIds.has(turn.runId));
|
|
76
|
+
const officialHasTurns = official.turns.length > 0;
|
|
77
|
+
const absentLocalTurns = runtimeTurns.filter((turn, index) => (turn.runId === null || !initialOfficialRunIds.has(turn.runId)) &&
|
|
78
|
+
(!officialHasTurns || index > lastRepresentedRuntimeIndex));
|
|
79
|
+
const missingLocalTurns = absentLocalTurns.slice(-limit);
|
|
33
80
|
if (input.cursor === undefined &&
|
|
34
81
|
missingLocalTurns.length > 0 &&
|
|
35
82
|
official.turns.length + missingLocalTurns.length > limit) {
|
|
36
83
|
const reserved = Math.min(missingLocalTurns.length, Math.max(0, limit - 1));
|
|
37
84
|
const narrowed = await this.readOfficial(session, { ...input, limit: limit - reserved });
|
|
38
85
|
if (narrowed !== undefined)
|
|
39
|
-
official = narrowed;
|
|
86
|
+
official = { ...narrowed, source: 'official' };
|
|
40
87
|
}
|
|
41
88
|
const officialTurns = await this.attachNativeImages(session, official.turns);
|
|
42
89
|
const officialRunIds = new Set(officialTurns.flatMap((turn) => (turn.runId === null ? [] : [turn.runId])));
|
|
@@ -49,7 +96,7 @@ export class ConversationHistoryService {
|
|
|
49
96
|
pendingItemsByRunId.set(turn.runId, pendingItems);
|
|
50
97
|
}
|
|
51
98
|
const availableLocalSlots = Math.max(0, limit - officialTurns.length);
|
|
52
|
-
const localCandidates =
|
|
99
|
+
const localCandidates = input.cursor === undefined ? missingLocalTurns : [];
|
|
53
100
|
const localTurns = availableLocalSlots === 0 ? [] : localCandidates.slice(-availableLocalSlots);
|
|
54
101
|
const localRequestIds = new Set([
|
|
55
102
|
...localTurns.flatMap((turn) => turn.items.flatMap((item) => {
|
|
@@ -78,47 +125,157 @@ export class ConversationHistoryService {
|
|
|
78
125
|
})
|
|
79
126
|
.filter((turn) => turn.items.length > 0),
|
|
80
127
|
...localTurns
|
|
81
|
-
]
|
|
128
|
+
];
|
|
82
129
|
return this.page(official.nextCursor, turns, {
|
|
83
130
|
snapshotSequence,
|
|
84
131
|
source: 'official',
|
|
85
132
|
readAt
|
|
86
133
|
});
|
|
87
134
|
}
|
|
88
|
-
if (session.externalSessionId !== null &&
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
source: 'native',
|
|
101
|
-
readAt
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
const runtimePage = this.options.runtime.listConversationTurns({
|
|
105
|
-
sessionId: session.id,
|
|
106
|
-
...input
|
|
107
|
-
});
|
|
108
|
-
if (runtimePage.turns.length === 0)
|
|
109
|
-
throw new Error('NATIVE_TRANSCRIPT_INCOMPLETE');
|
|
110
|
-
return this.page(runtimePage.nextCursor, runtimePage.turns, {
|
|
111
|
-
snapshotSequence,
|
|
112
|
-
source: 'runtime',
|
|
113
|
-
readAt
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
if (session.nativeControl === 'EXTERNAL')
|
|
117
|
-
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
|
+
});
|
|
118
147
|
}
|
|
148
|
+
if (session.externalSessionId !== null && session.nativeControl === 'EXTERNAL')
|
|
149
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
119
150
|
const page = this.options.runtime.listConversationTurns({ sessionId: session.id, ...input });
|
|
120
151
|
return this.page(page.nextCursor, page.turns, { snapshotSequence, source: 'runtime', readAt });
|
|
121
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
|
+
}
|
|
192
|
+
async readManagedActive(session, input, cursor, snapshotSequence, readAt) {
|
|
193
|
+
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
194
|
+
const runtimeTurns = this.options.runtime.listConversationTurns({
|
|
195
|
+
sessionId: session.id,
|
|
196
|
+
limit: 500
|
|
197
|
+
}).turns;
|
|
198
|
+
const beforeAt = cursor?.beforeAt ?? oldestTurnActivityAt(runtimeTurns);
|
|
199
|
+
if (cursor?.stage === 'OFFICIAL') {
|
|
200
|
+
const prefetched = this.takeActiveHistoryRead(session.id, limit, cursor.cursor);
|
|
201
|
+
const selected = await (prefetched ??
|
|
202
|
+
this.readRunnerHistory(session, {
|
|
203
|
+
...(cursor.cursor === null ? {} : { cursor: cursor.cursor }),
|
|
204
|
+
limit
|
|
205
|
+
}));
|
|
206
|
+
const history = selected.page;
|
|
207
|
+
if (history === undefined)
|
|
208
|
+
return this.page(null, [], { snapshotSequence, source: 'runtime', readAt });
|
|
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
|
|
214
|
+
? null
|
|
215
|
+
: encodeActiveHistoryCursor({
|
|
216
|
+
sessionId: session.id,
|
|
217
|
+
stage: 'OFFICIAL',
|
|
218
|
+
beforeAt,
|
|
219
|
+
cursor: history.nextCursor
|
|
220
|
+
}), turns, { snapshotSequence, source: history.source, readAt });
|
|
221
|
+
}
|
|
222
|
+
if (cursor === undefined && session.externalSessionId !== null)
|
|
223
|
+
this.prefetchActiveHistory(session, limit);
|
|
224
|
+
const runtimePage = this.options.runtime.listConversationTurns({
|
|
225
|
+
sessionId: session.id,
|
|
226
|
+
...(cursor?.cursor === null || cursor === undefined ? {} : { cursor: cursor.cursor }),
|
|
227
|
+
limit
|
|
228
|
+
});
|
|
229
|
+
const nextCursor = runtimePage.nextCursor !== null
|
|
230
|
+
? encodeActiveHistoryCursor({
|
|
231
|
+
sessionId: session.id,
|
|
232
|
+
stage: 'RUNTIME',
|
|
233
|
+
beforeAt,
|
|
234
|
+
cursor: runtimePage.nextCursor
|
|
235
|
+
})
|
|
236
|
+
: session.externalSessionId === null
|
|
237
|
+
? null
|
|
238
|
+
: encodeActiveHistoryCursor({
|
|
239
|
+
sessionId: session.id,
|
|
240
|
+
stage: 'OFFICIAL',
|
|
241
|
+
beforeAt,
|
|
242
|
+
cursor: null
|
|
243
|
+
});
|
|
244
|
+
return this.page(nextCursor, runtimePage.turns, {
|
|
245
|
+
snapshotSequence,
|
|
246
|
+
source: 'runtime',
|
|
247
|
+
readAt,
|
|
248
|
+
deferredOlderHistory: true
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
prefetchActiveHistory(session, limit) {
|
|
252
|
+
const now = Date.now();
|
|
253
|
+
for (const [sessionId, read] of this.activeHistoryReads)
|
|
254
|
+
if (read.createdAt + 60_000 <= now)
|
|
255
|
+
this.activeHistoryReads.delete(sessionId);
|
|
256
|
+
const current = this.activeHistoryReads.get(session.id);
|
|
257
|
+
if (current !== undefined && current.limit === limit)
|
|
258
|
+
return;
|
|
259
|
+
this.activeHistoryReads.set(session.id, {
|
|
260
|
+
createdAt: now,
|
|
261
|
+
limit,
|
|
262
|
+
// The active Run fast path must stay independent from an optional
|
|
263
|
+
// background reader failure until the user explicitly requests older history.
|
|
264
|
+
page: this.readRunnerHistory(session, { limit }).catch(() => ({
|
|
265
|
+
page: undefined,
|
|
266
|
+
incompleteNative: false
|
|
267
|
+
}))
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
takeActiveHistoryRead(sessionId, limit, cursor) {
|
|
271
|
+
if (cursor !== null)
|
|
272
|
+
return undefined;
|
|
273
|
+
const read = this.activeHistoryReads.get(sessionId);
|
|
274
|
+
if (read === undefined || read.limit !== limit || read.createdAt + 60_000 <= Date.now())
|
|
275
|
+
return undefined;
|
|
276
|
+
this.activeHistoryReads.delete(sessionId);
|
|
277
|
+
return read.page;
|
|
278
|
+
}
|
|
122
279
|
async refreshExternalActivity(session) {
|
|
123
280
|
const runner = this.options.runners.require(session.runner);
|
|
124
281
|
if (!runner.available(this.options.capabilities())) {
|
|
@@ -139,7 +296,15 @@ export class ConversationHistoryService {
|
|
|
139
296
|
return readNativeSession(session.runner, session.cwd, session.externalSessionId);
|
|
140
297
|
}
|
|
141
298
|
async attachNativeImages(session, turns) {
|
|
299
|
+
const startedAt = performance.now();
|
|
142
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
|
+
});
|
|
143
308
|
if (history === undefined)
|
|
144
309
|
return turns;
|
|
145
310
|
const attachments = new Map();
|
|
@@ -166,7 +331,19 @@ export class ConversationHistoryService {
|
|
|
166
331
|
const runner = this.options.runners.require(session.runner);
|
|
167
332
|
if (session.externalSessionId === null || !runner.available(this.options.capabilities()))
|
|
168
333
|
return undefined;
|
|
169
|
-
|
|
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
|
+
}
|
|
170
347
|
}
|
|
171
348
|
managedRunnerTurn(session, nativeTurnId) {
|
|
172
349
|
const runId = this.options.runners
|
|
@@ -187,3 +364,56 @@ export class ConversationHistoryService {
|
|
|
187
364
|
};
|
|
188
365
|
}
|
|
189
366
|
}
|
|
367
|
+
const ACTIVE_HISTORY_CURSOR_PREFIX = 'mar-active-history:1:';
|
|
368
|
+
function encodeActiveHistoryCursor(cursor) {
|
|
369
|
+
return `${ACTIVE_HISTORY_CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString('base64url')}`;
|
|
370
|
+
}
|
|
371
|
+
function decodeActiveHistoryCursor(value, sessionId) {
|
|
372
|
+
if (value === undefined || !value.startsWith(ACTIVE_HISTORY_CURSOR_PREFIX))
|
|
373
|
+
return undefined;
|
|
374
|
+
try {
|
|
375
|
+
const decoded = JSON.parse(Buffer.from(value.slice(ACTIVE_HISTORY_CURSOR_PREFIX.length), 'base64url').toString('utf8'));
|
|
376
|
+
if (decoded.sessionId !== sessionId ||
|
|
377
|
+
(decoded.stage !== 'RUNTIME' && decoded.stage !== 'OFFICIAL') ||
|
|
378
|
+
typeof decoded.beforeAt !== 'number' ||
|
|
379
|
+
!Number.isFinite(decoded.beforeAt) ||
|
|
380
|
+
decoded.beforeAt < 0 ||
|
|
381
|
+
(decoded.cursor !== null && typeof decoded.cursor !== 'string'))
|
|
382
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
383
|
+
return decoded;
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function latestTurnActivityAt(turn) {
|
|
390
|
+
return Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0);
|
|
391
|
+
}
|
|
392
|
+
function oldestTurnActivityAt(turns) {
|
|
393
|
+
return turns.length === 0 ? 0 : Math.min(...turns.map(latestTurnActivityAt));
|
|
394
|
+
}
|
|
395
|
+
function sameConversationTurn(left, right) {
|
|
396
|
+
if (left.id === right.id)
|
|
397
|
+
return true;
|
|
398
|
+
if (left.runId !== null && left.runId === right.runId)
|
|
399
|
+
return true;
|
|
400
|
+
const leftMessageId = conversationClientMessageId(left);
|
|
401
|
+
return leftMessageId !== null && leftMessageId === conversationClientMessageId(right);
|
|
402
|
+
}
|
|
403
|
+
function conversationClientMessageId(turn) {
|
|
404
|
+
for (const item of turn.items) {
|
|
405
|
+
if (item.kind !== 'user_message' || !isPlainRecord(item.payload))
|
|
406
|
+
continue;
|
|
407
|
+
const value = item.payload.clientMessageId;
|
|
408
|
+
if (typeof value === 'string' && value.length > 0)
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
function sourceRank(source) {
|
|
414
|
+
if (source === 'official')
|
|
415
|
+
return 3;
|
|
416
|
+
if (source === 'native')
|
|
417
|
+
return 2;
|
|
418
|
+
return 1;
|
|
419
|
+
}
|
|
@@ -16,6 +16,7 @@ export function conversationSegments(turn) {
|
|
|
16
16
|
const latest = index === groups.length - 1;
|
|
17
17
|
const projectedItems = items.map((item) => ({
|
|
18
18
|
...item,
|
|
19
|
+
runId: turn.runId,
|
|
19
20
|
segmentId: id,
|
|
20
21
|
segmentIndex: index
|
|
21
22
|
}));
|
|
@@ -89,7 +90,10 @@ export class ConversationSegmentService {
|
|
|
89
90
|
snapshotSequence: page.snapshotSequence,
|
|
90
91
|
source: page.source,
|
|
91
92
|
readAt: page.readAt,
|
|
92
|
-
latestVisibleAt: page.latestVisibleAt
|
|
93
|
+
latestVisibleAt: page.latestVisibleAt,
|
|
94
|
+
...(boundary === undefined && page.deferredOlderHistory === true
|
|
95
|
+
? { deferredOlderHistory: true }
|
|
96
|
+
: {})
|
|
93
97
|
};
|
|
94
98
|
const pageSegments = page.turns.flatMap(conversationSegments);
|
|
95
99
|
let eligible = pageSegments;
|
|
@@ -108,6 +112,8 @@ export class ConversationSegmentService {
|
|
|
108
112
|
hasOlder = eligible.length > take.length || page.nextCursor !== null;
|
|
109
113
|
collected.unshift(...take);
|
|
110
114
|
}
|
|
115
|
+
if (boundary === undefined && page.deferredOlderHistory === true)
|
|
116
|
+
break;
|
|
111
117
|
if (collected.length >= limit || page.nextCursor === null)
|
|
112
118
|
break;
|
|
113
119
|
if (visited.has(page.nextCursor))
|
|
@@ -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;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const RESUME_FAILURE_TTL_MS = 30 * 60_000;
|
|
2
2
|
export class ExternalSessionResumeService {
|
|
3
3
|
options;
|
|
4
|
+
transitions = new Set();
|
|
4
5
|
constructor(options) {
|
|
5
6
|
this.options = options;
|
|
6
7
|
}
|
|
8
|
+
transitioning(sessionId) {
|
|
9
|
+
return this.transitions.has(sessionId);
|
|
10
|
+
}
|
|
7
11
|
async resume(external, force = false) {
|
|
8
12
|
this.clearFailure(external.id);
|
|
9
13
|
const priorId = this.options.state.resumedByDirectId.get(external.id);
|
|
@@ -15,40 +19,47 @@ export class ExternalSessionResumeService {
|
|
|
15
19
|
this.setFailure(external.id, 'RUNNER_UNAVAILABLE');
|
|
16
20
|
return undefined;
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
22
|
+
this.transitions.add(external.id);
|
|
23
|
+
let result;
|
|
24
|
+
try {
|
|
25
|
+
result = await runner.resumeExternal(external, {
|
|
26
|
+
available: this.options.available(external.runner),
|
|
27
|
+
projectionFor: this.options.projectionFor,
|
|
28
|
+
managedAliasFor: this.options.managedAliasFor,
|
|
29
|
+
currentResumed: (sessionId) => {
|
|
30
|
+
const resumedId = this.options.state.resumedByDirectId.get(sessionId);
|
|
31
|
+
return resumedId === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: this.options.runtime.getAgentSession(resumedId);
|
|
34
|
+
},
|
|
35
|
+
promote: (sessionId) => {
|
|
36
|
+
this.options.runtime.setNativeControl(sessionId, 'MAR_MANAGED');
|
|
37
|
+
return this.options.runtime.getAgentSession(sessionId);
|
|
38
|
+
},
|
|
39
|
+
ensureConfiguration: this.options.ensureConfiguration,
|
|
40
|
+
create: (session) => {
|
|
41
|
+
const configuration = this.options.configuration(session);
|
|
42
|
+
return this.options.runtime.createAgentSession({
|
|
43
|
+
workspaceId: session.workspaceId,
|
|
44
|
+
runner: session.runner,
|
|
45
|
+
...(session.externalSessionId === null
|
|
46
|
+
? {}
|
|
47
|
+
: { externalSessionId: session.externalSessionId }),
|
|
48
|
+
nativeControl: 'MAR_MANAGED',
|
|
49
|
+
cwd: session.cwd,
|
|
50
|
+
model: configuration.model,
|
|
51
|
+
effort: configuration.effort,
|
|
52
|
+
access: configuration.access,
|
|
53
|
+
...(session.runnerTitle === null ? {} : { title: session.runnerTitle })
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
workspacePath: this.options.workspacePath,
|
|
57
|
+
rememberManagedNative: this.options.rememberNative
|
|
58
|
+
}, force);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
this.transitions.delete(external.id);
|
|
62
|
+
}
|
|
52
63
|
if (result.activity !== undefined)
|
|
53
64
|
this.options.state.externalActivity.set(external.id, result.activity);
|
|
54
65
|
if (result.session !== undefined) {
|
|
@@ -32,6 +32,7 @@ export declare class NativeSessionProjectionService {
|
|
|
32
32
|
})[]>;
|
|
33
33
|
discoverWorkspace(workspaceId: string): Promise<readonly NodeAgentSession[]>;
|
|
34
34
|
resolve(sessionId: string): Promise<NodeAgentSession | undefined>;
|
|
35
|
+
resolveCurrent(sessionId: string): Promise<NodeAgentSession | undefined>;
|
|
35
36
|
createMetadataProjection(sessionId: string): Promise<NodeAgentSession | undefined>;
|
|
36
37
|
resumedConfiguration(session: NodeAgentSession): RunnerDefaultConfiguration;
|
|
37
38
|
ensureResumedConfiguration(session: NodeAgentSession): NodeAgentSession;
|
|
@@ -81,6 +81,10 @@ export class NativeSessionProjectionService {
|
|
|
81
81
|
createdAt: 0
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
async resolveCurrent(sessionId) {
|
|
85
|
+
const direct = await this.resolve(sessionId);
|
|
86
|
+
return direct === undefined ? undefined : (this.projectionForNative(direct) ?? direct);
|
|
87
|
+
}
|
|
84
88
|
async createMetadataProjection(sessionId) {
|
|
85
89
|
const direct = await this.resolve(sessionId);
|
|
86
90
|
if (direct === undefined || direct.externalSessionId === null)
|
|
@@ -2,24 +2,37 @@ import type { NodeAgentSession } from '../database.js';
|
|
|
2
2
|
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
3
3
|
export interface NativeSessionWatchOptions<TPage extends {
|
|
4
4
|
readonly turns: readonly unknown[];
|
|
5
|
+
readonly source?: unknown;
|
|
5
6
|
}> {
|
|
6
7
|
readonly resolve: (sessionId: string) => Promise<NodeAgentSession | undefined>;
|
|
7
8
|
readonly managedActive: (session: NodeAgentSession) => boolean;
|
|
9
|
+
readonly suspended?: (sessionId: string) => boolean;
|
|
8
10
|
readonly refreshActivity: (session: NodeAgentSession) => Promise<void>;
|
|
9
11
|
readonly present: (session: NodeAgentSession) => unknown;
|
|
10
12
|
readonly readPage: (session: NodeAgentSession, limit: number) => Promise<TPage>;
|
|
13
|
+
readonly convergePage: (previous: TPage | undefined, previousObserved: TPage | undefined, next: TPage, previousAuthority: unknown) => {
|
|
14
|
+
readonly page: TPage;
|
|
15
|
+
readonly authority: unknown;
|
|
16
|
+
};
|
|
11
17
|
readonly projectInitialPage?: (session: NodeAgentSession, page: TPage, unit: 'SEGMENT', limit: number) => unknown;
|
|
12
18
|
readonly emitSession: (session: unknown) => void;
|
|
13
|
-
readonly
|
|
19
|
+
readonly emitPage: (session: NodeAgentSession, page: TPage) => void;
|
|
20
|
+
readonly nodeId?: () => string;
|
|
21
|
+
readonly nodeGeneration?: () => string;
|
|
22
|
+
readonly emitWatchEvent?: (payload: unknown) => void;
|
|
14
23
|
}
|
|
15
24
|
export declare class NativeSessionWatchService<TPage extends {
|
|
16
25
|
readonly turns: readonly unknown[];
|
|
26
|
+
readonly source?: unknown;
|
|
17
27
|
}> {
|
|
18
28
|
private readonly options;
|
|
19
29
|
private readonly watches;
|
|
30
|
+
private readonly leases;
|
|
31
|
+
private snapshotSequence;
|
|
20
32
|
constructor(options: NativeSessionWatchOptions<TPage>);
|
|
21
33
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
22
34
|
private executeWatch;
|
|
35
|
+
private executeUnwatch;
|
|
23
36
|
watch(session: NodeAgentSession): {
|
|
24
37
|
readonly expiresAt: number;
|
|
25
38
|
} | undefined;
|
|
@@ -33,6 +46,17 @@ export declare class NativeSessionWatchService<TPage extends {
|
|
|
33
46
|
readonly expiresAt: number;
|
|
34
47
|
} | undefined;
|
|
35
48
|
get size(): number;
|
|
36
|
-
refresh(sessionId: string, limit?: number): Promise<TPage | undefined>;
|
|
49
|
+
refresh(sessionId: string, limit?: number, emit?: boolean): Promise<TPage | undefined>;
|
|
37
50
|
private performRefresh;
|
|
51
|
+
private sessionHash;
|
|
52
|
+
private ownerKey;
|
|
53
|
+
private accepted;
|
|
54
|
+
private initializeLease;
|
|
55
|
+
private removeLease;
|
|
56
|
+
private emitObservedPage;
|
|
57
|
+
private failSession;
|
|
58
|
+
private releaseSessionLeases;
|
|
59
|
+
private leaseTimer;
|
|
60
|
+
private latestLeaseExpiry;
|
|
61
|
+
private refreshTimer;
|
|
38
62
|
}
|