@markusylisiurunen/tau 0.3.19 → 0.3.20

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 (168) hide show
  1. package/README.md +19 -47
  2. package/dist/core/cli.js +6 -39
  3. package/dist/core/cli.js.map +1 -1
  4. package/dist/core/commands/index.js +1 -1
  5. package/dist/core/commands/index.js.map +1 -1
  6. package/dist/core/commands/registry.js +5 -76
  7. package/dist/core/commands/registry.js.map +1 -1
  8. package/dist/core/config/builtin_themes.js +0 -62
  9. package/dist/core/config/builtin_themes.js.map +1 -1
  10. package/dist/core/config/content_loader.js +4 -7
  11. package/dist/core/config/content_loader.js.map +1 -1
  12. package/dist/core/config/diff_tool.js +1 -1
  13. package/dist/core/config/diff_tool.js.map +1 -1
  14. package/dist/core/config/runtime.js +1 -1
  15. package/dist/core/config/runtime.js.map +1 -1
  16. package/dist/core/config/runtime_config_snapshot.js +1 -1
  17. package/dist/core/config/runtime_config_snapshot.js.map +1 -1
  18. package/dist/core/config/schema.js +10 -43
  19. package/dist/core/config/schema.js.map +1 -1
  20. package/dist/core/config/skill_parser.js +1 -1
  21. package/dist/core/config/skill_parser.js.map +1 -1
  22. package/dist/core/config/virtual_defaults.js +0 -1
  23. package/dist/core/config/virtual_defaults.js.map +1 -1
  24. package/dist/core/debug.js +1 -7
  25. package/dist/core/debug.js.map +1 -1
  26. package/dist/core/diff_review/bridge.js +1 -3
  27. package/dist/core/diff_review/bridge.js.map +1 -1
  28. package/dist/core/diff_review/index.js.map +1 -1
  29. package/dist/core/diff_review/protocol.js +13 -13
  30. package/dist/core/diff_review/protocol.js.map +1 -1
  31. package/dist/core/diff_review/review_instructions.js +48 -0
  32. package/dist/core/diff_review/review_instructions.js.map +1 -0
  33. package/dist/core/events/index.js.map +1 -1
  34. package/dist/core/events/parser.js +161 -38
  35. package/dist/core/events/parser.js.map +1 -1
  36. package/dist/core/events/types.js +1 -1
  37. package/dist/core/events/types.js.map +1 -1
  38. package/dist/core/index.js +1 -1
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/models/catalog.js +4 -4
  41. package/dist/core/models/catalog.js.map +1 -1
  42. package/dist/core/personas.js +29 -14
  43. package/dist/core/personas.js.map +1 -1
  44. package/dist/core/runtime/chat_runtime.js +12 -34
  45. package/dist/core/runtime/chat_runtime.js.map +1 -1
  46. package/dist/core/runtime/conversation_turn_runtime.js +0 -4
  47. package/dist/core/runtime/conversation_turn_runtime.js.map +1 -1
  48. package/dist/core/runtime/runtime_bootstrap.js +191 -317
  49. package/dist/core/runtime/runtime_bootstrap.js.map +1 -1
  50. package/dist/core/runtime/session_prompt_composer.js +2 -6
  51. package/dist/core/runtime/session_prompt_composer.js.map +1 -1
  52. package/dist/core/session/core_session.js +2 -5
  53. package/dist/core/session/core_session.js.map +1 -1
  54. package/dist/core/session/message_accumulator.js +6 -0
  55. package/dist/core/session/message_accumulator.js.map +1 -1
  56. package/dist/core/session/runner.js +137 -32
  57. package/dist/core/session/runner.js.map +1 -1
  58. package/dist/core/session/session_engine.js +365 -120
  59. package/dist/core/session/session_engine.js.map +1 -1
  60. package/dist/core/static/index.js +0 -11
  61. package/dist/core/static/index.js.map +1 -1
  62. package/dist/core/subagents/registry.js +0 -2
  63. package/dist/core/subagents/registry.js.map +1 -1
  64. package/dist/core/subagents/subagent_engine.js +1 -3
  65. package/dist/core/subagents/subagent_engine.js.map +1 -1
  66. package/dist/core/telegram/adapter.js +1 -1
  67. package/dist/core/telegram/adapter.js.map +1 -1
  68. package/dist/core/telegram/config.js +3 -13
  69. package/dist/core/telegram/config.js.map +1 -1
  70. package/dist/core/telegram/session_manager.js +2 -5
  71. package/dist/core/telegram/session_manager.js.map +1 -1
  72. package/dist/core/tools/bash.js +6 -21
  73. package/dist/core/tools/bash.js.map +1 -1
  74. package/dist/core/tools/edit.js +1 -4
  75. package/dist/core/tools/edit.js.map +1 -1
  76. package/dist/core/tools/emit_output.js +1 -1
  77. package/dist/core/tools/emit_output.js.map +1 -1
  78. package/dist/core/tools/execution_backend.js +3 -1
  79. package/dist/core/tools/execution_backend.js.map +1 -1
  80. package/dist/core/tools/grep.js +1 -1
  81. package/dist/core/tools/grep.js.map +1 -1
  82. package/dist/core/tools/list.js +1 -1
  83. package/dist/core/tools/list.js.map +1 -1
  84. package/dist/core/tools/nook.js +1 -10
  85. package/dist/core/tools/nook.js.map +1 -1
  86. package/dist/core/tools/read.js +1 -1
  87. package/dist/core/tools/read.js.map +1 -1
  88. package/dist/core/tools/registry.js.map +1 -1
  89. package/dist/core/tools/send_input_to_agent.js +1 -1
  90. package/dist/core/tools/send_input_to_agent.js.map +1 -1
  91. package/dist/core/tools/spawn_agent.js +40 -88
  92. package/dist/core/tools/spawn_agent.js.map +1 -1
  93. package/dist/core/tools/terminate_agent.js +1 -1
  94. package/dist/core/tools/terminate_agent.js.map +1 -1
  95. package/dist/core/tools/view_image.js +1 -1
  96. package/dist/core/tools/view_image.js.map +1 -1
  97. package/dist/core/tools/wait_for_agents.js +1 -1
  98. package/dist/core/tools/wait_for_agents.js.map +1 -1
  99. package/dist/core/tools/web_fetch.js +1 -1
  100. package/dist/core/tools/web_fetch.js.map +1 -1
  101. package/dist/core/tools/web_search.js +1 -1
  102. package/dist/core/tools/web_search.js.map +1 -1
  103. package/dist/core/tools/write.js +1 -4
  104. package/dist/core/tools/write.js.map +1 -1
  105. package/dist/core/types.js +0 -1
  106. package/dist/core/types.js.map +1 -1
  107. package/dist/core/utils/context.js +1 -1
  108. package/dist/core/utils/context.js.map +1 -1
  109. package/dist/core/utils/context_builder.js +1 -15
  110. package/dist/core/utils/context_builder.js.map +1 -1
  111. package/dist/core/utils/user_metadata.js +13 -19
  112. package/dist/core/utils/user_metadata.js.map +1 -1
  113. package/dist/core/version.js +1 -1
  114. package/dist/execution/cloudflare_sandbox_execution_environment.js +15 -10
  115. package/dist/execution/cloudflare_sandbox_execution_environment.js.map +1 -1
  116. package/dist/execution/fly_sprite_execution_environment.js +9 -1
  117. package/dist/execution/fly_sprite_execution_environment.js.map +1 -1
  118. package/dist/execution/local_execution_environment.js +9 -55
  119. package/dist/execution/local_execution_environment.js.map +1 -1
  120. package/dist/execution/tool_backend_execution_environment.js +7 -10
  121. package/dist/execution/tool_backend_execution_environment.js.map +1 -1
  122. package/dist/host/client_tool_broker.js +1 -1
  123. package/dist/host/client_tool_broker.js.map +1 -1
  124. package/dist/host/execution_runtime.js +35 -0
  125. package/dist/host/execution_runtime.js.map +1 -0
  126. package/dist/host/hosted_ephemeral_agent_session.js +20 -11
  127. package/dist/host/hosted_ephemeral_agent_session.js.map +1 -1
  128. package/dist/host/local_session_host.js +117 -109
  129. package/dist/host/local_session_host.js.map +1 -1
  130. package/dist/host/session_protocol_handler.js +0 -10
  131. package/dist/host/session_protocol_handler.js.map +1 -1
  132. package/dist/main.js +31 -63
  133. package/dist/main.js.map +1 -1
  134. package/dist/nook/README.md +1 -1
  135. package/dist/protocol/index.d.ts +1 -1
  136. package/dist/protocol/index.js.map +1 -1
  137. package/dist/protocol/session_protocol.d.ts +1 -11
  138. package/dist/protocol/session_protocol.js +242 -343
  139. package/dist/protocol/session_protocol.js.map +1 -1
  140. package/dist/sdk/client.js +2 -24
  141. package/dist/sdk/client.js.map +1 -1
  142. package/dist/sdk/index.d.ts +1 -1
  143. package/dist/sdk/index.js.map +1 -1
  144. package/dist/sdk/session.js +0 -9
  145. package/dist/sdk/session.js.map +1 -1
  146. package/dist/sdk/types.d.ts +0 -4
  147. package/dist/starter/skills/guided-review/SKILL.md +1 -1
  148. package/dist/tui/chat_controller/command_hints.js +0 -2
  149. package/dist/tui/chat_controller/command_hints.js.map +1 -1
  150. package/dist/tui/chat_view.js +0 -2
  151. package/dist/tui/chat_view.js.map +1 -1
  152. package/dist/tui/session_chat_app.js +1 -1
  153. package/dist/tui/session_chat_app.js.map +1 -1
  154. package/dist/tui/session_chat_controller.js +2 -38
  155. package/dist/tui/session_chat_controller.js.map +1 -1
  156. package/dist/tui/ui/custom_editor.js +0 -5
  157. package/dist/tui/ui/custom_editor.js.map +1 -1
  158. package/dist/tui/ui/footer.js +4 -37
  159. package/dist/tui/ui/footer.js.map +1 -1
  160. package/dist/tui/ui/slash_autocomplete.js +2 -33
  161. package/dist/tui/ui/slash_autocomplete.js.map +1 -1
  162. package/dist/tui/ui/theme/palette.js +0 -2
  163. package/dist/tui/ui/theme/palette.js.map +1 -1
  164. package/dist/tui/ui/theme/theme.js.map +1 -1
  165. package/package.json +4 -4
  166. package/dist/core/diff_review/review_thread.js +0 -264
  167. package/dist/core/diff_review/review_thread.js.map +0 -1
  168. package/dist/core/static/prompts/diff-review-wrapper.md +0 -26
