@markusylisiurunen/tau 0.3.93 → 0.3.95

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 (150) hide show
  1. package/dist/code_mode/command.js +3 -1
  2. package/dist/code_mode/command.js.map +1 -1
  3. package/dist/code_mode/output.d.ts +3 -0
  4. package/dist/code_mode/output.js +8 -2
  5. package/dist/code_mode/output.js.map +1 -1
  6. package/dist/code_mode/runtime.d.ts +7 -1
  7. package/dist/code_mode/runtime.js +18 -14
  8. package/dist/code_mode/runtime.js.map +1 -1
  9. package/dist/core/code_mode/agents.js +434 -0
  10. package/dist/core/code_mode/agents.js.map +1 -0
  11. package/dist/core/code_mode/bash.js +14 -17
  12. package/dist/core/code_mode/bash.js.map +1 -1
  13. package/dist/core/code_mode/history.js +1 -0
  14. package/dist/core/code_mode/history.js.map +1 -1
  15. package/dist/core/code_mode/models.js +27 -4
  16. package/dist/core/code_mode/models.js.map +1 -1
  17. package/dist/core/code_mode/sdk.js +6 -0
  18. package/dist/core/code_mode/sdk.js.map +1 -1
  19. package/dist/core/config/content_loader.js +3 -13
  20. package/dist/core/config/content_loader.js.map +1 -1
  21. package/dist/core/config/schema.js.map +1 -1
  22. package/dist/core/debug.js +2 -2
  23. package/dist/core/debug.js.map +1 -1
  24. package/dist/core/history/history_manager.js +7 -4
  25. package/dist/core/history/history_manager.js.map +1 -1
  26. package/dist/core/index.js +0 -4
  27. package/dist/core/index.js.map +1 -1
  28. package/dist/core/mcp/manager.js +7 -0
  29. package/dist/core/mcp/manager.js.map +1 -1
  30. package/dist/core/models/openrouter.js +25 -1
  31. package/dist/core/models/openrouter.js.map +1 -1
  32. package/dist/core/personas.js +3 -11
  33. package/dist/core/personas.js.map +1 -1
  34. package/dist/core/runtime/chat_runtime.js +11 -10
  35. package/dist/core/runtime/chat_runtime.js.map +1 -1
  36. package/dist/core/runtime/session_prompt_composer.js +9 -8
  37. package/dist/core/runtime/session_prompt_composer.js.map +1 -1
  38. package/dist/core/static/code_mode/history/documentation.md +2 -1
  39. package/dist/core/static/code_mode/sandbox_runner.mjs +4 -0
  40. package/dist/core/static/index.js +18 -5
  41. package/dist/core/static/index.js.map +1 -1
  42. package/dist/core/static/prompts/subagent-prefix.md +17 -0
  43. package/dist/core/static/tau_docs/client-tools.md +4 -0
  44. package/dist/core/static/tau_docs/code-mode.md +77 -0
  45. package/dist/core/static/tau_docs/config-reference.md +4 -2
  46. package/dist/core/static/tau_docs/history.md +5 -3
  47. package/dist/core/static/tau_docs/index.md +2 -1
  48. package/dist/core/static/tau_docs/manifest.json +1 -0
  49. package/dist/core/static/tau_docs/node-sdk.md +2 -0
  50. package/dist/core/static/tau_docs/openrouter.md +2 -0
  51. package/dist/core/static/tau_docs/pdf-unpacking.md +1 -1
  52. package/dist/core/static/tau_docs/personas.md +4 -5
  53. package/dist/core/static/tau_docs/prompts-and-project-context.md +1 -1
  54. package/dist/core/static/tau_docs/security.md +1 -1
  55. package/dist/core/static/tau_docs/sessions.md +1 -1
  56. package/dist/core/static/tau_docs/subagents.md +106 -27
  57. package/dist/core/static/tau_docs/telegram.md +20 -2
  58. package/dist/core/static/tau_docs/tools.md +32 -62
  59. package/dist/core/static/tau_docs/troubleshooting.md +2 -2
  60. package/dist/core/subagents/agent_supervisor.js +185 -339
  61. package/dist/core/subagents/agent_supervisor.js.map +1 -1
  62. package/dist/core/subagents/claude_code_protocol.js +4 -0
  63. package/dist/core/subagents/claude_code_protocol.js.map +1 -1
  64. package/dist/core/subagents/claude_code_runner.js +134 -11
  65. package/dist/core/subagents/claude_code_runner.js.map +1 -1
  66. package/dist/core/subagents/claude_code_runner.mjs +80 -77
  67. package/dist/core/subagents/claude_code_runtime.js +250 -9
  68. package/dist/core/subagents/claude_code_runtime.js.map +1 -1
  69. package/dist/core/subagents/errors.js +18 -0
  70. package/dist/core/subagents/errors.js.map +1 -0
  71. package/dist/core/subagents/events.js +25 -0
  72. package/dist/core/subagents/events.js.map +1 -0
  73. package/dist/core/subagents/format.js +2 -98
  74. package/dist/core/subagents/format.js.map +1 -1
  75. package/dist/core/subagents/native_subagent_runtime.js +270 -0
  76. package/dist/core/subagents/native_subagent_runtime.js.map +1 -0
  77. package/dist/core/subagents/prompt.js +3 -5
  78. package/dist/core/subagents/prompt.js.map +1 -1
  79. package/dist/core/subagents/registry.js +5 -3
  80. package/dist/core/subagents/registry.js.map +1 -1
  81. package/dist/core/subagents/runtime.js +13 -0
  82. package/dist/core/subagents/runtime.js.map +1 -0
  83. package/dist/core/subagents/trace.js +149 -0
  84. package/dist/core/subagents/trace.js.map +1 -0
  85. package/dist/core/telegram/adapter.js +40 -6
  86. package/dist/core/telegram/adapter.js.map +1 -1
  87. package/dist/core/telegram/config.js +6 -0
  88. package/dist/core/telegram/config.js.map +1 -1
  89. package/dist/core/telegram/runtime.js +1 -0
  90. package/dist/core/telegram/runtime.js.map +1 -1
  91. package/dist/core/tools/agent_jobs.js +51 -0
  92. package/dist/core/tools/agent_jobs.js.map +1 -0
  93. package/dist/core/tools/background_jobs.js +205 -0
  94. package/dist/core/tools/background_jobs.js.map +1 -0
  95. package/dist/core/tools/bash.js +11 -45
  96. package/dist/core/tools/bash.js.map +1 -1
  97. package/dist/core/tools/bash_jobs.js +44 -238
  98. package/dist/core/tools/bash_jobs.js.map +1 -1
  99. package/dist/core/tools/catalog.js +37 -72
  100. package/dist/core/tools/catalog.js.map +1 -1
  101. package/dist/core/tools/code.js +177 -54
  102. package/dist/core/tools/code.js.map +1 -1
  103. package/dist/core/tools/code_jobs.js +128 -0
  104. package/dist/core/tools/code_jobs.js.map +1 -0
  105. package/dist/core/tools/code_mode.js +88 -101
  106. package/dist/core/tools/code_mode.js.map +1 -1
  107. package/dist/core/tools/code_mode_worker.js +13 -7
  108. package/dist/core/tools/code_mode_worker.js.map +1 -1
  109. package/dist/core/tools/job_output.js +53 -0
  110. package/dist/core/tools/job_output.js.map +1 -0
  111. package/dist/core/tools/output_file.js +38 -0
  112. package/dist/core/tools/output_file.js.map +1 -0
  113. package/dist/core/tools/presentation.js +19 -9
  114. package/dist/core/tools/presentation.js.map +1 -1
  115. package/dist/core/tools/tool_names.js +8 -6
  116. package/dist/core/tools/tool_names.js.map +1 -1
  117. package/dist/core/tools/wait_for_agents.js +41 -33
  118. package/dist/core/tools/wait_for_agents.js.map +1 -1
  119. package/dist/core/version.js +1 -1
  120. package/dist/diff_tool/app/dist/assets/{core-DaUcjNBm.js → core-NkxL1yZC.js} +1 -1
  121. package/dist/diff_tool/app/dist/assets/{github-dark-default-adAWTKmX.js → github-dark-default-CM8LCIj6.js} +1 -1
  122. package/dist/diff_tool/app/dist/assets/highlights-Coi8xHW0.js +3 -0
  123. package/dist/diff_tool/app/dist/assets/index-Jt-l2Q96.js +205 -0
  124. package/dist/diff_tool/app/dist/assets/langs-qJAo26KT.js +1 -0
  125. package/dist/diff_tool/app/dist/assets/{loader-DrMMfKhl.js → loader-DYYMIDWx.js} +1 -1
  126. package/dist/diff_tool/app/dist/assets/{shiki-BDtWTy6K.js → shiki-B8l9Ufae.js} +2 -2
  127. package/dist/diff_tool/app/dist/assets/{stream-tokenizer-Dpnp6Te1.js → stream-tokenizer-h1lwRQEk.js} +1 -1
  128. package/dist/diff_tool/app/dist/assets/{themes-snQUZ0v5.js → themes-D3P9yN9_.js} +2 -2
  129. package/dist/diff_tool/app/dist/index.html +1 -1
  130. package/dist/host/hosted_ephemeral_agent_session.js +13 -3
  131. package/dist/host/hosted_ephemeral_agent_session.js.map +1 -1
  132. package/dist/host/local_session_host.js +5 -5
  133. package/dist/host/local_session_host.js.map +1 -1
  134. package/dist/sdk/code_mode.js +2 -2
  135. package/dist/sdk/code_mode.js.map +1 -1
  136. package/dist/store/session_snapshot_migrations.js +27 -1
  137. package/dist/store/session_snapshot_migrations.js.map +1 -1
  138. package/package.json +6 -6
  139. package/dist/core/static/prompts/subagent-wrapper.md +0 -20
  140. package/dist/core/tools/interrupt_agent.js +0 -142
  141. package/dist/core/tools/interrupt_agent.js.map +0 -1
  142. package/dist/core/tools/list_agents.js +0 -85
  143. package/dist/core/tools/list_agents.js.map +0 -1
  144. package/dist/core/tools/send_input_to_agent.js +0 -177
  145. package/dist/core/tools/send_input_to_agent.js.map +0 -1
  146. package/dist/core/tools/spawn_agent.js +0 -275
  147. package/dist/core/tools/spawn_agent.js.map +0 -1
  148. package/dist/diff_tool/app/dist/assets/highlights-ey_YWY7Q.js +0 -3
  149. package/dist/diff_tool/app/dist/assets/index-B7CqEx6P.js +0 -202
  150. package/dist/diff_tool/app/dist/assets/langs-BAlu0Oiy.js +0 -1
