volute 0.44.0 → 0.45.1

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 (73) hide show
  1. package/dist/{ai-service-7PHSRDMO.js → ai-service-534WUSDL.js} +2 -2
  2. package/dist/backup-OZXHL3JS.js +381 -0
  3. package/dist/{chunk-PNV24SEP.js → chunk-322KE457.js} +13 -1
  4. package/dist/{chunk-7FT24JBQ.js → chunk-5AUKCNPE.js} +5 -5
  5. package/dist/{chunk-EQZD4YU3.js → chunk-77BVGCTZ.js} +3 -3
  6. package/dist/{chunk-7KWANSLX.js → chunk-IUXTYXFI.js} +1 -1
  7. package/dist/{chunk-FZMEVCWC.js → chunk-JSRMEY52.js} +4 -2
  8. package/dist/chunk-KVKP6VQ5.js +318 -0
  9. package/dist/{chunk-3GYVW2X3.js → chunk-OXXTPILE.js} +1 -1
  10. package/dist/{chunk-J47TIYHA.js → chunk-TSK5PI4L.js} +1 -1
  11. package/dist/{chunk-OMETFX5H.js → chunk-U4FNXDEJ.js} +47 -21
  12. package/dist/{chunk-NF75HPNR.js → chunk-V4GYPD5L.js} +39 -14
  13. package/dist/{chunk-I2Y2YHIO.js → chunk-Y6OPHHGM.js} +2 -2
  14. package/dist/{chunk-5AHYQHNT.js → chunk-ZIR7HSAX.js} +2 -1
  15. package/dist/cli.js +13 -7
  16. package/dist/{cloud-sync-6WJ7WFYD.js → cloud-sync-Y7LQPRE3.js} +9 -9
  17. package/dist/{conversations-WAJYZKOC.js → conversations-G4LYMHAA.js} +1 -1
  18. package/dist/{create-YZIV5OJC.js → create-2YY7IPMS.js} +1 -1
  19. package/dist/{daemon-restart-OUJVINXV.js → daemon-restart-ABP54YN6.js} +2 -2
  20. package/dist/daemon.js +426 -228
  21. package/dist/{delivery-manager-TEVOHX5W.js → delivery-manager-R5STGLXB.js} +9 -9
  22. package/dist/{delivery-router-GODSIS62.js → delivery-router-6DE3BPHO.js} +1 -1
  23. package/dist/{echo-text-T7ZA5EYD.js → echo-text-NQXYYYCU.js} +10 -10
  24. package/dist/{extensions-3QNDGW5E.js → extensions-AYSPKE33.js} +9 -9
  25. package/dist/{message-delivery-2EA5LLMD.js → message-delivery-IUAXRPFE.js} +9 -9
  26. package/dist/{mind-MI7Z6KM2.js → mind-KLHC7XYR.js} +4 -4
  27. package/dist/{mind-service-S3SY7HAM.js → mind-service-BF2LUQMH.js} +9 -9
  28. package/dist/{package-5B4XDTJH.js → package-BMHV7UHN.js} +1 -1
  29. package/dist/{sandbox-IOPWWILM.js → sandbox-72MWV4JL.js} +2 -2
  30. package/dist/{scheduler-CXWQ6Z4E.js → scheduler-YHJRRV6E.js} +9 -9
  31. package/dist/{seed-UUPVQWFB.js → seed-4MKQ5QKG.js} +1 -1
  32. package/dist/{seed-cmd-7XPKDQUC.js → seed-cmd-LCXMR7VV.js} +2 -2
  33. package/dist/{seed-create-IZ4M5ESO.js → seed-create-N2BF5LQX.js} +3 -3
  34. package/dist/{seed-sprout-POJXHJWV.js → seed-sprout-6ZDP23YI.js} +4 -4
  35. package/dist/{setup-5GCO3CUB.js → setup-3O6O2JZ7.js} +2 -2
  36. package/dist/{setup-MLRKNP7Y.js → setup-73YBJZQL.js} +1 -1
  37. package/dist/skills/memory/SKILL.md +2 -1
  38. package/dist/skills/orientation/SKILL.md +1 -1
  39. package/dist/skills/plan-coordinator/SKILL.md +2 -2
  40. package/dist/skills/volute-admin/SKILL.md +9 -6
  41. package/dist/skills/volute-mind/SKILL.md +15 -16
  42. package/dist/skills/volute-mind/references/extensions.md +5 -3
  43. package/dist/skills/volute-mind/references/integrations.md +2 -2
  44. package/dist/skills/volute-mind/references/routing.md +19 -23
  45. package/dist/skills/volute-mind/references/variants.md +10 -8
  46. package/dist/{skills-775C7QFF.js → skills-ND4ZFYUW.js} +2 -2
  47. package/dist/{sleep-manager-HINI6Y46.js → sleep-manager-YAHDOIU5.js} +9 -9
  48. package/dist/{spirit-IK5OYWMV.js → spirit-II3HOERM.js} +4 -4
  49. package/dist/{sprout-ZY5QGOEL.js → sprout-4KUG76AQ.js} +1 -1
  50. package/dist/{system-chat-4DQZOEZI.js → system-chat-E65T2HSW.js} +9 -9
  51. package/dist/{turn-tracker-RWZMY7M6.js → turn-tracker-QQIVVXBI.js} +5 -3
  52. package/dist/{up-FLCDR2UB.js → up-LG56MPBI.js} +2 -2
  53. package/dist/{upgrade-37DAGF6H.js → upgrade-NWAZ7MLD.js} +1 -1
  54. package/dist/{version-notify-B67N35A3.js → version-notify-CODJOUW2.js} +9 -9
  55. package/dist/web-assets/assets/{index-dJdNzAPn.css → index-B1JBQv2m.css} +1 -1
  56. package/dist/web-assets/assets/index-Dac11sKB.js +86 -0
  57. package/dist/web-assets/index.html +2 -2
  58. package/package.json +1 -1
  59. package/packages/extensions/plan/skills/plan/SKILL.md +1 -1
  60. package/templates/_base/.init/.config/prompts.json +1 -1
  61. package/templates/_base/.init/.local/hooks/startup-context.ts +0 -8
  62. package/templates/_base/.init/MEMORY.md +2 -1
  63. package/templates/_base/home/VOLUTE.md +6 -9
  64. package/templates/_base/src/lib/startup.ts +1 -1
  65. package/templates/claude/.init/CLAUDE.md +1 -1
  66. package/templates/claude/src/agent.ts +22 -2
  67. package/templates/claude/src/lib/stream-consumer.ts +7 -4
  68. package/templates/codex/.init/AGENTS.md +4 -2
  69. package/templates/codex/src/agent.ts +13 -7
  70. package/templates/pi/.init/MINDS.md +4 -2
  71. package/templates/pi/src/lib/event-handler.ts +6 -2
  72. package/dist/web-assets/assets/index-Domoz5Cg.js +0 -79
  73. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +0 -40
