@markusylisiurunen/tau 0.3.37 → 0.3.39
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 +38 -4
- package/dist/core/agent/agent_runtime.js +22 -0
- package/dist/core/agent/agent_runtime.js.map +1 -1
- package/dist/core/cli.js +3 -0
- package/dist/core/cli.js.map +1 -1
- package/dist/core/config/content_loader.js +3 -1
- package/dist/core/config/content_loader.js.map +1 -1
- package/dist/core/config/index.js +1 -1
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.js +67 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/history/cli.js +90 -0
- package/dist/core/history/cli.js.map +1 -0
- package/dist/core/history/config.js +11 -0
- package/dist/core/history/config.js.map +1 -0
- package/dist/core/history/history_manager.js +170 -0
- package/dist/core/history/history_manager.js.map +1 -0
- package/dist/core/history/local_history_store.js +407 -0
- package/dist/core/history/local_history_store.js.map +1 -0
- package/dist/core/history/remote_history_client.js +152 -0
- package/dist/core/history/remote_history_client.js.map +1 -0
- package/dist/core/history/replication.js +89 -0
- package/dist/core/history/replication.js.map +1 -0
- package/dist/core/history/setup.js +187 -0
- package/dist/core/history/setup.js.map +1 -0
- package/dist/core/history/transcript.js +39 -0
- package/dist/core/history/transcript.js.map +1 -0
- package/dist/core/history/types.js +2 -0
- package/dist/core/history/types.js.map +1 -0
- package/dist/core/models/tau_model_overrides.js +63 -4
- package/dist/core/models/tau_model_overrides.js.map +1 -1
- package/dist/core/personas.js +2 -1
- package/dist/core/personas.js.map +1 -1
- package/dist/core/runtime/chat_runtime.js +9 -0
- package/dist/core/runtime/chat_runtime.js.map +1 -1
- package/dist/core/static/code_mode/history/documentation.md +133 -0
- package/dist/core/static/code_mode/history/sandbox_runner.mjs +108 -0
- package/dist/core/static/code_mode/web/documentation.md +7 -7
- package/dist/core/subagents/agent_supervisor.js +1 -1
- package/dist/core/subagents/agent_supervisor.js.map +1 -1
- package/dist/core/subagents/registry.js +2 -1
- package/dist/core/subagents/registry.js.map +1 -1
- package/dist/core/subagents/types.js +2 -1
- package/dist/core/subagents/types.js.map +1 -1
- package/dist/core/telegram/adapter.js +30 -0
- package/dist/core/telegram/adapter.js.map +1 -1
- package/dist/core/telegram/session_manager.js +143 -26
- package/dist/core/telegram/session_manager.js.map +1 -1
- package/dist/core/tools/catalog.js +11 -2
- package/dist/core/tools/catalog.js.map +1 -1
- package/dist/core/tools/history.js +125 -0
- package/dist/core/tools/history.js.map +1 -0
- package/dist/core/tools/spawn_agent.js +1 -0
- package/dist/core/tools/spawn_agent.js.map +1 -1
- package/dist/core/tools/tool_names.js +2 -0
- package/dist/core/tools/tool_names.js.map +1 -1
- package/dist/core/tools/web.js +1 -1
- package/dist/core/tools/web.js.map +1 -1
- package/dist/core/utils/repository.js +44 -0
- package/dist/core/utils/repository.js.map +1 -0
- package/dist/core/version.js +1 -1
- package/dist/history/worker/index.js +1106 -0
- package/dist/history/worker/index.js.map +1 -0
- package/dist/host/local_session_host.js +285 -179
- package/dist/host/local_session_host.js.map +1 -1
- package/dist/main.js +30 -3
- package/dist/main.js.map +1 -1
- package/dist/protocol/session_protocol.d.ts +4 -1
- package/dist/protocol/session_protocol.js +14 -6
- package/dist/protocol/session_protocol.js.map +1 -1
- package/dist/sdk/local_client.js +5 -0
- package/dist/sdk/local_client.js.map +1 -1
- package/dist/store/session_snapshot_migrations.js +61 -1
- package/dist/store/session_snapshot_migrations.js.map +1 -1
- package/dist/tui/session_chat_app.js +3 -0
- package/dist/tui/session_chat_app.js.map +1 -1
- package/dist/tui/session_chat_controller.js +46 -16
- package/dist/tui/session_chat_controller.js.map +1 -1
- package/dist/tui/session_creation_attributes.js +66 -0
- package/dist/tui/session_creation_attributes.js.map +1 -0
- package/package.json +2 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
3
|
import { resolvePromptTemplateWithBackend } from "../core/config/index.js";
|
|
4
|
+
import { assistantHistoryEntries, toolHistoryEntry, userHistoryEntry, } from "../core/history/transcript.js";
|
|
4
5
|
import { ChatRuntime } from "../core/runtime/chat_runtime.js";
|
|
5
6
|
import { formatSteeringUserMessage } from "../core/runtime/steering.js";
|
|
6
7
|
import { buildGoalContinuationText, prependGoalPolicy } from "../core/session/goal.js";
|
|
@@ -15,17 +16,22 @@ import { createExecutionEnvironmentSubagentRuntimeResolver } from "./execution_r
|
|
|
15
16
|
import { HostedEphemeralAgentSession } from "./hosted_ephemeral_agent_session.js";
|
|
16
17
|
import { SessionExecBusyError, SessionRetryUnavailableError, } from "./session_host.js";
|
|
17
18
|
const PATH_AUTOCOMPLETE_CACHE_TTL_MS = 5_000;
|
|
19
|
+
const HISTORY_UNAVAILABLE_NOTICE_ID = "notice-history-unavailable";
|
|
18
20
|
export class LocalSessionHost {
|
|
19
21
|
sessions = new Set();
|
|
20
22
|
sessionRecoveryPromises = new Map();
|
|
21
23
|
store;
|
|
24
|
+
history;
|
|
25
|
+
historyRemote;
|
|
22
26
|
clientToolBroker = new ClientToolBroker();
|
|
23
27
|
sessionOptions;
|
|
24
28
|
shutdownPromise;
|
|
25
29
|
shuttingDown = false;
|
|
26
30
|
constructor(options) {
|
|
27
|
-
const { store, ...sessionOptions } = options;
|
|
31
|
+
const { store, history, historyRemote, ...sessionOptions } = options;
|
|
28
32
|
this.store = store;
|
|
33
|
+
this.history = history;
|
|
34
|
+
this.historyRemote = historyRemote;
|
|
29
35
|
this.sessionOptions = sessionOptions;
|
|
30
36
|
}
|
|
31
37
|
registerClientTools(options) {
|
|
@@ -108,9 +114,9 @@ export class LocalSessionHost {
|
|
|
108
114
|
agentContextFiles: bootstrap.config?.agentContextFiles ?? [],
|
|
109
115
|
});
|
|
110
116
|
const catalog = createContentCatalogSnapshot(bootstrap);
|
|
111
|
-
return this.createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, forceNextSnapshotRevision);
|
|
117
|
+
return this.createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, createParams, forceNextSnapshotRevision);
|
|
112
118
|
}
|
|
113
|
-
createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, forceNextSnapshotRevision = false) {
|
|
119
|
+
async createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, createParams, forceNextSnapshotRevision = false) {
|
|
114
120
|
let hostedSession;
|
|
115
121
|
const runtime = ChatRuntime.create({
|
|
116
122
|
persona: bootstrap.persona,
|
|
@@ -131,6 +137,7 @@ export class LocalSessionHost {
|
|
|
131
137
|
createGoal: async (objective) => await hostedSession.createGoal(objective),
|
|
132
138
|
updateGoal: async (update) => await hostedSession.updateGoal(update),
|
|
133
139
|
},
|
|
140
|
+
history: this.history.query(this.historyRemote),
|
|
134
141
|
initialPromptComposition: committedSnapshot
|
|
135
142
|
? promptCompositionFromSnapshot(committedSnapshot)
|
|
136
143
|
: undefined,
|
|
@@ -138,7 +145,18 @@ export class LocalSessionHost {
|
|
|
138
145
|
recordUsage: this.sessionOptions.recordUsage,
|
|
139
146
|
deps: this.sessionOptions.deps,
|
|
140
147
|
});
|
|
141
|
-
|
|
148
|
+
const attributes = committedSnapshot?.attributes ?? createParams?.attributes;
|
|
149
|
+
if (!attributes) {
|
|
150
|
+
throw new Error("session attributes are required");
|
|
151
|
+
}
|
|
152
|
+
const createdAt = committedSnapshot?.createdAt ?? this.sessionOptions.environment.now();
|
|
153
|
+
hostedSession = new LocalHostedSessionHandle(runtime, runtimeContext.promptBootstrap, catalog, bootstrap, this.sessionOptions.includeAgentContext, executionEnvironment, this.store, this.history, this.historyRemote, attributes, createdAt, committedSnapshot, forceNextSnapshotRevision, this.sessionOptions.recordUsage ?? appendUsageLogEntry, (session) => this.sessions.delete(session));
|
|
154
|
+
const historyFailure = this.history.registerSession({
|
|
155
|
+
sessionId: committedSnapshot?.sessionId ?? hostedSession.sessionId,
|
|
156
|
+
attributes,
|
|
157
|
+
createdAt,
|
|
158
|
+
}, this.historyRemote);
|
|
159
|
+
await hostedSession.recordHistoryFailure(historyFailure);
|
|
142
160
|
this.sessions.add(hostedSession);
|
|
143
161
|
return hostedSession;
|
|
144
162
|
}
|
|
@@ -295,6 +313,13 @@ export class LocalSessionHost {
|
|
|
295
313
|
}
|
|
296
314
|
}
|
|
297
315
|
this.sessions.clear();
|
|
316
|
+
await this.history.flush();
|
|
317
|
+
try {
|
|
318
|
+
this.history.close();
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
errors.push(error);
|
|
322
|
+
}
|
|
298
323
|
if (errors.length > 0) {
|
|
299
324
|
throw new AggregateError(errors, "failed to shut down local session host");
|
|
300
325
|
}
|
|
@@ -326,6 +351,10 @@ class LocalHostedSessionHandle {
|
|
|
326
351
|
includeAgentContext;
|
|
327
352
|
executionEnvironment;
|
|
328
353
|
store;
|
|
354
|
+
history;
|
|
355
|
+
historyRemote;
|
|
356
|
+
attributes;
|
|
357
|
+
createdAt;
|
|
329
358
|
recordUsage;
|
|
330
359
|
removeFromHost;
|
|
331
360
|
session;
|
|
@@ -347,9 +376,7 @@ class LocalHostedSessionHandle {
|
|
|
347
376
|
ephemeralAgentSessions = new Map();
|
|
348
377
|
pathAutocompleteCache;
|
|
349
378
|
pathAutocompleteLoad;
|
|
350
|
-
|
|
351
|
-
snapshotQueue = Promise.resolve();
|
|
352
|
-
snapshotGeneration = 0;
|
|
379
|
+
mutationQueue = Promise.resolve();
|
|
353
380
|
activeWorkAbortControllers = new Set();
|
|
354
381
|
activeWorkPromises = new Set();
|
|
355
382
|
activeExecAbortControllers = new Map();
|
|
@@ -362,7 +389,7 @@ class LocalHostedSessionHandle {
|
|
|
362
389
|
pendingGoalCommit;
|
|
363
390
|
forceNextSnapshotRevision;
|
|
364
391
|
disposed = false;
|
|
365
|
-
constructor(runtime, promptBootstrap, catalog, bootstrap, includeAgentContext, executionEnvironment, store, committedSnapshot, forceNextSnapshotRevision, recordUsage, removeFromHost = () => { }) {
|
|
392
|
+
constructor(runtime, promptBootstrap, catalog, bootstrap, includeAgentContext, executionEnvironment, store, history, historyRemote, attributes, createdAt, committedSnapshot, forceNextSnapshotRevision, recordUsage, removeFromHost = () => { }) {
|
|
366
393
|
this.runtime = runtime;
|
|
367
394
|
this.promptBootstrap = promptBootstrap;
|
|
368
395
|
this.catalog = catalog;
|
|
@@ -370,6 +397,10 @@ class LocalHostedSessionHandle {
|
|
|
370
397
|
this.includeAgentContext = includeAgentContext;
|
|
371
398
|
this.executionEnvironment = executionEnvironment;
|
|
372
399
|
this.store = store;
|
|
400
|
+
this.history = history;
|
|
401
|
+
this.historyRemote = historyRemote;
|
|
402
|
+
this.attributes = attributes;
|
|
403
|
+
this.createdAt = createdAt;
|
|
373
404
|
this.recordUsage = recordUsage;
|
|
374
405
|
this.removeFromHost = removeFromHost;
|
|
375
406
|
this.session = runtime;
|
|
@@ -447,8 +478,10 @@ class LocalHostedSessionHandle {
|
|
|
447
478
|
async startGoal(objective) {
|
|
448
479
|
const goal = this.buildNewGoal(objective);
|
|
449
480
|
return await this.runLogicalTurn(async (logicalTurn) => {
|
|
450
|
-
this.
|
|
451
|
-
|
|
481
|
+
await this.enqueueMutation(() => {
|
|
482
|
+
this.goal = goal;
|
|
483
|
+
this.pendingGoalCommit = goal;
|
|
484
|
+
});
|
|
452
485
|
try {
|
|
453
486
|
const { userHistoryEntryId } = await this.record({
|
|
454
487
|
text: prependGoalPolicy(goal.objective, goal),
|
|
@@ -466,8 +499,10 @@ class LocalHostedSessionHandle {
|
|
|
466
499
|
}
|
|
467
500
|
catch (error) {
|
|
468
501
|
if (this.pendingGoalCommit === goal) {
|
|
469
|
-
this.
|
|
470
|
-
|
|
502
|
+
await this.enqueueMutation(() => {
|
|
503
|
+
this.goal = null;
|
|
504
|
+
this.pendingGoalCommit = undefined;
|
|
505
|
+
});
|
|
471
506
|
}
|
|
472
507
|
else {
|
|
473
508
|
await this.blockActiveGoal().catch(() => undefined);
|
|
@@ -527,6 +562,9 @@ class LocalHostedSessionHandle {
|
|
|
527
562
|
if (userMessage && hasGoalTurnMetadata(userMessage.message)) {
|
|
528
563
|
throw new SessionRetryUnavailableError("goal-controlled turns cannot be retried; resume a blocked goal or start a new goal");
|
|
529
564
|
}
|
|
565
|
+
if (!userMessage || !(await this.session.prepareRetry(userMessage.id))) {
|
|
566
|
+
throw new SessionRetryUnavailableError("no user turn is available to retry");
|
|
567
|
+
}
|
|
530
568
|
return await this.runTurn();
|
|
531
569
|
}
|
|
532
570
|
async runLogicalTurn(execute) {
|
|
@@ -549,7 +587,7 @@ class LocalHostedSessionHandle {
|
|
|
549
587
|
this.rejectBufferedLogicalSteering(logicalTurn, new Error("steering was not applied before the logical turn ended"));
|
|
550
588
|
this.activeTurnPromise = undefined;
|
|
551
589
|
this.activeLogicalTurn = undefined;
|
|
552
|
-
await this.
|
|
590
|
+
await this.enqueueSnapshotResetIfChanged("assistant-message");
|
|
553
591
|
}
|
|
554
592
|
}
|
|
555
593
|
}
|
|
@@ -584,20 +622,22 @@ class LocalHostedSessionHandle {
|
|
|
584
622
|
}
|
|
585
623
|
const initialOutcome = turnOutcomeFromResult(result, result.finalMessage);
|
|
586
624
|
const terminalOutcome = turnOutcomeFromResult(terminalResult, terminalResult.finalMessage);
|
|
587
|
-
this.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
625
|
+
await this.enqueueMutation(async () => {
|
|
626
|
+
this.turnOutcomes.set(userMessage.id, initialOutcome);
|
|
627
|
+
for (const steering of committedSteering) {
|
|
628
|
+
this.turnOutcomes.set(steering.historyEntryId, initialOutcome);
|
|
629
|
+
}
|
|
630
|
+
if (this.goal?.status === "active") {
|
|
631
|
+
goalRootHistoryEntryId ??= rootUserMessage.id;
|
|
632
|
+
}
|
|
633
|
+
if (goalRootHistoryEntryId) {
|
|
634
|
+
this.turnOutcomes.set(goalRootHistoryEntryId, terminalOutcome);
|
|
635
|
+
}
|
|
636
|
+
if (terminalOutcome.status !== "completed" && this.goal?.status === "active") {
|
|
637
|
+
this.goal = { ...this.goal, status: "blocked" };
|
|
638
|
+
}
|
|
639
|
+
await this.emitSnapshotResetIfChanged("assistant-message");
|
|
640
|
+
});
|
|
601
641
|
this.resolveCommittedLogicalSteering(committedSteering, initialOutcome);
|
|
602
642
|
if (logicalTurn.cancellationRequested) {
|
|
603
643
|
return await this.cancelLogicalTurn(rootUserMessage.id);
|
|
@@ -612,8 +652,7 @@ class LocalHostedSessionHandle {
|
|
|
612
652
|
}
|
|
613
653
|
catch (error) {
|
|
614
654
|
this.rejectCommittedLogicalSteering(committedSteering, error);
|
|
615
|
-
await this.cleanupFailedTurn().catch(() => undefined);
|
|
616
|
-
await this.blockActiveGoal().catch(() => undefined);
|
|
655
|
+
await this.cleanupFailedTurn(rootUserMessage.id, committedSteering, error).catch(() => undefined);
|
|
617
656
|
throw error;
|
|
618
657
|
}
|
|
619
658
|
}
|
|
@@ -645,7 +684,7 @@ class LocalHostedSessionHandle {
|
|
|
645
684
|
...(this.activeTurnPromise ? [this.activeTurnPromise] : []),
|
|
646
685
|
...this.activeWorkPromises,
|
|
647
686
|
]);
|
|
648
|
-
await this.
|
|
687
|
+
await this.mutationQueue;
|
|
649
688
|
}
|
|
650
689
|
requestTurnBoundaryStop() {
|
|
651
690
|
this.assertActive();
|
|
@@ -731,9 +770,7 @@ class LocalHostedSessionHandle {
|
|
|
731
770
|
}
|
|
732
771
|
async setReasoning(reasoning) {
|
|
733
772
|
this.assertActive();
|
|
734
|
-
|
|
735
|
-
.catch(() => undefined)
|
|
736
|
-
.then(async () => {
|
|
773
|
+
return await this.enqueueMutation(async () => {
|
|
737
774
|
const fromRevision = this.committedSnapshot?.revision;
|
|
738
775
|
this.runtime.setReasoning(reasoning);
|
|
739
776
|
this.bootstrap.persona.settings.reasoning = reasoning;
|
|
@@ -755,8 +792,6 @@ class LocalHostedSessionHandle {
|
|
|
755
792
|
}
|
|
756
793
|
return { revision: snapshot.revision, settings: snapshot.settings };
|
|
757
794
|
});
|
|
758
|
-
this.runtimeEventQueue = write.then(() => undefined, () => undefined);
|
|
759
|
-
return await write;
|
|
760
795
|
}
|
|
761
796
|
async setPersona(personaId) {
|
|
762
797
|
this.assertActive();
|
|
@@ -774,23 +809,25 @@ class LocalHostedSessionHandle {
|
|
|
774
809
|
includeAgentContext: this.includeAgentContext,
|
|
775
810
|
agentContextFiles: runtimeConfig.config.agentContextFiles ?? [],
|
|
776
811
|
});
|
|
777
|
-
this.
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
812
|
+
return await this.enqueueMutation(async () => {
|
|
813
|
+
this.runtime.setRuntimeConfig(runtimeConfig.config, runtimeConfig.bootstrap.modelResolver.resolveModel);
|
|
814
|
+
this.runtime.updatePromptContext(runtimeContext.promptBootstrap.promptContext);
|
|
815
|
+
this.runtime.setPersona(selectedPersona, {
|
|
816
|
+
skillsBlock: runtimeContext.promptBootstrap.promptContext.skillsBlock,
|
|
817
|
+
});
|
|
818
|
+
this.bootstrap = {
|
|
819
|
+
persona: clonePersona(selectedPersona),
|
|
820
|
+
discoveredSkills: structuredClone(runtimeConfig.skills),
|
|
821
|
+
personas: personas.map(clonePersona),
|
|
822
|
+
prompts: structuredClone(runtimeConfig.prompts),
|
|
823
|
+
modelResolver: runtimeConfig.bootstrap.modelResolver.resolveModel,
|
|
824
|
+
config: runtimeConfig.config,
|
|
825
|
+
};
|
|
826
|
+
this.catalog = createContentCatalogSnapshot(this.bootstrap);
|
|
827
|
+
const snapshot = await this.commitSnapshot();
|
|
828
|
+
this.emitSnapshotReset("configuration", snapshot);
|
|
829
|
+
return snapshot;
|
|
781
830
|
});
|
|
782
|
-
this.bootstrap = {
|
|
783
|
-
persona: clonePersona(selectedPersona),
|
|
784
|
-
discoveredSkills: structuredClone(runtimeConfig.skills),
|
|
785
|
-
personas: personas.map(clonePersona),
|
|
786
|
-
prompts: structuredClone(runtimeConfig.prompts),
|
|
787
|
-
modelResolver: runtimeConfig.bootstrap.modelResolver.resolveModel,
|
|
788
|
-
config: runtimeConfig.config,
|
|
789
|
-
};
|
|
790
|
-
this.catalog = createContentCatalogSnapshot(this.bootstrap);
|
|
791
|
-
const snapshot = await this.commitSnapshot();
|
|
792
|
-
this.emitSnapshotReset("configuration", snapshot);
|
|
793
|
-
return snapshot;
|
|
794
831
|
}
|
|
795
832
|
async reload() {
|
|
796
833
|
this.assertActive();
|
|
@@ -808,32 +845,34 @@ class LocalHostedSessionHandle {
|
|
|
808
845
|
includeAgentContext: this.includeAgentContext,
|
|
809
846
|
agentContextFiles: runtimeConfig.config.agentContextFiles ?? [],
|
|
810
847
|
});
|
|
811
|
-
this.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
848
|
+
return await this.enqueueMutation(async () => {
|
|
849
|
+
this.runtime.setRuntimeConfig(runtimeConfig.config, runtimeConfig.bootstrap.modelResolver.resolveModel);
|
|
850
|
+
this.runtime.updatePromptContext(runtimeContext.promptBootstrap.promptContext);
|
|
851
|
+
this.runtime.setPersona(nextPersona, {
|
|
852
|
+
skillsBlock: runtimeContext.promptBootstrap.promptContext.skillsBlock,
|
|
853
|
+
});
|
|
854
|
+
this.bootstrap = {
|
|
855
|
+
persona: clonePersona(nextPersona),
|
|
856
|
+
discoveredSkills: structuredClone(runtimeConfig.skills),
|
|
857
|
+
personas: personas.map(clonePersona),
|
|
858
|
+
prompts: structuredClone(runtimeConfig.prompts),
|
|
859
|
+
modelResolver: runtimeConfig.bootstrap.modelResolver.resolveModel,
|
|
860
|
+
config: runtimeConfig.config,
|
|
861
|
+
};
|
|
862
|
+
this.catalog = createContentCatalogSnapshot(this.bootstrap);
|
|
863
|
+
const unknownSkillWarnings = runtimeContext.promptBootstrap.unknownSkills.map((skill) => `unknown skill enabled by persona '${nextPersona.id}': ${skill}`);
|
|
864
|
+
const snapshot = await this.commitSnapshot();
|
|
865
|
+
this.emitSnapshotReset("configuration", snapshot);
|
|
866
|
+
return {
|
|
867
|
+
snapshot,
|
|
868
|
+
warnings: [...runtimeConfig.warnings, ...unknownSkillWarnings],
|
|
869
|
+
counts: {
|
|
870
|
+
personas: runtimeConfig.personas.length,
|
|
871
|
+
prompts: runtimeConfig.prompts.length,
|
|
872
|
+
skills: runtimeConfig.skills.length,
|
|
873
|
+
},
|
|
874
|
+
};
|
|
815
875
|
});
|
|
816
|
-
this.bootstrap = {
|
|
817
|
-
persona: clonePersona(nextPersona),
|
|
818
|
-
discoveredSkills: structuredClone(runtimeConfig.skills),
|
|
819
|
-
personas: personas.map(clonePersona),
|
|
820
|
-
prompts: structuredClone(runtimeConfig.prompts),
|
|
821
|
-
modelResolver: runtimeConfig.bootstrap.modelResolver.resolveModel,
|
|
822
|
-
config: runtimeConfig.config,
|
|
823
|
-
};
|
|
824
|
-
this.catalog = createContentCatalogSnapshot(this.bootstrap);
|
|
825
|
-
const unknownSkillWarnings = runtimeContext.promptBootstrap.unknownSkills.map((skill) => `unknown skill enabled by persona '${nextPersona.id}': ${skill}`);
|
|
826
|
-
const snapshot = await this.commitSnapshot();
|
|
827
|
-
this.emitSnapshotReset("configuration", snapshot);
|
|
828
|
-
return {
|
|
829
|
-
snapshot,
|
|
830
|
-
warnings: [...runtimeConfig.warnings, ...unknownSkillWarnings],
|
|
831
|
-
counts: {
|
|
832
|
-
personas: runtimeConfig.personas.length,
|
|
833
|
-
prompts: runtimeConfig.prompts.length,
|
|
834
|
-
skills: runtimeConfig.skills.length,
|
|
835
|
-
},
|
|
836
|
-
};
|
|
837
876
|
}
|
|
838
877
|
normalizeReloadedPersona(persona) {
|
|
839
878
|
return clonePersona(persona);
|
|
@@ -886,15 +925,16 @@ class LocalHostedSessionHandle {
|
|
|
886
925
|
signal,
|
|
887
926
|
});
|
|
888
927
|
signal.throwIfAborted();
|
|
889
|
-
await this.
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
928
|
+
return await this.enqueueMutation(async () => {
|
|
929
|
+
this.reconcileProjections();
|
|
930
|
+
const snapshot = await this.commitSnapshot();
|
|
931
|
+
this.emitSnapshotReset("maintenance", snapshot);
|
|
932
|
+
return {
|
|
933
|
+
snapshot,
|
|
934
|
+
compactionMessage: result.compactionMessage,
|
|
935
|
+
includedLastAssistant: result.includedLastAssistant,
|
|
936
|
+
};
|
|
937
|
+
});
|
|
898
938
|
});
|
|
899
939
|
}
|
|
900
940
|
async runMaintenance(operation) {
|
|
@@ -998,28 +1038,36 @@ class LocalHostedSessionHandle {
|
|
|
998
1038
|
}
|
|
999
1039
|
async snapshot() {
|
|
1000
1040
|
this.assertActive();
|
|
1001
|
-
return this.runtime.isTurnRunning && this.committedSnapshot
|
|
1041
|
+
return await this.enqueueMutation(async () => this.runtime.isTurnRunning && this.committedSnapshot
|
|
1002
1042
|
? await this.commitProjectedSnapshot()
|
|
1003
|
-
: await this.commitSnapshot();
|
|
1043
|
+
: await this.commitSnapshot());
|
|
1004
1044
|
}
|
|
1005
1045
|
async persistRecoveredAgentState(recovery) {
|
|
1006
1046
|
this.assertActive();
|
|
1007
|
-
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
? {
|
|
1014
|
-
...tool,
|
|
1015
|
-
status: "cancelled",
|
|
1016
|
-
finishedAt: recovered.message.timestamp,
|
|
1017
|
-
resultMessageId: recovered.historyEntryId,
|
|
1018
|
-
error: "Tool completion status is unknown after session recovery.",
|
|
1047
|
+
await this.enqueueMutation(async () => {
|
|
1048
|
+
const recoveredTools = [];
|
|
1049
|
+
for (const recovered of recovery.recoveredToolResults) {
|
|
1050
|
+
const tool = this.tools.get(recovered.message.toolCallId);
|
|
1051
|
+
if (!tool || tool.status === "streaming") {
|
|
1052
|
+
continue;
|
|
1019
1053
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1054
|
+
const nextTool = tool.status === "queued" || tool.status === "running"
|
|
1055
|
+
? {
|
|
1056
|
+
...tool,
|
|
1057
|
+
status: "cancelled",
|
|
1058
|
+
finishedAt: recovered.message.timestamp,
|
|
1059
|
+
resultMessageId: recovered.historyEntryId,
|
|
1060
|
+
error: "Tool completion status is unknown after session recovery.",
|
|
1061
|
+
}
|
|
1062
|
+
: { ...tool, resultMessageId: recovered.historyEntryId };
|
|
1063
|
+
this.tools.set(tool.id, nextTool);
|
|
1064
|
+
recoveredTools.push({ tool: nextTool, result: recovered });
|
|
1065
|
+
}
|
|
1066
|
+
await this.commitSnapshot();
|
|
1067
|
+
for (const { tool, result } of recoveredTools) {
|
|
1068
|
+
await this.appendToolHistory(tool, result.historyEntryId, result.message);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1023
1071
|
}
|
|
1024
1072
|
async dispose() {
|
|
1025
1073
|
if (!this.disposePromise) {
|
|
@@ -1081,15 +1129,10 @@ class LocalHostedSessionHandle {
|
|
|
1081
1129
|
return { objective: normalized, status: "active" };
|
|
1082
1130
|
}
|
|
1083
1131
|
async setGoal(goal) {
|
|
1084
|
-
|
|
1085
|
-
this.goal = structuredClone(goal);
|
|
1086
|
-
try {
|
|
1132
|
+
await this.enqueueMutation(async () => {
|
|
1087
1133
|
await this.emitPatch("goal", [{ type: "goal.set", goal: structuredClone(goal) }]);
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
this.goal = previousGoal;
|
|
1091
|
-
throw error;
|
|
1092
|
-
}
|
|
1134
|
+
this.goal = structuredClone(goal);
|
|
1135
|
+
});
|
|
1093
1136
|
}
|
|
1094
1137
|
async blockActiveGoal() {
|
|
1095
1138
|
if (this.goal?.status === "active") {
|
|
@@ -1174,37 +1217,21 @@ class LocalHostedSessionHandle {
|
|
|
1174
1217
|
}
|
|
1175
1218
|
async cancelLogicalTurn(rootHistoryEntryId, committedSteering = []) {
|
|
1176
1219
|
const outcome = abortedTurnOutcome();
|
|
1177
|
-
this.
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1220
|
+
await this.enqueueMutation(async () => {
|
|
1221
|
+
this.turnOutcomes.set(rootHistoryEntryId, outcome);
|
|
1222
|
+
for (const steering of committedSteering) {
|
|
1223
|
+
this.turnOutcomes.set(steering.historyEntryId, outcome);
|
|
1224
|
+
}
|
|
1225
|
+
if (this.goal?.status === "active") {
|
|
1226
|
+
this.goal = { ...this.goal, status: "blocked" };
|
|
1227
|
+
}
|
|
1228
|
+
await this.emitSnapshotResetIfChanged("assistant-message");
|
|
1229
|
+
});
|
|
1183
1230
|
this.resolveCommittedLogicalSteering(committedSteering, outcome);
|
|
1184
1231
|
return outcome;
|
|
1185
1232
|
}
|
|
1186
1233
|
async commitSnapshot() {
|
|
1187
|
-
const write = this.snapshotQueue.catch(() => undefined).then(() => this.writeSnapshot());
|
|
1188
|
-
this.snapshotQueue = write.catch(() => undefined);
|
|
1189
|
-
return await write;
|
|
1190
|
-
}
|
|
1191
|
-
async commitProjectedSnapshot() {
|
|
1192
|
-
const write = this.snapshotQueue
|
|
1193
|
-
.catch(() => undefined)
|
|
1194
|
-
.then(() => this.writeProjectedSnapshot());
|
|
1195
|
-
this.snapshotQueue = write.catch(() => undefined);
|
|
1196
|
-
return await write;
|
|
1197
|
-
}
|
|
1198
|
-
async commitSnapshotPatch(reason, changes) {
|
|
1199
|
-
const write = this.snapshotQueue
|
|
1200
|
-
.catch(() => undefined)
|
|
1201
|
-
.then(() => this.writeSnapshotPatch(reason, changes));
|
|
1202
|
-
this.snapshotQueue = write.catch(() => undefined);
|
|
1203
|
-
return await write;
|
|
1204
|
-
}
|
|
1205
|
-
async writeSnapshot() {
|
|
1206
1234
|
this.assertNotDisposed();
|
|
1207
|
-
const generation = this.snapshotGeneration;
|
|
1208
1235
|
const draft = this.buildSnapshotDraft();
|
|
1209
1236
|
await this.switchSnapshotSession(draft.sessionId);
|
|
1210
1237
|
const snapshot = {
|
|
@@ -1218,17 +1245,14 @@ class LocalHostedSessionHandle {
|
|
|
1218
1245
|
expectedRevision: this.persistedSnapshot?.revision ?? 0,
|
|
1219
1246
|
});
|
|
1220
1247
|
this.persistedSnapshot = cloneSessionProtocolSnapshot(snapshot);
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
}
|
|
1224
|
-
return cloneSessionProtocolSnapshot(this.updateCommittedSnapshotAfterWrite(snapshot));
|
|
1248
|
+
this.committedSnapshot = cloneSessionProtocolSnapshot(snapshot);
|
|
1249
|
+
return cloneSessionProtocolSnapshot(snapshot);
|
|
1225
1250
|
}
|
|
1226
|
-
async
|
|
1251
|
+
async commitProjectedSnapshot() {
|
|
1227
1252
|
this.assertNotDisposed();
|
|
1228
|
-
const generation = this.snapshotGeneration;
|
|
1229
1253
|
const current = this.committedSnapshot;
|
|
1230
1254
|
if (!current) {
|
|
1231
|
-
return await this.
|
|
1255
|
+
return await this.commitSnapshot();
|
|
1232
1256
|
}
|
|
1233
1257
|
const snapshot = cloneSessionProtocolSnapshot(current);
|
|
1234
1258
|
if (this.persistedSnapshot && isDeepStrictEqual(this.persistedSnapshot, snapshot)) {
|
|
@@ -1238,12 +1262,9 @@ class LocalHostedSessionHandle {
|
|
|
1238
1262
|
expectedRevision: this.persistedSnapshot?.revision ?? 0,
|
|
1239
1263
|
});
|
|
1240
1264
|
this.persistedSnapshot = cloneSessionProtocolSnapshot(snapshot);
|
|
1241
|
-
|
|
1242
|
-
return await this.writeProjectedSnapshot();
|
|
1243
|
-
}
|
|
1244
|
-
return cloneSessionProtocolSnapshot(this.committedSnapshot ?? snapshot);
|
|
1265
|
+
return snapshot;
|
|
1245
1266
|
}
|
|
1246
|
-
async
|
|
1267
|
+
async commitSnapshotPatch(reason, changes) {
|
|
1247
1268
|
this.assertNotDisposed();
|
|
1248
1269
|
const current = this.committedSnapshot;
|
|
1249
1270
|
if (!current) {
|
|
@@ -1262,7 +1283,6 @@ class LocalHostedSessionHandle {
|
|
|
1262
1283
|
});
|
|
1263
1284
|
this.persistedSnapshot = cloneSessionProtocolSnapshot(snapshot);
|
|
1264
1285
|
this.committedSnapshot = cloneSessionProtocolSnapshot(snapshot);
|
|
1265
|
-
this.snapshotGeneration += 1;
|
|
1266
1286
|
return delta;
|
|
1267
1287
|
}
|
|
1268
1288
|
async switchSnapshotSession(sessionId) {
|
|
@@ -1276,13 +1296,6 @@ class LocalHostedSessionHandle {
|
|
|
1276
1296
|
this.committedSnapshot = undefined;
|
|
1277
1297
|
this.persistedSnapshot = undefined;
|
|
1278
1298
|
}
|
|
1279
|
-
updateCommittedSnapshotAfterWrite(snapshot) {
|
|
1280
|
-
if (!this.committedSnapshot || this.committedSnapshot.revision < snapshot.revision) {
|
|
1281
|
-
this.committedSnapshot = cloneSessionProtocolSnapshot(snapshot);
|
|
1282
|
-
this.snapshotGeneration += 1;
|
|
1283
|
-
}
|
|
1284
|
-
return this.committedSnapshot;
|
|
1285
|
-
}
|
|
1286
1299
|
reconcileProjections(options = {}) {
|
|
1287
1300
|
const messageIds = new Set(this.session.rawHistoryEntries.map((entry) => entry.id));
|
|
1288
1301
|
messageIds.add("system");
|
|
@@ -1325,6 +1338,8 @@ class LocalHostedSessionHandle {
|
|
|
1325
1338
|
const agentState = this.session.snapshot();
|
|
1326
1339
|
return {
|
|
1327
1340
|
sessionId: this.session.sessionId,
|
|
1341
|
+
attributes: structuredClone(this.attributes),
|
|
1342
|
+
createdAt: this.createdAt,
|
|
1328
1343
|
agentState: {
|
|
1329
1344
|
revision: agentState.revision,
|
|
1330
1345
|
contextEpoch: agentState.contextEpoch,
|
|
@@ -1440,7 +1455,8 @@ class LocalHostedSessionHandle {
|
|
|
1440
1455
|
for (const [id, facet] of Object.entries(snapshot.facets)) {
|
|
1441
1456
|
this.facets.set(id, structuredClone(facet));
|
|
1442
1457
|
}
|
|
1443
|
-
|
|
1458
|
+
const historyEntryIds = new Set(this.session.rawHistoryEntries.map((entry) => entry.id));
|
|
1459
|
+
this.draftAssistantMessage = snapshot.messages.find((message) => message.state === "draft" && !historyEntryIds.has(message.id));
|
|
1444
1460
|
this.messageStates.clear();
|
|
1445
1461
|
this.turnOutcomes.clear();
|
|
1446
1462
|
for (const message of snapshot.messages) {
|
|
@@ -1452,6 +1468,24 @@ class LocalHostedSessionHandle {
|
|
|
1452
1468
|
}
|
|
1453
1469
|
}
|
|
1454
1470
|
}
|
|
1471
|
+
enqueueMutation(mutation) {
|
|
1472
|
+
const result = this.mutationQueue
|
|
1473
|
+
.catch(() => undefined)
|
|
1474
|
+
.then(async () => {
|
|
1475
|
+
try {
|
|
1476
|
+
return await mutation();
|
|
1477
|
+
}
|
|
1478
|
+
catch (error) {
|
|
1479
|
+
if (this.committedSnapshot) {
|
|
1480
|
+
this.goal = structuredClone(this.committedSnapshot.goal);
|
|
1481
|
+
this.restoreProtocolState(this.committedSnapshot);
|
|
1482
|
+
}
|
|
1483
|
+
throw error;
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
this.mutationQueue = result.then(() => undefined, () => undefined);
|
|
1487
|
+
return result;
|
|
1488
|
+
}
|
|
1455
1489
|
assertActive() {
|
|
1456
1490
|
if (this.disposing || this.disposed) {
|
|
1457
1491
|
throw new Error(`session is shut down: ${this.committedSessionId}`);
|
|
@@ -1474,23 +1508,13 @@ class LocalHostedSessionHandle {
|
|
|
1474
1508
|
return JSON.stringify(current) === JSON.stringify(next) ? revision : revision + 1;
|
|
1475
1509
|
}
|
|
1476
1510
|
async recordSubagentEvent(event) {
|
|
1477
|
-
|
|
1478
|
-
.catch(() => undefined)
|
|
1479
|
-
.then(() => this.recordSubagentUiEvent(event));
|
|
1480
|
-
this.runtimeEventQueue = write.catch(() => undefined);
|
|
1481
|
-
return await write;
|
|
1511
|
+
await this.enqueueMutation(() => this.recordSubagentUiEvent(event));
|
|
1482
1512
|
}
|
|
1483
1513
|
async enqueueRuntimeEvent(event) {
|
|
1484
|
-
|
|
1485
|
-
.catch(() => undefined)
|
|
1486
|
-
.then(() => this.recordRuntimeEvent(event));
|
|
1487
|
-
this.runtimeEventQueue = write.catch(() => undefined);
|
|
1488
|
-
return write;
|
|
1514
|
+
await this.enqueueMutation(() => this.recordRuntimeEvent(event));
|
|
1489
1515
|
}
|
|
1490
1516
|
async commitSteeringTurnOutcome(historyEntryId, turn) {
|
|
1491
|
-
|
|
1492
|
-
.catch(() => undefined)
|
|
1493
|
-
.then(async () => {
|
|
1517
|
+
await this.enqueueMutation(async () => {
|
|
1494
1518
|
this.turnOutcomes.set(historyEntryId, turn);
|
|
1495
1519
|
const message = this.committedSnapshot?.messages.find((candidate) => candidate.id === historyEntryId);
|
|
1496
1520
|
if (!message) {
|
|
@@ -1506,8 +1530,6 @@ class LocalHostedSessionHandle {
|
|
|
1506
1530
|
},
|
|
1507
1531
|
]);
|
|
1508
1532
|
});
|
|
1509
|
-
this.runtimeEventQueue = write.catch(() => undefined);
|
|
1510
|
-
await write;
|
|
1511
1533
|
}
|
|
1512
1534
|
removeToolRun(tool, changes) {
|
|
1513
1535
|
this.tools.delete(tool.id);
|
|
@@ -1517,6 +1539,43 @@ class LocalHostedSessionHandle {
|
|
|
1517
1539
|
}
|
|
1518
1540
|
changes.push({ type: "tool.remove", id: tool.id });
|
|
1519
1541
|
}
|
|
1542
|
+
async recordHistoryFailure(reason) {
|
|
1543
|
+
if (!reason || this.timelineExtras.some((item) => item.id === HISTORY_UNAVAILABLE_NOTICE_ID)) {
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
const item = {
|
|
1547
|
+
type: "notice",
|
|
1548
|
+
id: HISTORY_UNAVAILABLE_NOTICE_ID,
|
|
1549
|
+
notice: {
|
|
1550
|
+
severity: "warn",
|
|
1551
|
+
text: `Session history is unavailable: ${reason}. This session will continue without durable history recording or recall.`,
|
|
1552
|
+
timestamp: Date.now(),
|
|
1553
|
+
},
|
|
1554
|
+
};
|
|
1555
|
+
this.timelineExtras.push(item);
|
|
1556
|
+
await this.emitPatch("notice", [{ type: "timeline.append", item: structuredClone(item) }]);
|
|
1557
|
+
}
|
|
1558
|
+
async appendToolHistory(tool, resultHistoryEntryId, result) {
|
|
1559
|
+
const callMessage = this.committedSnapshot?.messages.find((message) => message.id === tool.call.messageId);
|
|
1560
|
+
const call = callMessage?.message.role === "assistant"
|
|
1561
|
+
? callMessage.message.content[tool.call.contentIndex]
|
|
1562
|
+
: undefined;
|
|
1563
|
+
if (call?.type !== "toolCall" || call.id !== tool.toolCallId) {
|
|
1564
|
+
throw new Error(`missing transcript tool call '${tool.toolCallId}'`);
|
|
1565
|
+
}
|
|
1566
|
+
if (tool.status === "queued" || tool.status === "running") {
|
|
1567
|
+
throw new Error(`transcript tool call '${tool.toolCallId}' is not terminal`);
|
|
1568
|
+
}
|
|
1569
|
+
await this.recordHistoryFailure(this.history.append(this.sessionId, [
|
|
1570
|
+
toolHistoryEntry({
|
|
1571
|
+
callHistoryEntryId: tool.call.messageId,
|
|
1572
|
+
resultHistoryEntryId,
|
|
1573
|
+
call,
|
|
1574
|
+
result,
|
|
1575
|
+
outcome: tool.status,
|
|
1576
|
+
}),
|
|
1577
|
+
], this.historyRemote));
|
|
1578
|
+
}
|
|
1520
1579
|
agentStateChange() {
|
|
1521
1580
|
const state = this.session.snapshot();
|
|
1522
1581
|
return {
|
|
@@ -1659,6 +1718,7 @@ class LocalHostedSessionHandle {
|
|
|
1659
1718
|
timelineItem: timelineItemForMessage(event.historyEntryId),
|
|
1660
1719
|
},
|
|
1661
1720
|
], { persist: true });
|
|
1721
|
+
await this.recordHistoryFailure(this.history.append(this.sessionId, [userHistoryEntry(event.historyEntryId, event.message)], this.historyRemote));
|
|
1662
1722
|
if (pendingGoal && this.pendingGoalCommit === pendingGoal) {
|
|
1663
1723
|
this.pendingGoalCommit = undefined;
|
|
1664
1724
|
}
|
|
@@ -1667,6 +1727,7 @@ class LocalHostedSessionHandle {
|
|
|
1667
1727
|
case "history_rewound":
|
|
1668
1728
|
this.reconcileProjections({ removeMissingAgents: true });
|
|
1669
1729
|
await this.emitSnapshotResetIfChanged("maintenance");
|
|
1730
|
+
await this.recordHistoryFailure(this.history.truncateFromSources(this.sessionId, event.removedEntryIds, this.historyRemote));
|
|
1670
1731
|
return;
|
|
1671
1732
|
case "assistant_final": {
|
|
1672
1733
|
this.draftAssistantMessage = undefined;
|
|
@@ -1713,6 +1774,7 @@ class LocalHostedSessionHandle {
|
|
|
1713
1774
|
: []),
|
|
1714
1775
|
...toolChanges,
|
|
1715
1776
|
]);
|
|
1777
|
+
await this.recordHistoryFailure(this.history.append(this.sessionId, assistantHistoryEntries(event.historyEntryId, event.message), this.historyRemote));
|
|
1716
1778
|
return;
|
|
1717
1779
|
}
|
|
1718
1780
|
case "tool_run_queued":
|
|
@@ -1771,6 +1833,7 @@ class LocalHostedSessionHandle {
|
|
|
1771
1833
|
},
|
|
1772
1834
|
{ type: "tool.set", tool: structuredClone(nextTool) },
|
|
1773
1835
|
]);
|
|
1836
|
+
await this.appendToolHistory(existing, event.historyEntryId, event.message);
|
|
1774
1837
|
return;
|
|
1775
1838
|
}
|
|
1776
1839
|
await this.emitPatch("tool-result", [
|
|
@@ -1801,6 +1864,7 @@ class LocalHostedSessionHandle {
|
|
|
1801
1864
|
},
|
|
1802
1865
|
},
|
|
1803
1866
|
];
|
|
1867
|
+
const historyTools = [];
|
|
1804
1868
|
for (const toolResult of event.toolResults) {
|
|
1805
1869
|
const existing = this.tools.get(toolResult.toolCallId);
|
|
1806
1870
|
if (!existing || existing.status === "streaming") {
|
|
@@ -1814,8 +1878,12 @@ class LocalHostedSessionHandle {
|
|
|
1814
1878
|
};
|
|
1815
1879
|
this.tools.set(nextTool.id, nextTool);
|
|
1816
1880
|
changes.push({ type: "tool.set", tool: structuredClone(nextTool) });
|
|
1881
|
+
historyTools.push({ tool: existing, result: toolResult });
|
|
1817
1882
|
}
|
|
1818
1883
|
await this.emitPatch("recovery", changes);
|
|
1884
|
+
for (const { tool, result } of historyTools) {
|
|
1885
|
+
await this.appendToolHistory(tool, event.historyEntryId, result);
|
|
1886
|
+
}
|
|
1819
1887
|
return;
|
|
1820
1888
|
}
|
|
1821
1889
|
case "notice": {
|
|
@@ -1952,7 +2020,6 @@ class LocalHostedSessionHandle {
|
|
|
1952
2020
|
if (options.persist === false) {
|
|
1953
2021
|
const snapshot = { ...this.buildSnapshotDraft(), revision: 1 };
|
|
1954
2022
|
this.committedSnapshot = snapshot;
|
|
1955
|
-
this.snapshotGeneration += 1;
|
|
1956
2023
|
this.emitSnapshotReset(reason, snapshot);
|
|
1957
2024
|
return;
|
|
1958
2025
|
}
|
|
@@ -1972,7 +2039,6 @@ class LocalHostedSessionHandle {
|
|
|
1972
2039
|
delta: { type: "snapshot.patch", changes },
|
|
1973
2040
|
});
|
|
1974
2041
|
this.committedSnapshot = applySessionProtocolDelta(this.committedSnapshot, delta);
|
|
1975
|
-
this.snapshotGeneration += 1;
|
|
1976
2042
|
this.emitDelta(delta);
|
|
1977
2043
|
}
|
|
1978
2044
|
emitSnapshotReset(reason, snapshot) {
|
|
@@ -1984,6 +2050,9 @@ class LocalHostedSessionHandle {
|
|
|
1984
2050
|
delta: { type: "snapshot.reset", snapshot },
|
|
1985
2051
|
}));
|
|
1986
2052
|
}
|
|
2053
|
+
async enqueueSnapshotResetIfChanged(reason) {
|
|
2054
|
+
await this.enqueueMutation(() => this.emitSnapshotResetIfChanged(reason));
|
|
2055
|
+
}
|
|
1987
2056
|
async emitSnapshotResetIfChanged(reason) {
|
|
1988
2057
|
const previousRevision = this.committedSnapshot?.revision;
|
|
1989
2058
|
const snapshot = await this.commitSnapshot();
|
|
@@ -2049,14 +2118,51 @@ class LocalHostedSessionHandle {
|
|
|
2049
2118
|
const interruptedMessage = createInterruptedAssistantMessage(draft, this.runtime.persona.model);
|
|
2050
2119
|
await this.session.commitInterruptedAssistant(interruptedMessage, draft.id);
|
|
2051
2120
|
}
|
|
2052
|
-
async cleanupFailedTurn() {
|
|
2121
|
+
async cleanupFailedTurn(rootHistoryEntryId, committedSteering, error) {
|
|
2053
2122
|
if (this.draftAssistantMessage) {
|
|
2054
|
-
await this.interruptDraftAssistantMessage();
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
await this.
|
|
2123
|
+
await this.interruptDraftAssistantMessage().catch(() => undefined);
|
|
2124
|
+
}
|
|
2125
|
+
const diagnostic = formatErrorDiagnostic(error);
|
|
2126
|
+
await this.enqueueMutation(async () => {
|
|
2127
|
+
const outcome = {
|
|
2128
|
+
status: "failed",
|
|
2129
|
+
stopReason: "error",
|
|
2130
|
+
errorMessage: diagnostic,
|
|
2131
|
+
};
|
|
2132
|
+
this.turnOutcomes.set(rootHistoryEntryId, outcome);
|
|
2133
|
+
for (const steering of committedSteering) {
|
|
2134
|
+
this.turnOutcomes.set(steering.historyEntryId, outcome);
|
|
2135
|
+
}
|
|
2136
|
+
if (this.goal?.status === "active") {
|
|
2137
|
+
this.goal = { ...this.goal, status: "blocked" };
|
|
2138
|
+
}
|
|
2139
|
+
const timestamp = Date.now();
|
|
2140
|
+
for (const [id, tool] of this.tools) {
|
|
2141
|
+
if (tool.status !== "queued" && tool.status !== "running") {
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
2144
|
+
this.tools.set(id, {
|
|
2145
|
+
...tool,
|
|
2146
|
+
status: "cancelled",
|
|
2147
|
+
finishedAt: timestamp,
|
|
2148
|
+
error: `Turn failed before tool completion: ${diagnostic}`,
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
await this.emitSnapshotResetIfChanged("assistant-message");
|
|
2152
|
+
});
|
|
2058
2153
|
}
|
|
2059
2154
|
}
|
|
2155
|
+
function formatErrorDiagnostic(error) {
|
|
2156
|
+
const messages = [];
|
|
2157
|
+
const seen = new Set();
|
|
2158
|
+
let current = error;
|
|
2159
|
+
while (current !== undefined && !seen.has(current)) {
|
|
2160
|
+
seen.add(current);
|
|
2161
|
+
messages.push(current instanceof Error ? current.message : String(current));
|
|
2162
|
+
current = current instanceof Error ? current.cause : undefined;
|
|
2163
|
+
}
|
|
2164
|
+
return messages.filter((message, index) => message && message !== messages[index - 1]).join(": ");
|
|
2165
|
+
}
|
|
2060
2166
|
function abortedTurnOutcome() {
|
|
2061
2167
|
return { status: "aborted", stopReason: "aborted" };
|
|
2062
2168
|
}
|