@myagentroam/node 0.10.61 → 0.10.63
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/connector.js +106 -873
- package/dist/database.js +4 -2
- package/dist/runner/claude-code-runner.js +15 -1
- package/dist/runner/mar-agent/managed-run-controller.js +126 -521
- package/dist/runner/mar-agent/session-runtime-registry.js +271 -0
- package/dist/runner/mar-agent/session-runtime.js +282 -0
- package/dist/service/conversation-hosting-lifecycle-service.js +195 -0
- package/dist/service/conversation-observation.js +240 -0
- package/dist/service/goal-continuation-coordinator.js +622 -0
- package/dist/service/native-session-watch-service.js +88 -270
- package/dist/service/restart-session-restorer.js +1 -1
- package/dist/service/runner-interaction-service.js +27 -1
- package/dist/service/scheduled-session-coordinator.js +119 -48
- package/dist/service/session-lifecycle-service.js +5 -2
- package/package.json +4 -4
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { isPlainRecord } from '../
|
|
1
|
+
import { isPlainRecord } from '../util/node-operation-parsers.js';
|
|
2
|
+
import { ConversationObservation } from './conversation-observation.js';
|
|
2
3
|
import { safeErrorCode } from '../util/safe-error.js';
|
|
3
4
|
import { SessionWatchSlotCoordinator } from './session-watch-slot-coordinator.js';
|
|
4
5
|
const INTERVAL_MS = 2_000;
|
|
5
6
|
const TTL_MS = 45_000;
|
|
6
7
|
const MAX_FAILURES = 3;
|
|
7
|
-
const INITIAL_TURN_LIMIT = 30;
|
|
8
|
-
const EMITTED_TURN_LIMIT = 10;
|
|
9
8
|
export class NativeSessionWatchService {
|
|
10
9
|
options;
|
|
11
10
|
watches = new Map();
|
|
12
11
|
leases = new Map();
|
|
13
12
|
sessionWatchSlots;
|
|
14
|
-
|
|
13
|
+
snapshotNumber = 0;
|
|
15
14
|
constructor(options) {
|
|
16
15
|
this.options = options;
|
|
17
16
|
this.sessionWatchSlots = options.sessionWatchSlots ?? new SessionWatchSlotCoordinator();
|
|
@@ -111,23 +110,11 @@ export class NativeSessionWatchService {
|
|
|
111
110
|
return { expiresAt: existing.expiresAt };
|
|
112
111
|
}
|
|
113
112
|
const watch = {
|
|
114
|
-
session,
|
|
115
|
-
mode: this.options.managedActive(session) ? 'RUNNING' : 'HISTORY',
|
|
113
|
+
observation: new ConversationObservation(session, this.options),
|
|
116
114
|
expiresAt: Date.now() + TTL_MS,
|
|
117
115
|
refresh: undefined,
|
|
118
|
-
refreshMode: undefined,
|
|
119
116
|
sessionSignature: undefined,
|
|
120
|
-
signature: undefined,
|
|
121
|
-
page: undefined,
|
|
122
|
-
runtimePage: undefined,
|
|
123
|
-
runtimeRevision: undefined,
|
|
124
|
-
runtimeRead: undefined,
|
|
125
|
-
historyRead: undefined,
|
|
126
|
-
observedPage: undefined,
|
|
127
|
-
authority: undefined,
|
|
128
|
-
eventRevision: 0,
|
|
129
117
|
failures: 0,
|
|
130
|
-
transitionGeneration: 0,
|
|
131
118
|
timer: undefined
|
|
132
119
|
};
|
|
133
120
|
watch.timer = this.refreshTimer(session.id);
|
|
@@ -136,25 +123,16 @@ export class NativeSessionWatchService {
|
|
|
136
123
|
}
|
|
137
124
|
emitRealtime(sessionId, event) {
|
|
138
125
|
const watch = this.watches.get(sessionId);
|
|
139
|
-
if (watch !== undefined)
|
|
140
|
-
watch.eventRevision = event.sequence;
|
|
141
126
|
const leases = [...this.leases.values()].filter((lease) => lease.sessionId === sessionId);
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
watch.mode = 'RUNNING';
|
|
145
|
-
watch.transitionGeneration += 1;
|
|
127
|
+
const transition = watch?.observation.observe(event);
|
|
128
|
+
if (transition !== undefined && watch !== undefined) {
|
|
146
129
|
for (const lease of leases)
|
|
147
130
|
lease.ready = false;
|
|
148
|
-
void this.
|
|
149
|
-
}
|
|
150
|
-
else if (watch !== undefined && activity === 'IDLE' && watch.mode === 'RUNNING') {
|
|
151
|
-
watch.mode = 'CONVERGING';
|
|
152
|
-
const generation = ++watch.transitionGeneration;
|
|
153
|
-
for (const lease of leases)
|
|
154
|
-
lease.ready = false;
|
|
155
|
-
setTimeout(() => void this.convergeHistory(watch, generation), 0);
|
|
131
|
+
void this.completeRefresh(sessionId, watch, transition, 'observation');
|
|
156
132
|
}
|
|
157
133
|
for (const lease of leases) {
|
|
134
|
+
if (watch?.observation.snapshotPending === true)
|
|
135
|
+
lease.ready = false;
|
|
158
136
|
if (!lease.ready) {
|
|
159
137
|
// Observations are useful only to an already watching page, never after a snapshot.
|
|
160
138
|
if (event.category !== 'conversation' ||
|
|
@@ -181,6 +159,7 @@ export class NativeSessionWatchService {
|
|
|
181
159
|
if (watch === undefined)
|
|
182
160
|
return;
|
|
183
161
|
clearTimeout(watch.timer);
|
|
162
|
+
watch.observation.close();
|
|
184
163
|
this.watches.delete(sessionId);
|
|
185
164
|
}
|
|
186
165
|
invalidate(sessionId) {
|
|
@@ -190,8 +169,10 @@ export class NativeSessionWatchService {
|
|
|
190
169
|
this.stop(sessionId);
|
|
191
170
|
}
|
|
192
171
|
clear() {
|
|
193
|
-
for (const watch of this.watches.values())
|
|
172
|
+
for (const watch of this.watches.values()) {
|
|
194
173
|
clearTimeout(watch.timer);
|
|
174
|
+
watch.observation.close();
|
|
175
|
+
}
|
|
195
176
|
this.watches.clear();
|
|
196
177
|
for (const ownerKey of [...this.leases.keys()])
|
|
197
178
|
this.removeLease(ownerKey);
|
|
@@ -228,58 +209,26 @@ export class NativeSessionWatchService {
|
|
|
228
209
|
this.stop(sessionId);
|
|
229
210
|
return undefined;
|
|
230
211
|
}
|
|
231
|
-
|
|
232
|
-
if (this.watches.get(sessionId) === watch) {
|
|
233
|
-
watch.refresh = undefined;
|
|
234
|
-
watch.refreshMode = undefined;
|
|
235
|
-
clearTimeout(watch.timer);
|
|
236
|
-
watch.timer = this.refreshTimer(sessionId);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
watch.refresh = refresh;
|
|
240
|
-
watch.refreshMode = mode;
|
|
241
|
-
return refresh;
|
|
212
|
+
return this.completeRefresh(sessionId, watch, this.readObservation(sessionId, watch, limit, mode), mode);
|
|
242
213
|
}
|
|
243
|
-
async
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
214
|
+
async readObservation(sessionId, watch, limit, mode) {
|
|
215
|
+
const session = await this.options.resolve(sessionId);
|
|
216
|
+
if (this.watches.get(sessionId) !== watch)
|
|
217
|
+
return undefined;
|
|
218
|
+
if (session === undefined || session.externalSessionId === null) {
|
|
219
|
+
this.releaseSessionLeases(sessionId);
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
watch.observation.session = session;
|
|
223
|
+
return watch.observation.refresh(mode, limit);
|
|
224
|
+
}
|
|
225
|
+
completeRefresh(sessionId, watch, read, mode) {
|
|
226
|
+
const current = () => this.watches.get(sessionId) === watch && watch.refresh === refresh;
|
|
227
|
+
const refresh = read
|
|
228
|
+
.then((update) => {
|
|
229
|
+
if (!current() || update === undefined)
|
|
253
230
|
return undefined;
|
|
254
|
-
|
|
255
|
-
watch.session = session;
|
|
256
|
-
if (mode === 'observation' && this.options.managedActive(session))
|
|
257
|
-
return watch.page;
|
|
258
|
-
stage = 'refresh_activity';
|
|
259
|
-
await this.options.refreshActivity(session);
|
|
260
|
-
if (this.options.suspended?.(sessionId) === true)
|
|
261
|
-
return watch.page;
|
|
262
|
-
stage = 'read_page';
|
|
263
|
-
const revisionBeforeRead = watch.eventRevision;
|
|
264
|
-
const page = await (readHistoricalPage
|
|
265
|
-
? this.options.readHistoryPage(session, limit)
|
|
266
|
-
: this.options.readPage(session, limit));
|
|
267
|
-
if (revisionBeforeRead !== watch.eventRevision) {
|
|
268
|
-
// Initial still needs a full page for its new lease, whose pending events will
|
|
269
|
-
// replay above that page's sequence. Never publish this invalidated read to
|
|
270
|
-
// existing leases or replace their shared observation state.
|
|
271
|
-
if (mode !== 'initial')
|
|
272
|
-
return watch.page;
|
|
273
|
-
const initial = this.options.convergePage(watch.page, watch.observedPage, page, watch.authority).page;
|
|
274
|
-
if (watch.page === undefined && watch.signature === undefined)
|
|
275
|
-
watch.signature = JSON.stringify({
|
|
276
|
-
source: initial.source,
|
|
277
|
-
turns: initial.turns.slice(-(EMITTED_TURN_LIMIT + 1))
|
|
278
|
-
});
|
|
279
|
-
return initial;
|
|
280
|
-
}
|
|
281
|
-
stage = 'present_session';
|
|
282
|
-
const presented = this.options.present(session);
|
|
231
|
+
const presented = this.options.present(watch.observation.session);
|
|
283
232
|
const sessionSignature = JSON.stringify(presented);
|
|
284
233
|
if (sessionSignature !== watch.sessionSignature) {
|
|
285
234
|
watch.sessionSignature = sessionSignature;
|
|
@@ -287,63 +236,48 @@ export class NativeSessionWatchService {
|
|
|
287
236
|
this.options.emitSession(presented);
|
|
288
237
|
}
|
|
289
238
|
watch.failures = 0;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const converged = convergence.page;
|
|
297
|
-
const accepted = converged.turns.length <= EMITTED_TURN_LIMIT + 1
|
|
298
|
-
? converged
|
|
299
|
-
: {
|
|
300
|
-
...converged,
|
|
301
|
-
turns: converged.turns.slice(-(EMITTED_TURN_LIMIT + 1))
|
|
302
|
-
};
|
|
303
|
-
watch.observedPage = observed;
|
|
304
|
-
watch.authority = convergence.authority;
|
|
305
|
-
const completingConvergence = watch.mode === 'CONVERGING' && !this.options.managedActive(session);
|
|
306
|
-
if (completingConvergence) {
|
|
307
|
-
watch.mode = 'HISTORY';
|
|
308
|
-
watch.runtimePage = undefined;
|
|
309
|
-
watch.runtimeRevision = undefined;
|
|
310
|
-
}
|
|
311
|
-
const signature = JSON.stringify({ source: accepted.source, turns: accepted.turns });
|
|
312
|
-
const changed = signature !== watch.signature;
|
|
313
|
-
if (changed) {
|
|
314
|
-
watch.signature = signature;
|
|
315
|
-
}
|
|
316
|
-
if (changed || watch.page === undefined)
|
|
317
|
-
watch.page = accepted;
|
|
318
|
-
stage = 'emit_page';
|
|
319
|
-
if (completingConvergence && accepted.source === 'runtime') {
|
|
320
|
-
if (changed)
|
|
321
|
-
this.emitSnapshotToLeases(session, accepted, true);
|
|
322
|
-
else
|
|
323
|
-
this.releasePendingEvents(session.id);
|
|
324
|
-
}
|
|
325
|
-
else if (completingConvergence)
|
|
326
|
-
this.emitSnapshotToLeases(session, accepted, true);
|
|
327
|
-
else if (changed)
|
|
328
|
-
this.emitObservedPage(session, accepted);
|
|
329
|
-
// Initial uses the full result of the same authority decision as observation.
|
|
330
|
-
return mode === 'initial' ? converged : accepted;
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
239
|
+
this.deliverObservation(watch, update);
|
|
240
|
+
return update.page;
|
|
241
|
+
})
|
|
242
|
+
.catch((error) => {
|
|
243
|
+
if (!current())
|
|
244
|
+
return undefined;
|
|
333
245
|
watch.failures += 1;
|
|
334
246
|
this.options.log?.('session.watch.refresh.failed', {
|
|
335
247
|
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
336
248
|
sessionId,
|
|
337
|
-
|
|
338
|
-
stage,
|
|
249
|
+
runner: watch.observation.session.runner,
|
|
250
|
+
stage: watch.observation.stage,
|
|
339
251
|
errorCode: diagnosticErrorCode(error, 'SESSION_WATCH_REFRESH_FAILED'),
|
|
340
252
|
failureCount: watch.failures,
|
|
341
253
|
terminal: watch.failures >= MAX_FAILURES
|
|
342
254
|
});
|
|
343
255
|
if (watch.failures >= MAX_FAILURES)
|
|
344
256
|
this.failSession(sessionId);
|
|
257
|
+
else
|
|
258
|
+
this.releasePendingEvents(sessionId);
|
|
345
259
|
return undefined;
|
|
346
|
-
}
|
|
260
|
+
})
|
|
261
|
+
.finally(() => {
|
|
262
|
+
if (!current())
|
|
263
|
+
return;
|
|
264
|
+
watch.refresh = undefined;
|
|
265
|
+
clearTimeout(watch.timer);
|
|
266
|
+
watch.timer = this.refreshTimer(sessionId);
|
|
267
|
+
});
|
|
268
|
+
watch.refresh = refresh;
|
|
269
|
+
return refresh;
|
|
270
|
+
}
|
|
271
|
+
deliverObservation(watch, update) {
|
|
272
|
+
const session = watch.observation.session;
|
|
273
|
+
if (this.watches.get(session.id) !== watch || update === undefined)
|
|
274
|
+
return;
|
|
275
|
+
if (update.snapshot)
|
|
276
|
+
this.emitSnapshotToLeases(session, update.page, true);
|
|
277
|
+
else if (update.handoff)
|
|
278
|
+
this.releasePendingEvents(session.id);
|
|
279
|
+
else if (update.changed)
|
|
280
|
+
this.emitObservedPage(session, update.page);
|
|
347
281
|
}
|
|
348
282
|
sessionHash(input) {
|
|
349
283
|
return typeof input.__workspaceWatchSessionHash === 'string'
|
|
@@ -375,41 +309,36 @@ export class NativeSessionWatchService {
|
|
|
375
309
|
stage = 'initial_refresh';
|
|
376
310
|
this.watch(session);
|
|
377
311
|
const watch = this.watches.get(session.id);
|
|
378
|
-
if (watch
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (
|
|
385
|
-
if (activeAtStart)
|
|
386
|
-
page = this.composeRuntimePage(watch.page, await this.readRuntimePage(watch, session));
|
|
387
|
-
else {
|
|
388
|
-
// An initial subscription must read after any older observation, not join its
|
|
389
|
-
// potentially smaller or already-stale page.
|
|
390
|
-
if (watch.refresh !== undefined &&
|
|
391
|
-
(watch.page !== undefined || watch.refreshMode === 'observation'))
|
|
392
|
-
await watch.refresh;
|
|
393
|
-
page = await this.refresh(session.id, INITIAL_TURN_LIMIT, 'initial');
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
if (page === undefined)
|
|
312
|
+
if (watch === undefined)
|
|
313
|
+
return;
|
|
314
|
+
watch.observation.session = session;
|
|
315
|
+
const update = await watch.observation.initial();
|
|
316
|
+
if (this.leases.get(lease.ownerKey) !== lease || this.watches.get(session.id) !== watch)
|
|
317
|
+
return;
|
|
318
|
+
if (update === undefined)
|
|
397
319
|
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
320
|
+
watch.failures = 0;
|
|
321
|
+
this.deliverObservation(watch, update);
|
|
398
322
|
if (lease.snapshotSent)
|
|
399
323
|
return;
|
|
400
|
-
const active = this.options.managedActive(session);
|
|
401
|
-
if (active && !activeAtStart && watch !== undefined) {
|
|
402
|
-
watch.mode = 'RUNNING';
|
|
403
|
-
page = this.composeRuntimePage(page, await this.readRuntimePage(watch, session));
|
|
404
|
-
}
|
|
405
324
|
stage = 'project_snapshot';
|
|
325
|
+
const page = update.page;
|
|
406
326
|
const payload = lease.unit === 'SEGMENT' ? projectInitialPage(session, page, 'SEGMENT', 10) : page;
|
|
407
|
-
if (this.leases.get(lease.ownerKey) !== lease)
|
|
408
|
-
return;
|
|
409
327
|
this.emitSnapshot(lease, session, payload);
|
|
410
328
|
this.releaseSnapshotPendingEvents(lease, page);
|
|
411
|
-
|
|
412
|
-
|
|
329
|
+
const hydration = watch.observation.hydrate();
|
|
330
|
+
if (hydration !== undefined)
|
|
331
|
+
void hydration
|
|
332
|
+
.then((result) => this.deliverObservation(watch, result))
|
|
333
|
+
.catch((error) => {
|
|
334
|
+
if (this.watches.get(session.id) !== watch)
|
|
335
|
+
return;
|
|
336
|
+
this.options.log?.('session.watch.running-history.failed', {
|
|
337
|
+
sessionId: session.id,
|
|
338
|
+
errorCode: diagnosticErrorCode(error, 'SESSION_WATCH_HISTORY_FAILED')
|
|
339
|
+
});
|
|
340
|
+
this.releasePendingEvents(session.id);
|
|
341
|
+
});
|
|
413
342
|
}
|
|
414
343
|
catch (error) {
|
|
415
344
|
if (this.leases.get(lease.ownerKey) !== lease)
|
|
@@ -453,100 +382,6 @@ export class NativeSessionWatchService {
|
|
|
453
382
|
else if (observer !== undefined)
|
|
454
383
|
observer.expiresAt = nextExpiry;
|
|
455
384
|
}
|
|
456
|
-
composeRuntimePage(history, runtime) {
|
|
457
|
-
return this.options.composeRuntimePage?.(history, runtime) ?? runtime;
|
|
458
|
-
}
|
|
459
|
-
async readRuntimePage(watch, session) {
|
|
460
|
-
if (watch.runtimePage !== undefined && watch.runtimeRevision === watch.eventRevision)
|
|
461
|
-
return watch.runtimePage;
|
|
462
|
-
if (watch.runtimeRead !== undefined)
|
|
463
|
-
return watch.runtimeRead;
|
|
464
|
-
const revision = watch.eventRevision;
|
|
465
|
-
const read = this.options.readPage(session, INITIAL_TURN_LIMIT);
|
|
466
|
-
watch.runtimeRead = read;
|
|
467
|
-
try {
|
|
468
|
-
const page = await read;
|
|
469
|
-
if (this.watches.get(session.id) === watch) {
|
|
470
|
-
watch.runtimePage = page;
|
|
471
|
-
watch.runtimeRevision = revision;
|
|
472
|
-
}
|
|
473
|
-
return page;
|
|
474
|
-
}
|
|
475
|
-
finally {
|
|
476
|
-
if (this.watches.get(session.id) === watch && watch.runtimeRead === read)
|
|
477
|
-
watch.runtimeRead = undefined;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
async publishRuntimeSnapshot(watch, generation) {
|
|
481
|
-
const session = watch.session;
|
|
482
|
-
if (session === undefined)
|
|
483
|
-
return;
|
|
484
|
-
try {
|
|
485
|
-
const runtime = await this.readRuntimePage(watch, session);
|
|
486
|
-
if (this.watches.get(session.id) !== watch ||
|
|
487
|
-
watch.mode !== 'RUNNING' ||
|
|
488
|
-
watch.transitionGeneration !== generation)
|
|
489
|
-
return;
|
|
490
|
-
watch.runtimePage = runtime;
|
|
491
|
-
this.emitSnapshotToLeases(session, this.composeRuntimePage(watch.page, runtime), true);
|
|
492
|
-
}
|
|
493
|
-
catch (error) {
|
|
494
|
-
this.options.log?.('session.watch.runtime-snapshot.failed', {
|
|
495
|
-
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
496
|
-
sessionId: session.id,
|
|
497
|
-
runner: session.runner,
|
|
498
|
-
errorCode: diagnosticErrorCode(error, 'SESSION_WATCH_RUNTIME_SNAPSHOT_FAILED')
|
|
499
|
-
});
|
|
500
|
-
this.releasePendingEvents(session.id);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
async hydrateRunningHistory(watch, session) {
|
|
504
|
-
if (this.options.readHistoryPage === undefined || watch.historyRead !== undefined)
|
|
505
|
-
return;
|
|
506
|
-
const read = this.options.readHistoryPage(session, INITIAL_TURN_LIMIT);
|
|
507
|
-
watch.historyRead = read;
|
|
508
|
-
try {
|
|
509
|
-
const history = await read;
|
|
510
|
-
if (this.watches.get(session.id) !== watch || watch.mode !== 'RUNNING')
|
|
511
|
-
return;
|
|
512
|
-
watch.page = history;
|
|
513
|
-
watch.authority = history.source;
|
|
514
|
-
const generation = watch.transitionGeneration;
|
|
515
|
-
for (const lease of this.leases.values())
|
|
516
|
-
if (lease.sessionId === session.id)
|
|
517
|
-
lease.ready = false;
|
|
518
|
-
await this.publishRuntimeSnapshot(watch, generation);
|
|
519
|
-
}
|
|
520
|
-
catch (error) {
|
|
521
|
-
this.options.log?.('session.watch.running-history.failed', {
|
|
522
|
-
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
523
|
-
sessionId: session.id,
|
|
524
|
-
runner: session.runner,
|
|
525
|
-
errorCode: diagnosticErrorCode(error, 'SESSION_WATCH_HISTORY_FAILED')
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
finally {
|
|
529
|
-
if (this.watches.get(session.id) === watch && watch.historyRead === read)
|
|
530
|
-
watch.historyRead = undefined;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
async convergeHistory(watch, generation) {
|
|
534
|
-
if (watch.historyRead !== undefined) {
|
|
535
|
-
try {
|
|
536
|
-
await watch.historyRead;
|
|
537
|
-
}
|
|
538
|
-
catch {
|
|
539
|
-
// The running snapshot remains usable; the authoritative convergence below retries.
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
const session = watch.session;
|
|
543
|
-
if (session === undefined ||
|
|
544
|
-
this.watches.get(session.id) !== watch ||
|
|
545
|
-
watch.mode !== 'CONVERGING' ||
|
|
546
|
-
watch.transitionGeneration !== generation)
|
|
547
|
-
return;
|
|
548
|
-
await this.refresh(session.id, INITIAL_TURN_LIMIT);
|
|
549
|
-
}
|
|
550
385
|
emitLeaseEvent(lease, event) {
|
|
551
386
|
this.options.emitWatchEvent?.({
|
|
552
387
|
type: 'watch.session.event',
|
|
@@ -583,7 +418,7 @@ export class NativeSessionWatchService {
|
|
|
583
418
|
workspaceId: session.workspaceId,
|
|
584
419
|
sessionId: session.id,
|
|
585
420
|
revision: 1,
|
|
586
|
-
snapshotSequence: ++this.
|
|
421
|
+
snapshotSequence: ++this.snapshotNumber,
|
|
587
422
|
expiresAt: lease.expiresAt,
|
|
588
423
|
payload
|
|
589
424
|
});
|
|
@@ -617,7 +452,7 @@ export class NativeSessionWatchService {
|
|
|
617
452
|
workspaceId: session.workspaceId,
|
|
618
453
|
sessionId: session.id,
|
|
619
454
|
revision: 1,
|
|
620
|
-
snapshotSequence: ++this.
|
|
455
|
+
snapshotSequence: ++this.snapshotNumber,
|
|
621
456
|
expiresAt: lease.expiresAt,
|
|
622
457
|
payload
|
|
623
458
|
});
|
|
@@ -635,8 +470,7 @@ export class NativeSessionWatchService {
|
|
|
635
470
|
}
|
|
636
471
|
}
|
|
637
472
|
releaseSnapshotPendingEvents(lease, page) {
|
|
638
|
-
const
|
|
639
|
-
const candidateSequence = pageRecord?.snapshotSequence;
|
|
473
|
+
const candidateSequence = page.snapshotSequence;
|
|
640
474
|
const snapshotSequence = typeof candidateSequence === 'number' &&
|
|
641
475
|
Number.isSafeInteger(candidateSequence) &&
|
|
642
476
|
candidateSequence >= 0
|
|
@@ -704,19 +538,3 @@ function diagnosticErrorCode(error, fallback) {
|
|
|
704
538
|
return error.code;
|
|
705
539
|
return fallback;
|
|
706
540
|
}
|
|
707
|
-
function sessionActivity(event) {
|
|
708
|
-
if (!isPlainRecord(event.payload))
|
|
709
|
-
return undefined;
|
|
710
|
-
if (event.category === 'run' && isPlainRecord(event.payload.run)) {
|
|
711
|
-
const status = event.payload.run.status;
|
|
712
|
-
if (status === 'STARTING' || status === 'RUNNING' || status === 'CANCELLING')
|
|
713
|
-
return 'RUNNING';
|
|
714
|
-
return undefined;
|
|
715
|
-
}
|
|
716
|
-
if (event.category !== 'session')
|
|
717
|
-
return undefined;
|
|
718
|
-
const session = event.payload.session;
|
|
719
|
-
if (!isPlainRecord(session) || typeof session.activity !== 'string')
|
|
720
|
-
return undefined;
|
|
721
|
-
return session.activity === 'MANAGED_ACTIVE' ? 'RUNNING' : 'IDLE';
|
|
722
|
-
}
|
|
@@ -115,7 +115,7 @@ export class RestartSessionRestorer {
|
|
|
115
115
|
deliveryIntent: 'QUEUE',
|
|
116
116
|
content: `${goals.prompt(session.id, 'continue')}
|
|
117
117
|
${RESTART_NOTICE}`,
|
|
118
|
-
goal:
|
|
118
|
+
goal: this.options.goalReference(session.id),
|
|
119
119
|
initiatedByUserId: entry.ownerUserId,
|
|
120
120
|
suppressNotifications: entry.suppressNotifications === true,
|
|
121
121
|
secretEnvironment: context.secretEnvironment,
|
|
@@ -5,6 +5,32 @@ export class RunnerInteractionService {
|
|
|
5
5
|
constructor(options) {
|
|
6
6
|
this.options = options;
|
|
7
7
|
}
|
|
8
|
+
publishApproval(input) {
|
|
9
|
+
const run = this.options.runtime.getRun(this.options.runtime.canonicalRunId(input.runId));
|
|
10
|
+
if (run?.source !== 'SCHEDULED') {
|
|
11
|
+
this.options.publishApproval(input);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
this.options.emitRun(input.runId, 'approval.request', input);
|
|
15
|
+
queueMicrotask(() => {
|
|
16
|
+
try {
|
|
17
|
+
this.options.runtime.decideApproval(input.approvalId, 'APPROVED');
|
|
18
|
+
this.options.emitRun(input.runId, 'approval.decided', {
|
|
19
|
+
approvalId: input.approvalId,
|
|
20
|
+
decision: 'APPROVED',
|
|
21
|
+
source: 'SCHEDULED'
|
|
22
|
+
});
|
|
23
|
+
this.options.runners.decideApproval({
|
|
24
|
+
runId: input.runId,
|
|
25
|
+
approvalId: input.approvalId,
|
|
26
|
+
decision: 'APPROVED'
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// A cancelled execution cannot receive an approval after its pending request expires.
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
8
34
|
requestClaudeApproval(runId, toolName, toolInput) {
|
|
9
35
|
const approvalId = `claude:${crypto.randomUUID()}`;
|
|
10
36
|
return new Promise((resolve) => {
|
|
@@ -49,7 +75,7 @@ export class RunnerInteractionService {
|
|
|
49
75
|
timer,
|
|
50
76
|
resolve: settle
|
|
51
77
|
});
|
|
52
|
-
this.
|
|
78
|
+
this.publishApproval({
|
|
53
79
|
runId,
|
|
54
80
|
approvalId,
|
|
55
81
|
payload: { toolName, input: toolInput },
|