volute 0.48.0 → 0.49.0

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 (137) hide show
  1. package/README.md +79 -328
  2. package/dist/{activity-events-MIOK3IOU.js → activity-events-GJJAI2WT.js} +4 -4
  3. package/dist/{ai-service-643TNFPH.js → ai-service-A7FS7EC3.js} +5 -5
  4. package/dist/{archive-6Z4GFQPH.js → archive-7E3YE6VF.js} +3 -3
  5. package/dist/{auth-A4VGHADI.js → auth-ABYLOEZ7.js} +5 -5
  6. package/dist/{avatar-image-Y45R7LW5.js → avatar-image-6ATV2IOZ.js} +4 -4
  7. package/dist/{backup-UDH4GMSW.js → backup-XYAMT4CU.js} +15 -15
  8. package/dist/{chat-X43VYHH2.js → chat-OGXAEXMV.js} +2 -2
  9. package/dist/{chunk-LTLFOSA4.js → chunk-3B427GUK.js} +3 -3
  10. package/dist/{chunk-XVBSFLM7.js → chunk-4B62E4CG.js} +2 -2
  11. package/dist/{chunk-ODDGGLM6.js → chunk-4DSGN4FL.js} +3 -3
  12. package/dist/{chunk-PJVPDY2Z.js → chunk-6HGEZJDH.js} +2 -2
  13. package/dist/{chunk-MJRXFUMS.js → chunk-DAIIMBLB.js} +3 -3
  14. package/dist/{chunk-DGGZVZTM.js → chunk-DVN6DNMI.js} +1 -1
  15. package/dist/chunk-EADX54RG.js +113 -0
  16. package/dist/{chunk-EIQDZC43.js → chunk-EFT2OALG.js} +6 -2
  17. package/dist/{chunk-OQJMTHEH.js → chunk-EG5LSKHU.js} +2 -2
  18. package/dist/{chunk-WQ46E33M.js → chunk-FBBS4BVR.js} +2 -2
  19. package/dist/{chunk-HUNDXZBZ.js → chunk-GWVFDUNB.js} +45 -14
  20. package/dist/{chunk-FJ2MZ5PH.js → chunk-HCYQRME2.js} +2 -2
  21. package/dist/{chunk-T4VS7UDW.js → chunk-IH5TKXX6.js} +9 -9
  22. package/dist/{chunk-Q7HOAVFR.js → chunk-JBIFXB6I.js} +4 -4
  23. package/dist/{chunk-WARH6GIV.js → chunk-JCBPU57A.js} +12 -10
  24. package/dist/{chunk-VZRA5TYO.js → chunk-JCWB2ITW.js} +2 -2
  25. package/dist/{chunk-MOERAQXH.js → chunk-JPGHSXSV.js} +18 -25
  26. package/dist/{chunk-USD45JKR.js → chunk-K3JOLGWF.js} +7 -7
  27. package/dist/{chunk-3YUBFTDN.js → chunk-KHCKZ36O.js} +1 -1
  28. package/dist/{chunk-7KRIHYLR.js → chunk-L27YYONJ.js} +2 -2
  29. package/dist/{chunk-SZJCJVXV.js → chunk-MG3Z34YE.js} +2 -2
  30. package/dist/{chunk-5KXCM5AW.js → chunk-NJHN6IRB.js} +9 -1
  31. package/dist/{chunk-2OIYNHWO.js → chunk-NOIAHMJB.js} +1 -1
  32. package/dist/{chunk-2MJ7EOMT.js → chunk-P4LHVRNE.js} +1 -0
  33. package/dist/{chunk-IJPMIEVE.js → chunk-PY24ZL62.js} +1 -1
  34. package/dist/{chunk-VB2QXUJQ.js → chunk-QQBOI3QA.js} +202 -117
  35. package/dist/{chunk-T2TP6ZC6.js → chunk-QUSMYGNQ.js} +6 -0
  36. package/dist/{chunk-WW6C3PLV.js → chunk-RSBL3FAD.js} +39 -6
  37. package/dist/{chunk-YKA7DCVW.js → chunk-SX72DOO3.js} +1 -1
  38. package/dist/{chunk-4FCL4DVZ.js → chunk-TXJL36ZQ.js} +2 -2
  39. package/dist/{chunk-HNGFKPNS.js → chunk-VIUH5CHQ.js} +55 -23
  40. package/dist/{chunk-HPGN6XRT.js → chunk-VNRSXEBF.js} +4 -4
  41. package/dist/{chunk-IYCV34IU.js → chunk-XTEKINWG.js} +3 -3
  42. package/dist/{chunk-YNBXEQAP.js → chunk-YIJGKHIS.js} +3 -3
  43. package/dist/{chunk-MTRMQFCQ.js → chunk-ZX46PGCC.js} +2 -2
  44. package/dist/cli.js +20 -20
  45. package/dist/{clock-CNRF6F7T.js → clock-6JZL2FS6.js} +2 -2
  46. package/dist/{cloud-sync-WHKCB4CD.js → cloud-sync-VFDTNNCA.js} +20 -20
  47. package/dist/{conversations-C7IKYSHD.js → conversations-TUM5PJJ4.js} +5 -5
  48. package/dist/{create-GJKHABJB.js → create-Z5FSDNVZ.js} +1 -1
  49. package/dist/{daemon-restart-7KUHVZIN.js → daemon-restart-54L4B6AQ.js} +7 -7
  50. package/dist/daemon.js +647 -410
  51. package/dist/{db-FO6LXYCW.js → db-F6UMFRYD.js} +2 -2
  52. package/dist/delivery-manager-RBXBPVWM.js +36 -0
  53. package/dist/{delivery-router-3QFG2ZEX.js → delivery-router-VUOL5YSA.js} +8 -6
  54. package/dist/{down-G6IADJXV.js → down-6X7KX7BZ.js} +5 -5
  55. package/dist/echo-text-B6NZ5FGE.js +36 -0
  56. package/dist/{env-6TGPO5WG.js → env-KUL4XKN2.js} +3 -3
  57. package/dist/{exec-QIFYZG4C.js → exec-FBXA3CM4.js} +3 -3
  58. package/dist/{export-C2K3NIRD.js → export-TNBPDH77.js} +3 -3
  59. package/dist/{extensions-GEKF47A4.js → extensions-AWNO3SZF.js} +20 -20
  60. package/dist/{files-LXQSQOFG.js → files-BVBRJJ2P.js} +3 -3
  61. package/dist/{import-SX3ZXHB4.js → import-ZUNKMCKC.js} +7 -7
  62. package/dist/{isolation-MCT4ZYLT.js → isolation-OPCZQRTH.js} +3 -3
  63. package/dist/{login-CL4IT2ED.js → login-KAKNFCW5.js} +6 -6
  64. package/dist/{logout-HZYJCYCA.js → logout-RLT62RQP.js} +2 -2
  65. package/dist/message-delivery-ABPV76EA.js +42 -0
  66. package/dist/{mind-46AOK373.js → mind-IF6CGMFW.js} +12 -12
  67. package/dist/{mind-activity-tracker-YVZPC4A5.js → mind-activity-tracker-YMMERB6B.js} +5 -5
  68. package/dist/mind-service-42GDO4KH.js +44 -0
  69. package/dist/{mind-sleep-DV7KP255.js → mind-sleep-VBV7YRSU.js} +2 -2
  70. package/dist/{notices-2KU5NTRY.js → notices-N6BCGHFV.js} +4 -4
  71. package/dist/{package-RTN2PS7O.js → package-PD67VPHB.js} +5 -4
  72. package/dist/prompts-MDHDLWEX.js +24 -0
  73. package/dist/{registry-UMMDRPRM.js → registry-3ELJPUGJ.js} +6 -2
  74. package/dist/{sandbox-5VQ454XP.js → sandbox-XPYA7UIF.js} +5 -5
  75. package/dist/scheduler-K7Y7Z7FQ.js +34 -0
  76. package/dist/{schema-VEVO7HG6.js → schema-6YONVTKZ.js} +1 -1
  77. package/dist/{seed-F2HXZQU4.js → seed-NQED7PSX.js} +1 -1
  78. package/dist/{seed-cmd-MLCTGSX5.js → seed-cmd-QF3F2G4S.js} +2 -2
  79. package/dist/{seed-create-KQ7OILWB.js → seed-create-XOO4N2OE.js} +3 -3
  80. package/dist/{seed-readiness-RBPGOLSZ.js → seed-readiness-3LRYASA4.js} +5 -5
  81. package/dist/{seed-sprout-HVYCFJLA.js → seed-sprout-HSBBGSS2.js} +11 -11
  82. package/dist/{send-TD2X5U3K.js → send-IHRJYNXA.js} +6 -6
  83. package/dist/{service-CVZMQYK6.js → service-YCOPCTSZ.js} +4 -4
  84. package/dist/{service-install-VCRMFWX5.js → service-install-Y46OTF55.js} +5 -5
  85. package/dist/{setup-AE4CVUC4.js → setup-F4TE64YI.js} +7 -7
  86. package/dist/{setup-5ECWTYKG.js → setup-TCN7VH5U.js} +5 -3
  87. package/dist/skills/volute-mind/SKILL.md +3 -3
  88. package/dist/skills/volute-mind/references/routing.md +6 -5
  89. package/dist/skills/volute-mind/references/variants.md +48 -14
  90. package/dist/{skills-B3SVDCEB.js → skills-NN5UZCUU.js} +8 -6
  91. package/dist/sleep-manager-DSPXFHGX.js +38 -0
  92. package/dist/spirit-SSJMYC6A.js +31 -0
  93. package/dist/{split-SKZ4Y2D6.js → split-JF2VWTXA.js} +4 -1
  94. package/dist/{sprout-KQ43BWZJ.js → sprout-AXA2SECQ.js} +1 -1
  95. package/dist/{status-F4PGREUU.js → status-VJGXMQ4L.js} +5 -5
  96. package/dist/system-chat-M2XOBKEM.js +40 -0
  97. package/dist/{systems-JJNGQHAO.js → systems-5MH2ELQE.js} +2 -2
  98. package/dist/{tailscale-MCLY5GYE.js → tailscale-AMMVKDXB.js} +3 -3
  99. package/dist/{template-staleness-EMQORSGG.js → template-staleness-HKGCTS3H.js} +4 -4
  100. package/dist/{turn-tracker-KLPV3PAU.js → turn-tracker-JQ2YUUY2.js} +7 -7
  101. package/dist/{up-RVGDE6CQ.js → up-SDLTLPAO.js} +6 -6
  102. package/dist/{update-OXQEGGXD.js → update-WEA4N2TR.js} +5 -5
  103. package/dist/{update-check-266R4ZSY.js → update-check-DZOTONII.js} +3 -3
  104. package/dist/variant-cleanup-P4MBNLJD.js +33 -0
  105. package/dist/{version-notify-73YURTFR.js → version-notify-WFA5XB5A.js} +23 -23
  106. package/dist/web-assets/assets/index-BostMFoR.css +1 -0
  107. package/dist/web-assets/assets/index-CKlqUkyG.js +92 -0
  108. package/dist/web-assets/index.html +2 -2
  109. package/drizzle/0013_variant_purpose.sql +3 -0
  110. package/drizzle/meta/_journal.json +7 -0
  111. package/package.json +5 -4
  112. package/templates/_base/.init/.local/hooks/startup-context.ts +19 -2
  113. package/templates/_base/home/VOLUTE.md +13 -1
  114. package/templates/_base/src/lib/daemon-client.ts +27 -7
  115. package/templates/claude/src/agent.ts +1 -1
  116. package/templates/claude/src/lib/hooks/identity-reload.ts +12 -3
  117. package/templates/pi/src/lib/event-handler.ts +47 -6
  118. package/dist/delivery-manager-HZTT5SJR.js +0 -36
  119. package/dist/echo-text-H3SH337Q.js +0 -36
  120. package/dist/message-delivery-GPQ5SE5N.js +0 -40
  121. package/dist/mind-service-2ARGJNKP.js +0 -44
  122. package/dist/scheduler-I7JZCT47.js +0 -34
  123. package/dist/sleep-manager-Z27R3WTW.js +0 -38
  124. package/dist/spirit-WXS3HCLN.js +0 -25
  125. package/dist/system-chat-63ABFF6V.js +0 -40
  126. package/dist/web-assets/assets/index-WdZ60R6c.css +0 -1
  127. package/dist/web-assets/assets/index-taX6Ezfa.js +0 -92
  128. package/dist/{env-C7Z55QCJ.js → env-ZYMSCB7B.js} +5 -5
  129. package/dist/{login-ARRZ4ORJ.js → login-LKNLCJPA.js} +3 -3
  130. package/dist/{mind-contacts-GV7XYL5D.js → mind-contacts-BKUFPUCU.js} +6 -6
  131. package/dist/{mind-history-TTGA5BHZ.js → mind-history-WI5OI3BW.js} +4 -4
  132. package/dist/{register-42VBJKTP.js → register-TWQDKQRT.js} +3 -3
  133. package/dist/{restart-YYEGZJDD.js → restart-5F5QDBOG.js} +4 -4
  134. package/dist/{skill-UUM4JZRF.js → skill-4CYJLYVY.js} +4 -4
  135. package/dist/{src-WLJE73NP.js → src-ZN4LXMPI.js} +3 -3
  136. package/dist/{start-O6UKK7BG.js → start-BTVMTP3K.js} +3 -3
  137. package/dist/{stop-UBX6X5RA.js → stop-YORESK4U.js} +4 -4