@@ -1,13 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { AgentRuntime, createAgentSpec } from "../agent/agent_runtime.js";
3
- import { resolveAgentModel } from "../runtime/agent_model.js";
4
2
  import { createDefaultCoreDeps } from "../runtime/deps.js";
5
- import { createAutoCompactionArchiver } from "../session/auto_compaction_archive.js";
6
- import { ToolCatalog } from "../tools/catalog.js";
7
- import { appendUsageLogEntry, getUsageCostTotal, getUsageTotals, } from "../usage/logs.js";
8
- import { extractAssistantText } from "../utils/messages.js";
9
- import { ClaudeCodeRuntime } from "./claude_code_runtime.js";
3
+ import { appendUsageLogEntry } from "../usage/logs.js";
4
+ import { AgentRequestError } from "./errors.js";
10
5
  import { formatActiveSubagentsForCompaction } from "./format.js";
6
+ import { createSubagentRuntime } from "./runtime.js";
7
+ import { AgentTrace } from "./trace.js";
11
8
  const MAX_ACTIVE_SUBAGENTS = 8;
12
9
  async function raceWithAbort(promise, signal) {
13
10
  if (!signal)
@@ -51,7 +48,7 @@ export class AgentSupervisor {
51
48
  return [...this.records.values()].filter((record) => record.run.status === "running").length;
52
49
  }
53
50
  getActiveCompactionContext() {
54
- const active = this.listSnapshots().filter((state) => state.availability === "running");
51
+ const active = this.list().filter((state) => state.status !== "idle");
55
52
  if (active.length === 0)
56
53
  return undefined;
57
54
  const state = formatActiveSubagentsForCompaction(active);
@@ -59,63 +56,25 @@ export class AgentSupervisor {
59
56
  }
60
57
  spawn(options) {
61
58
  if (this.getActiveCount() >= MAX_ACTIVE_SUBAGENTS) {
62
- return {
63
- ok: false,
64
- reason: `Subagent limit reached (max ${MAX_ACTIVE_SUBAGENTS} active). Wait for existing agents to finish.`,
65
- };
59
+ throw new AgentRequestError("operation_failed", `Subagent limit reached (max ${MAX_ACTIVE_SUBAGENTS} active). Wait for existing agents to finish.`);
66
60
  }
67
61
  const workingDirectory = options.runtimeConfig.workingDirectory.trim();
68
62
  if (!workingDirectory) {
69
- return { ok: false, reason: "Subagent workingDirectory must not be blank." };
63
+ throw new AgentRequestError("invalid_arguments", "Subagent workingDirectory must not be blank.");
70
64
  }
71
65
  const id = randomUUID();
72
66
  const runtimeConfig = { ...options.runtimeConfig, workingDirectory };
73
- let runtime;
74
- let reasoning;
75
- if (runtimeConfig.kind === "claude-code") {
76
- runtime = new ClaudeCodeRuntime({
77
- config: runtimeConfig,
78
- backend: options.backend,
79
- onEvent: async (event) => await this.recordClaudeCodeEvent(id, event),
80
- });
81
- reasoning = runtimeConfig.effort;
82
- }
83
- else {
84
- runtime = new AgentRuntime({
85
- spec: createAgentSpec({
86
- autoCompactThresholdTokens: null,
87
- ...resolveAgentModel({
88
- id: `${options.personaId ?? "agent"}-subagent`,
89
- label: "subagent",
90
- description: "Background agent",
91
- source: "project",
92
- systemPrompt: runtimeConfig.systemPrompt,
93
- model: runtimeConfig.model,
94
- settings: runtimeConfig.settings,
95
- tools: runtimeConfig.tools,
96
- subagentLaunchModels: [],
97
- }, options.config, {
98
- includeModelNotice: true,
99
- deps: this.deps,
100
- }),
101
- systemPrompt: runtimeConfig.systemPrompt,
102
- tools: ToolCatalog.createSubagentRegistry(runtimeConfig.tools, options.backend, workingDirectory, options.config, options.bashJobs, options.history, options.mcp),
103
- }),
104
- eventSink: async (event) => await this.recordAgentEvent(id, event),
105
- clock: this.deps.clock,
106
- archiveAutoCompaction: createAutoCompactionArchiver(options.backend),
107
- });
108
- reasoning = runtime.spec.attribution.reasoningEffort;
109
- }
67
+ const runtime = createSubagentRuntime(runtimeConfig, {
68
+ ...options,
69
+ id,
70
+ deps: this.deps,
71
+ onEvent: async (event) => await this.recordRuntimeEvent(id, event),
72
+ });
110
73
  const createdAt = this.deps.clock.now();
111
74
  const record = {
112
75
  id,
113
76
  title: options.title,
114
- model: {
115
- provider: runtimeConfig.model.provider,
116
- id: runtimeConfig.model.id,
117
- reasoning,
118
- },
77
+ model: runtime.model,
119
78
  workingDirectory,
120
79
  createdAt,
121
80
  originHistoryEntryId: options.originHistoryEntryId,
@@ -134,157 +93,185 @@ export class AgentSupervisor {
134
93
  cacheRead: 0,
135
94
  cacheWrite: 0,
136
95
  contextWindowUsageTokens: 0,
137
- contextWindow: runtimeConfig.kind === "native" ? runtimeConfig.model.contextWindow : 0,
96
+ contextWindow: runtime.contextWindow,
138
97
  },
139
- claudeMessageUsage: new Map(),
140
- toolPresentations: new Map(),
141
- blockedToolCalls: new Map(),
142
- completion: Promise.resolve(undefined),
98
+ completion: Promise.resolve(),
99
+ trace: new AgentTrace(),
100
+ pending: [],
143
101
  };
144
102
  this.records.set(id, record);
103
+ record.trace.append({
104
+ kind: "input",
105
+ mode: "initial",
106
+ content: [{ type: "text", text: options.prompt }],
107
+ });
145
108
  this.startRun(record, options.prompt, "subagent_spawned");
146
- return { ok: true, state: this.toSnapshot(record), capacity: this.getCapacity() };
109
+ return this.read(record.id);
147
110
  }
148
111
  sendInput(options) {
149
- const record = this.records.get(options.id);
150
- if (!record)
151
- return { ok: false, reason: `Unknown subagent ID: ${options.id}` };
112
+ const record = this.requireRecord(options.id);
113
+ if (record.run.interruptRequested && record.run.status === "running")
114
+ throw new AgentRequestError("operation_failed", "Subagent is stopping");
115
+ if (record.run.status !== "running" && this.getActiveCount() >= MAX_ACTIVE_SUBAGENTS)
116
+ throw new AgentRequestError("operation_failed", "Subagent capacity reached");
117
+ if (record.pending.length + record.runtime.pendingSteering >= 64)
118
+ throw new AgentRequestError("operation_failed", "Subagent input queue is full");
119
+ record.trace.append({
120
+ kind: "input",
121
+ mode: options.mode,
122
+ content: [{ type: "text", text: options.prompt }],
123
+ });
152
124
  if (record.run.status === "running") {
153
- return {
154
- ok: false,
155
- reason: `Subagent ${options.id} is already running. Wait for it to finish before sending input.`,
156
- };
125
+ if (options.mode === "steer")
126
+ record.runtime.steer(options.prompt);
127
+ else
128
+ record.pending.push({ prompt: options.prompt, mode: options.mode });
157
129
  }
158
- if (this.getActiveCount() >= MAX_ACTIVE_SUBAGENTS) {
159
- return {
160
- ok: false,
161
- reason: `Subagent limit reached (max ${MAX_ACTIVE_SUBAGENTS} active). Wait for existing agents to finish.`,
162
- };
130
+ else {
131
+ this.beginFollowup(record, options.prompt);
163
132
  }
133
+ return this.read(record.id);
134
+ }
135
+ beginFollowup(record, prompt) {
136
+ record.lastAssistantSeq = undefined;
164
137
  record.run = {
165
138
  revision: record.run.revision + 1,
166
139
  status: "running",
167
140
  startedAt: this.deps.clock.now(),
168
141
  interruptRequested: false,
169
142
  };
170
- record.toolPresentations.clear();
171
- record.blockedToolCalls.clear();
172
- this.startRun(record, options.prompt, "subagent_run_started");
173
- return { ok: true, state: this.toSnapshot(record), capacity: this.getCapacity() };
143
+ this.startRun(record, prompt, "subagent_run_started");
144
+ }
145
+ read(id) {
146
+ const record = this.requireRecord(id);
147
+ const { run } = record;
148
+ return {
149
+ id,
150
+ title: record.title,
151
+ model: `${record.model.provider}/${record.model.id}:${record.model.reasoning}`,
152
+ workingDirectory: record.workingDirectory,
153
+ ...(run.status === "running"
154
+ ? { status: run.interruptRequested ? "stopping" : "working" }
155
+ : run.status === "failed"
156
+ ? {
157
+ status: "idle",
158
+ reason: "failed",
159
+ error: { code: run.failure.kind, message: run.failure.message },
160
+ }
161
+ : { status: "idle", reason: run.status === "succeeded" ? "completed" : "interrupted" }),
162
+ pending: {
163
+ queue: record.pending.filter((input) => input.mode === "queue").length,
164
+ steer: record.runtime.pendingSteering +
165
+ record.pending.filter((input) => input.mode === "steer").length,
166
+ },
167
+ usage: {
168
+ input: record.usage.input,
169
+ output: record.usage.output,
170
+ cacheRead: record.usage.cacheRead,
171
+ cacheWrite: record.usage.cacheWrite,
172
+ contextTokens: record.usage.contextWindowUsageTokens,
173
+ contextWindow: record.usage.contextWindow,
174
+ cost: record.costTotal,
175
+ },
176
+ };
177
+ }
178
+ getTrace(id) {
179
+ const record = this.requireRecord(id);
180
+ return record.trace;
174
181
  }
175
182
  async waitForAgents(ids, signal) {
176
183
  const missing = ids.filter((id) => !this.records.has(id));
177
184
  if (missing.length > 0)
178
- throw new Error(`Unknown subagent ID(s): ${missing.join(", ")}`);
185
+ throw new AgentRequestError("unknown_agent", `Unknown subagent ID(s): ${missing.join(", ")}`);
179
186
  await raceWithAbort(Promise.race(ids.map((id) => this.waitForRecord(id))), signal);
180
- return ids.map((id) => {
181
- const record = this.records.get(id);
182
- if (!record)
183
- throw new Error(`Unknown subagent ID: ${id}`);
184
- return this.toSnapshot(record);
185
- });
187
+ return ids.map((id) => this.read(id));
186
188
  }
187
- async interrupt(id, signal) {
188
- const record = this.records.get(id);
189
- if (!record)
190
- return undefined;
189
+ async interrupt(id, signal, wait = true) {
190
+ const record = this.requireRecord(id);
191
191
  if (record.run.status === "running") {
192
+ if (record.pending.length || record.runtime.pendingSteering)
193
+ record.trace.append({
194
+ kind: "notice",
195
+ code: "input.discarded",
196
+ content: [{ type: "text", text: "Pending inputs discarded by interruption." }],
197
+ });
198
+ record.pending = [];
192
199
  record.run.interruptRequested = true;
200
+ record.trace.append({ kind: "status", status: "stopping" });
193
201
  record.runtime.interrupt();
194
202
  await this.emit({ type: "subagent_interrupt_requested", state: this.toSnapshot(record) });
195
203
  }
196
- return this.toSnapshot(await raceWithAbort(this.waitForRecord(id), signal));
204
+ if (wait)
205
+ await raceWithAbort(this.waitForRecord(id), signal);
206
+ return this.read(id);
207
+ }
208
+ getLastResponse(id) {
209
+ const record = this.requireRecord(id);
210
+ const seq = record.lastAssistantSeq;
211
+ if (record.run.status === "running" || seq === undefined)
212
+ return undefined;
213
+ try {
214
+ return { seq, ...record.trace.readText(seq, Number.POSITIVE_INFINITY) };
215
+ }
216
+ catch (error) {
217
+ if (!(error instanceof AgentRequestError) || error.code !== "expired_content")
218
+ throw error;
219
+ return { seq, text: "", complete: false };
220
+ }
197
221
  }
198
222
  getSnapshot(id) {
199
223
  const record = this.records.get(id);
200
224
  return record ? this.toSnapshot(record) : undefined;
201
225
  }
202
- listSnapshots() {
203
- return [...this.records.values()].map((record) => this.toSnapshot(record));
204
- }
205
226
  startRun(record, prompt, eventType) {
206
- record.completion = this.emit({ type: eventType, state: this.toSnapshot(record) })
207
- .then(async () => {
208
- if (record.run.interruptRequested) {
209
- this.finishInterrupted(record);
210
- }
211
- else if (record.runtime instanceof ClaudeCodeRuntime) {
212
- const completion = await record.runtime.submit(prompt);
213
- if (record.run.interruptRequested || completion.status === "interrupted") {
227
+ record.trace.append({ kind: "status", status: "working" });
228
+ record.completion = (async () => {
229
+ try {
230
+ await this.emit({ type: eventType, state: this.toSnapshot(record) });
231
+ if (record.run.interruptRequested) {
214
232
  this.finishInterrupted(record);
215
233
  }
216
- else if (completion.result.failed) {
217
- this.finishFailed(record, {
218
- kind: "provider-error",
219
- message: completion.result.response,
220
- stopReason: "error",
221
- });
222
- }
223
234
  else {
224
- this.finishSucceeded(record, completion.result.response);
235
+ const { outcome, unconsumed } = await record.runtime.submit(prompt);
236
+ if (record.run.interruptRequested || outcome.status === "interrupted") {
237
+ this.finishInterrupted(record);
238
+ }
239
+ else {
240
+ record.pending.push(...unconsumed.map((prompt) => ({ prompt, mode: "steer" })));
241
+ if (outcome.status === "failed")
242
+ this.finishFailed(record, outcome.failure);
243
+ else
244
+ this.finishSucceeded(record, outcome.response);
245
+ }
225
246
  }
226
247
  }
227
- else {
228
- this.applyTurnResult(record, await record.runtime.submit(prompt));
229
- }
230
- return record;
231
- })
232
- .catch((error) => {
233
- if (record.run.interruptRequested) {
234
- this.finishInterrupted(record);
248
+ catch (error) {
249
+ if (record.run.interruptRequested)
250
+ this.finishInterrupted(record);
251
+ else
252
+ this.finishFailed(record, {
253
+ kind: "runtime-error",
254
+ message: error instanceof Error ? error.message : String(error),
255
+ });
235
256
  }
236
- else {
237
- this.finishFailed(record, {
238
- kind: "runtime-error",
239
- message: error instanceof Error ? error.message : String(error),
257
+ if (this.records.get(record.id) === record) {
258
+ const next = record.pending.shift();
259
+ if (next && !record.run.interruptRequested) {
260
+ this.beginFollowup(record, next.prompt);
261
+ return await record.completion;
262
+ }
263
+ record.trace.append({
264
+ kind: "status",
265
+ status: "idle",
266
+ reason: record.run.status === "succeeded"
267
+ ? "completed"
268
+ : record.run.status === "interrupted"
269
+ ? "interrupted"
270
+ : "failed",
240
271
  });
272
+ await this.emit({ type: "subagent_finished", state: this.toSnapshot(record) });
241
273
  }
242
- return record;
243
- })
244
- .then(async (resolved) => {
245
- if (this.records.get(resolved.id) === resolved) {
246
- await this.emit({ type: "subagent_finished", state: this.toSnapshot(resolved) });
247
- }
248
- return resolved;
249
- });
250
- }
251
- applyTurnResult(record, result) {
252
- if (record.run.interruptRequested ||
253
- result.aborted ||
254
- result.finalMessage?.stopReason === "aborted") {
255
- this.finishInterrupted(record);
256
- return;
257
- }
258
- if (result.blocked) {
259
- this.finishFailed(record, {
260
- kind: result.blocked.reason,
261
- message: result.blocked.message,
262
- });
263
- return;
264
- }
265
- if (result.limitReached) {
266
- this.finishFailed(record, {
267
- kind: result.limitReached.reason,
268
- message: result.limitReached.message,
269
- });
270
- return;
271
- }
272
- if (!result.finalMessage) {
273
- this.finishFailed(record, {
274
- kind: "runtime-error",
275
- message: "Subagent run completed without a final assistant message.",
276
- });
277
- return;
278
- }
279
- if (result.finalMessage.stopReason === "error") {
280
- this.finishFailed(record, {
281
- kind: "provider-error",
282
- message: result.finalMessage.errorMessage ?? "model returned an unspecified error.",
283
- stopReason: result.finalMessage.stopReason,
284
- });
285
- return;
286
- }
287
- this.finishSucceeded(record, extractAssistantText(result.finalMessage).trim());
274
+ })();
288
275
  }
289
276
  finishSucceeded(record, response) {
290
277
  record.run = {
@@ -307,6 +294,11 @@ export class AgentSupervisor {
307
294
  };
308
295
  }
309
296
  finishFailed(record, failure) {
297
+ record.trace.append({
298
+ kind: "notice",
299
+ code: failure.kind,
300
+ content: [{ type: "text", text: failure.message }],
301
+ });
310
302
  record.run = {
311
303
  revision: record.run.revision,
312
304
  status: "failed",
@@ -316,203 +308,57 @@ export class AgentSupervisor {
316
308
  failure,
317
309
  };
318
310
  }
319
- async recordClaudeCodeEvent(id, event) {
311
+ async recordRuntimeEvent(id, event) {
320
312
  const record = this.records.get(id);
321
313
  if (!record)
322
314
  return;
323
- if (event.type === "usage" || event.type === "result") {
324
- let reported = event.usage;
325
- if (event.type === "usage") {
326
- const previous = record.claudeMessageUsage.get(event.messageId);
327
- // Final result totals already cover these messages, including resumed replays.
328
- if (previous === null)
329
- return;
330
- const current = {
331
- input: Math.max(previous?.input ?? 0, event.usage.input),
332
- output: Math.max(previous?.output ?? 0, event.usage.output),
333
- cacheRead: Math.max(previous?.cacheRead ?? 0, event.usage.cacheRead),
334
- cacheWrite: Math.max(previous?.cacheWrite ?? 0, event.usage.cacheWrite),
335
- };
336
- record.claudeMessageUsage.set(event.messageId, current);
337
- reported = {
338
- input: record.usage.input + current.input - (previous?.input ?? 0),
339
- output: record.usage.output + current.output - (previous?.output ?? 0),
340
- cacheRead: record.usage.cacheRead + current.cacheRead - (previous?.cacheRead ?? 0),
341
- cacheWrite: record.usage.cacheWrite + current.cacheWrite - (previous?.cacheWrite ?? 0),
342
- };
343
- if (getUsageTotals(current).total === getUsageTotals(previous).total)
344
- return;
345
- record.usage.contextWindowUsageTokens = getUsageTotals(current).total;
346
- }
347
- else {
348
- for (const messageId of record.claudeMessageUsage.keys()) {
349
- record.claudeMessageUsage.set(messageId, null);
350
- }
351
- record.usage.contextWindowUsageTokens = event.usage.contextWindowUsageTokens;
352
- record.usage.contextWindow = event.usage.contextWindow;
353
- }
354
- // SDK totals include the history restored on resume. Record only the delta,
355
- // and replace (rather than sum) the current context-window measurement.
356
- const usage = getUsageTotals({
357
- input: Math.max(0, reported.input - record.usage.input),
358
- output: Math.max(0, reported.output - record.usage.output),
359
- cacheRead: Math.max(0, reported.cacheRead - record.usage.cacheRead),
360
- cacheWrite: Math.max(0, reported.cacheWrite - record.usage.cacheWrite),
361
- });
362
- const cost = event.type === "result" ? Math.max(0, event.cost - record.costTotal) : 0;
363
- // Startup failures can report zero totals even for a resumed session.
364
- record.costTotal += cost;
365
- record.usage = {
366
- ...record.usage,
367
- input: record.usage.input + usage.input,
368
- output: record.usage.output + usage.output,
369
- cacheRead: record.usage.cacheRead + usage.cacheRead,
370
- cacheWrite: record.usage.cacheWrite + usage.cacheWrite,
371
- };
372
- (this.options.recordUsage ?? appendUsageLogEntry)({
373
- timestamp: this.deps.clock.now(),
374
- sessionId: record.id,
375
- personaId: record.personaId,
376
- provider: record.model.provider,
377
- model: record.model.id,
378
- api: "claude-agent-sdk",
379
- reasoningEffort: record.model.reasoning,
380
- usage,
381
- cost: { total: cost },
382
- agent: { type: "subagent" },
383
- });
384
- await this.emit({ type: "subagent_updated", state: this.toSnapshot(record) });
315
+ if (event.type === "trace") {
316
+ const seq = record.trace.append(event.event);
317
+ if (event.event.kind === "assistant")
318
+ record.lastAssistantSeq = seq;
385
319
  }
386
- else if (event.type === "assistant") {
320
+ else if (event.type === "activity") {
387
321
  await this.emit({
388
322
  type: "subagent_activity",
389
323
  state: this.toSnapshot(record),
390
- activity: { type: "assistant", text: event.text },
324
+ activity: event.activity,
391
325
  });
392
326
  }
393
- else if (event.type === "tool") {
394
- await this.emit({
395
- type: "subagent_activity",
396
- state: this.toSnapshot(record),
397
- activity: {
398
- type: "tool",
399
- toolName: event.name,
400
- outcome: event.failed ? "failed" : "succeeded",
401
- presentation: {
402
- action: event.failed ? "failed" : "completed",
403
- subject: event.subject,
404
- subjectWrap: "word",
405
- details: [],
406
- metadata: [],
407
- },
408
- },
409
- });
410
- }
411
- }
412
- async recordAgentEvent(id, event) {
413
- const record = this.records.get(id);
414
- if (!record)
415
- return;
416
- if (event.type === "assistant_final") {
417
- const usage = getUsageTotals(event.message.usage);
418
- const cost = getUsageCostTotal(event.message.usage);
419
- record.costTotal += cost;
327
+ else {
328
+ const { usage, cost } = event.entry;
329
+ record.costTotal += cost.total;
420
330
  record.usage = {
421
331
  input: record.usage.input + usage.input,
422
332
  output: record.usage.output + usage.output,
423
333
  cacheRead: record.usage.cacheRead + usage.cacheRead,
424
334
  cacheWrite: record.usage.cacheWrite + usage.cacheWrite,
425
- contextWindowUsageTokens: usage.input + usage.output + usage.cacheRead + usage.cacheWrite,
426
- contextWindow: record.usage.contextWindow,
335
+ contextWindowUsageTokens: event.contextTokens,
336
+ contextWindow: event.contextWindow,
427
337
  };
428
338
  (this.options.recordUsage ?? appendUsageLogEntry)({
429
- timestamp: event.message.timestamp,
339
+ ...event.entry,
430
340
  sessionId: record.id,
431
- personaId: record.personaId ?? event.personaId,
432
- provider: event.message.provider,
433
- model: event.message.model,
434
- api: event.message.api,
435
- reasoningEffort: event.reasoningEffort,
436
- usage,
437
- cost: { total: cost },
341
+ personaId: record.personaId ?? event.entry.personaId,
438
342
  agent: { type: "subagent" },
439
343
  });
440
344
  await this.emit({ type: "subagent_updated", state: this.toSnapshot(record) });
441
- const text = extractAssistantText(event.message).trim();
442
- if (text) {
443
- await this.emit({
444
- type: "subagent_activity",
445
- state: this.toSnapshot(record),
446
- activity: { type: "assistant", text },
447
- });
448
- }
449
- return;
450
- }
451
- if (event.type === "tool_activity") {
452
- record.toolPresentations.set(event.activity.toolCallId, event.activity.presentation);
453
- const blockedToolName = record.blockedToolCalls.get(event.activity.toolCallId);
454
- if (blockedToolName) {
455
- record.blockedToolCalls.delete(event.activity.toolCallId);
456
- await this.emitSettledToolActivity(record, blockedToolName, "blocked", event.activity.presentation);
457
- record.toolPresentations.delete(event.activity.toolCallId);
458
- }
459
- return;
460
- }
461
- if (event.type === "tool_run_blocked") {
462
- const presentation = record.toolPresentations.get(event.toolCallId);
463
- if (!presentation) {
464
- record.blockedToolCalls.set(event.toolCallId, event.toolName);
465
- return;
466
- }
467
- record.toolPresentations.delete(event.toolCallId);
468
- await this.emitSettledToolActivity(record, event.toolName, "blocked", presentation);
469
- return;
470
- }
471
- if (event.type === "tool_run_finished") {
472
- const presentation = record.toolPresentations.get(event.toolCallId);
473
- if (!presentation) {
474
- throw new Error(`missing presentation for settled tool call '${event.toolCallId}'`);
475
- }
476
- record.toolPresentations.delete(event.toolCallId);
477
- await this.emitSettledToolActivity(record, event.toolName, event.outcome, presentation);
478
- return;
479
- }
480
- if (event.type === "feedback") {
481
- await this.emit({
482
- type: "subagent_activity",
483
- state: this.toSnapshot(record),
484
- activity: {
485
- type: "notice",
486
- severity: event.tone === "error" ? "error" : "info",
487
- title: event.title,
488
- ...(event.presentation === "transcript" && event.content
489
- ? { content: event.content }
490
- : {}),
491
- },
492
- });
493
345
  }
494
346
  }
495
- async emitSettledToolActivity(record, toolName, outcome, presentation) {
496
- const { actionByStatus, ...terminalPresentation } = presentation;
497
- await this.emit({
498
- type: "subagent_activity",
499
- state: this.toSnapshot(record),
500
- activity: {
501
- type: "tool",
502
- toolName,
503
- outcome,
504
- presentation: {
505
- action: actionByStatus[outcome],
506
- ...structuredClone(terminalPresentation),
507
- },
508
- },
509
- });
510
- }
511
- async waitForRecord(id) {
347
+ requireRecord(id) {
512
348
  const record = this.records.get(id);
513
349
  if (!record)
514
- throw new Error(`Unknown subagent ID: ${id}`);
515
- return await record.completion;
350
+ throw new AgentRequestError("unknown_agent", `Unknown subagent ID: ${id}`);
351
+ return record;
352
+ }
353
+ list() {
354
+ return [...this.records.keys()].map((id) => this.read(id));
355
+ }
356
+ async waitForRecord(id) {
357
+ const record = this.requireRecord(id);
358
+ do {
359
+ await record.completion;
360
+ } while (record.run.status === "running");
361
+ return record;
516
362
  }
517
363
  toSnapshot(record) {
518
364
  return {