@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
|
@@ -3,15 +3,19 @@ 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();
|
|
10
|
+
leases = new Map();
|
|
11
|
+
snapshotSequence = 0;
|
|
9
12
|
constructor(options) {
|
|
10
13
|
this.options = options;
|
|
11
14
|
}
|
|
12
15
|
operations() {
|
|
13
16
|
return {
|
|
14
|
-
'session.watch': (data) => this.executeWatch(isPlainRecord(data) ? data : {})
|
|
17
|
+
'session.watch': (data) => this.executeWatch(isPlainRecord(data) ? data : {}),
|
|
18
|
+
'session.unwatch': (data) => this.executeUnwatch(isPlainRecord(data) ? data : {})
|
|
15
19
|
};
|
|
16
20
|
}
|
|
17
21
|
async executeWatch(input) {
|
|
@@ -19,39 +23,75 @@ export class NativeSessionWatchService {
|
|
|
19
23
|
if (input.unit === 'SEGMENT' && projectInitialPage === undefined)
|
|
20
24
|
throw new Error('SESSION_INVALID');
|
|
21
25
|
if (typeof input.sessionId !== 'string' ||
|
|
26
|
+
(input.workspaceId !== undefined && typeof input.workspaceId !== 'string') ||
|
|
27
|
+
typeof input.watchId !== 'string' ||
|
|
28
|
+
typeof input.workbenchConnectionId !== 'string' ||
|
|
22
29
|
(input.mode !== 'initial' && input.mode !== 'renew') ||
|
|
23
30
|
(input.unit !== undefined && input.unit !== 'SEGMENT'))
|
|
24
31
|
throw new Error('SESSION_INVALID');
|
|
25
|
-
const
|
|
26
|
-
if (session === undefined || session.externalSessionId === null)
|
|
27
|
-
throw new Error('SESSION_NOT_FOUND');
|
|
32
|
+
const ownerKey = this.ownerKey(input);
|
|
28
33
|
if (input.mode === 'renew') {
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
34
|
+
const lease = this.leases.get(ownerKey);
|
|
35
|
+
if (lease === undefined ||
|
|
36
|
+
lease.watchId !== input.watchId ||
|
|
37
|
+
lease.sessionId !== input.sessionId ||
|
|
38
|
+
(input.workspaceId !== undefined && lease.workspaceId !== input.workspaceId))
|
|
39
|
+
throw new Error('SESSION_WATCH_NOT_FOUND');
|
|
40
|
+
if (lease.expiresAt <= Date.now()) {
|
|
41
|
+
this.removeLease(ownerKey);
|
|
31
42
|
throw new Error('SESSION_WATCH_NOT_FOUND');
|
|
32
|
-
return { renewed: true };
|
|
33
|
-
}
|
|
34
|
-
const watch = this.watch(session);
|
|
35
|
-
const turnSnapshot = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT);
|
|
36
|
-
if (watch !== undefined && turnSnapshot === undefined) {
|
|
37
|
-
this.stop(session.id);
|
|
38
|
-
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
39
|
-
}
|
|
40
|
-
let snapshot = turnSnapshot;
|
|
41
|
-
if (turnSnapshot !== undefined && input.unit === 'SEGMENT') {
|
|
42
|
-
try {
|
|
43
|
-
snapshot = projectInitialPage(session, turnSnapshot, 'SEGMENT', 10);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
this.stop(session.id);
|
|
47
|
-
throw error;
|
|
48
43
|
}
|
|
44
|
+
lease.expiresAt = Date.now() + TTL_MS;
|
|
45
|
+
const observer = this.watches.get(lease.sessionId);
|
|
46
|
+
if (observer !== undefined)
|
|
47
|
+
observer.expiresAt = this.latestLeaseExpiry(lease.sessionId);
|
|
48
|
+
clearTimeout(lease.timer);
|
|
49
|
+
lease.timer = this.leaseTimer(ownerKey);
|
|
50
|
+
return this.accepted(lease);
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const existing = this.leases.get(ownerKey);
|
|
53
|
+
const unit = input.unit === 'SEGMENT' ? 'SEGMENT' : 'TURN';
|
|
54
|
+
if (existing?.watchId === input.watchId &&
|
|
55
|
+
existing.sessionId === input.sessionId &&
|
|
56
|
+
(input.workspaceId === undefined || existing.workspaceId === input.workspaceId) &&
|
|
57
|
+
existing.unit === unit &&
|
|
58
|
+
existing.expiresAt > Date.now()) {
|
|
59
|
+
existing.expiresAt = Date.now() + TTL_MS;
|
|
60
|
+
clearTimeout(existing.timer);
|
|
61
|
+
existing.timer = this.leaseTimer(ownerKey);
|
|
62
|
+
return this.accepted(existing);
|
|
63
|
+
}
|
|
64
|
+
this.removeLease(ownerKey);
|
|
65
|
+
const lease = {
|
|
66
|
+
ownerKey,
|
|
67
|
+
sessionHash: this.sessionHash(input),
|
|
68
|
+
workbenchConnectionId: input.workbenchConnectionId,
|
|
69
|
+
watchId: input.watchId,
|
|
70
|
+
sessionId: input.sessionId,
|
|
71
|
+
unit,
|
|
72
|
+
nodeGeneration: this.options.nodeGeneration?.() ?? 'local-test-generation',
|
|
73
|
+
...(typeof input.workspaceId === 'string' ? { workspaceId: input.workspaceId } : {}),
|
|
74
|
+
expiresAt: Date.now() + TTL_MS,
|
|
75
|
+
timer: undefined
|
|
54
76
|
};
|
|
77
|
+
lease.timer = this.leaseTimer(ownerKey);
|
|
78
|
+
this.leases.set(ownerKey, lease);
|
|
79
|
+
const observer = this.watches.get(lease.sessionId);
|
|
80
|
+
if (observer !== undefined)
|
|
81
|
+
observer.expiresAt = this.latestLeaseExpiry(lease.sessionId);
|
|
82
|
+
void this.initializeLease(lease, projectInitialPage);
|
|
83
|
+
return this.accepted(lease);
|
|
84
|
+
}
|
|
85
|
+
executeUnwatch(input) {
|
|
86
|
+
if (typeof input.sessionId !== 'string' ||
|
|
87
|
+
typeof input.watchId !== 'string' ||
|
|
88
|
+
typeof input.workbenchConnectionId !== 'string')
|
|
89
|
+
throw new Error('SESSION_INVALID');
|
|
90
|
+
const ownerKey = this.ownerKey(input);
|
|
91
|
+
const lease = this.leases.get(ownerKey);
|
|
92
|
+
if (lease?.watchId === input.watchId && lease.sessionId === input.sessionId)
|
|
93
|
+
this.removeLease(ownerKey);
|
|
94
|
+
return { stopped: true };
|
|
55
95
|
}
|
|
56
96
|
watch(session) {
|
|
57
97
|
if (this.options.managedActive(session)) {
|
|
@@ -68,11 +108,13 @@ export class NativeSessionWatchService {
|
|
|
68
108
|
refresh: undefined,
|
|
69
109
|
sessionSignature: undefined,
|
|
70
110
|
signature: undefined,
|
|
111
|
+
page: undefined,
|
|
112
|
+
observedPage: undefined,
|
|
113
|
+
authority: undefined,
|
|
71
114
|
failures: 0,
|
|
72
115
|
timer: undefined
|
|
73
116
|
};
|
|
74
|
-
watch.timer =
|
|
75
|
-
watch.timer.unref();
|
|
117
|
+
watch.timer = this.refreshTimer(session.id);
|
|
76
118
|
this.watches.set(session.id, watch);
|
|
77
119
|
return { expiresAt: watch.expiresAt };
|
|
78
120
|
}
|
|
@@ -93,13 +135,16 @@ export class NativeSessionWatchService {
|
|
|
93
135
|
const watch = this.watches.get(sessionId);
|
|
94
136
|
if (watch === undefined)
|
|
95
137
|
return;
|
|
96
|
-
|
|
138
|
+
clearTimeout(watch.timer);
|
|
97
139
|
this.watches.delete(sessionId);
|
|
98
140
|
}
|
|
99
141
|
clear() {
|
|
100
142
|
for (const watch of this.watches.values())
|
|
101
|
-
|
|
143
|
+
clearTimeout(watch.timer);
|
|
102
144
|
this.watches.clear();
|
|
145
|
+
for (const lease of this.leases.values())
|
|
146
|
+
clearTimeout(lease.timer);
|
|
147
|
+
this.leases.clear();
|
|
103
148
|
}
|
|
104
149
|
has(sessionId) {
|
|
105
150
|
return this.watches.has(sessionId);
|
|
@@ -110,7 +155,7 @@ export class NativeSessionWatchService {
|
|
|
110
155
|
get size() {
|
|
111
156
|
return this.watches.size;
|
|
112
157
|
}
|
|
113
|
-
async refresh(sessionId, limit = 10) {
|
|
158
|
+
async refresh(sessionId, limit = 10, emit = true) {
|
|
114
159
|
const watch = this.watches.get(sessionId);
|
|
115
160
|
if (watch === undefined)
|
|
116
161
|
return undefined;
|
|
@@ -120,26 +165,33 @@ export class NativeSessionWatchService {
|
|
|
120
165
|
this.stop(sessionId);
|
|
121
166
|
return undefined;
|
|
122
167
|
}
|
|
123
|
-
const refresh = this.performRefresh(sessionId, watch, limit);
|
|
168
|
+
const refresh = this.performRefresh(sessionId, watch, limit, emit);
|
|
124
169
|
watch.refresh = refresh;
|
|
125
170
|
try {
|
|
126
171
|
return await refresh;
|
|
127
172
|
}
|
|
128
173
|
finally {
|
|
129
|
-
if (this.watches.get(sessionId) === watch)
|
|
174
|
+
if (this.watches.get(sessionId) === watch) {
|
|
130
175
|
watch.refresh = undefined;
|
|
176
|
+
clearTimeout(watch.timer);
|
|
177
|
+
watch.timer = this.refreshTimer(sessionId);
|
|
178
|
+
}
|
|
131
179
|
}
|
|
132
180
|
}
|
|
133
|
-
async performRefresh(sessionId, watch, limit) {
|
|
181
|
+
async performRefresh(sessionId, watch, limit, emit) {
|
|
134
182
|
try {
|
|
183
|
+
if (this.options.suspended?.(sessionId) === true)
|
|
184
|
+
return watch.page;
|
|
135
185
|
const session = await this.options.resolve(sessionId);
|
|
136
186
|
if (session === undefined ||
|
|
137
187
|
session.externalSessionId === null ||
|
|
138
188
|
this.options.managedActive(session)) {
|
|
139
|
-
this.
|
|
189
|
+
this.releaseSessionLeases(sessionId);
|
|
140
190
|
return undefined;
|
|
141
191
|
}
|
|
142
192
|
await this.options.refreshActivity(session);
|
|
193
|
+
if (this.options.suspended?.(sessionId) === true)
|
|
194
|
+
return watch.page;
|
|
143
195
|
const presented = this.options.present(session);
|
|
144
196
|
const sessionSignature = JSON.stringify(presented);
|
|
145
197
|
if (sessionSignature !== watch.sessionSignature) {
|
|
@@ -148,20 +200,184 @@ export class NativeSessionWatchService {
|
|
|
148
200
|
}
|
|
149
201
|
const page = await this.options.readPage(session, limit);
|
|
150
202
|
watch.failures = 0;
|
|
151
|
-
const turns = page.turns.length <=
|
|
152
|
-
|
|
203
|
+
const turns = page.turns.length <= EMITTED_TURN_LIMIT
|
|
204
|
+
? page.turns
|
|
205
|
+
: page.turns.slice(-EMITTED_TURN_LIMIT);
|
|
206
|
+
const observed = { ...page, turns };
|
|
207
|
+
const convergence = this.options.convergePage(watch.page, watch.observedPage, observed, watch.authority);
|
|
208
|
+
const converged = convergence.page;
|
|
209
|
+
const accepted = converged.turns.length <= EMITTED_TURN_LIMIT + 1
|
|
210
|
+
? converged
|
|
211
|
+
: {
|
|
212
|
+
...converged,
|
|
213
|
+
turns: converged.turns.slice(-(EMITTED_TURN_LIMIT + 1))
|
|
214
|
+
};
|
|
215
|
+
watch.observedPage = observed;
|
|
216
|
+
watch.authority = convergence.authority;
|
|
217
|
+
const signature = JSON.stringify({ source: accepted.source, turns: accepted.turns });
|
|
153
218
|
if (signature === watch.signature)
|
|
154
|
-
return
|
|
219
|
+
return accepted;
|
|
155
220
|
watch.signature = signature;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
221
|
+
watch.page = accepted;
|
|
222
|
+
if (emit)
|
|
223
|
+
this.emitObservedPage(session, accepted);
|
|
224
|
+
return emit ? accepted : page;
|
|
159
225
|
}
|
|
160
226
|
catch {
|
|
161
227
|
watch.failures += 1;
|
|
162
228
|
if (watch.failures >= MAX_FAILURES)
|
|
163
|
-
this.
|
|
229
|
+
this.failSession(sessionId);
|
|
164
230
|
return undefined;
|
|
165
231
|
}
|
|
166
232
|
}
|
|
233
|
+
sessionHash(input) {
|
|
234
|
+
return typeof input.__workspaceWatchSessionHash === 'string'
|
|
235
|
+
? input.__workspaceWatchSessionHash
|
|
236
|
+
: 'local-test';
|
|
237
|
+
}
|
|
238
|
+
ownerKey(input) {
|
|
239
|
+
return `${this.sessionHash(input)}:${input.workbenchConnectionId}`;
|
|
240
|
+
}
|
|
241
|
+
accepted(lease) {
|
|
242
|
+
return {
|
|
243
|
+
accepted: true,
|
|
244
|
+
watchId: lease.watchId,
|
|
245
|
+
nodeGeneration: lease.nodeGeneration,
|
|
246
|
+
expiresAt: lease.expiresAt
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
async initializeLease(lease, projectInitialPage) {
|
|
250
|
+
try {
|
|
251
|
+
const session = await this.options.resolve(lease.sessionId);
|
|
252
|
+
if (session === undefined || session.externalSessionId === null)
|
|
253
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
254
|
+
if (lease.workspaceId !== undefined && session.workspaceId !== lease.workspaceId)
|
|
255
|
+
throw new Error('SESSION_WORKSPACE_MISMATCH');
|
|
256
|
+
if (this.leases.get(lease.ownerKey) !== lease)
|
|
257
|
+
return;
|
|
258
|
+
lease.workspaceId = session.workspaceId;
|
|
259
|
+
const watch = this.watch(session);
|
|
260
|
+
const page = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT, false);
|
|
261
|
+
if (page === undefined)
|
|
262
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
263
|
+
const payload = lease.unit === 'SEGMENT' ? projectInitialPage(session, page, 'SEGMENT', 10) : page;
|
|
264
|
+
if (this.leases.get(lease.ownerKey) !== lease)
|
|
265
|
+
return;
|
|
266
|
+
this.options.emitWatchEvent?.({
|
|
267
|
+
type: 'watch.session.snapshot',
|
|
268
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
269
|
+
watchId: lease.watchId,
|
|
270
|
+
nodeGeneration: lease.nodeGeneration,
|
|
271
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
272
|
+
workspaceId: session.workspaceId,
|
|
273
|
+
sessionId: session.id,
|
|
274
|
+
revision: 1,
|
|
275
|
+
snapshotSequence: ++this.snapshotSequence,
|
|
276
|
+
expiresAt: lease.expiresAt,
|
|
277
|
+
payload
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (this.leases.get(lease.ownerKey) !== lease)
|
|
282
|
+
return;
|
|
283
|
+
this.options.emitWatchEvent?.({
|
|
284
|
+
type: 'watch.failed',
|
|
285
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
286
|
+
watchType: 'session',
|
|
287
|
+
watchId: lease.watchId,
|
|
288
|
+
nodeGeneration: lease.nodeGeneration,
|
|
289
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
290
|
+
workspaceId: lease.workspaceId ?? 'unknown',
|
|
291
|
+
sessionId: lease.sessionId,
|
|
292
|
+
code: error instanceof Error && /^[A-Z][A-Z0-9_]+$/.test(error.message)
|
|
293
|
+
? error.message
|
|
294
|
+
: 'SESSION_WATCH_SNAPSHOT_FAILED',
|
|
295
|
+
message: 'Session watch snapshot failed.'
|
|
296
|
+
});
|
|
297
|
+
this.removeLease(lease.ownerKey);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
removeLease(ownerKey) {
|
|
301
|
+
const lease = this.leases.get(ownerKey);
|
|
302
|
+
if (lease === undefined)
|
|
303
|
+
return;
|
|
304
|
+
clearTimeout(lease.timer);
|
|
305
|
+
this.leases.delete(ownerKey);
|
|
306
|
+
const observer = this.watches.get(lease.sessionId);
|
|
307
|
+
const nextExpiry = this.latestLeaseExpiry(lease.sessionId);
|
|
308
|
+
if (nextExpiry === 0)
|
|
309
|
+
this.stop(lease.sessionId);
|
|
310
|
+
else if (observer !== undefined)
|
|
311
|
+
observer.expiresAt = nextExpiry;
|
|
312
|
+
}
|
|
313
|
+
emitObservedPage(session, page) {
|
|
314
|
+
const leases = [...this.leases.values()].filter((lease) => lease.sessionId === session.id);
|
|
315
|
+
if (leases.length === 0) {
|
|
316
|
+
this.options.emitPage(session, page);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
for (const lease of leases) {
|
|
320
|
+
const payload = lease.unit === 'SEGMENT'
|
|
321
|
+
? this.options.projectInitialPage?.(session, page, 'SEGMENT', 10)
|
|
322
|
+
: page;
|
|
323
|
+
if (payload === undefined)
|
|
324
|
+
continue;
|
|
325
|
+
this.options.emitWatchEvent?.({
|
|
326
|
+
type: 'watch.session.snapshot',
|
|
327
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
328
|
+
watchId: lease.watchId,
|
|
329
|
+
nodeGeneration: lease.nodeGeneration,
|
|
330
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
331
|
+
workspaceId: session.workspaceId,
|
|
332
|
+
sessionId: session.id,
|
|
333
|
+
revision: 1,
|
|
334
|
+
snapshotSequence: ++this.snapshotSequence,
|
|
335
|
+
expiresAt: lease.expiresAt,
|
|
336
|
+
payload
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
failSession(sessionId) {
|
|
341
|
+
for (const lease of [...this.leases.values()]) {
|
|
342
|
+
if (lease.sessionId !== sessionId)
|
|
343
|
+
continue;
|
|
344
|
+
this.options.emitWatchEvent?.({
|
|
345
|
+
type: 'watch.failed',
|
|
346
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
347
|
+
watchType: 'session',
|
|
348
|
+
watchId: lease.watchId,
|
|
349
|
+
nodeGeneration: lease.nodeGeneration,
|
|
350
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
351
|
+
workspaceId: lease.workspaceId ?? 'unknown',
|
|
352
|
+
sessionId,
|
|
353
|
+
code: 'NATIVE_TRANSCRIPT_UNAVAILABLE',
|
|
354
|
+
message: 'Session watch snapshot failed.'
|
|
355
|
+
});
|
|
356
|
+
this.removeLease(lease.ownerKey);
|
|
357
|
+
}
|
|
358
|
+
this.stop(sessionId);
|
|
359
|
+
}
|
|
360
|
+
releaseSessionLeases(sessionId) {
|
|
361
|
+
for (const lease of [...this.leases.values()])
|
|
362
|
+
if (lease.sessionId === sessionId)
|
|
363
|
+
this.removeLease(lease.ownerKey);
|
|
364
|
+
this.stop(sessionId);
|
|
365
|
+
}
|
|
366
|
+
leaseTimer(ownerKey) {
|
|
367
|
+
const timer = setTimeout(() => this.removeLease(ownerKey), TTL_MS);
|
|
368
|
+
timer.unref();
|
|
369
|
+
return timer;
|
|
370
|
+
}
|
|
371
|
+
latestLeaseExpiry(sessionId) {
|
|
372
|
+
let latest = 0;
|
|
373
|
+
for (const lease of this.leases.values())
|
|
374
|
+
if (lease.sessionId === sessionId)
|
|
375
|
+
latest = Math.max(latest, lease.expiresAt);
|
|
376
|
+
return latest;
|
|
377
|
+
}
|
|
378
|
+
refreshTimer(sessionId) {
|
|
379
|
+
const timer = setTimeout(() => void this.refresh(sessionId), INTERVAL_MS);
|
|
380
|
+
timer.unref();
|
|
381
|
+
return timer;
|
|
382
|
+
}
|
|
167
383
|
}
|
|
@@ -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' &&
|
|
@@ -4,10 +4,8 @@ export interface RunWorkbenchServiceOptions {
|
|
|
4
4
|
readonly runtime: NodeRuntimeState;
|
|
5
5
|
readonly readImage: (runId: string, imageIndex: number) => Promise<unknown | undefined>;
|
|
6
6
|
readonly respondUserInput: (runId: string, requestId: string, answers: Record<string, unknown>) => void;
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
9
|
-
readonly cleanupAttachments: (runId: string) => void;
|
|
10
|
-
readonly emitQueue: (workspaceId: string, sessionId: string) => void;
|
|
7
|
+
readonly cancelQueued: (runId: string) => void;
|
|
8
|
+
readonly pauseQueue: (sessionId: string) => void;
|
|
11
9
|
readonly emitRun: (runId: string, eventType: string, payload: unknown, status?: 'CANCELLED') => void;
|
|
12
10
|
readonly control: (operation: string, payload: Record<string, unknown>) => void;
|
|
13
11
|
readonly markInterrupted: (runId: string) => void;
|
|
@@ -55,11 +55,7 @@ export class RunWorkbenchService {
|
|
|
55
55
|
if (operation === 'run.cancel' &&
|
|
56
56
|
run.status === 'QUEUED' &&
|
|
57
57
|
this.options.runtime.isQueuedRun(run.id)) {
|
|
58
|
-
this.options.
|
|
59
|
-
this.options.deleteQueuedStart(run.id);
|
|
60
|
-
this.options.clearImages(run.id);
|
|
61
|
-
this.options.cleanupAttachments(run.id);
|
|
62
|
-
this.options.emitQueue(run.workspaceId, run.sessionId);
|
|
58
|
+
this.options.cancelQueued(run.id);
|
|
63
59
|
return { cancelled: true };
|
|
64
60
|
}
|
|
65
61
|
const next = this.options.runtime.transitionRun(run.id, operation === 'run.cancel' && run.status === 'QUEUED' ? 'CANCELLED' : 'CANCELLING');
|
|
@@ -68,8 +64,7 @@ export class RunWorkbenchService {
|
|
|
68
64
|
return { run: next };
|
|
69
65
|
}
|
|
70
66
|
if (operation === 'run.interrupt') {
|
|
71
|
-
this.options.
|
|
72
|
-
this.options.emitQueue(run.workspaceId, run.sessionId);
|
|
67
|
+
this.options.pauseQueue(run.sessionId);
|
|
73
68
|
this.options.markInterrupted(run.id);
|
|
74
69
|
}
|
|
75
70
|
this.options.control(operation, { runId: run.id });
|
|
@@ -36,7 +36,7 @@ export class SessionCommandService {
|
|
|
36
36
|
input.input.length > 20_000)
|
|
37
37
|
throw new Error('RUNNER_COMMAND_INVALID');
|
|
38
38
|
const resolved = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
39
|
-
(await this.options.projection.
|
|
39
|
+
(await this.options.projection.resolveCurrent(input.sessionId));
|
|
40
40
|
if (resolved === undefined)
|
|
41
41
|
throw new Error('SESSION_NOT_FOUND');
|
|
42
42
|
const session = resolved.nativeControl === 'EXTERNAL' ? await this.options.resume.resume(resolved) : resolved;
|
|
@@ -8,7 +8,7 @@ export interface SessionIdentityServiceOptions {
|
|
|
8
8
|
readonly migrateRunnerState: (previousId: string, nextId: string) => void;
|
|
9
9
|
readonly emitSession: (session: NodeAgentSession) => void;
|
|
10
10
|
readonly emitRun: (run: unknown) => void;
|
|
11
|
-
readonly emitQueue: (
|
|
11
|
+
readonly emitQueue: (sessionId: string) => void;
|
|
12
12
|
readonly emitTurn: (turn: unknown) => void;
|
|
13
13
|
}
|
|
14
14
|
export declare class SessionIdentityService {
|
|
@@ -29,7 +29,7 @@ export class SessionIdentityService {
|
|
|
29
29
|
this.options.emitSession(session);
|
|
30
30
|
for (const run of this.options.runtime.listRunsForSession(nativeSessionId))
|
|
31
31
|
this.options.emitRun(run);
|
|
32
|
-
this.options.emitQueue(
|
|
32
|
+
this.options.emitQueue(nativeSessionId);
|
|
33
33
|
for (const turn of this.options.runtime.listConversationTurns({ sessionId: nativeSessionId })
|
|
34
34
|
.turns)
|
|
35
35
|
if (turn.status !== 'QUEUED')
|
|
@@ -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,9 +200,13 @@ 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 }),
|
|
207
|
+
...(input.personalInstructions === undefined
|
|
208
|
+
? {}
|
|
209
|
+
: { personalInstructions: input.personalInstructions }),
|
|
215
210
|
...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
|
|
216
211
|
}));
|
|
217
212
|
const nativeSessionId = await identity;
|
|
@@ -18,11 +18,11 @@ interface SessionMessageServiceOptions {
|
|
|
18
18
|
readonly resolve: (sessionId: string) => Promise<NodeAgentSession>;
|
|
19
19
|
readonly present: (session: NodeAgentSession) => unknown;
|
|
20
20
|
readonly emitSession: (session: NodeAgentSession) => void;
|
|
21
|
-
readonly emitQueue: (workspaceId: string, sessionId: string) => void;
|
|
22
21
|
readonly channelToken: (sessionId: string) => string | undefined;
|
|
23
22
|
}
|
|
24
23
|
export declare class SessionMessageService {
|
|
25
24
|
private readonly options;
|
|
25
|
+
private readonly pendingMessages;
|
|
26
26
|
constructor(options: SessionMessageServiceOptions);
|
|
27
27
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
28
28
|
private message;
|