@myagentroam/node 0.9.3 → 0.9.4
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/codex-app-server.d.ts +24 -0
- package/dist/codex-app-server.js +100 -5
- package/dist/connector.js +6 -2
- package/dist/database.d.ts +2 -0
- package/dist/runner/codex/managed-run-controller.d.ts +9 -0
- package/dist/runner/codex/managed-run-controller.js +57 -17
- package/dist/runner/codex-runner.d.ts +1 -0
- package/dist/runner/codex-runner.js +12 -1
- package/dist/runtime-state.d.ts +2 -0
- package/dist/runtime-state.js +11 -7
- package/dist/service/conversation-history-service.d.ts +8 -0
- package/dist/service/conversation-history-service.js +181 -9
- package/dist/service/conversation-segment-service.js +7 -1
- package/dist/service/native-session-watch-service.d.ts +8 -2
- package/dist/service/native-session-watch-service.js +28 -10
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-event-service.js +16 -5
- package/dist/service/session-lifecycle-service.js +4 -12
- package/dist/service/session-message-service.d.ts +1 -0
- package/dist/service/session-message-service.js +107 -78
- package/dist/service/workspace-change-service.js +1 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +12 -2
- package/dist/service/workspace-queue-workbench-service.js +7 -2
- package/dist/util/runner-native-session-parsers.d.ts +2 -1
- package/dist/util/runner-native-session-parsers.js +27 -16
- package/dist/workspace.js +33 -8
- package/package.json +2 -2
|
@@ -8,14 +8,55 @@ export function hasManagedActiveRun(runtime, session) {
|
|
|
8
8
|
.listRunsForSession(session.id)
|
|
9
9
|
.some((run) => run.status === 'STARTING' || run.status === 'RUNNING' || run.status === 'CANCELLING'));
|
|
10
10
|
}
|
|
11
|
+
export function convergeConversationWatchPage(previous, previousObserved, next, previousAuthority) {
|
|
12
|
+
const authority = isConversationHistorySource(previousAuthority)
|
|
13
|
+
? previousAuthority
|
|
14
|
+
: previous?.source;
|
|
15
|
+
if (previous === undefined ||
|
|
16
|
+
authority === undefined ||
|
|
17
|
+
sourceRank(next.source) >= sourceRank(authority))
|
|
18
|
+
return { page: next, authority: next.source };
|
|
19
|
+
if (previous.turns.length === 0)
|
|
20
|
+
return { page: next, authority };
|
|
21
|
+
const boundary = next.turns.findLastIndex((turn) => previous.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
22
|
+
const observedBoundary = boundary >= 0 || previousObserved === undefined
|
|
23
|
+
? -1
|
|
24
|
+
: next.turns.findLastIndex((turn) => previousObserved.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
25
|
+
const safeBoundary = Math.max(boundary, observedBoundary);
|
|
26
|
+
if (safeBoundary < 0)
|
|
27
|
+
return { page: previous, authority };
|
|
28
|
+
const additions = next.turns
|
|
29
|
+
.slice(safeBoundary + 1)
|
|
30
|
+
.filter((turn) => !previous.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
31
|
+
if (additions.length === 0)
|
|
32
|
+
return { page: previous, authority };
|
|
33
|
+
return {
|
|
34
|
+
page: {
|
|
35
|
+
...previous,
|
|
36
|
+
source: next.source,
|
|
37
|
+
snapshotSequence: Math.max(previous.snapshotSequence, next.snapshotSequence),
|
|
38
|
+
readAt: next.readAt,
|
|
39
|
+
latestVisibleAt: next.latestVisibleAt ?? previous.latestVisibleAt,
|
|
40
|
+
turns: [...previous.turns, ...additions]
|
|
41
|
+
},
|
|
42
|
+
authority
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function isConversationHistorySource(value) {
|
|
46
|
+
return value === 'official' || value === 'native' || value === 'runtime';
|
|
47
|
+
}
|
|
11
48
|
export class ConversationHistoryService {
|
|
12
49
|
options;
|
|
50
|
+
activeOfficialReads = new Map();
|
|
13
51
|
constructor(options) {
|
|
14
52
|
this.options = options;
|
|
15
53
|
}
|
|
16
54
|
async read(session, input) {
|
|
17
55
|
const snapshotSequence = this.options.snapshotSequence();
|
|
18
56
|
const readAt = Date.now();
|
|
57
|
+
const activeCursor = decodeActiveHistoryCursor(input.cursor, session.id);
|
|
58
|
+
if (activeCursor !== undefined || this.options.hasManagedActiveRun(session))
|
|
59
|
+
return this.readManagedActive(session, input, activeCursor, snapshotSequence, readAt);
|
|
19
60
|
let official = await this.readOfficial(session, input);
|
|
20
61
|
if (official !== undefined) {
|
|
21
62
|
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
@@ -23,13 +64,11 @@ export class ConversationHistoryService {
|
|
|
23
64
|
? []
|
|
24
65
|
: this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 }).turns;
|
|
25
66
|
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);
|
|
67
|
+
const lastRepresentedRuntimeIndex = runtimeTurns.findLastIndex((turn) => turn.runId !== null && initialOfficialRunIds.has(turn.runId));
|
|
68
|
+
const officialHasTurns = official.turns.length > 0;
|
|
69
|
+
const absentLocalTurns = runtimeTurns.filter((turn, index) => (turn.runId === null || !initialOfficialRunIds.has(turn.runId)) &&
|
|
70
|
+
(!officialHasTurns || index > lastRepresentedRuntimeIndex));
|
|
71
|
+
const missingLocalTurns = absentLocalTurns.slice(-limit);
|
|
33
72
|
if (input.cursor === undefined &&
|
|
34
73
|
missingLocalTurns.length > 0 &&
|
|
35
74
|
official.turns.length + missingLocalTurns.length > limit) {
|
|
@@ -49,7 +88,7 @@ export class ConversationHistoryService {
|
|
|
49
88
|
pendingItemsByRunId.set(turn.runId, pendingItems);
|
|
50
89
|
}
|
|
51
90
|
const availableLocalSlots = Math.max(0, limit - officialTurns.length);
|
|
52
|
-
const localCandidates =
|
|
91
|
+
const localCandidates = input.cursor === undefined ? missingLocalTurns : [];
|
|
53
92
|
const localTurns = availableLocalSlots === 0 ? [] : localCandidates.slice(-availableLocalSlots);
|
|
54
93
|
const localRequestIds = new Set([
|
|
55
94
|
...localTurns.flatMap((turn) => turn.items.flatMap((item) => {
|
|
@@ -78,7 +117,7 @@ export class ConversationHistoryService {
|
|
|
78
117
|
})
|
|
79
118
|
.filter((turn) => turn.items.length > 0),
|
|
80
119
|
...localTurns
|
|
81
|
-
]
|
|
120
|
+
];
|
|
82
121
|
return this.page(official.nextCursor, turns, {
|
|
83
122
|
snapshotSequence,
|
|
84
123
|
source: 'official',
|
|
@@ -119,6 +158,86 @@ export class ConversationHistoryService {
|
|
|
119
158
|
const page = this.options.runtime.listConversationTurns({ sessionId: session.id, ...input });
|
|
120
159
|
return this.page(page.nextCursor, page.turns, { snapshotSequence, source: 'runtime', readAt });
|
|
121
160
|
}
|
|
161
|
+
async readManagedActive(session, input, cursor, snapshotSequence, readAt) {
|
|
162
|
+
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
163
|
+
const runtimeTurns = this.options.runtime.listConversationTurns({
|
|
164
|
+
sessionId: session.id,
|
|
165
|
+
limit: 500
|
|
166
|
+
}).turns;
|
|
167
|
+
const beforeAt = cursor?.beforeAt ?? oldestTurnActivityAt(runtimeTurns);
|
|
168
|
+
if (cursor?.stage === 'OFFICIAL') {
|
|
169
|
+
const prefetched = this.takeActiveOfficialRead(session.id, limit, cursor.cursor);
|
|
170
|
+
const official = await (prefetched ??
|
|
171
|
+
this.readOfficial(session, {
|
|
172
|
+
...(cursor.cursor === null ? {} : { cursor: cursor.cursor }),
|
|
173
|
+
limit
|
|
174
|
+
}));
|
|
175
|
+
if (official === undefined)
|
|
176
|
+
return this.page(null, [], { snapshotSequence, source: 'runtime', readAt });
|
|
177
|
+
const turns = (await this.attachNativeImages(session, official.turns)).filter((turn) => latestTurnActivityAt(turn) < beforeAt);
|
|
178
|
+
return this.page(official.nextCursor === null
|
|
179
|
+
? null
|
|
180
|
+
: encodeActiveHistoryCursor({
|
|
181
|
+
sessionId: session.id,
|
|
182
|
+
stage: 'OFFICIAL',
|
|
183
|
+
beforeAt,
|
|
184
|
+
cursor: official.nextCursor
|
|
185
|
+
}), turns, { snapshotSequence, source: 'official', readAt });
|
|
186
|
+
}
|
|
187
|
+
if (cursor === undefined && session.externalSessionId !== null)
|
|
188
|
+
this.prefetchActiveOfficial(session, limit);
|
|
189
|
+
const runtimePage = this.options.runtime.listConversationTurns({
|
|
190
|
+
sessionId: session.id,
|
|
191
|
+
...(cursor?.cursor === null || cursor === undefined ? {} : { cursor: cursor.cursor }),
|
|
192
|
+
limit
|
|
193
|
+
});
|
|
194
|
+
const nextCursor = runtimePage.nextCursor !== null
|
|
195
|
+
? encodeActiveHistoryCursor({
|
|
196
|
+
sessionId: session.id,
|
|
197
|
+
stage: 'RUNTIME',
|
|
198
|
+
beforeAt,
|
|
199
|
+
cursor: runtimePage.nextCursor
|
|
200
|
+
})
|
|
201
|
+
: session.externalSessionId === null
|
|
202
|
+
? null
|
|
203
|
+
: encodeActiveHistoryCursor({
|
|
204
|
+
sessionId: session.id,
|
|
205
|
+
stage: 'OFFICIAL',
|
|
206
|
+
beforeAt,
|
|
207
|
+
cursor: null
|
|
208
|
+
});
|
|
209
|
+
return this.page(nextCursor, runtimePage.turns, {
|
|
210
|
+
snapshotSequence,
|
|
211
|
+
source: 'runtime',
|
|
212
|
+
readAt,
|
|
213
|
+
deferredOlderHistory: true
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
prefetchActiveOfficial(session, limit) {
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
for (const [sessionId, read] of this.activeOfficialReads)
|
|
219
|
+
if (read.createdAt + 60_000 <= now)
|
|
220
|
+
this.activeOfficialReads.delete(sessionId);
|
|
221
|
+
const current = this.activeOfficialReads.get(session.id);
|
|
222
|
+
if (current !== undefined && current.limit === limit)
|
|
223
|
+
return;
|
|
224
|
+
this.activeOfficialReads.set(session.id, {
|
|
225
|
+
createdAt: now,
|
|
226
|
+
limit,
|
|
227
|
+
// The active Run fast path must stay independent from an optional
|
|
228
|
+
// background reader failure until the user explicitly requests older history.
|
|
229
|
+
page: this.readOfficial(session, { limit }).catch(() => undefined)
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
takeActiveOfficialRead(sessionId, limit, cursor) {
|
|
233
|
+
if (cursor !== null)
|
|
234
|
+
return undefined;
|
|
235
|
+
const read = this.activeOfficialReads.get(sessionId);
|
|
236
|
+
if (read === undefined || read.limit !== limit || read.createdAt + 60_000 <= Date.now())
|
|
237
|
+
return undefined;
|
|
238
|
+
this.activeOfficialReads.delete(sessionId);
|
|
239
|
+
return read.page;
|
|
240
|
+
}
|
|
122
241
|
async refreshExternalActivity(session) {
|
|
123
242
|
const runner = this.options.runners.require(session.runner);
|
|
124
243
|
if (!runner.available(this.options.capabilities())) {
|
|
@@ -187,3 +306,56 @@ export class ConversationHistoryService {
|
|
|
187
306
|
};
|
|
188
307
|
}
|
|
189
308
|
}
|
|
309
|
+
const ACTIVE_HISTORY_CURSOR_PREFIX = 'mar-active-history:1:';
|
|
310
|
+
function encodeActiveHistoryCursor(cursor) {
|
|
311
|
+
return `${ACTIVE_HISTORY_CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString('base64url')}`;
|
|
312
|
+
}
|
|
313
|
+
function decodeActiveHistoryCursor(value, sessionId) {
|
|
314
|
+
if (value === undefined || !value.startsWith(ACTIVE_HISTORY_CURSOR_PREFIX))
|
|
315
|
+
return undefined;
|
|
316
|
+
try {
|
|
317
|
+
const decoded = JSON.parse(Buffer.from(value.slice(ACTIVE_HISTORY_CURSOR_PREFIX.length), 'base64url').toString('utf8'));
|
|
318
|
+
if (decoded.sessionId !== sessionId ||
|
|
319
|
+
(decoded.stage !== 'RUNTIME' && decoded.stage !== 'OFFICIAL') ||
|
|
320
|
+
typeof decoded.beforeAt !== 'number' ||
|
|
321
|
+
!Number.isFinite(decoded.beforeAt) ||
|
|
322
|
+
decoded.beforeAt < 0 ||
|
|
323
|
+
(decoded.cursor !== null && typeof decoded.cursor !== 'string'))
|
|
324
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
325
|
+
return decoded;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function latestTurnActivityAt(turn) {
|
|
332
|
+
return Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0);
|
|
333
|
+
}
|
|
334
|
+
function oldestTurnActivityAt(turns) {
|
|
335
|
+
return turns.length === 0 ? 0 : Math.min(...turns.map(latestTurnActivityAt));
|
|
336
|
+
}
|
|
337
|
+
function sameConversationTurn(left, right) {
|
|
338
|
+
if (left.id === right.id)
|
|
339
|
+
return true;
|
|
340
|
+
if (left.runId !== null && left.runId === right.runId)
|
|
341
|
+
return true;
|
|
342
|
+
const leftMessageId = conversationClientMessageId(left);
|
|
343
|
+
return leftMessageId !== null && leftMessageId === conversationClientMessageId(right);
|
|
344
|
+
}
|
|
345
|
+
function conversationClientMessageId(turn) {
|
|
346
|
+
for (const item of turn.items) {
|
|
347
|
+
if (item.kind !== 'user_message' || !isPlainRecord(item.payload))
|
|
348
|
+
continue;
|
|
349
|
+
const value = item.payload.clientMessageId;
|
|
350
|
+
if (typeof value === 'string' && value.length > 0)
|
|
351
|
+
return value;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
function sourceRank(source) {
|
|
356
|
+
if (source === 'official')
|
|
357
|
+
return 3;
|
|
358
|
+
if (source === 'native')
|
|
359
|
+
return 2;
|
|
360
|
+
return 1;
|
|
361
|
+
}
|
|
@@ -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))
|
|
@@ -2,18 +2,24 @@ 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;
|
|
8
9
|
readonly refreshActivity: (session: NodeAgentSession) => Promise<void>;
|
|
9
10
|
readonly present: (session: NodeAgentSession) => unknown;
|
|
10
11
|
readonly readPage: (session: NodeAgentSession, limit: number) => Promise<TPage>;
|
|
12
|
+
readonly convergePage: (previous: TPage | undefined, previousObserved: TPage | undefined, next: TPage, previousAuthority: unknown) => {
|
|
13
|
+
readonly page: TPage;
|
|
14
|
+
readonly authority: unknown;
|
|
15
|
+
};
|
|
11
16
|
readonly projectInitialPage?: (session: NodeAgentSession, page: TPage, unit: 'SEGMENT', limit: number) => unknown;
|
|
12
17
|
readonly emitSession: (session: unknown) => void;
|
|
13
|
-
readonly
|
|
18
|
+
readonly emitPage: (session: NodeAgentSession, page: TPage) => void;
|
|
14
19
|
}
|
|
15
20
|
export declare class NativeSessionWatchService<TPage extends {
|
|
16
21
|
readonly turns: readonly unknown[];
|
|
22
|
+
readonly source?: unknown;
|
|
17
23
|
}> {
|
|
18
24
|
private readonly options;
|
|
19
25
|
private readonly watches;
|
|
@@ -33,6 +39,6 @@ export declare class NativeSessionWatchService<TPage extends {
|
|
|
33
39
|
readonly expiresAt: number;
|
|
34
40
|
} | undefined;
|
|
35
41
|
get size(): number;
|
|
36
|
-
refresh(sessionId: string, limit?: number): Promise<TPage | undefined>;
|
|
42
|
+
refresh(sessionId: string, limit?: number, emit?: boolean): Promise<TPage | undefined>;
|
|
37
43
|
private performRefresh;
|
|
38
44
|
}
|
|
@@ -3,6 +3,7 @@ const INTERVAL_MS = 2_000;
|
|
|
3
3
|
const TTL_MS = 45_000;
|
|
4
4
|
const MAX_FAILURES = 3;
|
|
5
5
|
const INITIAL_TURN_LIMIT = 30;
|
|
6
|
+
const EMITTED_TURN_LIMIT = 10;
|
|
6
7
|
export class NativeSessionWatchService {
|
|
7
8
|
options;
|
|
8
9
|
watches = new Map();
|
|
@@ -32,7 +33,7 @@ export class NativeSessionWatchService {
|
|
|
32
33
|
return { renewed: true };
|
|
33
34
|
}
|
|
34
35
|
const watch = this.watch(session);
|
|
35
|
-
const turnSnapshot = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT);
|
|
36
|
+
const turnSnapshot = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT, false);
|
|
36
37
|
if (watch !== undefined && turnSnapshot === undefined) {
|
|
37
38
|
this.stop(session.id);
|
|
38
39
|
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
@@ -68,6 +69,9 @@ export class NativeSessionWatchService {
|
|
|
68
69
|
refresh: undefined,
|
|
69
70
|
sessionSignature: undefined,
|
|
70
71
|
signature: undefined,
|
|
72
|
+
page: undefined,
|
|
73
|
+
observedPage: undefined,
|
|
74
|
+
authority: undefined,
|
|
71
75
|
failures: 0,
|
|
72
76
|
timer: undefined
|
|
73
77
|
};
|
|
@@ -110,7 +114,7 @@ export class NativeSessionWatchService {
|
|
|
110
114
|
get size() {
|
|
111
115
|
return this.watches.size;
|
|
112
116
|
}
|
|
113
|
-
async refresh(sessionId, limit = 10) {
|
|
117
|
+
async refresh(sessionId, limit = 10, emit = true) {
|
|
114
118
|
const watch = this.watches.get(sessionId);
|
|
115
119
|
if (watch === undefined)
|
|
116
120
|
return undefined;
|
|
@@ -120,7 +124,7 @@ export class NativeSessionWatchService {
|
|
|
120
124
|
this.stop(sessionId);
|
|
121
125
|
return undefined;
|
|
122
126
|
}
|
|
123
|
-
const refresh = this.performRefresh(sessionId, watch, limit);
|
|
127
|
+
const refresh = this.performRefresh(sessionId, watch, limit, emit);
|
|
124
128
|
watch.refresh = refresh;
|
|
125
129
|
try {
|
|
126
130
|
return await refresh;
|
|
@@ -130,7 +134,7 @@ export class NativeSessionWatchService {
|
|
|
130
134
|
watch.refresh = undefined;
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
|
-
async performRefresh(sessionId, watch, limit) {
|
|
137
|
+
async performRefresh(sessionId, watch, limit, emit) {
|
|
134
138
|
try {
|
|
135
139
|
const session = await this.options.resolve(sessionId);
|
|
136
140
|
if (session === undefined ||
|
|
@@ -148,14 +152,28 @@ export class NativeSessionWatchService {
|
|
|
148
152
|
}
|
|
149
153
|
const page = await this.options.readPage(session, limit);
|
|
150
154
|
watch.failures = 0;
|
|
151
|
-
const turns = page.turns.length <=
|
|
152
|
-
|
|
155
|
+
const turns = page.turns.length <= EMITTED_TURN_LIMIT
|
|
156
|
+
? page.turns
|
|
157
|
+
: page.turns.slice(-EMITTED_TURN_LIMIT);
|
|
158
|
+
const observed = { ...page, turns };
|
|
159
|
+
const convergence = this.options.convergePage(watch.page, watch.observedPage, observed, watch.authority);
|
|
160
|
+
const converged = convergence.page;
|
|
161
|
+
const accepted = converged.turns.length <= EMITTED_TURN_LIMIT + 1
|
|
162
|
+
? converged
|
|
163
|
+
: {
|
|
164
|
+
...converged,
|
|
165
|
+
turns: converged.turns.slice(-(EMITTED_TURN_LIMIT + 1))
|
|
166
|
+
};
|
|
167
|
+
watch.observedPage = observed;
|
|
168
|
+
watch.authority = convergence.authority;
|
|
169
|
+
const signature = JSON.stringify({ source: accepted.source, turns: accepted.turns });
|
|
153
170
|
if (signature === watch.signature)
|
|
154
|
-
return
|
|
171
|
+
return accepted;
|
|
155
172
|
watch.signature = signature;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
173
|
+
watch.page = accepted;
|
|
174
|
+
if (emit)
|
|
175
|
+
this.options.emitPage(session, accepted);
|
|
176
|
+
return emit ? accepted : page;
|
|
159
177
|
}
|
|
160
178
|
catch {
|
|
161
179
|
watch.failures += 1;
|
|
@@ -23,7 +23,8 @@ export class NodeRequestService {
|
|
|
23
23
|
? {
|
|
24
24
|
...payload.data,
|
|
25
25
|
__workspaceWatchSessionHash: payload.userAuthorization
|
|
26
|
-
.sessionHash
|
|
26
|
+
.sessionHash,
|
|
27
|
+
__requestUserId: payload.userAuthorization.userId
|
|
27
28
|
}
|
|
28
29
|
: payload.data;
|
|
29
30
|
const result = await this.options.execute(payload.operation, data);
|
|
@@ -95,11 +95,19 @@ export class RunEventService {
|
|
|
95
95
|
this.options.emitWorkbench('conversation', {
|
|
96
96
|
event: { ...event, createdAt: persisted?.createdAt ?? Date.now() }
|
|
97
97
|
});
|
|
98
|
-
if (notificationPayload !== undefined)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
if (notificationPayload !== undefined) {
|
|
99
|
+
const occurredAt = persisted?.createdAt ?? Date.now();
|
|
100
|
+
nodeLog('run.event', { runId, eventType: 'notification' });
|
|
101
|
+
this.options.emitWorkbench('conversation', {
|
|
102
|
+
event: {
|
|
103
|
+
runId,
|
|
104
|
+
sequence: event.sequence,
|
|
105
|
+
eventType: 'notification',
|
|
106
|
+
payload: { ...notificationPayload, occurredAt },
|
|
107
|
+
createdAt: occurredAt
|
|
108
|
+
}
|
|
102
109
|
});
|
|
110
|
+
}
|
|
103
111
|
if (status === undefined || !isTerminalRunStatus(status))
|
|
104
112
|
return;
|
|
105
113
|
const turn = this.options.runtime.conversationTurnForRun(runId);
|
|
@@ -126,11 +134,14 @@ export class RunEventService {
|
|
|
126
134
|
if (session === undefined)
|
|
127
135
|
return undefined;
|
|
128
136
|
const base = {
|
|
137
|
+
userId: run.initiatedByUserId,
|
|
129
138
|
workspaceId: run.workspaceId,
|
|
130
139
|
sessionId: run.sessionId,
|
|
131
140
|
sessionTitle: session.customTitle ?? session.runnerTitle ?? session.id,
|
|
132
141
|
runId
|
|
133
142
|
};
|
|
143
|
+
if (!Number.isSafeInteger(base.userId))
|
|
144
|
+
return undefined;
|
|
134
145
|
if (status !== undefined && isTerminalRunStatus(status)) {
|
|
135
146
|
if (this.options.state.interruptRequested.has(runId))
|
|
136
147
|
return undefined;
|
|
@@ -159,7 +170,7 @@ export class RunEventService {
|
|
|
159
170
|
if (payload.kind === 'plan' && typeof payload.itemId === 'string')
|
|
160
171
|
return {
|
|
161
172
|
...base,
|
|
162
|
-
notificationId: `plan:${run.workspaceId}:${
|
|
173
|
+
notificationId: `plan:${run.workspaceId}:${runId}:${payload.itemId}`,
|
|
163
174
|
kind: 'PLAN_READY'
|
|
164
175
|
};
|
|
165
176
|
if (payload.kind === 'user_input_request' &&
|
|
@@ -112,25 +112,16 @@ export class SessionLifecycleService {
|
|
|
112
112
|
async updateConfiguration(input) {
|
|
113
113
|
if (typeof input.sessionId !== 'string' ||
|
|
114
114
|
typeof input.model !== 'string' ||
|
|
115
|
+
!validSessionOption(input.model) ||
|
|
115
116
|
typeof input.effort !== 'string' ||
|
|
116
117
|
!validSessionEffort(input.effort) ||
|
|
117
|
-
typeof input.access !== 'string'
|
|
118
|
+
typeof input.access !== 'string' ||
|
|
119
|
+
!validSessionOption(input.access))
|
|
118
120
|
throw new Error('SESSION_INVALID');
|
|
119
121
|
const current = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
120
122
|
(await this.options.projection.createMetadataProjection(input.sessionId));
|
|
121
123
|
if (current === undefined)
|
|
122
124
|
throw new Error('SESSION_NOT_FOUND');
|
|
123
|
-
const configuration = {
|
|
124
|
-
runner: current.runner,
|
|
125
|
-
model: input.model,
|
|
126
|
-
effort: input.effort,
|
|
127
|
-
access: input.access
|
|
128
|
-
};
|
|
129
|
-
const workspace = this.options.database().getWorkspace(current.workspaceId);
|
|
130
|
-
const environment = parseSecretEnvironment(input.secretEnvironment);
|
|
131
|
-
if (workspace === undefined ||
|
|
132
|
-
!this.options.runners.supportsConfiguration(configuration, workspace, environment))
|
|
133
|
-
throw new Error('RUNNER_CONFIGURATION_UNSUPPORTED');
|
|
134
125
|
const session = this.options.runtime.updateAgentSessionConfiguration({
|
|
135
126
|
sessionId: current.id,
|
|
136
127
|
model: input.model,
|
|
@@ -209,6 +200,7 @@ export class SessionLifecycleService {
|
|
|
209
200
|
sessionId: messageSessionId,
|
|
210
201
|
clientMessageId: input.clientMessageId,
|
|
211
202
|
content: input.content,
|
|
203
|
+
...(input.__requestUserId === undefined ? {} : { __requestUserId: input.__requestUserId }),
|
|
212
204
|
...(input.secretEnvironment === undefined
|
|
213
205
|
? {}
|
|
214
206
|
: { secretEnvironment: input.secretEnvironment }),
|
|
@@ -23,6 +23,7 @@ interface SessionMessageServiceOptions {
|
|
|
23
23
|
}
|
|
24
24
|
export declare class SessionMessageService {
|
|
25
25
|
private readonly options;
|
|
26
|
+
private readonly pendingMessages;
|
|
26
27
|
constructor(options: SessionMessageServiceOptions);
|
|
27
28
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
28
29
|
private message;
|