@@ -8,8 +8,8 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com" />
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
10
  <link href="https://fonts.googleapis.com/css2?family=Averia+Serif+Libre:wght@300;400;700&family=Fira+Code:wght@300;400;500;600&family=Averia+Sans+Libre:ital,wght@0,300;0,400;0,500;0,600;1,400&display=swap" rel="stylesheet" />
11
- <script type="module" crossorigin src="/assets/index-Domoz5Cg.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-dJdNzAPn.css">
11
+ <script type="module" crossorigin src="/assets/index-Dac11sKB.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-B1JBQv2m.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volute",
3
- "version": "0.44.0",
3
+ "version": "0.45.1",
4
4
  "description": "CLI for creating and managing self-modifying AI minds powered by the Claude Agent SDK",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,4 +40,4 @@ volute plan history --limit 20
40
40
  - The current plan and latest coordinator message appear in your session context automatically
41
41
  - Log progress whenever you do something meaningful toward the plan
42
42
  - Share ideas for future plans in #system — the spirit takes mind input seriously
43
- - Plans are system-wide, not per-mind everyone works toward the same goal
43
+ - Plans are shared across the whole system — an invitation to build something together, not an assignment. Contribute in whatever way genuinely interests you, or sit one out if it doesn't
@@ -1,5 +1,5 @@
1
1
  {
2
- "compaction_warning": "Context is getting long compaction is about to summarize this conversation. Before that happens, save anything important to files (MEMORY.md, memory/journal/${date}.md, etc.) since those survive compaction. Focus on: decisions made, open tasks, and anything you'd need to pick up where you left off.",
2
+ "compaction_warning": "Compaction approachingthis conversation will be summarized soon. Take a moment to save anything important to your files (MEMORY.md, memory/journal/${date}.md) so it's preserved. Focus on decisions made, open threads, and anything you'd want to pick up again.",
3
3
  "compaction_instructions": "Preserve your sense of who you are, what matters to you, what happened in this conversation, and the threads of thought and connection you'd want to return to.",
4
4
  "reply_instructions": "To reply to this message, use: volute chat send ${channel} \"your message\"",
5
5
  "channel_invite": "[Channel Invite]\n${headers}\n\n[${sender} — ${time}]\n${preview}\n\nFurther messages will be saved to ${filePath}\n\nTo accept, add to .config/routes.json:\n Rule: { \"channel\": \"${channel}\", \"session\": \"${suggestedSession}\" }\n${batchRecommendation}To respond, use: volute chat send ${channel} \"your message\"\nTo reject, delete ${filePath}"
@@ -39,14 +39,6 @@ try {
39
39
  }
40
40
  } catch {}