@@ -2,9 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import { resolvePromptTemplateWithBackend } from "../core/config/index.js";
3
3
  import { ChatRuntime } from "../core/runtime/chat_runtime.js";
4
4
  import { filterProjectPathAutocompleteEntries, loadProjectPathAutocompleteEntriesWithBackend, } from "../core/utils/project_files.js";
5
- import { hasAutoCompactionContinuationMetadata } from "../core/utils/user_metadata.js";
5
+ import { hasAutoCompactionContinuationMetadata, isTauUserMessageHidden, } from "../core/utils/user_metadata.js";
6
6
  import { applySessionProtocolDelta, createSessionProtocolDeltaMessage, createSessionProtocolEphemeralMessage, } from "../protocol/session_protocol.js";
7
7
  import { ClientToolBroker } from "./client_tool_broker.js";
8
+ import { createExecutionEnvironmentSubagentRuntimeResolver } from "./execution_runtime.js";
8
9
  import { HostedEphemeralAgentSession } from "./hosted_ephemeral_agent_session.js";
9
10
  const PATH_AUTOCOMPLETE_CACHE_TTL_MS = 5_000;
10
11
  export class LocalSessionHost {
@@ -53,14 +54,6 @@ export class LocalSessionHost {
53
54
  }
54
55
  return hostedSession;
55
56
  }
