@markusylisiurunen/tau 0.3.41 → 0.3.43
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 +14 -6
- package/dist/core/agent/agent_runtime.js +32 -44
- package/dist/core/agent/agent_runtime.js.map +1 -1
- package/dist/core/auth/auth_manager.js +25 -9
- package/dist/core/auth/auth_manager.js.map +1 -1
- package/dist/core/auth/auth_storage.js +1 -0
- package/dist/core/auth/auth_storage.js.map +1 -1
- package/dist/core/auth/cli.js +49 -35
- package/dist/core/auth/cli.js.map +1 -1
- package/dist/core/auth/credential_store.js +54 -22
- package/dist/core/auth/credential_store.js.map +1 -1
- package/dist/core/auth/index.js +1 -1
- package/dist/core/auth/index.js.map +1 -1
- package/dist/core/auth/providers/openai_codex.js +71 -23
- package/dist/core/auth/providers/openai_codex.js.map +1 -1
- package/dist/core/cli.js +4 -2
- package/dist/core/cli.js.map +1 -1
- package/dist/core/config/builtin_themes.js +62 -155
- package/dist/core/config/builtin_themes.js.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/models/tau_model_overrides.js +1 -66
- package/dist/core/models/tau_model_overrides.js.map +1 -1
- package/dist/core/session/direct_bash.js +1 -0
- package/dist/core/session/direct_bash.js.map +1 -1
- package/dist/core/session/runner.js +21 -14
- package/dist/core/session/runner.js.map +1 -1
- package/dist/core/static/code_mode/capabilities.mjs +25 -0
- package/dist/core/static/code_mode/history/documentation.md +2 -0
- package/dist/core/static/code_mode/history/sandbox_runner.mjs +4 -1
- package/dist/core/static/code_mode/nook/documentation.md +2 -0
- package/dist/core/static/code_mode/nook/sandbox_runner.mjs +4 -1
- package/dist/core/static/code_mode/web/documentation.md +2 -0
- package/dist/core/static/code_mode/web/sandbox_runner.mjs +4 -1
- package/dist/core/subagents/agent_supervisor.js +3 -3
- package/dist/core/subagents/agent_supervisor.js.map +1 -1
- package/dist/core/telegram/adapter.js +0 -23
- package/dist/core/telegram/adapter.js.map +1 -1
- package/dist/core/telegram/session_manager.js +34 -7
- package/dist/core/telegram/session_manager.js.map +1 -1
- package/dist/core/tools/bash.js +10 -2
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/code_mode.js +1 -0
- package/dist/core/tools/code_mode.js.map +1 -1
- package/dist/core/tools/presentation.js +1 -1
- package/dist/core/utils/auto_retry.js +1 -1
- package/dist/core/utils/auto_retry.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/host/local_session_host.js +319 -198
- package/dist/host/local_session_host.js.map +1 -1
- package/dist/host/session_protocol_handler.js +1 -1
- package/dist/host/session_protocol_handler.js.map +1 -1
- package/dist/main.js +16 -2
- package/dist/main.js.map +1 -1
- package/dist/protocol/index.d.ts +2 -2
- package/dist/protocol/index.js +1 -1
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/session_protocol.d.ts +115 -40
- package/dist/protocol/session_protocol.js +377 -98
- package/dist/protocol/session_protocol.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -2
- package/dist/sdk/index.js +0 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/store/session_snapshot_migrations.js +311 -4
- package/dist/store/session_snapshot_migrations.js.map +1 -1
- package/dist/tui/chat_controller/assistant_clipboard.js +12 -8
- package/dist/tui/chat_controller/assistant_clipboard.js.map +1 -1
- package/dist/tui/chat_controller/diff_review_service.js +13 -9
- package/dist/tui/chat_controller/diff_review_service.js.map +1 -1
- package/dist/tui/chat_controller/history_message_model.js +3 -3
- package/dist/tui/chat_controller/history_message_model.js.map +1 -1
- package/dist/tui/chat_controller/status_format.js +18 -0
- package/dist/tui/chat_controller/status_format.js.map +1 -1
- package/dist/tui/chat_view.js +39 -38
- package/dist/tui/chat_view.js.map +1 -1
- package/dist/tui/session_chat_app.js +1 -3
- package/dist/tui/session_chat_app.js.map +1 -1
- package/dist/tui/session_chat_controller.js +375 -241
- package/dist/tui/session_chat_controller.js.map +1 -1
- package/dist/tui/speech_playback.js +4 -4
- package/dist/tui/speech_playback.js.map +1 -1
- package/dist/tui/ui/assistant_message.js +1 -15
- package/dist/tui/ui/assistant_message.js.map +1 -1
- package/dist/tui/ui/chat_message_model.js +26 -7
- package/dist/tui/ui/chat_message_model.js.map +1 -1
- package/dist/tui/ui/custom_editor.js +1 -1
- package/dist/tui/ui/custom_editor.js.map +1 -1
- package/dist/tui/ui/footer.js +161 -47
- package/dist/tui/ui/footer.js.map +1 -1
- package/dist/tui/ui/theme/palette.js +10 -16
- package/dist/tui/ui/theme/palette.js.map +1 -1
- package/dist/tui/ui/theme/theme.js.map +1 -1
- package/dist/tui/ui/tool_card.js +12 -9
- package/dist/tui/ui/tool_card.js.map +1 -1
- package/dist/tui/ui/transcript_notice.js +44 -0
- package/dist/tui/ui/transcript_notice.js.map +1 -0
- package/dist/tui/ui/transcript_text.js +14 -0
- package/dist/tui/ui/transcript_text.js.map +1 -0
- package/package.json +10 -12
- package/dist/sdk/turn_outcome.d.ts +0 -2
- package/dist/sdk/turn_outcome.js +0 -4
- package/dist/sdk/turn_outcome.js.map +0 -1
- package/dist/tui/ui/system_message.js +0 -22
- package/dist/tui/ui/system_message.js.map +0 -1
|
@@ -9,9 +9,9 @@ import { SUBAGENT_ACTIVITY_FACET_KIND } from "../core/subagents/types.js";
|
|
|
9
9
|
import { buildToolRunPresentation, TOOL_UI_FACET_VERSION } from "../core/tools/presentation.js";
|
|
10
10
|
import { appendUsageLogEntry, getUsageCostTotal, getUsageTotals, } from "../core/usage/logs.js";
|
|
11
11
|
import { filterProjectPathAutocompleteEntries, loadProjectPathAutocompleteEntriesWithBackend, } from "../core/utils/project_files.js";
|
|
12
|
-
import {
|
|
13
|
-
import { applySessionProtocolDelta, createSessionProtocolDeltaMessage, createSessionProtocolEphemeralMessage, } from "../protocol/session_protocol.js";
|
|
14
|
-
import {
|
|
12
|
+
import { hasGoalTurnMetadata } from "../core/utils/user_metadata.js";
|
|
13
|
+
import { applySessionProtocolDelta, createSessionProtocolDeltaMessage, createSessionProtocolEphemeralMessage, projectSessionProtocolNoticePresentation, } from "../protocol/session_protocol.js";
|
|
14
|
+
import { LEGACY_SESSION_MODEL_CONTEXT_KEY } from "../store/session_snapshot_migrations.js";
|
|
15
15
|
import { ClientToolBroker } from "./client_tool_broker.js";
|
|
16
16
|
import { createExecutionEnvironmentSubagentRuntimeResolver } from "./execution_runtime.js";
|
|
17
17
|
import { HostedEphemeralAgentSession } from "./hosted_ephemeral_agent_session.js";
|
|
@@ -76,7 +76,7 @@ export class LocalSessionHost {
|
|
|
76
76
|
const agentRecovery = hostedSession.runtime.restoreState({
|
|
77
77
|
agentId: recovered.snapshot.sessionId,
|
|
78
78
|
revision: recovered.snapshot.agentState.revision,
|
|
79
|
-
|
|
79
|
+
modelContextKey: recovered.snapshot.agentState.modelContextKey,
|
|
80
80
|
historyEntries: recovered.snapshot.messages.flatMap((entry) => entry.modelVisible && isCoreMessage(entry.message)
|
|
81
81
|
? [{ id: entry.id, message: entry.message }]
|
|
82
82
|
: []),
|
|
@@ -344,6 +344,9 @@ export class LocalSessionHost {
|
|
|
344
344
|
return false;
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
|
+
function normalizeDeltaCause(cause) {
|
|
348
|
+
return typeof cause === "string" ? { type: cause } : cause;
|
|
349
|
+
}
|
|
347
350
|
class LocalHostedSessionHandle {
|
|
348
351
|
runtime;
|
|
349
352
|
promptBootstrap;
|
|
@@ -364,11 +367,9 @@ class LocalHostedSessionHandle {
|
|
|
364
367
|
persistedSnapshot;
|
|
365
368
|
draftAssistantMessage;
|
|
366
369
|
messageStates = new Map();
|
|
367
|
-
|
|
368
|
-
restoredMessageIds;
|
|
369
|
-
restoredTimelineMessageIds;
|
|
370
|
-
timelineExtras = [];
|
|
370
|
+
timeline = { epoch: 1, sequence: 0, items: [] };
|
|
371
371
|
tools = new Map();
|
|
372
|
+
operations = new Map();
|
|
372
373
|
agents = new Map();
|
|
373
374
|
agentCostTotals = new Map();
|
|
374
375
|
facets = new Map();
|
|
@@ -490,7 +491,7 @@ class LocalHostedSessionHandle {
|
|
|
490
491
|
if (logicalTurn.cancellationRequested) {
|
|
491
492
|
return {
|
|
492
493
|
userHistoryEntryId,
|
|
493
|
-
turn: await this.cancelLogicalTurn(
|
|
494
|
+
turn: await this.cancelLogicalTurn(),
|
|
494
495
|
};
|
|
495
496
|
}
|
|
496
497
|
return {
|
|
@@ -529,7 +530,7 @@ class LocalHostedSessionHandle {
|
|
|
529
530
|
text: buildGoalContinuationText(goal),
|
|
530
531
|
});
|
|
531
532
|
if (logicalTurn.cancellationRequested) {
|
|
532
|
-
return { turn: await this.cancelLogicalTurn(
|
|
533
|
+
return { turn: await this.cancelLogicalTurn() };
|
|
533
534
|
}
|
|
534
535
|
return { turn: await this.runTurnNow(logicalTurn, userHistoryEntryId) };
|
|
535
536
|
}
|
|
@@ -602,7 +603,7 @@ class LocalHostedSessionHandle {
|
|
|
602
603
|
let goalRootHistoryEntryId = this.goal?.status === "active" ? rootUserMessage.id : undefined;
|
|
603
604
|
while (true) {
|
|
604
605
|
if (logicalTurn.cancellationRequested) {
|
|
605
|
-
return await this.cancelLogicalTurn(
|
|
606
|
+
return await this.cancelLogicalTurn();
|
|
606
607
|
}
|
|
607
608
|
const committedSteering = [];
|
|
608
609
|
try {
|
|
@@ -610,11 +611,7 @@ class LocalHostedSessionHandle {
|
|
|
610
611
|
committedSteering.push(await this.commitBufferedLogicalSteering(logicalTurn));
|
|
611
612
|
}
|
|
612
613
|
if (logicalTurn.cancellationRequested) {
|
|
613
|
-
return await this.cancelLogicalTurn(
|
|
614
|
-
}
|
|
615
|
-
const userMessage = this.session.rawHistoryEntries.findLast((entry) => entry.message.role === "user");
|
|
616
|
-
if (!userMessage) {
|
|
617
|
-
throw new Error("cannot run a turn without a user message");
|
|
614
|
+
return await this.cancelLogicalTurn(committedSteering);
|
|
618
615
|
}
|
|
619
616
|
const result = await this.runtime.runTurn();
|
|
620
617
|
const terminalResult = result.terminalResult;
|
|
@@ -624,16 +621,9 @@ class LocalHostedSessionHandle {
|
|
|
624
621
|
const initialOutcome = turnOutcomeFromResult(result, result.finalMessage);
|
|
625
622
|
const terminalOutcome = turnOutcomeFromResult(terminalResult, terminalResult.finalMessage);
|
|
626
623
|
await this.enqueueMutation(async () => {
|
|
627
|
-
this.turnOutcomes.set(userMessage.id, initialOutcome);
|
|
628
|
-
for (const steering of committedSteering) {
|
|
629
|
-
this.turnOutcomes.set(steering.historyEntryId, initialOutcome);
|
|
630
|
-
}
|
|
631
624
|
if (this.goal?.status === "active") {
|
|
632
625
|
goalRootHistoryEntryId ??= rootUserMessage.id;
|
|
633
626
|
}
|
|
634
|
-
if (goalRootHistoryEntryId) {
|
|
635
|
-
this.turnOutcomes.set(goalRootHistoryEntryId, terminalOutcome);
|
|
636
|
-
}
|
|
637
627
|
if (terminalOutcome.status !== "completed" && this.goal?.status === "active") {
|
|
638
628
|
this.goal = { ...this.goal, status: "blocked" };
|
|
639
629
|
}
|
|
@@ -641,7 +631,7 @@ class LocalHostedSessionHandle {
|
|
|
641
631
|
});
|
|
642
632
|
this.resolveCommittedLogicalSteering(committedSteering, initialOutcome);
|
|
643
633
|
if (logicalTurn.cancellationRequested) {
|
|
644
|
-
return await this.cancelLogicalTurn(
|
|
634
|
+
return await this.cancelLogicalTurn();
|
|
645
635
|
}
|
|
646
636
|
if (terminalOutcome.status !== "completed" || this.goal?.status !== "active") {
|
|
647
637
|
return goalRootHistoryEntryId ? terminalOutcome : initialOutcome;
|
|
@@ -652,9 +642,15 @@ class LocalHostedSessionHandle {
|
|
|
652
642
|
await this.session.commitUserText(buildGoalContinuationText(this.goal));
|
|
653
643
|
}
|
|
654
644
|
catch (error) {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
645
|
+
try {
|
|
646
|
+
const outcome = await this.cleanupFailedTurn(rootUserMessage.id, error);
|
|
647
|
+
this.resolveCommittedLogicalSteering(committedSteering, outcome);
|
|
648
|
+
return outcome;
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
this.rejectCommittedLogicalSteering(committedSteering, error);
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
658
654
|
}
|
|
659
655
|
}
|
|
660
656
|
}
|
|
@@ -704,14 +700,10 @@ class LocalHostedSessionHandle {
|
|
|
704
700
|
applied: submission.applied.then((association) => ({
|
|
705
701
|
userHistoryEntryId: association.historyEntryId,
|
|
706
702
|
})),
|
|
707
|
-
result: submission.result.then(
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
userHistoryEntryId: association.historyEntryId,
|
|
712
|
-
turn,
|
|
713
|
-
};
|
|
714
|
-
}),
|
|
703
|
+
result: submission.result.then((association) => ({
|
|
704
|
+
userHistoryEntryId: association.historyEntryId,
|
|
705
|
+
turn: turnOutcomeFromResult(association.result, association.result.finalMessage),
|
|
706
|
+
})),
|
|
715
707
|
};
|
|
716
708
|
}
|
|
717
709
|
const logicalTurn = this.activeLogicalTurn;
|
|
@@ -784,7 +776,7 @@ class LocalHostedSessionHandle {
|
|
|
784
776
|
sessionId: snapshot.sessionId,
|
|
785
777
|
fromRevision,
|
|
786
778
|
toRevision: snapshot.revision,
|
|
787
|
-
|
|
779
|
+
cause: { type: "configuration" },
|
|
788
780
|
delta: {
|
|
789
781
|
type: "snapshot.patch",
|
|
790
782
|
changes: [{ type: "settings.set", settings: snapshot.settings }],
|
|
@@ -926,16 +918,11 @@ class LocalHostedSessionHandle {
|
|
|
926
918
|
signal,
|
|
927
919
|
});
|
|
928
920
|
signal.throwIfAborted();
|
|
929
|
-
return
|
|
930
|
-
this.
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
snapshot,
|
|
935
|
-
compactionMessage: result.compactionMessage,
|
|
936
|
-
includedLastAssistant: result.includedLastAssistant,
|
|
937
|
-
};
|
|
938
|
-
});
|
|
921
|
+
return {
|
|
922
|
+
snapshot: await this.snapshot(),
|
|
923
|
+
compactionMessage: result.compactionMessage,
|
|
924
|
+
includedLastAssistant: result.includedLastAssistant,
|
|
925
|
+
};
|
|
939
926
|
});
|
|
940
927
|
}
|
|
941
928
|
async runMaintenance(operation) {
|
|
@@ -979,6 +966,9 @@ class LocalHostedSessionHandle {
|
|
|
979
966
|
}
|
|
980
967
|
async rewindToHistoryEntryId(historyEntryId) {
|
|
981
968
|
this.assertActive();
|
|
969
|
+
if (!this.timeline.items.some((item) => item.type === "message" && item.messageId === historyEntryId)) {
|
|
970
|
+
throw new Error("rewind failed");
|
|
971
|
+
}
|
|
982
972
|
const result = await this.session.rewindToHistoryEntryId(historyEntryId);
|
|
983
973
|
if (!result) {
|
|
984
974
|
throw new Error("rewind failed");
|
|
@@ -1216,13 +1206,9 @@ class LocalHostedSessionHandle {
|
|
|
1216
1206
|
submission.rejectResult(error);
|
|
1217
1207
|
}
|
|
1218
1208
|
}
|
|
1219
|
-
async cancelLogicalTurn(
|
|
1209
|
+
async cancelLogicalTurn(committedSteering = []) {
|
|
1220
1210
|
const outcome = abortedTurnOutcome();
|
|
1221
1211
|
await this.enqueueMutation(async () => {
|
|
1222
|
-
this.turnOutcomes.set(rootHistoryEntryId, outcome);
|
|
1223
|
-
for (const steering of committedSteering) {
|
|
1224
|
-
this.turnOutcomes.set(steering.historyEntryId, outcome);
|
|
1225
|
-
}
|
|
1226
1212
|
if (this.goal?.status === "active") {
|
|
1227
1213
|
this.goal = { ...this.goal, status: "blocked" };
|
|
1228
1214
|
}
|
|
@@ -1233,6 +1219,7 @@ class LocalHostedSessionHandle {
|
|
|
1233
1219
|
}
|
|
1234
1220
|
async commitSnapshot() {
|
|
1235
1221
|
this.assertNotDisposed();
|
|
1222
|
+
this.reconcileProjections();
|
|
1236
1223
|
const draft = this.buildSnapshotDraft();
|
|
1237
1224
|
await this.switchSnapshotSession(draft.sessionId);
|
|
1238
1225
|
const snapshot = {
|
|
@@ -1265,7 +1252,7 @@ class LocalHostedSessionHandle {
|
|
|
1265
1252
|
this.persistedSnapshot = cloneSessionProtocolSnapshot(snapshot);
|
|
1266
1253
|
return snapshot;
|
|
1267
1254
|
}
|
|
1268
|
-
async commitSnapshotPatch(
|
|
1255
|
+
async commitSnapshotPatch(cause, changes) {
|
|
1269
1256
|
this.assertNotDisposed();
|
|
1270
1257
|
const current = this.committedSnapshot;
|
|
1271
1258
|
if (!current) {
|
|
@@ -1275,7 +1262,7 @@ class LocalHostedSessionHandle {
|
|
|
1275
1262
|
sessionId: current.sessionId,
|
|
1276
1263
|
fromRevision: current.revision,
|
|
1277
1264
|
toRevision: current.revision + 1,
|
|
1278
|
-
|
|
1265
|
+
cause: normalizeDeltaCause(cause),
|
|
1279
1266
|
delta: { type: "snapshot.patch", changes },
|
|
1280
1267
|
});
|
|
1281
1268
|
const snapshot = applySessionProtocolDelta(current, delta);
|
|
@@ -1300,10 +1287,8 @@ class LocalHostedSessionHandle {
|
|
|
1300
1287
|
reconcileProjections(options = {}) {
|
|
1301
1288
|
const messageIds = new Set(this.session.rawHistoryEntries.map((entry) => entry.id));
|
|
1302
1289
|
messageIds.add("system");
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
this.turnOutcomes.delete(id);
|
|
1306
|
-
}
|
|
1290
|
+
if (this.draftAssistantMessage) {
|
|
1291
|
+
messageIds.add(this.draftAssistantMessage.id);
|
|
1307
1292
|
}
|
|
1308
1293
|
for (const [id, tool] of this.tools) {
|
|
1309
1294
|
const messageId = tool.status === "streaming" ? tool.origin.messageId : tool.call.messageId;
|
|
@@ -1322,13 +1307,36 @@ class LocalHostedSessionHandle {
|
|
|
1322
1307
|
}
|
|
1323
1308
|
}
|
|
1324
1309
|
}
|
|
1325
|
-
const
|
|
1310
|
+
for (const id of this.operations.keys()) {
|
|
1311
|
+
const hasTimelineItem = this.timeline.items.some((item) => item.type === "operation" && item.operationId === id);
|
|
1312
|
+
if (!hasTimelineItem) {
|
|
1313
|
+
this.operations.delete(id);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
this.timeline.items = this.timeline.items.filter((item) => {
|
|
1317
|
+
switch (item.type) {
|
|
1318
|
+
case "message":
|
|
1319
|
+
return messageIds.has(item.messageId);
|
|
1320
|
+
case "tool":
|
|
1321
|
+
return this.tools.has(item.toolId);
|
|
1322
|
+
case "notice":
|
|
1323
|
+
return (item.notice.subject.type === "session" ||
|
|
1324
|
+
(item.notice.subject.type === "message" && messageIds.has(item.notice.subject.id)) ||
|
|
1325
|
+
(item.notice.subject.type === "tool" && this.tools.has(item.notice.subject.id)) ||
|
|
1326
|
+
(item.notice.subject.type === "agent" && this.agents.has(item.notice.subject.id)) ||
|
|
1327
|
+
(item.notice.subject.type === "operation" &&
|
|
1328
|
+
this.operations.has(item.notice.subject.id)));
|
|
1329
|
+
case "operation":
|
|
1330
|
+
return this.operations.has(item.operationId);
|
|
1331
|
+
}
|
|
1332
|
+
return false;
|
|
1333
|
+
});
|
|
1326
1334
|
for (const [id, facet] of this.facets) {
|
|
1327
1335
|
const subjectExists = facet.subject.type === "session" ||
|
|
1328
1336
|
(facet.subject.type === "message" && messageIds.has(facet.subject.id)) ||
|
|
1329
1337
|
(facet.subject.type === "tool" && this.tools.has(facet.subject.id)) ||
|
|
1330
1338
|
(facet.subject.type === "agent" && this.agents.has(facet.subject.id)) ||
|
|
1331
|
-
(facet.subject.type === "operation" &&
|
|
1339
|
+
(facet.subject.type === "operation" && this.operations.has(facet.subject.id));
|
|
1332
1340
|
if (!subjectExists) {
|
|
1333
1341
|
this.facets.delete(id);
|
|
1334
1342
|
}
|
|
@@ -1343,7 +1351,7 @@ class LocalHostedSessionHandle {
|
|
|
1343
1351
|
createdAt: this.createdAt,
|
|
1344
1352
|
agentState: {
|
|
1345
1353
|
revision: agentState.revision,
|
|
1346
|
-
|
|
1354
|
+
modelContextKey: agentState.modelContextKey,
|
|
1347
1355
|
...(agentState.usageCheckpoint
|
|
1348
1356
|
? { usageCheckpoint: { ...agentState.usageCheckpoint } }
|
|
1349
1357
|
: {}),
|
|
@@ -1370,8 +1378,9 @@ class LocalHostedSessionHandle {
|
|
|
1370
1378
|
catalog: structuredClone(this.catalog),
|
|
1371
1379
|
executionEnvironment: this.executionEnvironment.snapshot(),
|
|
1372
1380
|
messages,
|
|
1373
|
-
timeline: this.
|
|
1381
|
+
timeline: structuredClone(this.timeline),
|
|
1374
1382
|
tools: Object.fromEntries(this.tools),
|
|
1383
|
+
operations: Object.fromEntries(this.operations),
|
|
1375
1384
|
agents: Object.fromEntries(this.agents),
|
|
1376
1385
|
facets: Object.fromEntries(this.facets),
|
|
1377
1386
|
};
|
|
@@ -1387,64 +1396,86 @@ class LocalHostedSessionHandle {
|
|
|
1387
1396
|
timestamp: 0,
|
|
1388
1397
|
},
|
|
1389
1398
|
};
|
|
1390
|
-
const historyMessages = this.session.rawHistoryEntries.map((entry) => {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
message: entry.message,
|
|
1397
|
-
...(turn ? { turn } : {}),
|
|
1398
|
-
};
|
|
1399
|
-
});
|
|
1399
|
+
const historyMessages = this.session.rawHistoryEntries.map((entry) => ({
|
|
1400
|
+
id: entry.id,
|
|
1401
|
+
state: this.messageStates.get(entry.id) ?? "committed",
|
|
1402
|
+
modelVisible: true,
|
|
1403
|
+
message: entry.message,
|
|
1404
|
+
}));
|
|
1400
1405
|
return [
|
|
1401
1406
|
systemMessage,
|
|
1402
1407
|
...historyMessages,
|
|
1403
1408
|
...(this.draftAssistantMessage ? [structuredClone(this.draftAssistantMessage)] : []),
|
|
1404
1409
|
];
|
|
1405
1410
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1411
|
+
nextTimelineItemBase(id, createdAt) {
|
|
1412
|
+
this.timeline.sequence += 1;
|
|
1413
|
+
return { id, sequence: this.timeline.sequence, createdAt };
|
|
1414
|
+
}
|
|
1415
|
+
appendMessageTimelineItem(messageId, createdAt) {
|
|
1416
|
+
const item = {
|
|
1417
|
+
...this.nextTimelineItemBase(`timeline-message-${messageId}`, createdAt),
|
|
1410
1418
|
type: "message",
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
return
|
|
1419
|
+
messageId,
|
|
1420
|
+
};
|
|
1421
|
+
this.timeline.items.push(item);
|
|
1422
|
+
return item;
|
|
1415
1423
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1425
|
-
if (!this.restoredTimelineMessageIds || !this.restoredMessageIds) {
|
|
1426
|
-
return true;
|
|
1427
|
-
}
|
|
1428
|
-
return (this.restoredTimelineMessageIds.has(message.id) || !this.restoredMessageIds.has(message.id));
|
|
1424
|
+
appendToolTimelineItem(toolId, createdAt) {
|
|
1425
|
+
const item = {
|
|
1426
|
+
...this.nextTimelineItemBase(`timeline-tool-${toolId}`, createdAt),
|
|
1427
|
+
type: "tool",
|
|
1428
|
+
toolId,
|
|
1429
|
+
};
|
|
1430
|
+
this.timeline.items.push(item);
|
|
1431
|
+
return item;
|
|
1429
1432
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1433
|
+
appendNoticeTimelineItem(id, createdAt, notice) {
|
|
1434
|
+
const item = {
|
|
1435
|
+
...this.nextTimelineItemBase(id, createdAt),
|
|
1436
|
+
type: "notice",
|
|
1437
|
+
notice,
|
|
1438
|
+
};
|
|
1439
|
+
this.timeline.items.push(item);
|
|
1440
|
+
return item;
|
|
1441
|
+
}
|
|
1442
|
+
appendTurnFailureTimelineItem(historyEntryId, failure) {
|
|
1443
|
+
const blocked = failure.reason === "auto-compaction-failed";
|
|
1444
|
+
return this.appendNoticeTimelineItem(`turn-${blocked ? "blocked" : "failed"}-${randomUUID()}`, Date.now(), {
|
|
1445
|
+
kind: blocked ? "tau.turn.blocked" : "tau.turn.failed",
|
|
1446
|
+
version: 1,
|
|
1447
|
+
severity: "error",
|
|
1448
|
+
subject: { type: "message", id: historyEntryId },
|
|
1449
|
+
presentation: projectSessionProtocolNoticePresentation(blocked ? "turn blocked" : "turn failed", [failure.message]),
|
|
1450
|
+
data: { reason: failure.reason },
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
appendOperationTimelineItem(operation) {
|
|
1454
|
+
const item = {
|
|
1455
|
+
...this.nextTimelineItemBase(`timeline-operation-${operation.id}`, operation.startedAt),
|
|
1456
|
+
type: "operation",
|
|
1457
|
+
operationId: operation.id,
|
|
1458
|
+
};
|
|
1459
|
+
this.operations.set(operation.id, operation);
|
|
1460
|
+
this.timeline.items.push(item);
|
|
1461
|
+
return item;
|
|
1462
|
+
}
|
|
1463
|
+
removeTimelineItem(id) {
|
|
1464
|
+
this.timeline.items = this.timeline.items.filter((item) => item.id !== id);
|
|
1434
1465
|
}
|
|
1435
1466
|
restoreProtocolState(snapshot) {
|
|
1436
1467
|
if (!snapshot) {
|
|
1437
1468
|
return;
|
|
1438
1469
|
}
|
|
1439
|
-
this.
|
|
1440
|
-
this.restoredTimelineMessageIds = new Set(snapshot.timeline.filter((item) => item.type === "message").map((item) => item.messageId));
|
|
1441
|
-
this.timelineExtras.splice(0, this.timelineExtras.length, ...snapshot.timeline
|
|
1442
|
-
.filter((item) => item.type !== "message")
|
|
1443
|
-
.map((item) => structuredClone(item)));
|
|
1470
|
+
this.timeline = structuredClone(snapshot.timeline);
|
|
1444
1471
|
this.tools.clear();
|
|
1445
1472
|
for (const [id, tool] of Object.entries(snapshot.tools)) {
|
|
1446
1473
|
this.tools.set(id, structuredClone(tool));
|
|
1447
1474
|
}
|
|
1475
|
+
this.operations.clear();
|
|
1476
|
+
for (const [id, operation] of Object.entries(snapshot.operations)) {
|
|
1477
|
+
this.operations.set(id, structuredClone(operation));
|
|
1478
|
+
}
|
|
1448
1479
|
this.agents.clear();
|
|
1449
1480
|
this.agentCostTotals.clear();
|
|
1450
1481
|
for (const [id, agent] of Object.entries(snapshot.agents)) {
|
|
@@ -1459,14 +1490,10 @@ class LocalHostedSessionHandle {
|
|
|
1459
1490
|
const historyEntryIds = new Set(this.session.rawHistoryEntries.map((entry) => entry.id));
|
|
1460
1491
|
this.draftAssistantMessage = snapshot.messages.find((message) => message.state === "draft" && !historyEntryIds.has(message.id));
|
|
1461
1492
|
this.messageStates.clear();
|
|
1462
|
-
this.turnOutcomes.clear();
|
|
1463
1493
|
for (const message of snapshot.messages) {
|
|
1464
1494
|
if (message.state !== "committed" && message.state !== "draft") {
|
|
1465
1495
|
this.messageStates.set(message.id, message.state);
|
|
1466
1496
|
}
|
|
1467
|
-
if (message.turn) {
|
|
1468
|
-
this.turnOutcomes.set(message.id, message.turn);
|
|
1469
|
-
}
|
|
1470
1497
|
}
|
|
1471
1498
|
}
|
|
1472
1499
|
enqueueMutation(mutation) {
|
|
@@ -1514,47 +1541,34 @@ class LocalHostedSessionHandle {
|
|
|
1514
1541
|
async enqueueRuntimeEvent(event) {
|
|
1515
1542
|
await this.enqueueMutation(() => this.recordRuntimeEvent(event));
|
|
1516
1543
|
}
|
|
1517
|
-
async commitSteeringTurnOutcome(historyEntryId, turn) {
|
|
1518
|
-
await this.enqueueMutation(async () => {
|
|
1519
|
-
this.turnOutcomes.set(historyEntryId, turn);
|
|
1520
|
-
const message = this.committedSnapshot?.messages.find((candidate) => candidate.id === historyEntryId);
|
|
1521
|
-
if (!message) {
|
|
1522
|
-
throw new Error(`missing steering user message '${historyEntryId}'`);
|
|
1523
|
-
}
|
|
1524
|
-
if (isDeepStrictEqual(message.turn, turn)) {
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
await this.emitPatch("assistant-message", [
|
|
1528
|
-
{
|
|
1529
|
-
type: "message.replace",
|
|
1530
|
-
message: { ...structuredClone(message), turn },
|
|
1531
|
-
},
|
|
1532
|
-
]);
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
1544
|
removeToolRun(tool, changes) {
|
|
1536
1545
|
this.tools.delete(tool.id);
|
|
1546
|
+
const timelineItemId = `timeline-tool-${tool.id}`;
|
|
1547
|
+
this.removeTimelineItem(timelineItemId);
|
|
1537
1548
|
for (const facetId of tool.facetIds) {
|
|
1538
1549
|
this.facets.delete(facetId);
|
|
1539
1550
|
changes.push({ type: "facet.remove", id: facetId });
|
|
1540
1551
|
}
|
|
1541
|
-
changes.push({ type: "tool.remove", id: tool.id });
|
|
1552
|
+
changes.push({ type: "tool.remove", id: tool.id }, { type: "timeline.remove", id: timelineItemId });
|
|
1542
1553
|
}
|
|
1543
1554
|
async recordHistoryFailure(reason) {
|
|
1544
|
-
if (!reason || this.
|
|
1555
|
+
if (!reason || this.timeline.items.some((item) => item.id === HISTORY_UNAVAILABLE_NOTICE_ID)) {
|
|
1545
1556
|
return;
|
|
1546
1557
|
}
|
|
1547
|
-
const item = {
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
+
const item = this.appendNoticeTimelineItem(HISTORY_UNAVAILABLE_NOTICE_ID, Date.now(), {
|
|
1559
|
+
kind: "tau.history.unavailable",
|
|
1560
|
+
version: 1,
|
|
1561
|
+
severity: "warn",
|
|
1562
|
+
subject: { type: "session" },
|
|
1563
|
+
presentation: projectSessionProtocolNoticePresentation("session history is unavailable", [
|
|
1564
|
+
reason,
|
|
1565
|
+
"this session will continue without durable history recording or recall",
|
|
1566
|
+
]),
|
|
1567
|
+
data: {},
|
|
1568
|
+
});
|
|
1569
|
+
await this.emitPatch({ type: "notice" }, [
|
|
1570
|
+
{ type: "timeline.append", item: structuredClone(item) },
|
|
1571
|
+
]);
|
|
1558
1572
|
}
|
|
1559
1573
|
async appendToolHistory(tool, resultHistoryEntryId, result) {
|
|
1560
1574
|
const callMessage = this.committedSnapshot?.messages.find((message) => message.id === tool.call.messageId);
|
|
@@ -1583,7 +1597,7 @@ class LocalHostedSessionHandle {
|
|
|
1583
1597
|
type: "agent-state.set",
|
|
1584
1598
|
agentState: {
|
|
1585
1599
|
revision: state.revision,
|
|
1586
|
-
|
|
1600
|
+
modelContextKey: state.modelContextKey,
|
|
1587
1601
|
...(state.usageCheckpoint ? { usageCheckpoint: { ...state.usageCheckpoint } } : {}),
|
|
1588
1602
|
},
|
|
1589
1603
|
};
|
|
@@ -1597,13 +1611,13 @@ class LocalHostedSessionHandle {
|
|
|
1597
1611
|
modelVisible: false,
|
|
1598
1612
|
message: { role: "assistant", content: [], timestamp: Date.now() },
|
|
1599
1613
|
};
|
|
1600
|
-
const timelineItem =
|
|
1614
|
+
const timelineItem = this.appendMessageTimelineItem(this.draftAssistantMessage.id, this.draftAssistantMessage.message.timestamp);
|
|
1601
1615
|
await this.emitPatch("assistant-stream", [
|
|
1602
1616
|
{
|
|
1603
1617
|
type: "message.append",
|
|
1604
1618
|
message: structuredClone(this.draftAssistantMessage),
|
|
1605
|
-
timelineItem,
|
|
1606
1619
|
},
|
|
1620
|
+
{ type: "timeline.append", item: structuredClone(timelineItem) },
|
|
1607
1621
|
{ type: "lifecycle.set", lifecycle: "running" },
|
|
1608
1622
|
], { persist: false });
|
|
1609
1623
|
return;
|
|
@@ -1651,7 +1665,8 @@ class LocalHostedSessionHandle {
|
|
|
1651
1665
|
};
|
|
1652
1666
|
this.tools.set(tool.id, tool);
|
|
1653
1667
|
this.facets.set(facet.id, facet);
|
|
1654
|
-
|
|
1668
|
+
const timelineItem = this.appendToolTimelineItem(tool.id, Date.now());
|
|
1669
|
+
changes.push({ type: "tool.set", tool: structuredClone(tool) }, { type: "facet.set", facet: structuredClone(facet) }, { type: "timeline.append", item: structuredClone(timelineItem) });
|
|
1655
1670
|
await this.emitPatch("tool-run", changes, { persist: false });
|
|
1656
1671
|
return;
|
|
1657
1672
|
}
|
|
@@ -1706,6 +1721,7 @@ class LocalHostedSessionHandle {
|
|
|
1706
1721
|
}
|
|
1707
1722
|
case "user_message": {
|
|
1708
1723
|
const pendingGoal = this.pendingGoalCommit;
|
|
1724
|
+
const timelineItem = this.appendMessageTimelineItem(event.historyEntryId, event.message.timestamp);
|
|
1709
1725
|
await this.emitPatch("user-message", [
|
|
1710
1726
|
this.agentStateChange(),
|
|
1711
1727
|
...(pendingGoal
|
|
@@ -1719,8 +1735,8 @@ class LocalHostedSessionHandle {
|
|
|
1719
1735
|
modelVisible: true,
|
|
1720
1736
|
message: event.message,
|
|
1721
1737
|
},
|
|
1722
|
-
timelineItem: timelineItemForMessage(event.historyEntryId),
|
|
1723
1738
|
},
|
|
1739
|
+
{ type: "timeline.append", item: structuredClone(timelineItem) },
|
|
1724
1740
|
], { persist: true });
|
|
1725
1741
|
await this.recordHistoryFailure(this.history.append(this.sessionId, [userHistoryEntry(event.historyEntryId, event.message)], this.historyRemote));
|
|
1726
1742
|
if (pendingGoal && this.pendingGoalCommit === pendingGoal) {
|
|
@@ -1728,11 +1744,19 @@ class LocalHostedSessionHandle {
|
|
|
1728
1744
|
}
|
|
1729
1745
|
return;
|
|
1730
1746
|
}
|
|
1731
|
-
case "history_rewound":
|
|
1747
|
+
case "history_rewound": {
|
|
1748
|
+
const rewindItem = this.timeline.items.find((item) => item.type === "message" && item.messageId === event.historyEntryId);
|
|
1749
|
+
if (!rewindItem) {
|
|
1750
|
+
throw new Error(`missing timeline item for rewind message '${event.historyEntryId}'`);
|
|
1751
|
+
}
|
|
1752
|
+
const cutoffSequence = rewindItem.sequence - 1;
|
|
1753
|
+
this.timeline.items = this.timeline.items.filter((item) => item.sequence <= cutoffSequence);
|
|
1732
1754
|
this.reconcileProjections({ removeMissingAgents: true });
|
|
1733
|
-
await this.
|
|
1755
|
+
const snapshot = await this.commitSnapshot();
|
|
1756
|
+
this.emitSnapshotReset({ type: "rewind", epoch: this.timeline.epoch, cutoffSequence }, snapshot);
|
|
1734
1757
|
await this.recordHistoryFailure(this.history.truncateFromSources(this.sessionId, event.removedEntryIds, this.historyRemote));
|
|
1735
1758
|
return;
|
|
1759
|
+
}
|
|
1736
1760
|
case "assistant_final": {
|
|
1737
1761
|
this.draftAssistantMessage = undefined;
|
|
1738
1762
|
const messageState = event.message.stopReason === "aborted" ? "interrupted" : "committed";
|
|
@@ -1833,7 +1857,6 @@ class LocalHostedSessionHandle {
|
|
|
1833
1857
|
modelVisible: true,
|
|
1834
1858
|
message: event.message,
|
|
1835
1859
|
},
|
|
1836
|
-
timelineItem: timelineItemForMessage(event.historyEntryId),
|
|
1837
1860
|
},
|
|
1838
1861
|
{ type: "tool.set", tool: structuredClone(nextTool) },
|
|
1839
1862
|
]);
|
|
@@ -1850,7 +1873,6 @@ class LocalHostedSessionHandle {
|
|
|
1850
1873
|
modelVisible: true,
|
|
1851
1874
|
message: event.message,
|
|
1852
1875
|
},
|
|
1853
|
-
timelineItem: timelineItemForMessage(event.historyEntryId),
|
|
1854
1876
|
},
|
|
1855
1877
|
]);
|
|
1856
1878
|
return;
|
|
@@ -1884,52 +1906,137 @@ class LocalHostedSessionHandle {
|
|
|
1884
1906
|
changes.push({ type: "tool.set", tool: structuredClone(nextTool) });
|
|
1885
1907
|
historyTools.push({ tool: existing, result: toolResult });
|
|
1886
1908
|
}
|
|
1887
|
-
await this.emitPatch("
|
|
1909
|
+
await this.emitPatch({ type: "resync" }, changes);
|
|
1888
1910
|
for (const { tool, result } of historyTools) {
|
|
1889
1911
|
await this.appendToolHistory(tool, event.historyEntryId, result);
|
|
1890
1912
|
}
|
|
1891
1913
|
return;
|
|
1892
1914
|
}
|
|
1893
|
-
case "
|
|
1915
|
+
case "feedback": {
|
|
1916
|
+
if (event.presentation === "footer") {
|
|
1917
|
+
this.emitEphemeral(createSessionProtocolEphemeralMessage({
|
|
1918
|
+
sessionId: this.sessionId,
|
|
1919
|
+
event: {
|
|
1920
|
+
type: "feedback.notice",
|
|
1921
|
+
title: projectSessionProtocolNoticePresentation(event.title).title,
|
|
1922
|
+
tone: event.tone,
|
|
1923
|
+
presentation: "footer",
|
|
1924
|
+
durationMs: event.durationMs,
|
|
1925
|
+
},
|
|
1926
|
+
}));
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
const createdAt = Date.now();
|
|
1894
1930
|
const item = {
|
|
1931
|
+
...this.nextTimelineItemBase(`ephemeral-notice-${randomUUID()}`, createdAt),
|
|
1895
1932
|
type: "notice",
|
|
1896
|
-
id: `notice-${Date.now()}-${this.timelineExtras.length}`,
|
|
1897
1933
|
notice: {
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1934
|
+
kind: "tau.runtime.feedback",
|
|
1935
|
+
version: 1,
|
|
1936
|
+
severity: event.tone === "error" ? "error" : "info",
|
|
1937
|
+
subject: { type: "session" },
|
|
1938
|
+
presentation: projectSessionProtocolNoticePresentation(event.title, event.content),
|
|
1939
|
+
data: {},
|
|
1901
1940
|
},
|
|
1902
1941
|
};
|
|
1903
|
-
this.
|
|
1904
|
-
|
|
1942
|
+
await this.emitPatch("notice", [
|
|
1943
|
+
{
|
|
1944
|
+
type: "timeline.advance",
|
|
1945
|
+
epoch: this.timeline.epoch,
|
|
1946
|
+
sequence: item.sequence,
|
|
1947
|
+
},
|
|
1948
|
+
]);
|
|
1949
|
+
this.emitEphemeral(createSessionProtocolEphemeralMessage({
|
|
1950
|
+
sessionId: this.sessionId,
|
|
1951
|
+
event: { type: "timeline.item", epoch: this.timeline.epoch, item },
|
|
1952
|
+
}));
|
|
1905
1953
|
return;
|
|
1906
1954
|
}
|
|
1907
1955
|
case "compaction_start": {
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
id:
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
startedAt: Date.now(),
|
|
1915
|
-
},
|
|
1956
|
+
const kind = event.reason === "manual" ? "manual-compaction" : "auto-compaction";
|
|
1957
|
+
const operation = {
|
|
1958
|
+
id: `${kind}-${randomUUID()}`,
|
|
1959
|
+
kind,
|
|
1960
|
+
status: "running",
|
|
1961
|
+
startedAt: Date.now(),
|
|
1916
1962
|
};
|
|
1917
|
-
this.
|
|
1963
|
+
const item = this.appendOperationTimelineItem(operation);
|
|
1918
1964
|
await this.emitPatch("maintenance", [
|
|
1965
|
+
{ type: "operation.set", operation: structuredClone(operation) },
|
|
1919
1966
|
{ type: "timeline.append", item: structuredClone(item) },
|
|
1920
1967
|
]);
|
|
1921
1968
|
return;
|
|
1922
1969
|
}
|
|
1923
|
-
case "compaction_end":
|
|
1924
|
-
this.
|
|
1925
|
-
|
|
1926
|
-
|
|
1970
|
+
case "compaction_end": {
|
|
1971
|
+
const operation = [...this.operations.values()].findLast((candidate) => (candidate.kind === "auto-compaction" || candidate.kind === "manual-compaction") &&
|
|
1972
|
+
candidate.status === "running");
|
|
1973
|
+
if (operation) {
|
|
1974
|
+
const finishedAt = Date.now();
|
|
1975
|
+
const finishedOperation = event.outcome === "compacted"
|
|
1976
|
+
? { ...operation, status: "succeeded", finishedAt }
|
|
1977
|
+
: event.outcome === "failed"
|
|
1978
|
+
? {
|
|
1979
|
+
...operation,
|
|
1980
|
+
status: "failed",
|
|
1981
|
+
finishedAt,
|
|
1982
|
+
error: event.errorMessage,
|
|
1983
|
+
}
|
|
1984
|
+
: event.outcome === "skipped"
|
|
1985
|
+
? {
|
|
1986
|
+
...operation,
|
|
1987
|
+
status: "skipped",
|
|
1988
|
+
finishedAt,
|
|
1989
|
+
reason: "no-eligible-history",
|
|
1990
|
+
}
|
|
1991
|
+
: {
|
|
1992
|
+
...operation,
|
|
1993
|
+
status: "cancelled",
|
|
1994
|
+
finishedAt,
|
|
1995
|
+
reason: "interrupted",
|
|
1996
|
+
};
|
|
1997
|
+
this.operations.set(operation.id, finishedOperation);
|
|
1998
|
+
await this.emitPatch("maintenance", [
|
|
1999
|
+
{ type: "operation.set", operation: structuredClone(finishedOperation) },
|
|
2000
|
+
]);
|
|
2001
|
+
}
|
|
2002
|
+
if (event.outcome !== "compacted") {
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
const previousEpoch = this.timeline.epoch;
|
|
2006
|
+
this.timeline = { epoch: previousEpoch + 1, sequence: 0, items: [] };
|
|
2007
|
+
this.tools.clear();
|
|
2008
|
+
this.operations.clear();
|
|
2009
|
+
this.facets.clear();
|
|
2010
|
+
this.draftAssistantMessage = undefined;
|
|
2011
|
+
this.reconcileProjections({ removeMissingAgents: true });
|
|
2012
|
+
const summary = this.session.rawHistoryEntries.find((entry) => entry.id === event.result.summaryHistoryEntryId);
|
|
2013
|
+
if (!summary) {
|
|
2014
|
+
throw new Error(`missing compaction summary '${event.result.summaryHistoryEntryId}' in runtime history`);
|
|
2015
|
+
}
|
|
2016
|
+
this.appendMessageTimelineItem(summary.id, summary.message.timestamp);
|
|
2017
|
+
const snapshot = await this.commitSnapshot();
|
|
2018
|
+
this.emitSnapshotReset({
|
|
2019
|
+
type: "compaction",
|
|
2020
|
+
previousEpoch,
|
|
2021
|
+
epoch: this.timeline.epoch,
|
|
2022
|
+
kind: event.reason === "manual" ? "manual" : "auto",
|
|
2023
|
+
cutType: event.result.cutType,
|
|
2024
|
+
retainedMessageCount: event.result.retainedMessageCount,
|
|
2025
|
+
}, snapshot);
|
|
1927
2026
|
return;
|
|
2027
|
+
}
|
|
1928
2028
|
case "tool_activity":
|
|
1929
2029
|
await this.recordToolUiEvent(event.activity);
|
|
1930
2030
|
return;
|
|
2031
|
+
case "turn_finished": {
|
|
2032
|
+
if (!event.failure) {
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const item = this.appendTurnFailureTimelineItem(event.historyEntryId, event.failure);
|
|
2036
|
+
await this.emitPatch("notice", [{ type: "timeline.append", item: structuredClone(item) }]);
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
1931
2039
|
case "turn_started":
|
|
1932
|
-
case "turn_finished":
|
|
1933
2040
|
case "tool_call_admitted":
|
|
1934
2041
|
case "model_retry_scheduled":
|
|
1935
2042
|
case "model_retry_started":
|
|
@@ -2001,12 +2108,13 @@ class LocalHostedSessionHandle {
|
|
|
2001
2108
|
}
|
|
2002
2109
|
buildAssistantPartialChanges(previousDraft, nextDraft) {
|
|
2003
2110
|
if (!previousDraft) {
|
|
2111
|
+
const timelineItem = this.appendMessageTimelineItem(nextDraft.id, nextDraft.message.timestamp);
|
|
2004
2112
|
return [
|
|
2005
2113
|
{
|
|
2006
2114
|
type: "message.append",
|
|
2007
2115
|
message: structuredClone(nextDraft),
|
|
2008
|
-
timelineItem: timelineItemForMessage(nextDraft.id),
|
|
2009
2116
|
},
|
|
2117
|
+
{ type: "timeline.append", item: structuredClone(timelineItem) },
|
|
2010
2118
|
];
|
|
2011
2119
|
}
|
|
2012
2120
|
const appendChange = buildAssistantContentAppendChange(previousDraft, nextDraft);
|
|
@@ -2018,7 +2126,7 @@ class LocalHostedSessionHandle {
|
|
|
2018
2126
|
}
|
|
2019
2127
|
return [{ type: "message.replace", message: structuredClone(nextDraft) }];
|
|
2020
2128
|
}
|
|
2021
|
-
async emitPatch(
|
|
2129
|
+
async emitPatch(cause, changes, options = {}) {
|
|
2022
2130
|
if (changes.length === 0) {
|
|
2023
2131
|
return;
|
|
2024
2132
|
}
|
|
@@ -2026,44 +2134,44 @@ class LocalHostedSessionHandle {
|
|
|
2026
2134
|
if (options.persist === false) {
|
|
2027
2135
|
const snapshot = { ...this.buildSnapshotDraft(), revision: 1 };
|
|
2028
2136
|
this.committedSnapshot = snapshot;
|
|
2029
|
-
this.emitSnapshotReset(
|
|
2137
|
+
this.emitSnapshotReset(cause, snapshot);
|
|
2030
2138
|
return;
|
|
2031
2139
|
}
|
|
2032
2140
|
const snapshot = await this.commitSnapshot();
|
|
2033
|
-
this.emitSnapshotReset(
|
|
2141
|
+
this.emitSnapshotReset(cause, snapshot);
|
|
2034
2142
|
return;
|
|
2035
2143
|
}
|
|
2036
2144
|
if (options.persist !== false) {
|
|
2037
|
-
this.emitDelta(await this.commitSnapshotPatch(
|
|
2145
|
+
this.emitDelta(await this.commitSnapshotPatch(cause, changes));
|
|
2038
2146
|
return;
|
|
2039
2147
|
}
|
|
2040
2148
|
const delta = createSessionProtocolDeltaMessage({
|
|
2041
2149
|
sessionId: this.committedSnapshot.sessionId,
|
|
2042
2150
|
fromRevision: this.committedSnapshot.revision,
|
|
2043
2151
|
toRevision: this.committedSnapshot.revision + 1,
|
|
2044
|
-
|
|
2152
|
+
cause: normalizeDeltaCause(cause),
|
|
2045
2153
|
delta: { type: "snapshot.patch", changes },
|
|
2046
2154
|
});
|
|
2047
2155
|
this.committedSnapshot = applySessionProtocolDelta(this.committedSnapshot, delta);
|
|
2048
2156
|
this.emitDelta(delta);
|
|
2049
2157
|
}
|
|
2050
|
-
emitSnapshotReset(
|
|
2158
|
+
emitSnapshotReset(cause, snapshot) {
|
|
2051
2159
|
this.emitDelta(createSessionProtocolDeltaMessage({
|
|
2052
2160
|
sessionId: snapshot.sessionId,
|
|
2053
2161
|
fromRevision: null,
|
|
2054
2162
|
toRevision: snapshot.revision,
|
|
2055
|
-
|
|
2163
|
+
cause: normalizeDeltaCause(cause),
|
|
2056
2164
|
delta: { type: "snapshot.reset", snapshot },
|
|
2057
2165
|
}));
|
|
2058
2166
|
}
|
|
2059
|
-
async enqueueSnapshotResetIfChanged(
|
|
2060
|
-
await this.enqueueMutation(() => this.emitSnapshotResetIfChanged(
|
|
2167
|
+
async enqueueSnapshotResetIfChanged(cause) {
|
|
2168
|
+
await this.enqueueMutation(() => this.emitSnapshotResetIfChanged(cause));
|
|
2061
2169
|
}
|
|
2062
|
-
async emitSnapshotResetIfChanged(
|
|
2170
|
+
async emitSnapshotResetIfChanged(cause) {
|
|
2063
2171
|
const previousRevision = this.committedSnapshot?.revision;
|
|
2064
2172
|
const snapshot = await this.commitSnapshot();
|
|
2065
2173
|
if (snapshot.revision !== previousRevision) {
|
|
2066
|
-
this.emitSnapshotReset(
|
|
2174
|
+
this.emitSnapshotReset(cause, snapshot);
|
|
2067
2175
|
}
|
|
2068
2176
|
}
|
|
2069
2177
|
emitDelta(delta) {
|
|
@@ -2113,6 +2221,10 @@ class LocalHostedSessionHandle {
|
|
|
2113
2221
|
};
|
|
2114
2222
|
this.tools.set(content.id, nextTool);
|
|
2115
2223
|
changes.push({ type: "tool.set", tool: structuredClone(nextTool) });
|
|
2224
|
+
if (!existing) {
|
|
2225
|
+
const timelineItem = this.appendToolTimelineItem(content.id, Date.now());
|
|
2226
|
+
changes.push({ type: "timeline.append", item: structuredClone(timelineItem) });
|
|
2227
|
+
}
|
|
2116
2228
|
}
|
|
2117
2229
|
return changes;
|
|
2118
2230
|
}
|
|
@@ -2124,21 +2236,16 @@ class LocalHostedSessionHandle {
|
|
|
2124
2236
|
const interruptedMessage = createInterruptedAssistantMessage(draft, this.runtime.persona.model);
|
|
2125
2237
|
await this.session.commitInterruptedAssistant(interruptedMessage, draft.id);
|
|
2126
2238
|
}
|
|
2127
|
-
async cleanupFailedTurn(rootHistoryEntryId,
|
|
2239
|
+
async cleanupFailedTurn(rootHistoryEntryId, error) {
|
|
2128
2240
|
if (this.draftAssistantMessage) {
|
|
2129
2241
|
await this.interruptDraftAssistantMessage().catch(() => undefined);
|
|
2130
2242
|
}
|
|
2131
2243
|
const diagnostic = formatErrorDiagnostic(error);
|
|
2132
2244
|
await this.enqueueMutation(async () => {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
};
|
|
2138
|
-
this.turnOutcomes.set(rootHistoryEntryId, outcome);
|
|
2139
|
-
for (const steering of committedSteering) {
|
|
2140
|
-
this.turnOutcomes.set(steering.historyEntryId, outcome);
|
|
2141
|
-
}
|
|
2245
|
+
this.appendTurnFailureTimelineItem(rootHistoryEntryId, {
|
|
2246
|
+
reason: "runtime-error",
|
|
2247
|
+
message: diagnostic,
|
|
2248
|
+
});
|
|
2142
2249
|
if (this.goal?.status === "active") {
|
|
2143
2250
|
this.goal = { ...this.goal, status: "blocked" };
|
|
2144
2251
|
}
|
|
@@ -2156,6 +2263,7 @@ class LocalHostedSessionHandle {
|
|
|
2156
2263
|
}
|
|
2157
2264
|
await this.emitSnapshotResetIfChanged("assistant-message");
|
|
2158
2265
|
});
|
|
2266
|
+
return { status: "failed", stopReason: "error", errorMessage: diagnostic };
|
|
2159
2267
|
}
|
|
2160
2268
|
}
|
|
2161
2269
|
function formatErrorDiagnostic(error) {
|
|
@@ -2197,6 +2305,9 @@ function turnOutcomeFromResult(result, assistantMessage) {
|
|
|
2197
2305
|
if (assistantMessage.stopReason === "pending") {
|
|
2198
2306
|
throw new Error("session turn completed with a pending assistant message");
|
|
2199
2307
|
}
|
|
2308
|
+
if (assistantMessage.stopReason === "deferred") {
|
|
2309
|
+
throw new Error("session turn completed with a deferred assistant message");
|
|
2310
|
+
}
|
|
2200
2311
|
return { status: "completed", stopReason: assistantMessage.stopReason };
|
|
2201
2312
|
}
|
|
2202
2313
|
function cloneSessionProtocolSnapshot(snapshot) {
|
|
@@ -2257,7 +2368,7 @@ function getAssistantDraftBlockText(message, type) {
|
|
|
2257
2368
|
}
|
|
2258
2369
|
function normalizeRecoveredSnapshot(snapshot) {
|
|
2259
2370
|
const recovered = cloneSessionProtocolSnapshot(snapshot);
|
|
2260
|
-
const legacyAgentState = recovered.agentState.
|
|
2371
|
+
const legacyAgentState = recovered.agentState.modelContextKey === LEGACY_SESSION_MODEL_CONTEXT_KEY;
|
|
2261
2372
|
let changed = recovered.lifecycle !== "idle" || legacyAgentState;
|
|
2262
2373
|
recovered.lifecycle = "idle";
|
|
2263
2374
|
if (recovered.goal?.status === "active") {
|
|
@@ -2269,6 +2380,19 @@ function normalizeRecoveredSnapshot(snapshot) {
|
|
|
2269
2380
|
recovered.agents = {};
|
|
2270
2381
|
recovered.facets = Object.fromEntries(Object.entries(recovered.facets).filter(([, facet]) => facet.subject.type !== "agent"));
|
|
2271
2382
|
}
|
|
2383
|
+
const recoveredAt = Date.now();
|
|
2384
|
+
for (const [id, operation] of Object.entries(recovered.operations)) {
|
|
2385
|
+
if (operation.status !== "running") {
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
changed = true;
|
|
2389
|
+
recovered.operations[id] = {
|
|
2390
|
+
...operation,
|
|
2391
|
+
status: "cancelled",
|
|
2392
|
+
finishedAt: recoveredAt,
|
|
2393
|
+
reason: "session-recovered",
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2272
2396
|
const streamingToolIds = new Set(Object.values(recovered.tools)
|
|
2273
2397
|
.filter((tool) => tool.status === "streaming")
|
|
2274
2398
|
.map((tool) => tool.id));
|
|
@@ -2276,6 +2400,10 @@ function normalizeRecoveredSnapshot(snapshot) {
|
|
|
2276
2400
|
changed = true;
|
|
2277
2401
|
recovered.tools = Object.fromEntries(Object.entries(recovered.tools).filter(([id]) => !streamingToolIds.has(id)));
|
|
2278
2402
|
recovered.facets = Object.fromEntries(Object.entries(recovered.facets).filter(([, facet]) => facet.subject.type !== "tool" || !streamingToolIds.has(facet.subject.id)));
|
|
2403
|
+
recovered.timeline.items = recovered.timeline.items.filter((item) => !(item.type === "tool" && streamingToolIds.has(item.toolId)) &&
|
|
2404
|
+
!(item.type === "notice" &&
|
|
2405
|
+
item.notice.subject.type === "tool" &&
|
|
2406
|
+
streamingToolIds.has(item.notice.subject.id)));
|
|
2279
2407
|
}
|
|
2280
2408
|
let recoveredAgentHistory = false;
|
|
2281
2409
|
recovered.messages = recovered.messages.map((message) => {
|
|
@@ -2294,7 +2422,7 @@ function normalizeRecoveredSnapshot(snapshot) {
|
|
|
2294
2422
|
if (recoveredAgentHistory) {
|
|
2295
2423
|
recovered.agentState = {
|
|
2296
2424
|
revision: recovered.agentState.revision + 1,
|
|
2297
|
-
|
|
2425
|
+
modelContextKey: recovered.agentState.modelContextKey,
|
|
2298
2426
|
...(recovered.agentState.usageCheckpoint
|
|
2299
2427
|
? { usageCheckpoint: { ...recovered.agentState.usageCheckpoint } }
|
|
2300
2428
|
: {}),
|
|
@@ -2316,13 +2444,6 @@ function promptCompositionFromSnapshot(snapshot) {
|
|
|
2316
2444
|
function cloneSubagentPrompts(subagentPrompts) {
|
|
2317
2445
|
return { ...subagentPrompts };
|
|
2318
2446
|
}
|
|
2319
|
-
function timelineItemForMessage(messageId) {
|
|
2320
|
-
return {
|
|
2321
|
-
type: "message",
|
|
2322
|
-
id: `timeline-${messageId}`,
|
|
2323
|
-
messageId,
|
|
2324
|
-
};
|
|
2325
|
-
}
|
|
2326
2447
|
function isCoreMessage(message) {
|
|
2327
2448
|
switch (message.role) {
|
|
2328
2449
|
case "user":
|