@markusylisiurunen/tau 0.3.34 → 0.3.35
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/README.md +6 -1
- package/dist/core/agent/agent_runtime.js +6 -5
- package/dist/core/agent/agent_runtime.js.map +1 -1
- package/dist/core/commands/registry.js +24 -2
- package/dist/core/commands/registry.js.map +1 -1
- package/dist/core/runtime/chat_runtime.js +13 -2
- package/dist/core/runtime/chat_runtime.js.map +1 -1
- package/dist/core/runtime/steering.js +2 -1
- package/dist/core/runtime/steering.js.map +1 -1
- package/dist/core/session/compaction.js +1 -1
- package/dist/core/session/compaction.js.map +1 -1
- package/dist/core/session/goal.js +54 -0
- package/dist/core/session/goal.js.map +1 -0
- package/dist/core/subagents/agent_supervisor.js +4 -1
- package/dist/core/subagents/agent_supervisor.js.map +1 -1
- package/dist/core/tools/catalog.js +14 -1
- package/dist/core/tools/catalog.js.map +1 -1
- package/dist/core/tools/goal.js +77 -0
- package/dist/core/tools/goal.js.map +1 -0
- package/dist/core/tools/tool_names.js +3 -0
- package/dist/core/tools/tool_names.js.map +1 -1
- package/dist/core/utils/user_metadata.js +14 -0
- package/dist/core/utils/user_metadata.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/host/client_tool_broker.js +4 -0
- package/dist/host/client_tool_broker.js.map +1 -1
- package/dist/host/local_session_host.js +391 -45
- package/dist/host/local_session_host.js.map +1 -1
- package/dist/host/session_host.js +2 -0
- package/dist/host/session_host.js.map +1 -1
- package/dist/host/session_protocol_handler.js +253 -236
- package/dist/host/session_protocol_handler.js.map +1 -1
- package/dist/protocol/index.d.ts +1 -1
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/session_protocol.d.ts +28 -3
- package/dist/protocol/session_protocol.js +53 -1
- package/dist/protocol/session_protocol.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/session.js +20 -0
- package/dist/sdk/session.js.map +1 -1
- package/dist/sdk/types.d.ts +7 -1
- package/dist/store/session_snapshot_migrations.js +10 -1
- package/dist/store/session_snapshot_migrations.js.map +1 -1
- package/dist/tui/chat_view.js +1 -0
- package/dist/tui/chat_view.js.map +1 -1
- package/dist/tui/session_chat_controller.js +40 -3
- package/dist/tui/session_chat_controller.js.map +1 -1
- package/dist/tui/ui/footer.js +7 -3
- package/dist/tui/ui/footer.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { createSessionProtocolErrorResponse, createSessionProtocolPendingUserMessagesMessage, createSessionProtocolReadyMessage, createSessionProtocolSuccessResponse, SESSION_PROTOCOL_ERROR_CODES, SESSION_PROTOCOL_METHODS, SESSION_PROTOCOL_VERSION, } from "../protocol/session_protocol.js";
|
|
3
|
-
import { EphemeralThreadBusyError, SessionExecBusyError, } from "./session_host.js";
|
|
3
|
+
import { EphemeralThreadBusyError, SessionExecBusyError, SessionRetryUnavailableError, } from "./session_host.js";
|
|
4
4
|
function isAbortError(error) {
|
|
5
5
|
return error instanceof Error && error.name === "AbortError";
|
|
6
6
|
}
|
|
@@ -12,29 +12,30 @@ function getSessionLiveState(session) {
|
|
|
12
12
|
state = {
|
|
13
13
|
interrupting: false,
|
|
14
14
|
revision: 1,
|
|
15
|
-
|
|
16
|
-
pendingQueuedSubmits: [],
|
|
15
|
+
pendingSubmissions: [],
|
|
17
16
|
listeners: new Set(),
|
|
18
17
|
};
|
|
19
18
|
sessionLiveStates.set(session, state);
|
|
20
19
|
}
|
|
21
20
|
return state;
|
|
22
21
|
}
|
|
22
|
+
function pendingSubmissionsInDeliveryOrder(state) {
|
|
23
|
+
return [
|
|
24
|
+
...state.pendingSubmissions.filter((pending) => pending.delivery === "turn-boundary"),
|
|
25
|
+
...state.pendingSubmissions.filter((pending) => pending.delivery === "when-idle"),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
function toPendingUserMessage(pending) {
|
|
29
|
+
return {
|
|
30
|
+
id: pending.id,
|
|
31
|
+
mode: pending.request.method === "session.queue" ? "queue" : "steer",
|
|
32
|
+
text: pending.request.params.text,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
23
35
|
function buildPendingUserMessagesState(state) {
|
|
24
36
|
return {
|
|
25
37
|
revision: state.revision,
|
|
26
|
-
messages:
|
|
27
|
-
...state.pendingSteeringSubmits.map((pending) => ({
|
|
28
|
-
id: pending.id,
|
|
29
|
-
mode: pending.mode,
|
|
30
|
-
text: pending.request.params.text,
|
|
31
|
-
})),
|
|
32
|
-
...state.pendingQueuedSubmits.map((pending) => ({
|
|
33
|
-
id: pending.id,
|
|
34
|
-
mode: pending.mode,
|
|
35
|
-
text: pending.request.params.text,
|
|
36
|
-
})),
|
|
37
|
-
],
|
|
38
|
+
messages: pendingSubmissionsInDeliveryOrder(state).map(toPendingUserMessage),
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
function publishPendingUserMessages(session, state) {
|
|
@@ -44,7 +45,12 @@ function publishPendingUserMessages(session, state) {
|
|
|
44
45
|
state: buildPendingUserMessagesState(state),
|
|
45
46
|
});
|
|
46
47
|
for (const listener of [...state.listeners]) {
|
|
47
|
-
|
|
48
|
+
try {
|
|
49
|
+
listener(message);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Pending-message observers must not be able to fail shared session work.
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
function getSessionMutationQueueState(session) {
|
|
@@ -124,6 +130,15 @@ export class SessionProtocolHandler {
|
|
|
124
130
|
case "session.snapshot":
|
|
125
131
|
await this.handleSnapshot(request);
|
|
126
132
|
return;
|
|
133
|
+
case "session.startGoal":
|
|
134
|
+
await this.handleStartGoal(request);
|
|
135
|
+
return;
|
|
136
|
+
case "session.resumeGoal":
|
|
137
|
+
await this.handleResumeGoal(request);
|
|
138
|
+
return;
|
|
139
|
+
case "session.clearGoal":
|
|
140
|
+
await this.handleClearGoal(request);
|
|
141
|
+
return;
|
|
127
142
|
case "session.setReasoning":
|
|
128
143
|
await this.handleSetReasoning(request);
|
|
129
144
|
return;
|
|
@@ -239,33 +254,15 @@ export class SessionProtocolHandler {
|
|
|
239
254
|
this.sendMessage(createSessionProtocolSuccessResponse(request.id, "session.clientTool.result", { accepted }));
|
|
240
255
|
}
|
|
241
256
|
async handleSubmit(request) {
|
|
242
|
-
|
|
243
|
-
if (!state) {
|
|
244
|
-
this.sendSessionNotFound(request.id, request.params.sessionId);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (this.getPendingMutationCount(state) > 0) {
|
|
248
|
-
this.sendSubmitBusy(state, request.id);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const startedSubmit = await this.enqueueMutation(state, () => this.startUserMessageTurn(state, request));
|
|
252
|
-
if (!startedSubmit) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const { activeSubmit } = startedSubmit;
|
|
256
|
-
try {
|
|
257
|
-
await activeSubmit.promise;
|
|
258
|
-
}
|
|
259
|
-
finally {
|
|
260
|
-
await this.enqueueMutation(state, () => {
|
|
261
|
-
if (state.live.activeSubmit === activeSubmit) {
|
|
262
|
-
state.live.activeSubmit = undefined;
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
this.schedulePendingSubmitDrains(state);
|
|
266
|
-
}
|
|
257
|
+
await this.handleUserSubmission(request);
|
|
267
258
|
}
|
|
268
259
|
async handleQueue(request) {
|
|
260
|
+
await this.handleUserSubmission(request);
|
|
261
|
+
}
|
|
262
|
+
async handleSteer(request) {
|
|
263
|
+
await this.handleUserSubmission(request);
|
|
264
|
+
}
|
|
265
|
+
async handleUserSubmission(request) {
|
|
269
266
|
const state = await this.getSessionState(request.params.sessionId);
|
|
270
267
|
if (!state) {
|
|
271
268
|
this.sendSessionNotFound(request.id, request.params.sessionId);
|
|
@@ -275,128 +272,103 @@ export class SessionProtocolHandler {
|
|
|
275
272
|
this.sendSubmitBusy(state, request.id);
|
|
276
273
|
return;
|
|
277
274
|
}
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
id: randomUUID(),
|
|
282
|
-
mode: "queue",
|
|
283
|
-
handler: this,
|
|
284
|
-
request,
|
|
285
|
-
});
|
|
286
|
-
publishPendingUserMessages(state.session, state.live);
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
return this.startUserMessageTurn(state, request);
|
|
290
|
-
});
|
|
291
|
-
if (!startedSubmit) {
|
|
275
|
+
const action = await this.enqueueMutation(state, () => this.dispatchUserSubmission(state, request));
|
|
276
|
+
if (action.type === "busy") {
|
|
277
|
+
this.sendSubmitBusy(state, request.id);
|
|
292
278
|
return;
|
|
293
279
|
}
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
await activeSubmit.promise;
|
|
297
|
-
}
|
|
298
|
-
finally {
|
|
299
|
-
await this.enqueueMutation(state, () => {
|
|
300
|
-
if (state.live.activeSubmit === activeSubmit) {
|
|
301
|
-
state.live.activeSubmit = undefined;
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
this.schedulePendingSubmitDrains(state);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
async handleSteer(request) {
|
|
308
|
-
const state = await this.getSessionState(request.params.sessionId);
|
|
309
|
-
if (!state) {
|
|
310
|
-
this.sendSessionNotFound(request.id, request.params.sessionId);
|
|
280
|
+
if (action.type === "pending") {
|
|
311
281
|
return;
|
|
312
282
|
}
|
|
313
|
-
if (
|
|
314
|
-
this.
|
|
283
|
+
if (action.type === "started") {
|
|
284
|
+
await this.finishActiveSubmit(state, action.activeSubmit);
|
|
315
285
|
return;
|
|
316
286
|
}
|
|
317
|
-
|
|
287
|
+
await this.finishBoundarySteering(state, action.pending, action.submission);
|
|
288
|
+
}
|
|
289
|
+
async dispatchUserSubmission(state, request) {
|
|
290
|
+
if (request.method === "session.steer") {
|
|
318
291
|
if (state.live.interrupting) {
|
|
319
292
|
return { type: "busy" };
|
|
320
293
|
}
|
|
321
|
-
if (state.session.
|
|
294
|
+
if (state.session.canAcceptSteering) {
|
|
322
295
|
const submission = state.session.steer(request.params.text);
|
|
323
296
|
const pending = {
|
|
324
297
|
id: randomUUID(),
|
|
325
|
-
|
|
298
|
+
delivery: "turn-boundary",
|
|
326
299
|
steeringId: submission.id,
|
|
327
300
|
handler: this,
|
|
328
301
|
request,
|
|
329
302
|
};
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return { type: "steer", pending, submission };
|
|
333
|
-
}
|
|
334
|
-
if (state.live.activeSubmit) {
|
|
335
|
-
state.live.pendingQueuedSubmits.push({
|
|
336
|
-
id: randomUUID(),
|
|
337
|
-
mode: "steer",
|
|
338
|
-
handler: this,
|
|
339
|
-
request,
|
|
340
|
-
});
|
|
341
|
-
publishPendingUserMessages(state.session, state.live);
|
|
342
|
-
return { type: "queued" };
|
|
303
|
+
this.addPendingSubmission(state, pending);
|
|
304
|
+
return { type: "boundary", pending, submission };
|
|
343
305
|
}
|
|
344
|
-
return {
|
|
345
|
-
type: "submit",
|
|
346
|
-
started: await this.startUserMessageTurn(state, request),
|
|
347
|
-
};
|
|
348
|
-
});
|
|
349
|
-
if (action.type === "busy") {
|
|
350
|
-
this.sendSubmitBusy(state, request.id);
|
|
351
|
-
return;
|
|
352
306
|
}
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (action.type === "submit") {
|
|
357
|
-
const started = await action.started;
|
|
358
|
-
if (!started)
|
|
359
|
-
return;
|
|
360
|
-
try {
|
|
361
|
-
await started.activeSubmit.promise;
|
|
362
|
-
}
|
|
363
|
-
finally {
|
|
364
|
-
await this.enqueueMutation(state, () => {
|
|
365
|
-
if (state.live.activeSubmit === started.activeSubmit) {
|
|
366
|
-
state.live.activeSubmit = undefined;
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
this.schedulePendingSubmitDrains(state);
|
|
307
|
+
if (state.live.activeSubmit || state.session.isTurnRunning) {
|
|
308
|
+
if (request.method === "session.submit") {
|
|
309
|
+
return { type: "busy" };
|
|
370
310
|
}
|
|
371
|
-
|
|
311
|
+
const pending = {
|
|
312
|
+
id: randomUUID(),
|
|
313
|
+
delivery: "when-idle",
|
|
314
|
+
handler: this,
|
|
315
|
+
request,
|
|
316
|
+
};
|
|
317
|
+
this.addPendingSubmission(state, pending);
|
|
318
|
+
return { type: "pending" };
|
|
372
319
|
}
|
|
320
|
+
return {
|
|
321
|
+
type: "started",
|
|
322
|
+
activeSubmit: await this.startUserMessageTurn(state, request),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
async finishBoundarySteering(state, pending, submission) {
|
|
373
326
|
let applied = false;
|
|
374
|
-
void
|
|
327
|
+
void submission.result.catch(() => { });
|
|
375
328
|
try {
|
|
376
|
-
await
|
|
329
|
+
await submission.applied;
|
|
377
330
|
applied = true;
|
|
378
|
-
await this.enqueueMutation(state, () =>
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
state.live.pendingSteeringSubmits.splice(index, 1);
|
|
382
|
-
publishPendingUserMessages(state.session, state.live);
|
|
383
|
-
});
|
|
384
|
-
const result = await action.submission.result;
|
|
385
|
-
this.sendMessage(createSessionProtocolSuccessResponse(request.id, "session.steer", result));
|
|
331
|
+
await this.enqueueMutation(state, () => this.removePendingSubmission(state, pending));
|
|
332
|
+
const result = await submission.result;
|
|
333
|
+
this.sendMessage(createSessionProtocolSuccessResponse(pending.request.id, "session.steer", result));
|
|
386
334
|
}
|
|
387
335
|
catch (error) {
|
|
388
|
-
if (applied || state.live.
|
|
389
|
-
this.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.internalError, "steering turn failed", { cause: error instanceof Error ? error.message : String(error) }));
|
|
336
|
+
if (applied || state.live.pendingSubmissions.includes(pending)) {
|
|
337
|
+
this.sendMessage(createSessionProtocolErrorResponse(pending.request.id, SESSION_PROTOCOL_ERROR_CODES.internalError, "steering turn failed", { cause: error instanceof Error ? error.message : String(error) }));
|
|
390
338
|
}
|
|
391
339
|
}
|
|
340
|
+
finally {
|
|
341
|
+
await this.enqueueMutation(state, () => this.removePendingSubmission(state, pending));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
addPendingSubmission(state, pending) {
|
|
345
|
+
state.live.pendingSubmissions.push(pending);
|
|
346
|
+
publishPendingUserMessages(state.session, state.live);
|
|
347
|
+
}
|
|
348
|
+
removePendingSubmission(state, pending) {
|
|
349
|
+
return this.removePendingSubmissions(state, [pending]);
|
|
350
|
+
}
|
|
351
|
+
removePendingSubmissions(state, pending) {
|
|
352
|
+
const removed = new Set(pending);
|
|
353
|
+
const remaining = state.live.pendingSubmissions.filter((submission) => !removed.has(submission));
|
|
354
|
+
if (remaining.length === state.live.pendingSubmissions.length) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
state.live.pendingSubmissions = remaining;
|
|
358
|
+
publishPendingUserMessages(state.session, state.live);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
async finishActiveSubmit(state, activeSubmit) {
|
|
362
|
+
try {
|
|
363
|
+
await activeSubmit.promise;
|
|
364
|
+
}
|
|
392
365
|
finally {
|
|
393
366
|
await this.enqueueMutation(state, () => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
state.live.pendingSteeringSubmits.splice(index, 1);
|
|
397
|
-
publishPendingUserMessages(state.session, state.live);
|
|
367
|
+
if (state.live.activeSubmit === activeSubmit) {
|
|
368
|
+
state.live.activeSubmit = undefined;
|
|
398
369
|
}
|
|
399
370
|
});
|
|
371
|
+
this.schedulePendingSubmissionDrain(state);
|
|
400
372
|
}
|
|
401
373
|
}
|
|
402
374
|
async handleCancelPendingMessages(request) {
|
|
@@ -407,21 +379,15 @@ export class SessionProtocolHandler {
|
|
|
407
379
|
}
|
|
408
380
|
const cancelled = await this.enqueueMutation(state, () => {
|
|
409
381
|
const cancelledSteeringIds = new Set(state.session.cancelSteering().map((submission) => submission.id));
|
|
410
|
-
const
|
|
411
|
-
state.live.pendingSteeringSubmits = state.live.pendingSteeringSubmits.filter((pending) => !cancelledSteeringIds.has(pending.steeringId));
|
|
412
|
-
const pending = [...cancelledSteering, ...state.live.pendingQueuedSubmits.splice(0)];
|
|
382
|
+
const pending = pendingSubmissionsInDeliveryOrder(state.live).filter((submission) => submission.delivery === "when-idle" || cancelledSteeringIds.has(submission.steeringId));
|
|
413
383
|
if (pending.length === 0) {
|
|
414
384
|
return [];
|
|
415
385
|
}
|
|
416
|
-
|
|
386
|
+
this.removePendingSubmissions(state, pending);
|
|
417
387
|
for (const item of pending) {
|
|
418
388
|
item.handler.sendMessage(createSessionProtocolErrorResponse(item.request.id, SESSION_PROTOCOL_ERROR_CODES.cancelled, "pending user message was cancelled"));
|
|
419
389
|
}
|
|
420
|
-
return pending.map(
|
|
421
|
-
id: item.id,
|
|
422
|
-
mode: item.mode,
|
|
423
|
-
text: item.request.params.text,
|
|
424
|
-
}));
|
|
390
|
+
return pending.map(toPendingUserMessage);
|
|
425
391
|
});
|
|
426
392
|
this.sendMessage(createSessionProtocolSuccessResponse(request.id, "session.cancelPendingMessages", {
|
|
427
393
|
cancelled,
|
|
@@ -489,21 +455,9 @@ export class SessionProtocolHandler {
|
|
|
489
455
|
this.sendSubmitBusy(state, request.id);
|
|
490
456
|
return;
|
|
491
457
|
}
|
|
492
|
-
const
|
|
493
|
-
if (
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
const { activeSubmit } = startedRetry;
|
|
497
|
-
try {
|
|
498
|
-
await activeSubmit.promise;
|
|
499
|
-
}
|
|
500
|
-
finally {
|
|
501
|
-
await this.enqueueMutation(state, () => {
|
|
502
|
-
if (state.live.activeSubmit === activeSubmit) {
|
|
503
|
-
state.live.activeSubmit = undefined;
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
this.schedulePendingSubmitDrains(state);
|
|
458
|
+
const activeSubmit = await this.enqueueMutation(state, () => this.startRetry(state, request));
|
|
459
|
+
if (activeSubmit) {
|
|
460
|
+
await this.finishActiveSubmit(state, activeSubmit);
|
|
507
461
|
}
|
|
508
462
|
}
|
|
509
463
|
async handleCreate(request) {
|
|
@@ -584,10 +538,6 @@ export class SessionProtocolHandler {
|
|
|
584
538
|
});
|
|
585
539
|
}
|
|
586
540
|
async startUserMessageTurn(state, request) {
|
|
587
|
-
if (state.live.activeSubmit || state.session.isTurnRunning) {
|
|
588
|
-
this.sendSubmitBusy(state, request.id);
|
|
589
|
-
return undefined;
|
|
590
|
-
}
|
|
591
541
|
const addOptions = request.method !== "session.steer" && request.params.historyEntryId
|
|
592
542
|
? { historyEntryId: request.params.historyEntryId }
|
|
593
543
|
: undefined;
|
|
@@ -595,67 +545,59 @@ export class SessionProtocolHandler {
|
|
|
595
545
|
text: request.params.text,
|
|
596
546
|
...(addOptions ? { historyEntryId: addOptions.historyEntryId } : {}),
|
|
597
547
|
});
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
state.live.activeSubmit = activeSubmit;
|
|
601
|
-
return {
|
|
602
|
-
activeSubmit,
|
|
548
|
+
const activeSubmit = {
|
|
549
|
+
promise: this.executeSubmit(state, request.id, request.method, userHistoryEntryId),
|
|
603
550
|
};
|
|
551
|
+
state.live.activeSubmit = activeSubmit;
|
|
552
|
+
return activeSubmit;
|
|
604
553
|
}
|
|
605
554
|
startRetry(state, request) {
|
|
606
555
|
if (state.live.activeSubmit || state.session.isTurnRunning) {
|
|
607
556
|
this.sendSubmitBusy(state, request.id);
|
|
608
557
|
return undefined;
|
|
609
558
|
}
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
state.live.activeSubmit = activeSubmit;
|
|
613
|
-
return {
|
|
614
|
-
activeSubmit,
|
|
559
|
+
const activeSubmit = {
|
|
560
|
+
promise: this.executeRetry(state, request.id),
|
|
615
561
|
};
|
|
562
|
+
state.live.activeSubmit = activeSubmit;
|
|
563
|
+
return activeSubmit;
|
|
616
564
|
}
|
|
617
|
-
|
|
618
|
-
void this.
|
|
619
|
-
this.
|
|
565
|
+
schedulePendingSubmissionDrain(state) {
|
|
566
|
+
void this.drainPendingIdleSubmissions(state).catch((error) => {
|
|
567
|
+
this.failPendingIdleSubmissions(state, error);
|
|
620
568
|
});
|
|
621
569
|
}
|
|
622
|
-
async
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
return undefined;
|
|
570
|
+
async drainPendingIdleSubmissions(state) {
|
|
571
|
+
const action = await this.enqueueMutation(state, async () => {
|
|
572
|
+
const pendingIndex = state.live.pendingSubmissions.findIndex((pending) => pending.delivery === "when-idle");
|
|
573
|
+
if (pendingIndex < 0 || state.live.activeSubmit || state.session.isTurnRunning) {
|
|
574
|
+
return { type: "idle" };
|
|
628
575
|
}
|
|
629
|
-
const pending = state.live.
|
|
576
|
+
const [pending] = state.live.pendingSubmissions.splice(pendingIndex, 1);
|
|
630
577
|
publishPendingUserMessages(state.session, state.live);
|
|
631
578
|
try {
|
|
632
|
-
return
|
|
579
|
+
return {
|
|
580
|
+
type: "started",
|
|
581
|
+
activeSubmit: await pending.handler.startUserMessageTurn(state, pending.request),
|
|
582
|
+
};
|
|
633
583
|
}
|
|
634
584
|
catch (error) {
|
|
635
585
|
this.sendUserMessageDrainFailure([pending], error);
|
|
636
|
-
return
|
|
586
|
+
return { type: "failed", error };
|
|
637
587
|
}
|
|
638
588
|
});
|
|
639
|
-
if (
|
|
589
|
+
if (action.type === "idle") {
|
|
640
590
|
return;
|
|
641
591
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
finally {
|
|
646
|
-
await this.enqueueMutation(state, () => {
|
|
647
|
-
if (state.live.activeSubmit === next.activeSubmit) {
|
|
648
|
-
state.live.activeSubmit = undefined;
|
|
649
|
-
}
|
|
650
|
-
});
|
|
651
|
-
this.schedulePendingSubmitDrains(state);
|
|
592
|
+
if (action.type === "failed") {
|
|
593
|
+
this.failPendingIdleSubmissions(state, action.error);
|
|
594
|
+
return;
|
|
652
595
|
}
|
|
596
|
+
await this.finishActiveSubmit(state, action.activeSubmit);
|
|
653
597
|
}
|
|
654
|
-
|
|
655
|
-
const pending = state.live.
|
|
656
|
-
|
|
657
|
-
publishPendingUserMessages(state.session, state.live);
|
|
658
|
-
}
|
|
598
|
+
failPendingIdleSubmissions(state, error) {
|
|
599
|
+
const pending = state.live.pendingSubmissions.filter((submission) => submission.delivery === "when-idle");
|
|
600
|
+
this.removePendingSubmissions(state, pending);
|
|
659
601
|
this.sendUserMessageDrainFailure(pending, error);
|
|
660
602
|
}
|
|
661
603
|
sendUserMessageDrainFailure(requests, error) {
|
|
@@ -663,7 +605,7 @@ export class SessionProtocolHandler {
|
|
|
663
605
|
handler.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.internalError, "failed to drain pending user message", { cause: error instanceof Error ? error.message : String(error) }));
|
|
664
606
|
}
|
|
665
607
|
}
|
|
666
|
-
async executeSubmit(state, requestId, method, userHistoryEntryId
|
|
608
|
+
async executeSubmit(state, requestId, method, userHistoryEntryId) {
|
|
667
609
|
try {
|
|
668
610
|
const turnResult = await state.session.runTurn();
|
|
669
611
|
await state.session.snapshot();
|
|
@@ -671,25 +613,11 @@ export class SessionProtocolHandler {
|
|
|
671
613
|
userHistoryEntryId,
|
|
672
614
|
turn: turnResult,
|
|
673
615
|
};
|
|
674
|
-
|
|
675
|
-
for (const { handler, request } of responseRequests) {
|
|
676
|
-
handler.sendMessage(createSessionProtocolSuccessResponse(request.id, method, result));
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
this.sendMessage(createSessionProtocolSuccessResponse(requestId, method, result));
|
|
681
|
-
}
|
|
616
|
+
this.sendMessage(createSessionProtocolSuccessResponse(requestId, method, result));
|
|
682
617
|
}
|
|
683
618
|
catch (error) {
|
|
684
619
|
await this.snapshotAfterFailedSubmit(state);
|
|
685
|
-
|
|
686
|
-
for (const { handler, request } of responseRequests) {
|
|
687
|
-
handler.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.internalError, "failed to run session turn", { cause: error instanceof Error ? error.message : String(error) }));
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
else {
|
|
691
|
-
this.sendMessage(createSessionProtocolErrorResponse(requestId, SESSION_PROTOCOL_ERROR_CODES.internalError, "failed to run session turn", { cause: error instanceof Error ? error.message : String(error) }));
|
|
692
|
-
}
|
|
620
|
+
this.sendMessage(createSessionProtocolErrorResponse(requestId, SESSION_PROTOCOL_ERROR_CODES.internalError, "failed to run session turn", { cause: error instanceof Error ? error.message : String(error) }));
|
|
693
621
|
}
|
|
694
622
|
}
|
|
695
623
|
async snapshotAfterFailedSubmit(state) {
|
|
@@ -703,7 +631,7 @@ export class SessionProtocolHandler {
|
|
|
703
631
|
}
|
|
704
632
|
async executeRetry(state, requestId) {
|
|
705
633
|
try {
|
|
706
|
-
const turnResult = await state.session.
|
|
634
|
+
const turnResult = await state.session.retryTurn();
|
|
707
635
|
await state.session.snapshot();
|
|
708
636
|
const result = {
|
|
709
637
|
turn: turnResult,
|
|
@@ -711,7 +639,12 @@ export class SessionProtocolHandler {
|
|
|
711
639
|
this.sendMessage(createSessionProtocolSuccessResponse(requestId, "session.retry", result));
|
|
712
640
|
}
|
|
713
641
|
catch (error) {
|
|
714
|
-
|
|
642
|
+
const retryUnavailable = error instanceof SessionRetryUnavailableError;
|
|
643
|
+
this.sendMessage(createSessionProtocolErrorResponse(requestId, retryUnavailable
|
|
644
|
+
? SESSION_PROTOCOL_ERROR_CODES.invalidRequest
|
|
645
|
+
: SESSION_PROTOCOL_ERROR_CODES.internalError, retryUnavailable ? error.message : "failed to run session turn", retryUnavailable
|
|
646
|
+
? undefined
|
|
647
|
+
: { cause: error instanceof Error ? error.message : String(error) }));
|
|
715
648
|
}
|
|
716
649
|
}
|
|
717
650
|
execKey(sessionId, execId) {
|
|
@@ -783,7 +716,7 @@ export class SessionProtocolHandler {
|
|
|
783
716
|
if (interrupted) {
|
|
784
717
|
state.live.interrupting = true;
|
|
785
718
|
}
|
|
786
|
-
this.
|
|
719
|
+
this.rejectPendingBoundarySubmissions(state, "session was interrupted");
|
|
787
720
|
return {
|
|
788
721
|
interrupted,
|
|
789
722
|
isTurnRunning: state.session.isTurnRunning || interrupted,
|
|
@@ -808,6 +741,95 @@ export class SessionProtocolHandler {
|
|
|
808
741
|
}
|
|
809
742
|
this.sendMessage(createSessionProtocolSuccessResponse(request.id, "session.snapshot", await state.session.snapshot()));
|
|
810
743
|
}
|
|
744
|
+
async handleStartGoal(request) {
|
|
745
|
+
await this.handleGoalTurn(request);
|
|
746
|
+
}
|
|
747
|
+
async handleResumeGoal(request) {
|
|
748
|
+
await this.handleGoalTurn(request);
|
|
749
|
+
}
|
|
750
|
+
async handleGoalTurn(request) {
|
|
751
|
+
const state = await this.getSessionState(request.params.sessionId);
|
|
752
|
+
if (!state) {
|
|
753
|
+
this.sendSessionNotFound(request.id, request.params.sessionId);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (this.getPendingMutationCount(state) > 0) {
|
|
757
|
+
this.sendSubmitBusy(state, request.id);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const started = await this.enqueueMutation(state, () => {
|
|
761
|
+
if (state.live.activeSubmit || state.session.isTurnRunning) {
|
|
762
|
+
this.sendSubmitBusy(state, request.id);
|
|
763
|
+
return undefined;
|
|
764
|
+
}
|
|
765
|
+
const promise = this.executeGoalTurn(state, request);
|
|
766
|
+
const activeSubmit = { requestId: request.id, promise };
|
|
767
|
+
state.live.activeSubmit = activeSubmit;
|
|
768
|
+
return { activeSubmit };
|
|
769
|
+
});
|
|
770
|
+
if (!started)
|
|
771
|
+
return;
|
|
772
|
+
try {
|
|
773
|
+
await started.activeSubmit.promise;
|
|
774
|
+
}
|
|
775
|
+
finally {
|
|
776
|
+
await this.enqueueMutation(state, () => {
|
|
777
|
+
if (state.live.activeSubmit === started.activeSubmit) {
|
|
778
|
+
state.live.activeSubmit = undefined;
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
this.schedulePendingSubmissionDrain(state);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async executeGoalTurn(state, request) {
|
|
785
|
+
try {
|
|
786
|
+
if (request.method === "session.startGoal") {
|
|
787
|
+
const result = await state.session.startGoal(request.params.objective);
|
|
788
|
+
await state.session.snapshot();
|
|
789
|
+
this.sendMessage(createSessionProtocolSuccessResponse(request.id, "session.startGoal", result));
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
const result = await state.session.resumeGoal();
|
|
793
|
+
await state.session.snapshot();
|
|
794
|
+
this.sendMessage(createSessionProtocolSuccessResponse(request.id, "session.resumeGoal", result));
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
await this.snapshotAfterFailedSubmit(state);
|
|
799
|
+
this.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.internalError, "failed to run session goal", { cause: error instanceof Error ? error.message : String(error) }));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async handleClearGoal(request) {
|
|
803
|
+
const state = await this.getSessionState(request.params.sessionId);
|
|
804
|
+
if (!state) {
|
|
805
|
+
this.sendSessionNotFound(request.id, request.params.sessionId);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
if (!state.session.getGoal()) {
|
|
809
|
+
this.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.invalidRequest, "no goal exists"));
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
await this.runSessionMutation(state, async () => {
|
|
813
|
+
if (this.closed) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (state.session.sessionId !== request.params.sessionId) {
|
|
817
|
+
this.sendSessionNotFound(request.id, request.params.sessionId);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (!state.session.getGoal()) {
|
|
821
|
+
this.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.invalidRequest, "no goal exists"));
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
await this.interruptAndWaitForActiveSubmit(state);
|
|
825
|
+
this.rejectPendingBoundarySubmissions(state, "session goal cleared");
|
|
826
|
+
this.rejectPendingIdleSubmissions(state, "session goal cleared");
|
|
827
|
+
const snapshot = state.session.getGoal()
|
|
828
|
+
? await state.session.clearGoal()
|
|
829
|
+
: await state.session.snapshot();
|
|
830
|
+
this.sendMessage(createSessionProtocolSuccessResponse(request.id, "session.clearGoal", snapshot));
|
|
831
|
+
});
|
|
832
|
+
}
|
|
811
833
|
async handleSetReasoning(request) {
|
|
812
834
|
const state = await this.getSessionState(request.params.sessionId);
|
|
813
835
|
if (!state) {
|
|
@@ -869,8 +891,7 @@ export class SessionProtocolHandler {
|
|
|
869
891
|
await this.withNonInterruptingSessionMutation(request, async (state) => {
|
|
870
892
|
if (state.live.activeSubmit ||
|
|
871
893
|
state.session.isTurnRunning ||
|
|
872
|
-
state.live.
|
|
873
|
-
state.live.pendingQueuedSubmits.length > 0) {
|
|
894
|
+
state.live.pendingSubmissions.length > 0) {
|
|
874
895
|
this.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.busy, "cannot rewind while session work is active or pending"));
|
|
875
896
|
return;
|
|
876
897
|
}
|
|
@@ -945,8 +966,8 @@ export class SessionProtocolHandler {
|
|
|
945
966
|
return;
|
|
946
967
|
}
|
|
947
968
|
await this.interruptAndWaitForActiveSubmit(state);
|
|
948
|
-
this.
|
|
949
|
-
this.
|
|
969
|
+
this.rejectPendingBoundarySubmissions(state, queuedSteeringRejectionMessage);
|
|
970
|
+
this.rejectPendingIdleSubmissions(state, queuedSteeringRejectionMessage);
|
|
950
971
|
await handler(state);
|
|
951
972
|
});
|
|
952
973
|
}
|
|
@@ -967,23 +988,19 @@ export class SessionProtocolHandler {
|
|
|
967
988
|
await handler(state);
|
|
968
989
|
});
|
|
969
990
|
}
|
|
970
|
-
|
|
991
|
+
rejectPendingBoundarySubmissions(state, message) {
|
|
971
992
|
const cancelledIds = new Set(state.session.cancelSteering().map((submission) => submission.id));
|
|
972
|
-
|
|
973
|
-
state.live.pendingSteeringSubmits = state.live.pendingSteeringSubmits.filter((pending) => !cancelledIds.has(pending.steeringId));
|
|
974
|
-
if (requests.length > 0) {
|
|
975
|
-
publishPendingUserMessages(state.session, state.live);
|
|
976
|
-
}
|
|
977
|
-
for (const { handler, request } of requests) {
|
|
978
|
-
handler.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.invalidRequest, message));
|
|
979
|
-
}
|
|
993
|
+
this.rejectPendingSubmissions(state, state.live.pendingSubmissions.filter((pending) => pending.delivery === "turn-boundary" && cancelledIds.has(pending.steeringId)), message);
|
|
980
994
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
995
|
+
rejectPendingIdleSubmissions(state, message) {
|
|
996
|
+
this.rejectPendingSubmissions(state, state.live.pendingSubmissions.filter((pending) => pending.delivery === "when-idle"), message);
|
|
997
|
+
}
|
|
998
|
+
rejectPendingSubmissions(state, pending, message) {
|
|
999
|
+
if (pending.length === 0) {
|
|
1000
|
+
return;
|
|
985
1001
|
}
|
|
986
|
-
|
|
1002
|
+
this.removePendingSubmissions(state, pending);
|
|
1003
|
+
for (const { handler, request } of pending) {
|
|
987
1004
|
handler.sendMessage(createSessionProtocolErrorResponse(request.id, SESSION_PROTOCOL_ERROR_CODES.invalidRequest, message));
|
|
988
1005
|
}
|
|
989
1006
|
}
|