56
- createSessionNow(executionEnvironment, initialHistory = []) {
57
- this.assertHostActive();
58
- const hostedSession = this.createLocalSessionHandleSync(executionEnvironment);
59
- for (const message of initialHistory) {
60
- hostedSession.session.addMessage(message);
61
- }
62
- return hostedSession;
63
- }
64
57
  async createRecoveredSession(snapshot, executionEnvironment) {
65
58
  const recovered = normalizeRecoveredSnapshot(snapshot);
66
59
  const hostedSession = await this.createLocalSessionHandle(executionEnvironment, recovered.snapshot, undefined, recovered.changed);
@@ -81,33 +74,26 @@ export class LocalSessionHost {
81
74
  this.applyCreateParamsToBootstrap(bootstrap, createParams);
82
75
  }
83
76
  const runtimeContext = await executionEnvironment.resolveRuntimeContext({
77
+ cwd: executionEnvironment.snapshot().cwd,
84
78
  persona: bootstrap.persona,
85
79
  discoveredSkills: bootstrap.discoveredSkills,
86
80
  includeAgentContext: this.sessionOptions.includeAgentContext,
81
+ agentContextFiles: bootstrap.config?.agentContextFiles ?? [],
87
82
  });
88
83
  const catalog = createContentCatalogSnapshot(bootstrap);
89
84
  return this.createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, forceNextSnapshotRevision);