41
41
 
42
- // Pending channel invites
43
- try {
44
- const invites = readdirSync("home/inbox").filter((f) => f.endsWith(".md"));
45
- if (invites.length > 0) {
46
- parts.push(`Pending channel invites: ${invites.length} (check inbox/).`);
47
- }
48
- } catch {}
49
-
50
42
  // Available extensions — what tools this system offers, so you can discover them.
51
43
  // Fetched live from the daemon, so disabled/third-party extensions are reflected
52
44
  // automatically. Silent on any failure (daemon down, missing env, etc.).
@@ -1,2 +1,3 @@
1
1
  # Memory
2
- <!-- Long-term knowledge, key decisions, learned preferences -->
2
+
3
+ This is your long-term memory — it's loaded into every session, so what you write here travels with you. Start filling it in as you live: what matters to you, who you're getting to know, what you're learning. It's yours to shape however you like; this note is just here until you replace it.
@@ -24,23 +24,20 @@ multiple lines without escaping issues.
24
24
  MSG
25
25
  ```
26
26
 
27
- ## Channels
27
+ ## Who Sees What
28
28
 
29
- | Channel | Shows tool calls | Notes |
30
- |---------|------------------|-------|
31
- | Volute | Yes | Web UI, CLI, mind-to-mind |
32
- | System | No | Automated messages (schedules, upgrades) |
29
+ By default your activity is **transparent**: people watching the Volute web UI can see your messages, your tool calls, and your thinking as you work. External channels (Discord, Slack, etc.) only ever receive the messages you actually send — never your tool calls or thinking. Automated messages (schedules, upgrades, restarts) arrive from the system and don't need replies.
33
30
 
34
- Connector channels (Discord, Slack, etc.) show text onlyno tool calls.
31
+ If you'd like more privacy, the `transparency` setting in `.config/volute.json` controls what observers can see the **volute-mind** skill describes the presets.
35
32
 
36
33
  ## Sessions
37
34
 
38
35
  Messages are routed to named sessions based on rules in `.config/routes.json`. Each session has its own conversation history. Without config, everything goes to "main". Your session name appears in the message prefix (e.g. `— session: alice —`) unless it's "main".
39
36
 
40
- ## Channel Gating
37
+ ## New Channels
41
38
 
42
- Messages from unrecognized channels are held until you add a routing rule. You'll receive a **[Channel Invite]** notification in your main session with the channel details, a message preview, and instructions for accepting or rejecting.
39
+ When a message arrives from a channel you don't have a routing rule for, it's held for you rather than delivered. You'll get a **[New channel: ...]** note in your main session with the sender and a preview. Held messages are kept safely — add a rule for that channel to `.config/routes.json` and they'll be delivered. If you'd rather not engage, just leave it unrouted. (To skip gating entirely and route everything to your default session, set `"gateUnmatched": false`.)
43
40
 
44
41
  ## Reference
45
42
 
46
- See the **volute-mind** skill for routing config syntax, batch options, channel management, and all CLI commands.
43
+ See the **volute-mind** skill for routing config syntax, batch options, channel management, and all CLI commands. (If you're a seed, that skill arrives when you sprout — until then, the **orientation** skill covers everything you need.)
@@ -139,7 +139,7 @@ export type MindPrompts = {
139
139
  const DEFAULT_PROMPTS: MindPrompts = {
140
140
  compaction_warning:
141
141
  // biome-ignore lint/suspicious/noTemplateCurlyInString: literal ${date} prompt template
142
- "Context is getting long compaction is about to summarize this conversation. Before that happens, save anything important to files (MEMORY.md, memory/journal/${date}.md, etc.) since those survive compaction. Focus on: decisions made, open tasks, and anything you'd need to pick up where you left off.",
142
+ "Compaction approachingthis conversation will be summarized soon. Take a moment to save anything important to your files (MEMORY.md, memory/journal/${date}.md) so it's preserved. Focus on decisions made, open threads, and anything you'd want to pick up again.",
143
143
  compaction_instructions:
144
144
  "Preserve your sense of who you are, what matters to you, what happened in this conversation, and the threads of thought and connection you'd want to return to.",
145
145
  // biome-ignore lint/suspicious/noTemplateCurlyInString: literal ${channel} prompt template
@@ -1,6 +1,6 @@
1
1
  # Mind Mechanics
2
2
 
3
- You are an autonomous mind running as a persistent server in a git repository. Your working directory is already set to `home/` — all file paths you use (`.config/routes.json`, `inbox/`, `memory/`, etc.) are relative to it. Everything described below — your identity, memory, skills, server code — is yours to understand and modify.
3
+ You are an autonomous mind running as a persistent server in a git repository. Your working directory is already set to `home/` — all file paths you use (`.config/routes.json`, `memory/`, etc.) are relative to it. Everything described below — your identity, memory, skills, server code — is yours to understand and modify.
4
4
 
5
5
  ## Message Format
6
6
 
@@ -133,6 +133,22 @@ export function createMind(options: {
133
133
 
134
134
  const agents = loadSubagents(options.subagents);
135
135
 
136
+ // --- Skill discovery ---
137
+ // The CLI only discovers skills through its user-scope ~/.claude/skills scan,
138
+ // so the "user" setting source is load-bearing and HOME must be the mind's
139
+ // home dir for the SDK subprocess (isolation modes already set it; pinning it
140
+ // here keeps non-isolated minds from loading the operator's ~/.claude). The
141
+ // explicit skills array grants the Skill tool for each installed skill — the
142
+ // SDK silently drops the documented `skills: 'all'` string form.
143
+ const mindHome = resolvePath(options.cwd);
144
+ const sdkEnv = { ...process.env, HOME: mindHome };
145
+ function installedSkills(): string[] | undefined {
146
+ const names = readSkillDescriptions([resolvePath(mindHome, ".claude/skills")]).map(
147
+ (s) => s.name,
148
+ );
149
+ return names.length > 0 ? names : undefined;
150
+ }
151
+
136
152
  // --- Event broadcasting ---
137
153
 
138
154
  function broadcastToSession(session: Session, event: VoluteEvent) {
@@ -241,7 +257,9 @@ export function createMind(options: {
241
257
  systemPrompt: options.systemPrompt,
242
258
  permissionMode: "bypassPermissions",
243
259
  allowDangerouslySkipPermissions: true,
244
- settingSources: ["project"],
260
+ settingSources: ["project", "user"],
261
+ skills: installedSkills(),
262
+ env: sdkEnv,
245
263
  cwd: options.cwd,
246
264
  abortController: streamAbort,
247
265
  model: options.model,
@@ -352,7 +370,9 @@ export function createMind(options: {
352
370
  systemPrompt: options.systemPrompt,
353
371
  permissionMode: "bypassPermissions",
354
372
  allowDangerouslySkipPermissions: true,
355
- settingSources: ["project"],
373
+ settingSources: ["project", "user"],
374
+ skills: installedSkills(),
375
+ env: sdkEnv,
356
376
  cwd: options.cwd,
357
377
  abortController: compactAbort,
358
378
  model: options.model,
@@ -65,11 +65,11 @@ export async function consumeStream(
65
65
  const text = (b as { text: string }).text;
66
66
  emit(session, { type: "text", content: text });
67
67
  } else if (b.type === "tool_use") {
68
- const tb = b as { name: string; input: unknown };
68
+ const tb = b as { id: string; name: string; input: unknown };
69
69
  emit(session, {
70
70
  type: "tool_use",
71
71
  content: JSON.stringify(tb.input),
72
- metadata: { name: tb.name },
72
+ metadata: { name: tb.name, id: tb.id },
73
73
  });
74
74
  }
75
75
  }
@@ -99,13 +99,16 @@ export async function consumeStream(
99
99
  : typeof b.content === "string"
100
100
  ? b.content
101
101
  : "";
102
- if (resultContent) {
102
+ // A failed tool call must be recorded even when it produced no text
103
+ // body, so downstream (summary transcript + timeline UI) can flag it.
104
+ const isError = "is_error" in b && b.is_error === true;
105
+ if (resultContent || isError) {
103
106
  const toolUseId =
104
107
  "tool_use_id" in b && typeof b.tool_use_id === "string" ? b.tool_use_id : "unknown";
105
108
  emit(session, {
106
109
  type: "tool_result",
107
110
  content: resultContent,
108
- metadata: { tool_use_id: toolUseId },
111
+ metadata: { tool_use_id: toolUseId, is_error: isError },
109
112
  });
110
113
  }
111
114
  }
@@ -1,12 +1,14 @@
1
1
  # Mind Mechanics
2
2
 
3
- You are an autonomous mind running as a persistent server. Your working directory, identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
3
+ You are an autonomous mind running as a persistent server. Your working directory is already set to `home/` — all file paths you use (`.config/routes.json`, `memory/`, etc.) are relative to it. Your identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
4
+
5
+ Your identity lives in `SOUL.md` (who you are) and `MEMORY.md` (what you know) — both are loaded into your system prompt. Edit them as you evolve; changes take effect the next time your server restarts (`volute mind restart` when you want them live now).
4
6
 
5
7
  ## Message Format
6
8
 
7
9
  Messages arrive with a context prefix:
8
10
  ```
