@markusylisiurunen/tau 0.3.43 → 0.3.45

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.
Files changed (47) hide show
  1. package/README.md +8 -11
  2. package/dist/core/agent/agent_runtime.js +23 -8
  3. package/dist/core/agent/agent_runtime.js.map +1 -1
  4. package/dist/core/config/content_loader.js +1 -63
  5. package/dist/core/config/content_loader.js.map +1 -1
  6. package/dist/core/nook/client.js +11 -4
  7. package/dist/core/nook/client.js.map +1 -1
  8. package/dist/core/nook/setup.js +5 -2
  9. package/dist/core/nook/setup.js.map +1 -1
  10. package/dist/core/runtime/chat_runtime.js +4 -4
  11. package/dist/core/subagents/agent_supervisor.js +1 -1
  12. package/dist/core/subagents/agent_supervisor.js.map +1 -1
  13. package/dist/core/subagents/registry.js +5 -9
  14. package/dist/core/subagents/registry.js.map +1 -1
  15. package/dist/core/subagents/types.js.map +1 -1
  16. package/dist/core/telegram/adapter.js +38 -14
  17. package/dist/core/telegram/adapter.js.map +1 -1
  18. package/dist/core/telegram/session_manager.js +404 -32
  19. package/dist/core/telegram/session_manager.js.map +1 -1
  20. package/dist/core/tools/catalog.js +2 -2
  21. package/dist/core/tools/spawn_agent.js +30 -40
  22. package/dist/core/tools/spawn_agent.js.map +1 -1
  23. package/dist/core/tools/web.js +5 -1
  24. package/dist/core/tools/web.js.map +1 -1
  25. package/dist/core/version.js +1 -1
  26. package/dist/host/execution_runtime.js +8 -18
  27. package/dist/host/execution_runtime.js.map +1 -1
  28. package/dist/host/local_session_host.js +140 -28
  29. package/dist/host/local_session_host.js.map +1 -1
  30. package/dist/host/session_host.js.map +1 -1
  31. package/dist/host/session_protocol_handler.js +2 -7
  32. package/dist/host/session_protocol_handler.js.map +1 -1
  33. package/dist/nook/README.md +3 -3
  34. package/dist/nook/worker/index.js +70 -1
  35. package/dist/nook/worker/index.js.map +1 -1
  36. package/dist/protocol/index.d.ts +1 -1
  37. package/dist/protocol/index.js.map +1 -1
  38. package/dist/protocol/session_protocol.d.ts +13 -1
  39. package/dist/protocol/session_protocol.js +57 -3
  40. package/dist/protocol/session_protocol.js.map +1 -1
  41. package/dist/sdk/index.d.ts +4 -2
  42. package/dist/sdk/index.js +9 -0
  43. package/dist/sdk/index.js.map +1 -1
  44. package/dist/sdk/types.d.ts +2 -1
  45. package/dist/store/session_snapshot_migrations.js +21 -1
  46. package/dist/store/session_snapshot_migrations.js.map +1 -1
  47. package/package.json +1 -1
@@ -13,7 +13,7 @@ import { hasGoalTurnMetadata } from "../core/utils/user_metadata.js";
13
13
  import { applySessionProtocolDelta, createSessionProtocolDeltaMessage, createSessionProtocolEphemeralMessage, projectSessionProtocolNoticePresentation, } from "../protocol/session_protocol.js";
14
14
  import { LEGACY_SESSION_MODEL_CONTEXT_KEY } from "../store/session_snapshot_migrations.js";
15
15
  import { ClientToolBroker } from "./client_tool_broker.js";
16
- import { createExecutionEnvironmentSubagentRuntimeResolver } from "./execution_runtime.js";
16
+ import { createExecutionEnvironmentSubagentPromptResolver } from "./execution_runtime.js";
17
17
  import { HostedEphemeralAgentSession } from "./hosted_ephemeral_agent_session.js";
18
18
  import { SessionExecBusyError, SessionRetryUnavailableError, } from "./session_host.js";
19
19
  const PATH_AUTOCOMPLETE_CACHE_TTL_MS = 5_000;