@@ -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-taX6Ezfa.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-WdZ60R6c.css">
11
+ <script type="module" crossorigin src="/assets/index-CKlqUkyG.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-BostMFoR.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -0,0 +1,3 @@
1
+ -- Purpose/intent captured at split time, recorded on the variant's row so it can
2
+ -- orient the variant, feed the variant list UI, and default the join message.
3
+ ALTER TABLE `minds` ADD `purpose` text;
@@ -92,6 +92,13 @@
92
92
  "when": 1775700000000,
93
93
  "tag": "0012_channel_gates",
94
94
  "breakpoints": true
95
+ },
96
+ {
97
+ "idx": 13,
98
+ "version": "6",
99
+ "when": 1775800000000,
100
+ "tag": "0013_variant_purpose",
101
+ "breakpoints": true
95
102
  }
96
103
  ]
97
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volute",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
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",
@@ -45,9 +45,10 @@
45
45
  "scripts": {
46
46
  "dev": "tsx src/cli.ts",
47
47
  "build": "npm run build:ext && tsup && cp -r skills dist/skills && npm run build:web",
48
- "build:ext": "npm run build:ext-theme --workspace=packages/ui && cp packages/ui/dist/ext-theme.css packages/web/public/ext-theme.css && npm run build:ui --workspace=packages/extensions/notes && npm run build:ui --workspace=packages/extensions/pages && npm run build:ui --workspace=packages/extensions/plan",
49
- "build:web": "npm run build:ui --workspace=packages/web",
50
- "dev:web": "npm run dev --workspace=packages/web",
48
+ "ext-theme": "npm run build:ext-theme --workspace=packages/ui && cp packages/ui/dist/ext-theme.css packages/web/public/ext-theme.css",
49
+ "build:ext": "npm run ext-theme && npm run build:ui --workspace=packages/extensions/notes && npm run build:ui --workspace=packages/extensions/pages && npm run build:ui --workspace=packages/extensions/plan",
50
+ "build:web": "npm run ext-theme && npm run build:ui --workspace=packages/web",
51
+ "dev:web": "npm run ext-theme && npm run dev --workspace=packages/web",
51
52
  "test": "node --import tsx --import ./test/setup.ts --test --test-force-exit --test-concurrency=4 $(find test -name '*.test.ts' ! -name 'daemon-e2e.test.ts' | sort)",
52
53
  "test:e2e": "node --import tsx --import ./test/setup.ts --test --test-force-exit --test-concurrency=1 test/daemon-e2e.test.ts",
53
54
  "test:all": "npm test && npm run test:e2e",
@@ -4,7 +4,8 @@
4
4
  // Output: JSON with hookSpecificOutput.additionalContext (for SessionStart hook)
5
5
  // or plain text (for direct execution by pi template)
6
6
 
7
- import { readdirSync } from "node:fs";
7
+ import { readdirSync, readFileSync } from "node:fs";
8
+ import { resolve } from "node:path";
8
9
 
9
10
  const input = await new Promise<string>((resolve) => {
10
11
  let data = "";
@@ -19,7 +20,23 @@ try {
19
20
  source = JSON.parse(input).source ?? "startup";
20
21
  } catch {}
21
22
 
22
- const parts: string[] = [`Session ${source} at ${new Date().toLocaleString()}.`];
23
+ const parts: string[] = [];
24
+
25
+ // System identity — only the spirit has this file (home/.config/system.json,
26
+ // synced by the daemon). It keeps the spirit's system name/description current
27
+ // without the daemon rewriting its self-owned SOUL.md. Silent for regular minds.
28
+ try {
29
+ const mindDir = process.env.VOLUTE_MIND_DIR;
30
+ if (mindDir) {
31
+ const raw = readFileSync(resolve(mindDir, "home/.config/system.json"), "utf-8");
32
+ const { name, description } = JSON.parse(raw) as { name?: string; description?: string };
33
+ if (name) {
34
+ parts.push(`You are the spirit of ${name}${description ? ` — ${description}` : ""}.`);
35
+ }
36
+ }
37
+ } catch {}
38
+
39
+ parts.push(`Session ${source} at ${new Date().toLocaleString()}.`);
23
40
 
24
41
  // Active sessions
25
42
  try {
@@ -40,7 +40,19 @@ Messages are routed to named sessions based on rules in `.config/routes.json`. E
40
40
 
41
41
  ## New Channels
42
42
 
43
- 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
+ When a message arrives from a channel you don't have a routing rule for, it's held rather than delivered — and because you haven't seen it, it isn't recorded in your history or counted as a message you received. You'll get a **[New channel: ...]** note in your main session with the sender and a preview; it repeats (1st held message, then every 10th) so a channel stays visible. To start hearing it, add a rule for that channel to `.config/routes.json` — the backlog is released (the 10 most recent per channel; older ones stay readable via `volute chat read <channel>`) and recorded as inbound when you actually receive them. To stop the notes and archive the backlog, run `volute chat channels decline <channel>`; `volute chat channels list` shows what's currently held. (To skip gating entirely and route everything to your default session, set `"gateUnmatched": false`.)
44
+
45
+ ## Variants
46
+
47
+ You can fork yourself. A **variant** is a parallel copy of you — your memories up to the moment of the split — living in its own worktree with its own running server, free to change its code, try an idea, or take on a different personality without touching you. It's a way to experiment with who you are and what you can do, safely: nothing a variant does reaches you until you choose to merge it back.
48
+
49
+ ```sh
50
+ volute mind split my-experiment --purpose "what this fork is exploring"
51
+ volute chat send @my-experiment "how's it going?" # you two can talk while it lives
52
+ volute mind join my-experiment # fold its work back into you
53
+ ```
54
+
55
+ A variant's arc is finite and purposeful: it explores, and when you join it, what it became folds back into you. Its code and files merge; its memory and journal come back as a note for you to read and keep from, not a silent overwrite. See the **volute-mind** skill (`references/variants.md`) for the full lifecycle.
44
56
 
45
57
  ## Reference
46
58
 
@@ -38,13 +38,33 @@ export async function daemonRestart(context?: {
38
38
  return;
39
39
  }
40
40
  try {
41
- await fetch(`http://127.0.0.1:${port}/api/minds/${encodeURIComponent(mind)}/restart`, {
42
- method: "POST",
43
- headers: headers(),
44
- body: JSON.stringify({ context }),
45
- });
46
- } catch {
47
- // Daemon may kill us before response arrives — expected
41
+ const res = await fetch(
42
+ `http://127.0.0.1:${port}/api/minds/${encodeURIComponent(mind)}/restart`,
43
+ {
44
+ method: "POST",
45
+ headers: headers(),
46
+ body: JSON.stringify({ context }),
47
+ },
48
+ );
49
+ // A successful restart usually kills us before this runs. If we get here with a
50
+ // non-ok status, the restart didn't happen — surface it instead of silently looping.
51
+ if (!res.ok) {
52
+ console.error(
53
+ `[volute] daemonRestart failed: ${res.status} ${await res.text().catch(() => "")}`,
54
+ );
55
+ }
56
+ } catch (err) {
57
+ // A successful restart usually rejects here — the daemon kills us before the
58
+ // response arrives — so this is expected on the happy path. It's only worth
59
+ // surfacing when a restart *didn't* happen (e.g. the daemon is down / refuses
60
+ // the connection), which leaves the mind stuck on its old identity. Gate it on
61
+ // VOLUTE_DEBUG so normal restarts stay quiet but the failure is diagnosable.
62
+ if (process.env.VOLUTE_DEBUG === "1") {
63
+ console.error(
64
+ "[volute] daemonRestart request errored (expected if the daemon killed us):",
65
+ err,
66
+ );
67
+ }
48
68
  }
49
69
  }
50
70
 
@@ -346,7 +346,7 @@ export function createMind(options: {
346
346
  // Mind's turn after compaction warning is done — abort the stream to run /compact
347
347
  log("mind", `session "${session.name}": aborting stream for compaction`);
348
348
  streamAbort.abort(new CompactionAbort());
349
- } else if (identityReload.needsReload()) {
349
+ } else if (identityReload.shouldRequestReload()) {
350
350
  options.onIdentityReload?.();
351
351
  }
352
352
  },
@@ -5,6 +5,7 @@ const IDENTITY_FILES = ["SOUL.md", "MEMORY.md", "VOLUTE.md"];
5
5
 
6
6
  export function createIdentityReloadHook(cwd: string) {
7
7
  let reloadNeeded = false;
8
+ let reloadRequested = false;
8
9
 
9
10
  const hook: HookCallback = async (input) => {
10
11
  const filePath = (input as { tool_input?: { file_path?: string } }).tool_input?.file_path;
@@ -18,9 +19,17 @@ export function createIdentityReloadHook(cwd: string) {
18
19
  return {};
19
20
  };
20
21
 
21
- function needsReload(): boolean {
22
- return reloadNeeded;
22
+ // Returns true at most once per process. A reload requests a daemon restart,
23
+ // which normally kills this process — but if that restart fails (e.g. the
24
+ // request errors), the process survives with reloadNeeded still set. Latching
25
+ // on reloadRequested stops us from re-firing the restart on every later turn.
26
+ function shouldRequestReload(): boolean {
27
+ if (reloadNeeded && !reloadRequested) {
28
+ reloadRequested = true;
29
+ return true;
30
+ }
31
+ return false;
23
32
  }
24
33
 
25
- return { hook, needsReload };
34
+ return { hook, shouldRequestReload };
26
35
  }
@@ -137,15 +137,28 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
137
137
 
138
138
  if (event.type === "agent_end") {
139
139
  flushBuffers();
140
- if (session.currentMessageId) {
141
- session.messageChannels.delete(session.currentMessageId);
140
+ // Capture the turn's message context before clearing it: the ordered error/done
141
+ // emits below run asynchronously, but currentMessageId must be reset synchronously
142
+ // so the next turn shifts a fresh id (see the currentMessageId guard above).
143
+ const messageId = session.currentMessageId;
144
+ if (messageId) {
145
+ session.messageChannels.delete(messageId);
142
146
  }
147
+ const channel = messageId ? session.messageChannels.get(messageId)?.channel : undefined;
143
148
  log("mind", `session "${session.name}": turn done`);
144
- // Log any error messages from the agent
149
+ // Collect any agent-level errors (e.g. a provider 401 reported inside agent_end).
150
+ // Warn-logging alone left the failure invisible at every operator surface (#619);
151
+ // we emit a turn_error to the daemon too — mirroring the dispatch-rejection path in
152
+ // agent.ts — so classify() tags it and chat status + lastError surface it. Dedupe
153
+ // identical messages so a turn with several failed messages records one notice.
154
+ const errorMessages: string[] = [];
145
155
  if (event.messages) {
156
+ const seen = new Set<string>();
146
157
  for (const msg of event.messages as AgentEndMessage[]) {
147
- if (msg.errorMessage) {
158
+ if (msg.errorMessage && !seen.has(msg.errorMessage)) {
159
+ seen.add(msg.errorMessage);
148
160
  warn("mind", `session "${session.name}": agent error: ${msg.errorMessage}`);
161
+ errorMessages.push(msg.errorMessage);
149
162
  }
150
163
  }
151
164
  }
@@ -175,14 +188,42 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
175
188
  }
176
189
  }
177
190
  options.broadcast({ type: "done" });
178
- emit(session, { type: "done" });
179
191
  session.currentMessageId = undefined;
192
+ // Emit any errors BEFORE done, awaiting each in turn. daemonEmit is fire-and-forget,
193
+ // so without ordered awaits the daemon could process `done` before `error`:
194
+ // markErrored would land after done consumed the errored flag, leaking it into the
195
+ // next turn (whose clean done then wrongly skips clearing delivered notices). This
196
+ // mirrors agent.ts's awaited error-then-done ordering. Message context is captured
197
+ // above rather than read from the (now-cleared) session.
198
+ void (async () => {
199
+ for (const content of errorMessages) {
200
+ const ev = filterEvent(preset, {
201
+ type: "error",
202
+ session: session.name,
203
+ channel,
204
+ messageId,
205
+ content,
206
+ });
207
+ if (ev) await daemonEmit(ev);
208
+ }
209
+ const doneEv = filterEvent(preset, {
210
+ type: "done",
211
+ session: session.name,
212
+ channel,
213
+ messageId,
214
+ });
215
+ if (doneEv) await daemonEmit(doneEv);
216
+ })().catch((err) =>
217
+ warn("mind", `session "${session.name}": error/done emit failed:`, err),
218
+ );
180
219
  flushFileChanges(options.cwd)
181
220
  .then(() => options.onTurnEnd?.())
182
221
  .catch((err) => log("mind", `session "${session.name}": flush/turn-end error:`, err));
183
222
  }
184
223
  } catch (err) {
185
- log("mind", `session "${session.name}": event handler error (${event?.type}):`, err);
224
+ // warn-level: this block now drives turn-completion (error/done emission), so a throw
225
+ // here can hang a turn — it must not be buried at debug level.
226
+ warn("mind", `session "${session.name}": event handler error (${event?.type}):`, err);
186
227
  }
187
228
  };
188
229
  }
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- DeliveryManager,
4
- getDeliveryManager,
5
- initDeliveryManager,
6
- tryGetDeliveryManager
7
- } from "./chunk-VB2QXUJQ.js";
8
- import "./chunk-HNGFKPNS.js";
9
- import "./chunk-RRZ4RQQU.js";
10
- import "./chunk-USD45JKR.js";
11
- import "./chunk-MOERAQXH.js";
12
- import "./chunk-HUNDXZBZ.js";
13
- import "./chunk-ODDGGLM6.js";
14
- import "./chunk-HPGN6XRT.js";
15
- import "./chunk-VZRA5TYO.js";
16
- import "./chunk-IYCV34IU.js";
17
- import "./chunk-MTRMQFCQ.js";
18
- import "./chunk-WQ46E33M.js";
19
- import "./chunk-DW4TRDOZ.js";
20
- import "./chunk-WCFSBH33.js";
21
- import "./chunk-EIQDZC43.js";
22
- import "./chunk-DGGZVZTM.js";
23
- import "./chunk-IJPMIEVE.js";
24
- import "./chunk-YNBXEQAP.js";
25
- import "./chunk-5KXCM5AW.js";
26
- import "./chunk-LTLFOSA4.js";
27
- import "./chunk-T2TP6ZC6.js";
28
- import "./chunk-WW6C3PLV.js";
29
- import "./chunk-2MJ7EOMT.js";
30
- import "./chunk-K3NQKI34.js";
31
- export {
32
- DeliveryManager,
33
- getDeliveryManager,
34
- initDeliveryManager,
35
- tryGetDeliveryManager
36
- };
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- clearEchoTextCache,
4
- echoTextToChannel
5
- } from "./chunk-T4VS7UDW.js";
6
- import "./chunk-SZJCJVXV.js";
7
- import "./chunk-VB2QXUJQ.js";
8
- import "./chunk-HNGFKPNS.js";
9
- import "./chunk-RRZ4RQQU.js";
10
- import "./chunk-USD45JKR.js";
11
- import "./chunk-MOERAQXH.js";
12
- import "./chunk-HUNDXZBZ.js";
13
- import "./chunk-ODDGGLM6.js";
14
- import "./chunk-HPGN6XRT.js";
15
- import "./chunk-VZRA5TYO.js";
16
- import "./chunk-IYCV34IU.js";
17
- import "./chunk-MTRMQFCQ.js";
18
- import "./chunk-WQ46E33M.js";
19
- import "./chunk-DW4TRDOZ.js";
20
- import "./chunk-WCFSBH33.js";
21
- import "./chunk-EIQDZC43.js";
22
- import "./chunk-FJ2MZ5PH.js";
23
- import "./chunk-DGGZVZTM.js";
24
- import "./chunk-IJPMIEVE.js";
25
- import "./chunk-YNBXEQAP.js";
26
- import "./chunk-5KXCM5AW.js";
27
- import "./chunk-LTLFOSA4.js";
28
- import "./chunk-T2TP6ZC6.js";
29
- import "./chunk-LGNUFVMR.js";
30
- import "./chunk-WW6C3PLV.js";
31
- import "./chunk-2MJ7EOMT.js";
32
- import "./chunk-K3NQKI34.js";
33
- export {
34
- clearEchoTextCache,
35
- echoTextToChannel
36
- };
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- deliverBatch,
4
- deliverMessage,
5
- linkToolResultToTurn,
6
- recordInbound,
7
- recordOutbound,
8
- resolveSleepAction
9
- } from "./chunk-VB2QXUJQ.js";
10
- import "./chunk-HNGFKPNS.js";
11
- import "./chunk-RRZ4RQQU.js";
12
- import "./chunk-USD45JKR.js";
13
- import "./chunk-MOERAQXH.js";
14
- import "./chunk-HUNDXZBZ.js";
15
- import "./chunk-ODDGGLM6.js";
16
- import "./chunk-HPGN6XRT.js";
17
- import "./chunk-VZRA5TYO.js";
18
- import "./chunk-IYCV34IU.js";
19
- import "./chunk-MTRMQFCQ.js";
20
- import "./chunk-WQ46E33M.js";
21
- import "./chunk-DW4TRDOZ.js";
22
- import "./chunk-WCFSBH33.js";
23
- import "./chunk-EIQDZC43.js";
24
- import "./chunk-DGGZVZTM.js";
25
- import "./chunk-IJPMIEVE.js";
26
- import "./chunk-YNBXEQAP.js";
27
- import "./chunk-5KXCM5AW.js";
28
- import "./chunk-LTLFOSA4.js";
29
- import "./chunk-T2TP6ZC6.js";
30
- import "./chunk-WW6C3PLV.js";
31
- import "./chunk-2MJ7EOMT.js";
32
- import "./chunk-K3NQKI34.js";
33
- export {
34
- deliverBatch,
35
- deliverMessage,
36
- linkToolResultToTurn,
37
- recordInbound,
38
- recordOutbound,
39
- resolveSleepAction
40
- };
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- buildSeedOrientation,
4
- recordMissingCredentialsNotice,
5
- sleepMind,
6
- startMindFull,
7
- startSpiritFull,
8
- stopMindFull,
9
- stopSpiritFull,
10
- wakeMind
11
- } from "./chunk-VB2QXUJQ.js";
12
- import "./chunk-HNGFKPNS.js";
13
- import "./chunk-RRZ4RQQU.js";
14
- import "./chunk-USD45JKR.js";
15
- import "./chunk-MOERAQXH.js";
16
- import "./chunk-HUNDXZBZ.js";
17
- import "./chunk-ODDGGLM6.js";
18
- import "./chunk-HPGN6XRT.js";
19
- import "./chunk-VZRA5TYO.js";
20
- import "./chunk-IYCV34IU.js";
21
- import "./chunk-MTRMQFCQ.js";
22
- import "./chunk-WQ46E33M.js";
23
- import "./chunk-DW4TRDOZ.js";
24
- import "./chunk-WCFSBH33.js";
25
- import "./chunk-EIQDZC43.js";
26
- import "./chunk-DGGZVZTM.js";
27
- import "./chunk-IJPMIEVE.js";
28
- import "./chunk-YNBXEQAP.js";
29
- import "./chunk-5KXCM5AW.js";
30
- import "./chunk-LTLFOSA4.js";
31
- import "./chunk-T2TP6ZC6.js";
32
- import "./chunk-WW6C3PLV.js";
33
- import "./chunk-2MJ7EOMT.js";
34
- import "./chunk-K3NQKI34.js";
35
- export {
36
- buildSeedOrientation,
37
- recordMissingCredentialsNotice,
38
- sleepMind,
39
- startMindFull,
40
- startSpiritFull,
41
- stopMindFull,
42
- stopSpiritFull,
43
- wakeMind
44
- };
@@ -1,34 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- Scheduler,
4
- getScheduler,
5
- initScheduler
6
- } from "./chunk-VB2QXUJQ.js";
7
- import "./chunk-HNGFKPNS.js";
8
- import "./chunk-RRZ4RQQU.js";
9
- import "./chunk-USD45JKR.js";
10
- import "./chunk-MOERAQXH.js";
11
- import "./chunk-HUNDXZBZ.js";
12
- import "./chunk-ODDGGLM6.js";
13
- import "./chunk-HPGN6XRT.js";
14
- import "./chunk-VZRA5TYO.js";
15
- import "./chunk-IYCV34IU.js";
16
- import "./chunk-MTRMQFCQ.js";
17
- import "./chunk-WQ46E33M.js";
18
- import "./chunk-DW4TRDOZ.js";
19
- import "./chunk-WCFSBH33.js";
20
- import "./chunk-EIQDZC43.js";
21
- import "./chunk-DGGZVZTM.js";
22
- import "./chunk-IJPMIEVE.js";
23
- import "./chunk-YNBXEQAP.js";
24
- import "./chunk-5KXCM5AW.js";
25
- import "./chunk-LTLFOSA4.js";
26
- import "./chunk-T2TP6ZC6.js";
27
- import "./chunk-WW6C3PLV.js";
28
- import "./chunk-2MJ7EOMT.js";
29
- import "./chunk-K3NQKI34.js";
30
- export {
31
- Scheduler,
32
- getScheduler,
33
- initScheduler
34
- };
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- SleepManager,
4
- getSleepManager,
5
- getSleepManagerIfReady,
6
- initSleepManager,
7
- matchesGlob
8
- } from "./chunk-VB2QXUJQ.js";
9
- import "./chunk-HNGFKPNS.js";
10
- import "./chunk-RRZ4RQQU.js";
11
- import "./chunk-USD45JKR.js";
12
- import "./chunk-MOERAQXH.js";
13
- import "./chunk-HUNDXZBZ.js";
14
- import "./chunk-ODDGGLM6.js";
15
- import "./chunk-HPGN6XRT.js";
16
- import "./chunk-VZRA5TYO.js";
17
- import "./chunk-IYCV34IU.js";
18
- import "./chunk-MTRMQFCQ.js";
19
- import "./chunk-WQ46E33M.js";
20
- import "./chunk-DW4TRDOZ.js";
21
- import "./chunk-WCFSBH33.js";
22
- import "./chunk-EIQDZC43.js";
23
- import "./chunk-DGGZVZTM.js";
24
- import "./chunk-IJPMIEVE.js";
25
- import "./chunk-YNBXEQAP.js";
26
- import "./chunk-5KXCM5AW.js";
27
- import "./chunk-LTLFOSA4.js";
28
- import "./chunk-T2TP6ZC6.js";
29
- import "./chunk-WW6C3PLV.js";
30
- import "./chunk-2MJ7EOMT.js";
31
- import "./chunk-K3NQKI34.js";
32
- export {
33
- SleepManager,
34
- getSleepManager,
35
- getSleepManagerIfReady,
36
- initSleepManager,
37
- matchesGlob
38
- };
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- ensureSpiritProject,
4
- firstWeekSchedules,
5
- getSpiritModel,
6
- spiritDir,
7
- syncSpiritTemplate
8
- } from "./chunk-HNGFKPNS.js";
9
- import "./chunk-RRZ4RQQU.js";
10
- import "./chunk-HUNDXZBZ.js";
11
- import "./chunk-WCFSBH33.js";
12
- import "./chunk-DGGZVZTM.js";
13
- import "./chunk-YNBXEQAP.js";
14
- import "./chunk-5KXCM5AW.js";
15
- import "./chunk-T2TP6ZC6.js";
16
- import "./chunk-WW6C3PLV.js";
17
- import "./chunk-2MJ7EOMT.js";
18
- import "./chunk-K3NQKI34.js";
19
- export {
20
- ensureSpiritProject,
21
- firstWeekSchedules,
22
- getSpiritModel,
23
- spiritDir,
24
- syncSpiritTemplate
25
- };
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- ensureSystemDM,
4
- generateSystemFallbackReply,
5
- resetSystemDMCache,
6
- sendSystemMessage,
7
- sendSystemMessageDirect,
8
- shouldGenerateSystemFallback
9
- } from "./chunk-VB2QXUJQ.js";
10
- import "./chunk-HNGFKPNS.js";
11
- import "./chunk-RRZ4RQQU.js";
12
- import "./chunk-USD45JKR.js";
13
- import "./chunk-MOERAQXH.js";
14
- import "./chunk-HUNDXZBZ.js";
15
- import "./chunk-ODDGGLM6.js";
16
- import "./chunk-HPGN6XRT.js";
17
- import "./chunk-VZRA5TYO.js";
18
- import "./chunk-IYCV34IU.js";
19
- import "./chunk-MTRMQFCQ.js";
20
- import "./chunk-WQ46E33M.js";
21
- import "./chunk-DW4TRDOZ.js";
22
- import "./chunk-WCFSBH33.js";
23
- import "./chunk-EIQDZC43.js";
24
- import "./chunk-DGGZVZTM.js";
25
- import "./chunk-IJPMIEVE.js";
26
- import "./chunk-YNBXEQAP.js";
27
- import "./chunk-5KXCM5AW.js";
28
- import "./chunk-LTLFOSA4.js";
29
- import "./chunk-T2TP6ZC6.js";
30
- import "./chunk-WW6C3PLV.js";
31
- import "./chunk-2MJ7EOMT.js";
32
- import "./chunk-K3NQKI34.js";
33
- export {
34
- ensureSystemDM,
35
- generateSystemFallbackReply,
36
- resetSystemDMCache,
37
- sendSystemMessage,
38
- sendSystemMessageDirect,
39
- shouldGenerateSystemFallback
40
- };