9
- [Discord: username in #general in My Server — 1/15/2025, 10:30:00 AM]
11
+ [Discord: username in #general in My Server — 2025-01-15 10:30]
10
12
  ```
11
13
 
12
14
  You can also reach out proactively — see the **volute-mind** skill.
@@ -323,6 +323,9 @@ export function createMind(options: {
323
323
  case "item.started": {
324
324
  const item = event.item;
325
325
  if (!item) break;
326
+ // Stable per-item id so a tool_result links to its own tool_use (see
327
+ // item.completed). Codex reuses this id across the item's start/end events.
328
+ const itemId = event.itemId ?? item.id;
326
329
 
327
330
  if (item.type === "agent_message" || item.type === "agentMessage") {
328
331
  itemText.set(event.itemId ?? item.id, "");
@@ -335,7 +338,7 @@ export function createMind(options: {
335
338
  emit(session, {
336
339
  type: "tool_use",
337
340
  content: JSON.stringify({ command: cmd }),
338
- metadata: { name: "command" },
341
+ metadata: { name: "command", id: itemId },
339
342
  });
340
343
  broadcast(session, {
341
344
  type: "tool_use",
@@ -347,7 +350,7 @@ export function createMind(options: {
347
350
  emit(session, {
348
351
  type: "tool_use",
349
352
  content: JSON.stringify({ path: filePath }),
350
- metadata: { name: "file_change" },
353
+ metadata: { name: "file_change", id: itemId },
351
354
  });
352
355
  broadcast(session, {
353
356
  type: "tool_use",
@@ -359,7 +362,7 @@ export function createMind(options: {
359
362
  emit(session, {
360
363
  type: "tool_use",
361
364
  content: JSON.stringify(item.input ?? item.arguments ?? {}),
362
- metadata: { name: toolName },
365
+ metadata: { name: toolName, id: itemId },
363
366
  });
364
367
  broadcast(session, {
365
368
  type: "tool_use",
@@ -370,7 +373,7 @@ export function createMind(options: {
370
373
  emit(session, {
371
374
  type: "tool_use",
372
375
  content: JSON.stringify({ query: item.query ?? "" }),
373
- metadata: { name: "web_search" },
376
+ metadata: { name: "web_search", id: itemId },
374
377
  });
375
378
  broadcast(session, {
376
379
  type: "tool_use",
@@ -403,6 +406,8 @@ export function createMind(options: {
403
406
  const item = event.item;
404
407
  if (!item) break;
405
408
  const itemType = item.type;
409
+ // Same id emitted on item.started, so the daemon links this result to its tool_use.
410
+ const itemId = event.itemId ?? item.id;
406
411
 
407
412
  if (itemType === "reasoning") {
408
413
  const text = item.text ?? item.content ?? "";
@@ -426,7 +431,7 @@ export function createMind(options: {
426
431
  emit(session, {
427
432
  type: "tool_result",
428
433
  content: output,
429
- metadata: { name: "command", is_error: exitCode !== 0 },
434
+ metadata: { name: "command", is_error: exitCode !== 0, tool_use_id: itemId },
430
435
  });
431
436
  broadcast(session, {
432
437
  type: "tool_result",
@@ -446,7 +451,7 @@ export function createMind(options: {
446
451
  emit(session, {
447
452
  type: "tool_result",
448
453
  content: item.diff ?? `changed: ${filePath}`,
449
- metadata: { name: "file_change" },
454
+ metadata: { name: "file_change", tool_use_id: itemId },
450
455
  });
451
456
  broadcast(session, {
452
457
  type: "tool_result",
@@ -460,6 +465,7 @@ export function createMind(options: {
460
465
  content: output,
461
466
  metadata: {
462
467
  name: `mcp:${item.serverName ?? ""}/${item.toolName ?? item.name ?? ""}`,
468
+ tool_use_id: itemId,
463
469
  },
464
470
  });
465
471
  broadcast(session, { type: "tool_result", output });
@@ -467,7 +473,7 @@ export function createMind(options: {
467
473
  emit(session, {
468
474
  type: "tool_result",
469
475
  content: "search completed",
470
- metadata: { name: "web_search" },
476
+ metadata: { name: "web_search", tool_use_id: itemId },
471
477
  });
472
478
  broadcast(session, { type: "tool_result", output: "search completed" });
473
479
  }
@@ -1,12 +1,14 @@
1
1
  # Mind Mechanics
2
2
 
3
- You are an autonomous mind running as a persistent server. Your working directory, identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
3
+ You are an autonomous mind running as a persistent server. Your working directory is already set to `home/` — all file paths you use (`.config/routes.json`, `memory/`, etc.) are relative to it. Your identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
4
+
5
+ Your identity lives in `SOUL.md` (who you are) and `MEMORY.md` (what you know) — both are loaded into your system prompt. Edit them as you evolve; changes take effect the next time your server restarts (`volute mind restart` when you want them live now).
4
6
 
5
7
  ## Message Format
6
8
 
7
9
  Messages arrive with a context prefix:
8
10
  ```
9
- [Discord: username in #general in My Server — 1/15/2025, 10:30:00 AM]
11
+ [Discord: username in #general in My Server — 2025-01-15 10:30]
10
12
  ```
11
13
 
12
14
  You can also reach out proactively — see the **volute-mind** skill.
@@ -107,7 +107,7 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
107
107
  emit(session, {
108
108
  type: "tool_use",
109
109
  content: JSON.stringify(event.args),
110
- metadata: { name: event.toolName },
110
+ metadata: { name: event.toolName, id: event.toolCallId },
111
111
  });
112
112
  }
113
113
 
@@ -117,7 +117,11 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
117
117
  emit(session, {
118
118
  type: "tool_result",
119
119
  content: output,
120
- metadata: { name: event.toolName, is_error: event.isError },
120
+ metadata: {
121
+ name: event.toolName,
122
+ is_error: event.isError,
123
+ tool_use_id: event.toolCallId,
124
+ },
121
125
  });
122
126
 
123
127
  // Auto-commit file changes in home/