@@ -124,7 +124,7 @@ export class LocalSessionHost {
124
124
  backend: executionEnvironment.getToolExecutionBackend(),
125
125
  clientTools: (sessionId) => this.clientToolBroker.getToolDefinitions(sessionId),
126
126
  modelResolver: bootstrap.modelResolver,
127
- resolveSubagentRuntime: createExecutionEnvironmentSubagentRuntimeResolver({
127
+ resolveSubagentPrompts: createExecutionEnvironmentSubagentPromptResolver({
128
128
  executionEnvironment,
129
129
  includeAgentContext: this.sessionOptions.includeAgentContext,
130
130
  now: this.sessionOptions.environment.now,
@@ -368,6 +368,8 @@ class LocalHostedSessionHandle {
368
368
  draftAssistantMessage;
369
369
  messageStates = new Map();
370
370
  timeline = { epoch: 1, sequence: 0, items: [] };
371
+ turns = new Map();
372
+ pendingAcceptedTurnHistoryEntryIds = new Set();
371
373
  tools = new Map();
372
374
  operations = new Map();
373
375
  agents = new Map();
@@ -485,19 +487,14 @@ class LocalHostedSessionHandle {
485
487
  this.pendingGoalCommit = goal;
486
488
  });
487
489
  try {
488
- const { userHistoryEntryId } = await this.record({
490
+ const { userHistoryEntryId } = await this.acceptTurn({
489
491
  text: prependGoalPolicy(goal.objective, goal),
490
492
  });
491
- if (logicalTurn.cancellationRequested) {
492
- return {
493
- userHistoryEntryId,
494
- turn: await this.cancelLogicalTurn(),
495
- };
496
- }
497
- return {
498
- userHistoryEntryId,
499
- turn: await this.runTurnNow(logicalTurn, userHistoryEntryId),
500
- };
493
+ const turn = logicalTurn.cancellationRequested
494
+ ? await this.cancelLogicalTurn()
495
+ : await this.runTurnNow(logicalTurn, userHistoryEntryId);
496
+ await this.settleTurn(userHistoryEntryId, turn);
497
+ return { userHistoryEntryId, turn };
501
498
  }
502
499
  catch (error) {
503
500
  if (this.pendingGoalCommit === goal) {
@@ -526,13 +523,14 @@ class LocalHostedSessionHandle {
526
523
  await this.blockActiveGoal();
527
524
  return { turn: abortedTurnOutcome() };
528
525
  }
529
- const { userHistoryEntryId } = await this.record({
526
+ const { userHistoryEntryId } = await this.acceptTurn({
530
527
  text: buildGoalContinuationText(goal),
531
528
  });
532
- if (logicalTurn.cancellationRequested) {
533
- return { turn: await this.cancelLogicalTurn() };
534
- }
535
- return { turn: await this.runTurnNow(logicalTurn, userHistoryEntryId) };
529
+ const turn = logicalTurn.cancellationRequested
530
+ ? await this.cancelLogicalTurn()
531
+ : await this.runTurnNow(logicalTurn, userHistoryEntryId);
532
+ await this.settleTurn(userHistoryEntryId, turn);
533
+ return { turn };
536
534
  }
537
535
  catch (error) {
538
536
  await this.blockActiveGoal().catch(() => undefined);
@@ -550,15 +548,83 @@ class LocalHostedSessionHandle {
550
548
  }
551
549
  async record(options) {
552
550
  this.assertActive();
553
- const userHistoryEntryId = await this.session.commitUserText(options.text, options.historyEntryId ? { historyEntryId: options.historyEntryId } : undefined);
551
+ const historyEntryId = options.historyEntryId === undefined
552
+ ? undefined
553
+ : normalizeExplicitHistoryEntryId(options.historyEntryId);
554
+ if (historyEntryId !== undefined &&
555
+ (this.turns.has(historyEntryId) ||
556
+ this.pendingAcceptedTurnHistoryEntryIds.has(historyEntryId))) {
557
+ throw new Error(`history entry id '${historyEntryId}' belongs to an accepted turn`);
558
+ }
559
+ const userHistoryEntryId = await this.session.commitUserText(options.text, historyEntryId === undefined ? undefined : { historyEntryId });
554
560
  return {
555
561
  snapshot: await this.snapshot(),
556
562
  userHistoryEntryId,
557
563
  };
558
564
  }
565
+ async acceptTurn(options) {
566
+ this.assertActive();
567
+ const userHistoryEntryId = this.createTurnHistoryEntryId(options.historyEntryId);
568
+ this.pendingAcceptedTurnHistoryEntryIds.add(userHistoryEntryId);
569
+ try {
570
+ await this.session.commitUserText(options.text, { historyEntryId: userHistoryEntryId });
571
+ return { snapshot: await this.snapshot(), userHistoryEntryId };
572
+ }
573
+ finally {
574
+ this.pendingAcceptedTurnHistoryEntryIds.delete(userHistoryEntryId);
575
+ }
576
+ }
577
+ createTurnHistoryEntryId(preferredId) {
578
+ if (preferredId !== undefined) {
579
+ const normalizedId = normalizeExplicitHistoryEntryId(preferredId);
580
+ if (this.turns.has(normalizedId) ||
581
+ this.pendingAcceptedTurnHistoryEntryIds.has(normalizedId)) {
582
+ throw new Error(`turn '${normalizedId}' was already accepted`);
583
+ }
584
+ return normalizedId;
585
+ }
586
+ let id = `history-${randomUUID()}`;
587
+ const historyEntryIds = new Set(this.session.rawHistoryEntries.map((entry) => entry.id));
588
+ while (this.turns.has(id) ||
589
+ this.pendingAcceptedTurnHistoryEntryIds.has(id) ||
590
+ historyEntryIds.has(id)) {
591
+ id = `history-${randomUUID()}`;
592
+ }
593
+ return id;
594
+ }
595
+ async runAcceptedTurn(userHistoryEntryId) {
596
+ const acceptedTurn = this.turns.get(userHistoryEntryId);
597
+ if (!acceptedTurn)
598
+ throw new Error(`turn '${userHistoryEntryId}' was not accepted`);
599
+ if (acceptedTurn.state === "settled") {
600
+ throw new Error(`turn '${userHistoryEntryId}' was already settled`);
601
+ }
602
+ const turn = await this.runLogicalTurn((logicalTurn) => this.runTurnNow(logicalTurn, userHistoryEntryId));
603
+ await this.settleTurn(userHistoryEntryId, turn);
604
+ return { userHistoryEntryId, turn };
605
+ }
559
606
  async runTurn() {
560
607
  return await this.runLogicalTurn((logicalTurn) => this.runTurnNow(logicalTurn));
561
608
  }
609
+ async settleTurn(userHistoryEntryId, outcome) {
610
+ await this.enqueueMutation(async () => {
611
+ const existing = this.turns.get(userHistoryEntryId);
612
+ if (!existing) {
613
+ throw new Error(`turn '${userHistoryEntryId}' was not accepted`);
614
+ }
615
+ if (existing.state === "settled") {
616
+ if (isDeepStrictEqual(existing.outcome, outcome))
617
+ return;
618
+ throw new Error(`turn '${userHistoryEntryId}' was already settled`);
619
+ }
620
+ const turn = { userHistoryEntryId, state: "settled", outcome };
621
+ this.turns.set(userHistoryEntryId, turn);
622
+ const delta = await this.commitSnapshotPatch("assistant-message", [
623
+ { type: "turn.set", turn },
624
+ ]);
625
+ this.emitDelta(delta);
626
+ });
627
+ }
562
628
  async retryTurn() {
563
629
  const userMessage = this.session.rawHistoryEntries.findLast((entry) => entry.message.role === "user");
564
630
  if (userMessage && hasGoalTurnMetadata(userMessage.message)) {
@@ -629,7 +695,7 @@ class LocalHostedSessionHandle {
629
695
  }
630
696
  await this.emitSnapshotResetIfChanged("assistant-message");
631
697
  });
632
- this.resolveCommittedLogicalSteering(committedSteering, initialOutcome);
698
+ await this.resolveCommittedLogicalSteering(committedSteering, initialOutcome);
633
699
  if (logicalTurn.cancellationRequested) {
634
700
  return await this.cancelLogicalTurn();
635
701
  }
@@ -644,7 +710,7 @@ class LocalHostedSessionHandle {
644
710
  catch (error) {
645
711
  try {
646
712
  const outcome = await this.cleanupFailedTurn(rootUserMessage.id, error);
647
- this.resolveCommittedLogicalSteering(committedSteering, outcome);
713
+ await this.resolveCommittedLogicalSteering(committedSteering, outcome);
648
714
  return outcome;
649
715
  }
650
716
  catch {
@@ -700,10 +766,11 @@ class LocalHostedSessionHandle {
700
766
  applied: submission.applied.then((association) => ({
701
767
  userHistoryEntryId: association.historyEntryId,
702
768
  })),
703
- result: submission.result.then((association) => ({
704
- userHistoryEntryId: association.historyEntryId,
705
- turn: turnOutcomeFromResult(association.result, association.result.finalMessage),
706
- })),
769
+ result: submission.result.then(async (association) => {
770
+ const turn = turnOutcomeFromResult(association.result, association.result.finalMessage);
771
+ await this.settleTurn(association.historyEntryId, turn);
772
+ return { userHistoryEntryId: association.historyEntryId, turn };
773
+ }),
707
774
  };
708
775
  }
709
776
  const logicalTurn = this.activeLogicalTurn;
@@ -1170,7 +1237,14 @@ class LocalHostedSessionHandle {
1170
1237
  if (goal?.status !== "active") {
1171
1238
  throw new Error("cannot commit logical steering without an active goal");
1172
1239
  }
1173
- const historyEntryId = await this.session.commitUserText(prependGoalPolicy(formatSteeringUserMessage(submissions.map((submission) => submission.text)), goal));
1240
+ const historyEntryId = this.createTurnHistoryEntryId();
1241
+ this.pendingAcceptedTurnHistoryEntryIds.add(historyEntryId);
1242
+ try {
1243
+ await this.session.commitUserText(prependGoalPolicy(formatSteeringUserMessage(submissions.map((submission) => submission.text)), goal), { historyEntryId });
1244
+ }
1245
+ finally {
1246
+ this.pendingAcceptedTurnHistoryEntryIds.delete(historyEntryId);
1247
+ }
1174
1248
  for (const submission of submissions) {
1175
1249
  submission.resolveApplied({ userHistoryEntryId: historyEntryId });
1176
1250
  }
@@ -1185,8 +1259,9 @@ class LocalHostedSessionHandle {
1185
1259
  throw error;
1186
1260
  }
1187
1261
  }
1188
- resolveCommittedLogicalSteering(committed, turn) {
1262
+ async resolveCommittedLogicalSteering(committed, turn) {
1189
1263
  for (const { historyEntryId, submissions } of committed) {
1264
+ await this.settleTurn(historyEntryId, turn);
1190
1265
  for (const submission of submissions) {
1191
1266
  submission.resolveResult({ userHistoryEntryId: historyEntryId, turn });
1192
1267
  }
@@ -1214,7 +1289,7 @@ class LocalHostedSessionHandle {
1214
1289
  }
1215
1290
  await this.emitSnapshotResetIfChanged("assistant-message");
1216
1291
  });
1217
- this.resolveCommittedLogicalSteering(committedSteering, outcome);
1292
+ await this.resolveCommittedLogicalSteering(committedSteering, outcome);
1218
1293
  return outcome;
1219
1294
  }
1220
1295
  async commitSnapshot() {
@@ -1378,6 +1453,7 @@ class LocalHostedSessionHandle {
1378
1453
  catalog: structuredClone(this.catalog),
1379
1454
  executionEnvironment: this.executionEnvironment.snapshot(),
1380
1455
  messages,
1456
+ turns: Object.fromEntries(this.turns),
1381
1457
  timeline: structuredClone(this.timeline),
1382
1458
  tools: Object.fromEntries(this.tools),
1383
1459
  operations: Object.fromEntries(this.operations),
@@ -1468,6 +1544,10 @@ class LocalHostedSessionHandle {
1468
1544
  return;
1469
1545
  }
1470
1546
  this.timeline = structuredClone(snapshot.timeline);
1547
+ this.turns.clear();
1548
+ for (const [id, turn] of Object.entries(snapshot.turns)) {
1549
+ this.turns.set(id, structuredClone(turn));
1550
+ }
1471
1551
  this.tools.clear();
1472
1552
  for (const [id, tool] of Object.entries(snapshot.tools)) {
1473
1553
  this.tools.set(id, structuredClone(tool));
@@ -1721,12 +1801,24 @@ class LocalHostedSessionHandle {
1721
1801
  }
1722
1802
  case "user_message": {
1723
1803
  const pendingGoal = this.pendingGoalCommit;
1804
+ const acceptsTurn = this.pendingAcceptedTurnHistoryEntryIds.has(event.historyEntryId) ||
1805
+ event.origin === "steering";
1806
+ const turn = acceptsTurn
1807
+ ? { userHistoryEntryId: event.historyEntryId, state: "running" }
1808
+ : undefined;
1809
+ if (turn) {
1810
+ if (this.turns.has(event.historyEntryId)) {
1811
+ throw new Error(`turn '${event.historyEntryId}' was already accepted`);
1812
+ }
1813
+ this.turns.set(event.historyEntryId, turn);
1814
+ }
1724
1815
  const timelineItem = this.appendMessageTimelineItem(event.historyEntryId, event.message.timestamp);
1725
1816
  await this.emitPatch("user-message", [
1726
1817
  this.agentStateChange(),
1727
1818
  ...(pendingGoal
1728
1819
  ? [{ type: "goal.set", goal: structuredClone(pendingGoal) }]
1729
1820
  : []),
1821
+ ...(turn ? [{ type: "turn.set", turn }] : []),
1730
1822
  {
1731
1823
  type: "message.append",
1732
1824
  message: {
@@ -1738,6 +1830,9 @@ class LocalHostedSessionHandle {
1738
1830
  },
1739
1831
  { type: "timeline.append", item: structuredClone(timelineItem) },
1740
1832
  ], { persist: true });
1833
+ if (turn) {
1834
+ this.pendingAcceptedTurnHistoryEntryIds.delete(event.historyEntryId);
1835
+ }
1741
1836
  await this.recordHistoryFailure(this.history.append(this.sessionId, [userHistoryEntry(event.historyEntryId, event.message)], this.historyRemote));
1742
1837
  if (pendingGoal && this.pendingGoalCommit === pendingGoal) {
1743
1838
  this.pendingGoalCommit = undefined;
@@ -2266,6 +2361,13 @@ class LocalHostedSessionHandle {
2266
2361
  return { status: "failed", stopReason: "error", errorMessage: diagnostic };
2267
2362
  }
2268
2363
  }
2364
+ function normalizeExplicitHistoryEntryId(historyEntryId) {
2365
+ const normalizedId = historyEntryId.trim();
2366
+ if (!normalizedId) {
2367
+ throw new Error("history entry id must not be empty");
2368
+ }
2369
+ return normalizedId;
2370
+ }
2269
2371
  function formatErrorDiagnostic(error) {
2270
2372
  const messages = [];
2271
2373
  const seen = new Set();
@@ -2371,6 +2473,16 @@ function normalizeRecoveredSnapshot(snapshot) {
2371
2473
  const legacyAgentState = recovered.agentState.modelContextKey === LEGACY_SESSION_MODEL_CONTEXT_KEY;
2372
2474
  let changed = recovered.lifecycle !== "idle" || legacyAgentState;
2373
2475
  recovered.lifecycle = "idle";
2476
+ for (const [id, turn] of Object.entries(recovered.turns)) {
2477
+ if (turn.state === "running") {
2478
+ changed = true;
2479
+ recovered.turns[id] = {
2480
+ userHistoryEntryId: id,
2481
+ state: "settled",
2482
+ outcome: abortedTurnOutcome(),
2483
+ };
2484
+ }
2485
+ }
2374
2486
  if (recovered.goal?.status === "active") {
2375
2487
  changed = true;
2376
2488
  recovered.goal.status = "blocked";