90
85
  }
91
- createLocalSessionHandleSync(executionEnvironment, committedSnapshot) {
92
- const bootstrap = committedSnapshot
93
- ? this.applySnapshotSettingsToBootstrap(this.defaultSessionBootstrap(), committedSnapshot)
94
- : this.defaultSessionBootstrap();
95
- const runtimeContext = executionEnvironment.resolveRuntimeContext({
96
- persona: bootstrap.persona,
97
- discoveredSkills: bootstrap.discoveredSkills,
98
- includeAgentContext: this.sessionOptions.includeAgentContext,
99
- });
100
- if (isPromiseLike(runtimeContext)) {
101
- throw new Error("cannot synchronously create a session for an async execution environment");
102
- }
103
- return this.createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, createContentCatalogSnapshot(bootstrap), committedSnapshot);
104
- }
105
86
  createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, forceNextSnapshotRevision = false) {
106
87
  const runtime = ChatRuntime.create({
107
88
  persona: bootstrap.persona,
108
- riskLevel: bootstrap.riskLevel,
109
89
  toolRegistry: runtimeContext.toolRegistry,
110
90
  clientToolDefinitions: (sessionId) => this.clientToolBroker.getToolDefinitions(sessionId),
91
+ modelResolver: bootstrap.modelResolver,
92
+ resolveSubagentRuntime: createExecutionEnvironmentSubagentRuntimeResolver({
93
+ executionEnvironment,
94
+ includeAgentContext: this.sessionOptions.includeAgentContext,
95
+ now: this.sessionOptions.environment.now,
96
+ }),
111
97
  promptContext: runtimeContext.promptBootstrap.promptContext,
112
98
  environment: this.sessionOptions.environment,
113
99
  initialPromptComposition: committedSnapshot
@@ -139,7 +125,6 @@ export class LocalSessionHost {
139
125
  if (snapshot.settings.serviceTier !== undefined) {
140
126
  bootstrap.persona.settings.serviceTier = snapshot.settings.serviceTier;
141
127
  }
142
- bootstrap.riskLevel = snapshot.settings.riskLevel;
143
128
  return bootstrap;
144
129
  }
145
130
  applyCreateParamsToBootstrap(bootstrap, createParams) {
@@ -154,19 +139,13 @@ export class LocalSessionHost {
154
139
  if (createParams.reasoning !== undefined) {
155
140
  bootstrap.persona.settings.reasoning = createParams.reasoning;
156
141
  }
157
- if (createParams.riskLevel !== undefined) {
158
- bootstrap.riskLevel = createParams.riskLevel;
159
- }
160
142
  }
161
143
  defaultSessionBootstrap() {
162
- return cloneResolvedBootstrap({
163
- persona: this.sessionOptions.persona,
164
- riskLevel: this.sessionOptions.riskLevel,
165
- discoveredSkills: this.sessionOptions.discoveredSkills,
166
- personas: this.sessionOptions.personas,
167
- prompts: this.sessionOptions.prompts,
168
- config: this.sessionOptions.config,
169
- });
144
+ const bootstrap = this.sessionOptions.defaultBootstrap;
145
+ if (!bootstrap) {
146
+ throw new Error("local session host has no default session bootstrap");
147
+ }
148
+ return cloneResolvedBootstrap(bootstrap);
170
149
  }
171
150
  async observeSession(sessionId) {
172
151
  this.assertHostActive();
@@ -294,9 +273,6 @@ export class LocalSessionHost {
294
273
  return false;
295
274
  }
296
275
  }
297
- function isPromiseLike(value) {
298
- return typeof value.then === "function";
299
- }
300
276
  class LocalHostedSessionHandle {
301
277
  runtime;
302
278
  promptBootstrap;
@@ -427,13 +403,6 @@ class LocalHostedSessionHandle {
427
403
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
428
404
  });
429
405
  }
430
- async setRiskLevel(level) {
431
- this.assertActive();
432
- this.runtime.setRiskLevel(level);
433
- const snapshot = await this.commitSnapshot();
434
- this.emitSnapshotReset("configuration", snapshot);
435
- return snapshot;
436
- }
437
406
  async setReasoning(reasoning) {
438
407
  this.assertActive();
439
408
  const fromRevision = this.committedSnapshot?.revision;
@@ -459,7 +428,7 @@ class LocalHostedSessionHandle {
459
428
  }
460
429
  async setPersona(personaId) {
461
430
  this.assertActive();
462
- const runtimeConfig = await this.executionEnvironment.resolveRuntimeConfig();
431
+ const runtimeConfig = await this.executionEnvironment.resolveRuntimeConfig(this.executionEnvironment.snapshot().cwd);
463
432
  const personas = runtimeConfig.personas.map((persona) => this.normalizeReloadedPersona(persona));
464
433
  const persona = personas.find((persona) => persona.id.toLowerCase() === personaId.toLowerCase());
465
434
  if (!persona) {
@@ -467,21 +436,23 @@ class LocalHostedSessionHandle {
467
436
  }
468
437
  const selectedPersona = clonePersona(persona);
469
438
  const runtimeContext = await this.executionEnvironment.resolveRuntimeContext({
439
+ cwd: this.executionEnvironment.snapshot().cwd,
470
440
  persona: selectedPersona,
471
441
  discoveredSkills: runtimeConfig.skills,
472
442
  includeAgentContext: this.includeAgentContext,
443
+ agentContextFiles: runtimeConfig.config.agentContextFiles ?? [],
473
444
  });
474
- this.runtime.setConfig(runtimeConfig.config);
445
+ this.runtime.setRuntimeConfig(runtimeConfig.config, runtimeConfig.bootstrap.modelResolver.resolveModel);
475
446
  this.runtime.updatePromptContext(runtimeContext.promptBootstrap.promptContext);
476
447
  this.runtime.setPersona(selectedPersona, {
477
448
  skillsBlock: runtimeContext.promptBootstrap.promptContext.skillsBlock,
478
449
  });
479
450
  this.bootstrap = {
480
451
  persona: clonePersona(selectedPersona),
481
- riskLevel: this.runtime.currentRiskLevel,
482
452
  discoveredSkills: structuredClone(runtimeConfig.skills),
483
453
  personas: personas.map(clonePersona),
484
454
  prompts: structuredClone(runtimeConfig.prompts),
455
+ modelResolver: runtimeConfig.bootstrap.modelResolver.resolveModel,
485
456
  config: runtimeConfig.config,
486
457
  };
487
458
  this.catalog = createContentCatalogSnapshot(this.bootstrap);
@@ -491,7 +462,7 @@ class LocalHostedSessionHandle {
491
462
  }
492
463
  async reload() {
493
464
  this.assertActive();
494
- const runtimeConfig = await this.executionEnvironment.resolveRuntimeConfig();
465
+ const runtimeConfig = await this.executionEnvironment.resolveRuntimeConfig(this.executionEnvironment.snapshot().cwd);
495
466
  if (runtimeConfig.personas.length === 0) {
496
467
  throw new Error("reload failed: no personas available");
497
468
  }
@@ -499,21 +470,23 @@ class LocalHostedSessionHandle {
499
470
  const currentPersonaId = this.runtime.persona.id.toLowerCase();
500
471
  const nextPersona = personas.find((persona) => persona.id.toLowerCase() === currentPersonaId) ?? personas[0];
501
472
  const runtimeContext = await this.executionEnvironment.resolveRuntimeContext({
473
+ cwd: this.executionEnvironment.snapshot().cwd,
502
474
  persona: nextPersona,
503
475
  discoveredSkills: runtimeConfig.skills,
504
476
  includeAgentContext: this.includeAgentContext,
477
+ agentContextFiles: runtimeConfig.config.agentContextFiles ?? [],
505
478
  });
506
- this.runtime.setConfig(runtimeConfig.config);
479
+ this.runtime.setRuntimeConfig(runtimeConfig.config, runtimeConfig.bootstrap.modelResolver.resolveModel);
507
480
  this.runtime.updatePromptContext(runtimeContext.promptBootstrap.promptContext);
508
481
  this.runtime.setPersona(nextPersona, {
509
482
  skillsBlock: runtimeContext.promptBootstrap.promptContext.skillsBlock,
510
483
  });
511
484
  this.bootstrap = {
512
485
  persona: clonePersona(nextPersona),
513
- riskLevel: this.runtime.currentRiskLevel,
514
486
  discoveredSkills: structuredClone(runtimeConfig.skills),
515
487
  personas: personas.map(clonePersona),
516
488
  prompts: structuredClone(runtimeConfig.prompts),
489
+ modelResolver: runtimeConfig.bootstrap.modelResolver.resolveModel,
517
490
  config: runtimeConfig.config,
518
491
  };
519
492
  this.catalog = createContentCatalogSnapshot(this.bootstrap);
@@ -522,11 +495,7 @@ class LocalHostedSessionHandle {
522
495
  this.emitSnapshotReset("configuration", snapshot);
523
496
  return {
524
497
  snapshot,
525
- warnings: [
526
- ...runtimeConfig.warnings,
527
- ...runtimeContext.promptBootstrap.warnings,
528
- ...unknownSkillWarnings,
529
- ],
498
+ warnings: [...runtimeConfig.warnings, ...unknownSkillWarnings],
530
499
  counts: {
531
500
  personas: runtimeConfig.personas.length,
532
501
  prompts: runtimeConfig.prompts.length,
@@ -634,12 +603,12 @@ class LocalHostedSessionHandle {
634
603
  contextId,
635
604
  persona: this.runtime.persona,
636
605
  config: this.bootstrap.config ?? {},
606
+ modelResolver: this.bootstrap.modelResolver,
637
607
  discoveredSkills: this.bootstrap.discoveredSkills,
638
608
  includeAgentContext: this.includeAgentContext,
639
609
  executionEnvironment: this.executionEnvironment,
640
610
  instructions: options.instructions,
641
611
  tools: options.tools,
642
- riskLevel: options.riskLevel,
643
612
  emitUpdate: (threadId, update) => {
644
613
  this.emitEphemeral(createSessionProtocolEphemeralMessage({
645
614
  sessionId: this.session.sessionId,
@@ -774,7 +743,6 @@ class LocalHostedSessionHandle {
774
743
  costTotal: this.costTotal,
775
744
  settings: {
776
745
  personaId: this.runtime.persona.id,
777
- riskLevel: this.runtime.currentRiskLevel,
778
746
  ...(this.runtime.persona.settings.reasoning !== undefined
779
747
  ? { reasoning: this.runtime.persona.settings.reasoning }
780
748
  : {}),
@@ -835,7 +803,9 @@ class LocalHostedSessionHandle {
835
803
  if (message.id === "system") {
836
804
  return false;
837
805
  }
838
- if (isCoreMessage(message.message) && hasAutoCompactionContinuationMetadata(message.message)) {
806
+ if (isCoreMessage(message.message) &&
807
+ (isTauUserMessageHidden(message.message) ||
808
+ hasAutoCompactionContinuationMetadata(message.message))) {
839
809
  return false;
840
810
  }
841
811
  if (!this.restoredTimelineMessageIds || !this.restoredMessageIds) {
@@ -923,34 +893,37 @@ class LocalHostedSessionHandle {
923
893
  }
924
894
  case "assistant_partial": {
925
895
  const previousDraft = this.draftAssistantMessage;
896
+ const message = {
897
+ role: "assistant",
898
+ content: [
899
+ ...(event.snapshot.hasAnyThinking
900
+ ? [
901
+ {
902
+ type: "thinking",
903
+ thinking: event.snapshot.thinking,
904
+ },
905
+ ]
906
+ : []),
907
+ ...(event.snapshot.hasTextStarted
908
+ ? [{ type: "text", text: event.snapshot.text }]
909
+ : []),
910
+ ...event.snapshot.toolCalls,
911
+ ],
912
+ timestamp: Date.now(),
913
+ };
926
914
  const nextDraft = {
927
915
  id: event.historyEntryId,
928
916
  state: "draft",
929
917
  modelVisible: false,
930
- message: {
931
- role: "assistant",
932
- content: [
933
- ...(event.snapshot.hasAnyThinking
934
- ? [
935
- {
936
- type: "thinking",
937
- thinking: event.snapshot.thinking,
938
- },
939
- ]
940
- : []),
941
- ...(event.snapshot.hasTextStarted
942
- ? [{ type: "text", text: event.snapshot.text }]
943
- : []),
944
- ],
945
- timestamp: Date.now(),
946
- },
918
+ message,
947
919
  };
948
920
  const changes = this.buildAssistantPartialChanges(previousDraft, nextDraft);
949
- if (changes.length === 0) {
921
+ const toolChanges = this.syncToolRunsFromAssistantMessage(event.historyEntryId, message);
922
+ if (changes.length === 0 && toolChanges.length === 0) {
950
923
  return;
951
924
  }
952
925
  this.draftAssistantMessage = nextDraft;
953
- await this.emitPatch("assistant-stream", changes, {
926
+ await this.emitPatch("assistant-stream", [...changes, ...toolChanges], {
954
927
  persist: false,
955
928
  });
956
929
  return;
@@ -958,7 +931,7 @@ class LocalHostedSessionHandle {
958
931
  case "assistant_final": {
959
932
  this.draftAssistantMessage = undefined;
960
933
  this.messageStates.delete(event.historyEntryId);
961
- this.seedToolRunsFromAssistantMessage(event.historyEntryId, event.message);
934
+ const toolChanges = this.syncToolRunsFromAssistantMessage(event.historyEntryId, event.message);
962
935
  this.costTotal += event.message.usage?.cost?.total ?? 0;
963
936
  await this.emitPatch("assistant-message", [
964
937
  { type: "cost.set", costTotal: this.costTotal },
@@ -971,7 +944,7 @@ class LocalHostedSessionHandle {
971
944
  message: event.message,
972
945
  },
973
946
  },
974
- ...this.toolChangesForAssistantMessage(event.historyEntryId, event.message),
947
+ ...toolChanges,
975
948
  ]);
976
949
  return;
977
950
  }
@@ -1014,6 +987,34 @@ class LocalHostedSessionHandle {
1014
987
  ]);
1015
988
  return;
1016
989
  }
990
+ case "tool_recovery": {
991
+ const changes = [
992
+ {
993
+ type: "message.append",
994
+ message: {
995
+ id: event.historyEntryId,
996
+ state: "committed",
997
+ modelVisible: true,
998
+ message: event.message,
999
+ },
1000
+ },
1001
+ ];
1002
+ for (const toolResult of event.toolResults) {
1003
+ const existing = this.tools.get(toolResult.toolCallId);
1004
+ if (!existing) {
1005
+ throw new Error(`missing protocol tool run for '${toolResult.toolCallId}'`);
1006
+ }
1007
+ const nextTool = {
1008
+ ...existing,
1009
+ status: toolResult.isError ? "failed" : "succeeded",
1010
+ finishedAt: toolResult.timestamp,
1011
+ };
1012
+ this.tools.set(nextTool.id, nextTool);
1013
+ changes.push({ type: "tool.set", tool: structuredClone(nextTool) });
1014
+ }
1015
+ await this.emitPatch("recovery", changes);
1016
+ return;
1017
+ }
1017
1018
  case "notice": {
1018
1019
  const item = {
1019
1020
  type: "notice",
@@ -1212,38 +1213,35 @@ class LocalHostedSessionHandle {
1212
1213
  }
1213
1214
  }
1214
1215
  }
1215
- seedToolRunsFromAssistantMessage(messageId, message) {
1216
+ syncToolRunsFromAssistantMessage(messageId, message) {
1217
+ const changes = [];
1216
1218
  const toolCalls = message.content
1217
1219
  .map((content, index) => ({ content, index }))
1218
1220
  .filter((item) => item.content.type === "toolCall");
1219
1221
  for (const { content, index } of toolCalls) {
1220
- if (this.tools.has(content.id)) {
1221
- continue;
1222
- }
1223
- this.tools.set(content.id, {
1224
- id: content.id,
1225
- toolCallId: content.id,
1222
+ const existing = this.tools.get(content.id);
1223
+ const nextTool = {
1224
+ ...(existing ?? {
1225
+ id: content.id,
1226
+ toolCallId: content.id,
1227
+ status: "queued",
1228
+ facetIds: [],
1229
+ }),
1226
1230
  toolName: content.name,
1227
1231
  call: {
1228
1232
  messageId,
1229
1233
  contentIndex: index,
1230
1234
  },
1231
- status: "queued",
1232
- facetIds: [],
1233
- });
1234
- }
1235
- }
1236
- toolChangesForAssistantMessage(_messageId, message) {
1237
- return message.content
1238
- .map((content, index) => ({ content, index }))
1239
- .filter((item) => item.content.type === "toolCall")
1240
- .map(({ content }) => {
1241
- const tool = this.tools.get(content.id);
1242
- if (!tool) {
1243
- throw new Error(`missing protocol tool run for tool call '${content.id}'`);
1235
+ };
1236
+ if (existing?.toolName === nextTool.toolName &&
1237
+ existing.call.messageId === nextTool.call.messageId &&
1238
+ existing.call.contentIndex === nextTool.call.contentIndex) {
1239
+ continue;
1244
1240
  }
1245
- return { type: "tool.set", tool: structuredClone(tool) };
1246
- });
1241
+ this.tools.set(content.id, nextTool);
1242
+ changes.push({ type: "tool.set", tool: structuredClone(nextTool) });
1243
+ }
1244
+ return changes;
1247
1245
  }
1248
1246
  async interruptDraftAssistantMessage() {
1249
1247
  const draft = this.draftAssistantMessage;
@@ -1284,6 +1282,18 @@ function buildAssistantContentAppendChange(previousDraft, nextDraft) {
1284
1282
  nextDraft.message.role !== "assistant") {
1285
1283
  return undefined;
1286
1284
  }
1285
+ const previousContent = previousDraft.message.content;
1286
+ const nextContent = nextDraft.message.content;
1287
+ const previousStaticContent = previousContent.filter((content) => content.type !== "text" && content.type !== "thinking");
1288
+ const nextStaticContent = nextContent.filter((content) => content.type !== "text" && content.type !== "thinking");
1289
+ if (JSON.stringify(previousStaticContent) !== JSON.stringify(nextStaticContent)) {
1290
+ return undefined;
1291
+ }
1292
+ if (previousStaticContent.length > 0 &&
1293
+ (previousContent.length !== nextContent.length ||
1294
+ previousContent.some((content, index) => content.type !== nextContent[index].type))) {
1295
+ return undefined;
1296
+ }
1287
1297
  const previousText = getAssistantDraftBlockText(previousDraft, "text");
1288
1298
  const nextText = getAssistantDraftBlockText(nextDraft, "text");
1289
1299
  const previousThinking = getAssistantDraftBlockText(previousDraft, "thinking");
@@ -1306,10 +1316,9 @@ function buildAssistantContentAppendChange(previousDraft, nextDraft) {
1306
1316
  }
1307
1317
  function assistantDraftContentEquals(previousDraft, nextDraft) {
1308
1318
  return (previousDraft.id === nextDraft.id &&
1309
- getAssistantDraftBlockText(previousDraft, "text") ===
1310
- getAssistantDraftBlockText(nextDraft, "text") &&
1311
- getAssistantDraftBlockText(previousDraft, "thinking") ===
1312
- getAssistantDraftBlockText(nextDraft, "thinking"));
1319
+ previousDraft.message.role === "assistant" &&
1320
+ nextDraft.message.role === "assistant" &&
1321
+ JSON.stringify(previousDraft.message.content) === JSON.stringify(nextDraft.message.content));
1313
1322
  }
1314
1323
  function getAssistantDraftBlockText(message, type) {
1315
1324
  if (message.message.role !== "assistant") {
@@ -1474,10 +1483,10 @@ function agentRunFromSubagentEvent(event, originHistoryEntryId, existing) {
1474
1483
  function cloneResolvedBootstrap(bootstrap) {
1475
1484
  return {
1476
1485
  persona: clonePersona(bootstrap.persona),
1477
- riskLevel: bootstrap.riskLevel,
1478
1486
  discoveredSkills: structuredClone(bootstrap.discoveredSkills),
1479
1487
  personas: bootstrap.personas.map(clonePersona),
1480
1488
  prompts: structuredClone(bootstrap.prompts),
1489
+ modelResolver: bootstrap.modelResolver,
1481
1490
  ...(bootstrap.config !== undefined ? { config: structuredClone(bootstrap.config) } : {}),
1482
1491
  };
1483
1492
  }
@@ -1541,7 +1550,6 @@ function subagentSnapshotsFromConfig(subagents) {
1541
1550
  snapshots[name] = {
1542
1551
  ...(config.description !== undefined ? { description: config.description } : {}),
1543
1552
  ...(config.tools ? { tools: [...config.tools] } : {}),
1544
- ...(config.riskLevel !== undefined ? { riskLevel: config.riskLevel } : {}),
1545
1553
  ...(config.launchModels ? { launchModels: [...config.launchModels] } : {}),
1546
1554
  };
1547
1555
  }