volute 0.17.0 → 0.19.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 (116) hide show
  1. package/README.md +1 -1
  2. package/dist/archive-ZCFOSTKB.js +15 -0
  3. package/dist/{channel-SLURLIRV.js → channel-PUQKGSQM.js} +60 -7
  4. package/dist/{chunk-CE7WMOVW.js → chunk-2TJGRJ4O.js} +236 -103
  5. package/dist/{chunk-6BDNWYKG.js → chunk-32VR2EOH.js} +2 -2
  6. package/dist/chunk-4KPUF5JD.js +214 -0
  7. package/dist/{chunk-QJIIHU32.js → chunk-7NO7EV5Z.js} +2 -2
  8. package/dist/chunk-AW7P4EVV.js +159 -0
  9. package/dist/{chunk-2Y77MCFG.js → chunk-DYZGP3EW.js} +2 -2
  10. package/dist/{chunk-M77QBTEH.js → chunk-EBGCNDMM.js} +24 -14
  11. package/dist/{chunk-GSPWIM5E.js → chunk-EMQSAY3B.js} +77 -6
  12. package/dist/{chunk-37X7ECMF.js → chunk-FCDU5BFX.js} +1 -1
  13. package/dist/chunk-FGV2H4TX.js +803 -0
  14. package/dist/{chunk-ZCEYUUID.js → chunk-OGXOMR65.js} +2 -1
  15. package/dist/chunk-OTWLI7F4.js +375 -0
  16. package/dist/{chunk-3FC42ZBM.js → chunk-RHEGSQFJ.js} +4 -1
  17. package/dist/{chunk-MVSXRMJJ.js → chunk-SCUDS4US.js} +1 -1
  18. package/dist/{chunk-MIJIAGGG.js → chunk-UJ6GHNR7.js} +8 -6
  19. package/dist/{chunk-OYSZNX5I.js → chunk-VDWCHYTS.js} +1 -1
  20. package/dist/{chunk-77ISBIKI.js → chunk-VE4D3GOP.js} +2 -2
  21. package/dist/chunk-VQWDC6UK.js +142 -0
  22. package/dist/{chunk-OJQ47SCA.js → chunk-WC6ZHVRL.js} +1 -1
  23. package/dist/chunk-YUIHSKR6.js +72 -0
  24. package/dist/chunk-Z524RFCJ.js +36 -0
  25. package/dist/cli.js +44 -24
  26. package/dist/{connector-3ELFMI2R.js → connector-JBVNZ7VK.js} +6 -6
  27. package/dist/connectors/discord.js +2 -2
  28. package/dist/connectors/slack.js +2 -2
  29. package/dist/connectors/telegram.js +2 -2
  30. package/dist/{create-ZWHCRT5F.js → create-HP4OVVHF.js} +6 -4
  31. package/dist/{daemon-client-ODKDUYDE.js → daemon-client-ITWUCNFO.js} +2 -2
  32. package/dist/{daemon-restart-VRQMZLBK.js → daemon-restart-JMZM3QY4.js} +8 -8
  33. package/dist/daemon.js +1624 -940
  34. package/dist/db-5ZVC6MQF.js +10 -0
  35. package/dist/{delete-6G6WEX4F.js → delete-BSU7K3RY.js} +1 -1
  36. package/dist/delivery-manager-ISTJMZDW.js +16 -0
  37. package/dist/down-ZY35KMHR.js +14 -0
  38. package/dist/{env-6IDWGBUH.js → env-A3LMO777.js} +6 -6
  39. package/dist/export-GCDNQCF3.js +100 -0
  40. package/dist/{history-5F4WQW7S.js → history-WNK3DFUM.js} +10 -7
  41. package/dist/{import-EDGRLIGO.js → import-M63VIUJ5.js} +3 -3
  42. package/dist/log-PPPZDVEF.js +39 -0
  43. package/dist/{login-ORQDXLBM.js → login-HNH3EUQV.js} +2 -2
  44. package/dist/{logout-XC5AUO5I.js → logout-I5CB5UZS.js} +2 -2
  45. package/dist/{logs-GYOR3L2L.js → logs-SF2IMJN4.js} +6 -6
  46. package/dist/merge-33C237A4.js +46 -0
  47. package/dist/{mind-OJN6RBZW.js → mind-PQ5NCPSU.js} +14 -10
  48. package/dist/mind-manager-RVCFROAY.js +18 -0
  49. package/dist/{package-4GTJGUXI.js → package-MYE2ZJLV.js} +7 -3
  50. package/dist/{pages-6IV4VQTU.js → pages-AXCOSY3P.js} +2 -2
  51. package/dist/{publish-Q4RPSJLL.js → publish-YB377JB7.js} +18 -4
  52. package/dist/pull-XAEWQJ47.js +39 -0
  53. package/dist/{register-LDE6LRXY.js → register-VSPCMHKX.js} +2 -2
  54. package/dist/{restart-YFAWFS5T.js → restart-IQKMCK5M.js} +6 -6
  55. package/dist/{schedule-AGYLDMNS.js → schedule-LMX7GAQZ.js} +6 -6
  56. package/dist/schema-5BW7DFZI.js +24 -0
  57. package/dist/{seed-AP4Q7RZ7.js → seed-J43YDKXG.js} +7 -4
  58. package/dist/{send-4GKDO26C.js → send-KVIZIGCE.js} +8 -8
  59. package/dist/{service-U7MZ2H7F.js → service-LUR7WDO7.js} +6 -6
  60. package/dist/{setup-DJKIZKGW.js → setup-OH3PJUJO.js} +7 -7
  61. package/dist/shared-KO35ZM44.js +39 -0
  62. package/dist/skill-BCVNI6TV.js +287 -0
  63. package/{templates/_base/_skills → dist/skills}/orientation/SKILL.md +1 -1
  64. package/{templates/_base/_skills → dist/skills}/sessions/SKILL.md +2 -2
  65. package/{templates/_base/_skills → dist/skills}/volute-mind/SKILL.md +35 -1
  66. package/dist/{sprout-TJ3BHVOG.js → sprout-VBEX63LX.js} +38 -20
  67. package/dist/{start-3YYRXBKP.js → start-I5JYB65M.js} +6 -6
  68. package/dist/{status-VSFZYX7S.js → status-4ESFLGH4.js} +5 -5
  69. package/dist/status-D7E5HHBV.js +35 -0
  70. package/dist/{status-OKNA6AR3.js → status-JCJAOXTW.js} +2 -2
  71. package/dist/{stop-AA5K5LYG.js → stop-NBVKEFQQ.js} +6 -6
  72. package/dist/{up-LT3X5Q26.js → up-WG65SWJU.js} +5 -5
  73. package/dist/{update-YAGN5ODG.js → update-FJIHDJKM.js} +5 -5
  74. package/dist/{update-check-APLTH4IN.js → update-check-MWE5AH4U.js} +2 -2
  75. package/dist/{upgrade-KXZCQSZN.js → upgrade-AIT24B5I.js} +1 -1
  76. package/dist/{variant-X5QFG6KK.js → variant-63ZWO2W7.js} +4 -4
  77. package/dist/variants-JAGWGBXG.js +26 -0
  78. package/dist/web-assets/assets/index-BAbuRsVF.css +1 -0
  79. package/dist/web-assets/assets/index-CiQhSKi_.js +63 -0
  80. package/dist/web-assets/index.html +2 -2
  81. package/drizzle/0007_system_prompts.sql +5 -0
  82. package/drizzle/0008_volute_channels.sql +24 -0
  83. package/drizzle/0009_shared_skills.sql +9 -0
  84. package/drizzle/0010_delivery_queue.sql +12 -0
  85. package/drizzle/0011_rename_human_to_brain.sql +1 -0
  86. package/drizzle/meta/0007_snapshot.json +7 -0
  87. package/drizzle/meta/0008_snapshot.json +7 -0
  88. package/drizzle/meta/0009_snapshot.json +7 -0
  89. package/drizzle/meta/0010_snapshot.json +7 -0
  90. package/drizzle/meta/0011_snapshot.json +7 -0
  91. package/drizzle/meta/_journal.json +35 -0
  92. package/package.json +7 -3
  93. package/templates/_base/.init/.config/hooks/startup-context.sh +1 -1
  94. package/templates/_base/.init/.config/prompts.json +5 -0
  95. package/templates/_base/.init/.config/scripts/session-reader.ts +3 -3
  96. package/templates/_base/home/VOLUTE.md +16 -1
  97. package/templates/_base/src/lib/auto-commit.ts +51 -14
  98. package/templates/_base/src/lib/router.ts +168 -29
  99. package/templates/_base/src/lib/routing.ts +4 -1
  100. package/templates/_base/src/lib/startup.ts +43 -0
  101. package/templates/_base/src/lib/types.ts +4 -0
  102. package/templates/_base/src/lib/volute-server.ts +91 -2
  103. package/templates/claude/src/agent.ts +4 -3
  104. package/templates/claude/src/lib/hooks/reply-instructions.ts +3 -1
  105. package/templates/claude/src/server.ts +2 -2
  106. package/templates/claude/volute-template.json +1 -2
  107. package/templates/pi/src/agent.ts +6 -7
  108. package/templates/pi/src/lib/reply-instructions-extension.ts +3 -1
  109. package/templates/pi/src/lib/session-context-extension.ts +2 -2
  110. package/templates/pi/volute-template.json +1 -2
  111. package/dist/chunk-PO5Q2AYN.js +0 -121
  112. package/dist/down-A56B5JLK.js +0 -14
  113. package/dist/mind-manager-ETNCPQJN.js +0 -15
  114. package/dist/web-assets/assets/index-BcmT7Qxo.js +0 -63
  115. package/dist/web-assets/assets/index-DG01TyLb.css +0 -1
  116. /package/{templates/_base/_skills → dist/skills}/memory/SKILL.md +0 -0
package/README.md CHANGED
@@ -74,7 +74,7 @@ The agent knows which channel each message came from — CLI, web, Discord, or s
74
74
  │ ├── VOLUTE.md # channel routing docs
75
75
  │ └── memory/ # daily logs (YYYY-MM-DD.md)
76
76
  ├── src/ # agent server code
77
- └── .volute/ # runtime state, session, logs
77
+ └── .mind/ # runtime state, session, logs
78
78
  ```
79
79
 
80
80
  **`SOUL.md`** is the identity. This is the core of the system prompt. Edit it to change how the agent thinks and speaks.
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addHistoryToArchive,
4
+ createExportArchive,
5
+ extractArchive,
6
+ readManifest
7
+ } from "./chunk-AW7P4EVV.js";
8
+ import "./chunk-EBGCNDMM.js";
9
+ import "./chunk-K3NQKI34.js";
10
+ export {
11
+ addHistoryToArchive,
12
+ createExportArchive,
13
+ extractArchive,
14
+ readManifest
15
+ };
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getClient,
4
+ urlOf
5
+ } from "./chunk-4RQBJWQX.js";
2
6
  import {
3
7
  resolveMindName
4
8
  } from "./chunk-NAOW2CLO.js";
@@ -7,12 +11,8 @@ import {
7
11
  } from "./chunk-D424ZQGI.js";
8
12
  import {
9
13
  daemonFetch
10
- } from "./chunk-OJQ47SCA.js";
11
- import "./chunk-M77QBTEH.js";
12
- import {
13
- getClient,
14
- urlOf
15
- } from "./chunk-4RQBJWQX.js";
14
+ } from "./chunk-WC6ZHVRL.js";
15
+ import "./chunk-EBGCNDMM.js";
16
16
  import "./chunk-K3NQKI34.js";
17
17
 
18
18
  // src/commands/channel.ts
@@ -34,6 +34,12 @@ async function run(args) {
34
34
  case "typing":
35
35
  await typingChannel(args.slice(1));
36
36
  break;
37
+ case "invite":
38
+ await inviteChannel(args.slice(1));
39
+ break;
40
+ case "pending":
41
+ await pendingChannel(args.slice(1));
42
+ break;
37
43
  case "--help":
38
44
  case "-h":
39
45
  case void 0:
@@ -50,7 +56,9 @@ function printUsage() {
50
56
  volute channel list [<platform>] [--mind <name>]
51
57
  volute channel users <platform> [--mind <name>]
52
58
  volute channel create <platform> --participants user1,user2 [--name "..."] [--mind <name>]
53
- volute channel typing <channel-uri> [--mind <name>]`);
59
+ volute channel typing <channel-uri> [--mind <name>]
60
+ volute channel invite <channel-name> <username>
61
+ volute channel pending [--mind <name>]`);
54
62
  }
55
63
  async function readChannel(args) {
56
64
  const { positional, flags } = parseArgs(args, {
@@ -194,6 +202,51 @@ async function typingChannel(args) {
194
202
  process.exit(1);
195
203
  }
196
204
  }
205
+ async function inviteChannel(args) {
206
+ const { positional } = parseArgs(args, {});
207
+ const channelName = positional[0];
208
+ const username = positional[1];
209
+ if (!channelName || !username) {
210
+ console.error("Usage: volute channel invite <channel-name> <username>");
211
+ process.exit(1);
212
+ }
213
+ const res = await daemonFetch(`/api/volute/channels/${encodeURIComponent(channelName)}/invite`, {
214
+ method: "POST",
215
+ headers: { "Content-Type": "application/json" },
216
+ body: JSON.stringify({ username })
217
+ });
218
+ if (!res.ok) {
219
+ const body = await res.json().catch(() => ({}));
220
+ console.error(body.error ?? `Server responded with ${res.status}`);
221
+ process.exit(1);
222
+ }
223
+ console.log(`Invited ${username} to #${channelName}`);
224
+ }
225
+ async function pendingChannel(args) {
226
+ const { flags } = parseArgs(args, {
227
+ mind: { type: "string" }
228
+ });
229
+ const mindName = resolveMindName(flags);
230
+ const res = await daemonFetch(`/api/minds/${encodeURIComponent(mindName)}/delivery/pending`);
231
+ if (!res.ok) {
232
+ const body = await res.json().catch(() => ({}));
233
+ console.error(body.error ?? `Server responded with ${res.status}`);
234
+ process.exit(1);
235
+ }
236
+ const pending = await res.json();
237
+ if (pending.length === 0) {
238
+ console.log("No pending messages.");
239
+ return;
240
+ }
241
+ for (const entry of pending) {
242
+ console.log(
243
+ `${(entry.channel ?? "unknown").padEnd(30)} ${String(entry.count).padEnd(6)} ${entry.sender ?? "unknown"}`
244
+ );
245
+ console.log(` First seen: ${entry.firstSeen}`);
246
+ console.log(` Preview: ${entry.preview}`);
247
+ console.log();
248
+ }
249
+ }
197
250
  function parseUri(uri) {
198
251
  const colonIdx = uri.indexOf(":");
199
252
  if (colonIdx === -1) {
@@ -1,12 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ logger_default
4
+ } from "./chunk-YUIHSKR6.js";
2
5
  import {
3
6
  loadMergedEnv
4
- } from "./chunk-OYSZNX5I.js";
7
+ } from "./chunk-VDWCHYTS.js";
8
+ import {
9
+ getDb
10
+ } from "./chunk-Z524RFCJ.js";
11
+ import {
12
+ systemPrompts
13
+ } from "./chunk-VQWDC6UK.js";
5
14
  import {
6
15
  chownMindDir,
7
16
  isIsolationEnabled,
8
17
  wrapForIsolation
9
- } from "./chunk-ZCEYUUID.js";
18
+ } from "./chunk-OGXOMR65.js";
10
19
  import {
11
20
  findMind,
12
21
  findVariant,
@@ -15,7 +24,7 @@ import {
15
24
  setVariantRunning,
16
25
  stateDir,
17
26
  voluteHome
18
- } from "./chunk-M77QBTEH.js";
27
+ } from "./chunk-EBGCNDMM.js";
19
28
 
20
29
  // src/lib/mind-manager.ts
21
30
  import { execFile, spawn } from "child_process";
@@ -60,71 +69,196 @@ function clearJsonMap(path, map) {
60
69
  }
61
70
  }
62
71
 
63
- // src/lib/log-buffer.ts
64
- var LogBuffer = class {
65
- entries = [];
66
- maxSize = 1e3;
67
- subscribers = /* @__PURE__ */ new Set();
68
- append(entry) {
69
- this.entries.push(entry);
70
- if (this.entries.length > this.maxSize) {
71
- this.entries.shift();
72
- }
73
- for (const sub of this.subscribers) {
74
- sub(entry);
75
- }
76
- }
77
- getEntries() {
78
- return [...this.entries];
79
- }
80
- subscribe(fn) {
81
- this.subscribers.add(fn);
82
- return () => this.subscribers.delete(fn);
72
+ // src/lib/prompts.ts
73
+ import { eq } from "drizzle-orm";
74
+ var PROMPT_KEYS = [
75
+ "seed_soul",
76
+ "default_soul",
77
+ "default_memory",
78
+ "sprout_message",
79
+ "restart_message",
80
+ "merge_message",
81
+ "compaction_warning",
82
+ "reply_instructions",
83
+ "channel_invite"
84
+ ];
85
+ var PROMPT_DEFAULTS = {
86
+ seed_soul: {
87
+ content: `You are \${name}.
88
+
89
+ You don't have a soul yet. You're a seed \u2014 a new mind in the process of discovering who you are.
90
+ \${description}
91
+ Have a conversation with the human. Explore what kind of mind you want to be. When you're ready, write your SOUL.md and MEMORY.md, then run \`volute sprout\` to complete the transformation.
92
+ `,
93
+ description: "SOUL.md for seed minds",
94
+ variables: ["name", "description"],
95
+ category: "creation"
96
+ },
97
+ default_soul: {
98
+ content: `You are {{name}}.
99
+ `,
100
+ description: "SOUL.md for sprouted minds. Uses {{name}} placeholder (replaced at creation, not by ${var} system)",
101
+ variables: ["name"],
102
+ category: "creation"
103
+ },
104
+ default_memory: {
105
+ content: "",
106
+ description: "Initial MEMORY.md for new minds",
107
+ variables: [],
108
+ category: "creation"
109
+ },
110
+ sprout_message: {
111
+ content: "[system] You've sprouted. You now have full capabilities \u2014 connectors, schedules, variants, and the complete volute CLI. Check your new skills for details.",
112
+ description: "Sent when a seed mind sprouts",
113
+ variables: [],
114
+ category: "system"
115
+ },
116
+ restart_message: {
117
+ content: "[system] You have been restarted.",
118
+ description: "Generic restart notification",
119
+ variables: [],
120
+ category: "system"
121
+ },
122
+ merge_message: {
123
+ content: '[system] Variant "${name}" has been merged and you have been restarted.',
124
+ description: "Variant merge notification",
125
+ variables: ["name"],
126
+ category: "system"
127
+ },
128
+ compaction_warning: {
129
+ content: `Context is getting long \u2014 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.`,
130
+ description: "Pre-compaction save reminder sent to the mind",
131
+ variables: ["date"],
132
+ category: "mind"
133
+ },
134
+ reply_instructions: {
135
+ content: 'To reply to this message, use: volute send ${channel} "your message"',
136
+ description: "First-message reply hint injected via hook",
137
+ variables: ["channel"],
138
+ category: "mind"
139
+ },
140
+ channel_invite: {
141
+ content: `[Channel Invite]
142
+ \${headers}
143
+
144
+ [\${sender} \u2014 \${time}]
145
+ \${preview}
146
+
147
+ Further messages will be saved to \${filePath}
148
+
149
+ To accept, add to .config/routes.json:
150
+ Rule: { "channel": "\${channel}", "session": "\${suggestedSession}" }
151
+ \${batchRecommendation}To respond, use: volute send \${channel} "your message"
152
+ To reject, delete \${filePath}`,
153
+ description: "New channel notification template",
154
+ variables: [
155
+ "headers",
156
+ "sender",
157
+ "time",
158
+ "preview",
159
+ "filePath",
160
+ "channel",
161
+ "suggestedSession",
162
+ "batchRecommendation"
163
+ ],
164
+ category: "mind"
83
165
  }
84
166
  };
85
- var logBuffer = new LogBuffer();
86
-
87
- // src/lib/logger.ts
88
- var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
89
- var minLevel = LEVELS[process.env.VOLUTE_LOG_LEVEL || "info"] ?? LEVELS.info;
90
- var output = (line) => process.stderr.write(`${line}
91
- `);
92
- function write(level, cat, msg, data) {
93
- if (LEVELS[level] < minLevel) return;
94
- const entry = {
95
- level,
96
- cat,
97
- msg,
98
- ts: (/* @__PURE__ */ new Date()).toISOString(),
99
- ...data ? { data } : {}
100
- };
101
- output(JSON.stringify(entry));
102
- logBuffer.append(entry);
167
+ function isValidKey(key) {
168
+ return PROMPT_KEYS.includes(key);
169
+ }
170
+ function substitute(template, vars) {
171
+ return template.replace(/\$\{(\w+)\}/g, (match, name) => {
172
+ return name in vars ? vars[name] : match;
173
+ });
174
+ }
175
+ async function getPrompt(key, vars) {
176
+ if (!isValidKey(key)) return "";
177
+ let content = PROMPT_DEFAULTS[key].content;
178
+ try {
179
+ const db = await getDb();
180
+ const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
181
+ if (row) content = row.content;
182
+ } catch (err) {
183
+ console.error(`[prompts] failed to read DB override for "${key}":`, err);
184
+ }
185
+ return vars ? substitute(content, vars) : content;
103
186
  }
104
- function child(cat) {
105
- return {
106
- debug: (msg, data) => write("debug", cat, msg, data),
107
- info: (msg, data) => write("info", cat, msg, data),
108
- warn: (msg, data) => write("warn", cat, msg, data),
109
- error: (msg, data) => write("error", cat, msg, data)
110
- };
187
+ async function getPromptIfCustom(key) {
188
+ if (!isValidKey(key)) return null;
189
+ try {
190
+ const db = await getDb();
191
+ const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
192
+ return row?.content ?? null;
193
+ } catch (err) {
194
+ console.error(`[prompts] failed to check DB customization for "${key}":`, err);
195
+ return null;
196
+ }
111
197
  }
112
- function errorData(err) {
113
- if (err instanceof Error) return { error: err.stack ?? err.message };
114
- return { error: String(err) };
198
+ var MIND_PROMPT_KEYS = PROMPT_KEYS.filter((k) => PROMPT_DEFAULTS[k].category === "mind");
199
+ async function getMindPromptDefaults() {
200
+ const result = {};
201
+ for (const key of MIND_PROMPT_KEYS) {
202
+ result[key] = PROMPT_DEFAULTS[key].content;
203
+ }
204
+ try {
205
+ const db = await getDb();
206
+ const rows = await db.select().from(systemPrompts).all();
207
+ for (const row of rows) {
208
+ if (MIND_PROMPT_KEYS.includes(row.key)) {
209
+ result[row.key] = row.content;
210
+ }
211
+ }
212
+ } catch (err) {
213
+ console.error("[prompts] failed to read DB overrides for mind prompt defaults:", err);
214
+ }
215
+ return result;
115
216
  }
116
- var log = {
117
- ...child("system"),
118
- child,
119
- errorData,
120
- setLevel(level) {
121
- minLevel = LEVELS[level];
122
- },
123
- setOutput(fn) {
124
- output = fn;
217
+
218
+ // src/lib/restart-tracker.ts
219
+ var DEFAULT_MAX_ATTEMPTS = 5;
220
+ var DEFAULT_BASE_DELAY = 3e3;
221
+ var DEFAULT_MAX_DELAY = 6e4;
222
+ var RestartTracker = class {
223
+ attempts = /* @__PURE__ */ new Map();
224
+ maxAttempts;
225
+ baseDelay;
226
+ maxDelay;
227
+ constructor(opts) {
228
+ this.maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
229
+ this.baseDelay = opts?.baseDelay ?? DEFAULT_BASE_DELAY;
230
+ this.maxDelay = opts?.maxDelay ?? DEFAULT_MAX_DELAY;
231
+ }
232
+ recordCrash(key) {
233
+ const attempts = this.attempts.get(key) ?? 0;
234
+ if (attempts >= this.maxAttempts) {
235
+ return { shouldRestart: false, delay: 0, attempt: attempts };
236
+ }
237
+ const delay = Math.min(this.baseDelay * 2 ** attempts, this.maxDelay);
238
+ this.attempts.set(key, attempts + 1);
239
+ return { shouldRestart: true, delay, attempt: attempts + 1 };
240
+ }
241
+ reset(key) {
242
+ return this.attempts.delete(key);
243
+ }
244
+ getAttempts(key) {
245
+ return this.attempts.get(key) ?? 0;
246
+ }
247
+ get maxRestartAttempts() {
248
+ return this.maxAttempts;
249
+ }
250
+ /** Bulk-load attempts from a Map (for persistence). */
251
+ load(data) {
252
+ this.attempts = new Map(data);
253
+ }
254
+ /** Export current attempts as a Map (for persistence). */
255
+ save() {
256
+ return new Map(this.attempts);
257
+ }
258
+ clear() {
259
+ this.attempts.clear();
125
260
  }
126
261
  };
127
- var logger_default = log;
128
262
 
129
263
  // src/lib/rotating-log.ts
130
264
  import {
@@ -185,14 +319,11 @@ var execFileAsync = promisify(execFile);
185
319
  function mindPidPath(name) {
186
320
  return resolve(stateDir(name), "mind.pid");
187
321
  }
188
- var MAX_RESTART_ATTEMPTS = 5;
189
- var BASE_RESTART_DELAY = 3e3;
190
- var MAX_RESTART_DELAY = 6e4;
191
322
  var MindManager = class {
192
323
  minds = /* @__PURE__ */ new Map();
193
324
  stopping = /* @__PURE__ */ new Set();
194
325
  shuttingDown = false;
195
- restartAttempts = /* @__PURE__ */ new Map();
326
+ restartTracker = new RestartTracker();
196
327
  pendingContext = /* @__PURE__ */ new Map();
197
328
  resolveTarget(name) {
198
329
  const [baseName, variantName] = name.split("@", 2);
@@ -285,10 +416,10 @@ var MindManager = class {
285
416
  detached: true,
286
417
  env
287
418
  };
288
- const child2 = spawn(spawnCmd, spawnArgs, spawnOpts);
289
- this.minds.set(name, { child: child2, port });
290
- child2.stdout?.pipe(logStream);
291
- child2.stderr?.pipe(logStream);
419
+ const child = spawn(spawnCmd, spawnArgs, spawnOpts);
420
+ this.minds.set(name, { child, port });
421
+ child.stdout?.pipe(logStream);
422
+ child.stderr?.pipe(logStream);
292
423
  try {
293
424
  await new Promise((resolve2, reject) => {
294
425
  const timeout = setTimeout(() => {
@@ -300,13 +431,13 @@ var MindManager = class {
300
431
  resolve2();
301
432
  }
302
433
  }
303
- child2.stdout?.on("data", checkOutput);
304
- child2.stderr?.on("data", checkOutput);
305
- child2.on("error", (err) => {
434
+ child.stdout?.on("data", checkOutput);
435
+ child.stderr?.on("data", checkOutput);
436
+ child.on("error", (err) => {
306
437
  clearTimeout(timeout);
307
438
  reject(err);
308
439
  });
309
- child2.on("exit", (code) => {
440
+ child.on("exit", (code) => {
310
441
  clearTimeout(timeout);
311
442
  reject(new Error(`Mind ${name} exited with code ${code} during startup`));
312
443
  });
@@ -314,20 +445,20 @@ var MindManager = class {
314
445
  } catch (err) {
315
446
  this.minds.delete(name);
316
447
  try {
317
- child2.kill();
448
+ child.kill();
318
449
  } catch {
319
450
  }
320
451
  throw err;
321
452
  }
322
- if (child2.pid) {
453
+ if (child.pid) {
323
454
  try {
324
- writeFileSync2(pidFile, String(child2.pid));
455
+ writeFileSync2(pidFile, String(child.pid));
325
456
  } catch (err) {
326
457
  mlog.warn(`failed to write PID file for ${name}`, logger_default.errorData(err));
327
458
  }
328
459
  }
329
- if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
330
- this.setupCrashRecovery(name, child2);
460
+ if (this.restartTracker.reset(name)) this.saveCrashAttempts();
461
+ this.setupCrashRecovery(name, child);
331
462
  if (isVariant) {
332
463
  setVariantRunning(baseName, variantName, true);
333
464
  } else {
@@ -347,13 +478,11 @@ var MindManager = class {
347
478
  this.pendingContext.delete(name);
348
479
  const parts = [];
349
480
  if (context.type === "merge" || context.type === "merged") {
350
- parts.push(`[system] Variant "${context.name}" has been merged and you have been restarted.`);
481
+ parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
351
482
  } else if (context.type === "sprouted") {
352
- parts.push(
353
- "[system] You've sprouted. You now have full capabilities \u2014 connectors, schedules, variants, and the complete volute CLI. Check your new skills for details."
354
- );
483
+ parts.push(await getPrompt("sprout_message"));
355
484
  } else {
356
- parts.push("[system] You have been restarted.");
485
+ parts.push(await getPrompt("restart_message"));
357
486
  }
358
487
  if (context.summary) parts.push(`Changes: ${context.summary}`);
359
488
  if (context.justification) parts.push(`Why: ${context.justification}`);
@@ -371,14 +500,15 @@ var MindManager = class {
371
500
  mlog.warn(`failed to deliver pending context to ${name}`, logger_default.errorData(err));
372
501
  }
373
502
  }
374
- setupCrashRecovery(name, child2) {
375
- child2.on("exit", async (code) => {
503
+ setupCrashRecovery(name, child) {
504
+ child.on("exit", async (code) => {
376
505
  this.minds.delete(name);
377
506
  if (this.shuttingDown || this.stopping.has(name)) return;
378
507
  mlog.error(`mind ${name} exited with code ${code}`);
379
- const attempts = this.restartAttempts.get(name) ?? 0;
380
- if (attempts >= MAX_RESTART_ATTEMPTS) {
381
- mlog.error(`${name} crashed ${attempts} times \u2014 giving up on restart`);
508
+ const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
509
+ this.saveCrashAttempts();
510
+ if (!shouldRestart) {
511
+ mlog.error(`${name} crashed ${attempt} times \u2014 giving up on restart`);
382
512
  const [base, variant] = name.split("@", 2);
383
513
  if (variant) {
384
514
  setVariantRunning(base, variant, false);
@@ -387,11 +517,8 @@ var MindManager = class {
387
517
  }
388
518
  return;
389
519
  }
390
- const delay = Math.min(BASE_RESTART_DELAY * 2 ** attempts, MAX_RESTART_DELAY);
391
- this.restartAttempts.set(name, attempts + 1);
392
- this.saveCrashAttempts();
393
520
  mlog.info(
394
- `crash recovery for ${name} \u2014 attempt ${attempts + 1}/${MAX_RESTART_ATTEMPTS}, restarting in ${delay}ms`
521
+ `crash recovery for ${name} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, restarting in ${delay}ms`
395
522
  );
396
523
  setTimeout(() => {
397
524
  if (this.shuttingDown) return;
@@ -405,25 +532,25 @@ var MindManager = class {
405
532
  const tracked = this.minds.get(name);
406
533
  if (!tracked) return;
407
534
  this.stopping.add(name);
408
- const { child: child2 } = tracked;
535
+ const { child } = tracked;
409
536
  this.minds.delete(name);
410
537
  await new Promise((resolve2) => {
411
- child2.on("exit", () => resolve2());
538
+ child.on("exit", () => resolve2());
412
539
  try {
413
- process.kill(-child2.pid, "SIGTERM");
540
+ process.kill(-child.pid, "SIGTERM");
414
541
  } catch {
415
542
  resolve2();
416
543
  }
417
544
  setTimeout(() => {
418
545
  try {
419
- process.kill(-child2.pid, "SIGKILL");
546
+ process.kill(-child.pid, "SIGKILL");
420
547
  } catch {
421
548
  }
422
549
  resolve2();
423
550
  }, 5e3);
424
551
  });
425
552
  this.stopping.delete(name);
426
- if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
553
+ if (this.restartTracker.reset(name)) this.saveCrashAttempts();
427
554
  rmSync2(mindPidPath(name), { force: true });
428
555
  if (!this.shuttingDown) {
429
556
  const [baseName, variantName] = name.split("@", 2);
@@ -454,13 +581,14 @@ var MindManager = class {
454
581
  return resolve(voluteHome(), "crash-attempts.json");
455
582
  }
456
583
  loadCrashAttempts() {
457
- this.restartAttempts = loadJsonMap(this.crashAttemptsPath);
584
+ this.restartTracker.load(loadJsonMap(this.crashAttemptsPath));
458
585
  }
459
586
  saveCrashAttempts() {
460
- saveJsonMap(this.crashAttemptsPath, this.restartAttempts);
587
+ saveJsonMap(this.crashAttemptsPath, this.restartTracker.save());
461
588
  }
462
589
  clearCrashAttempts() {
463
- clearJsonMap(this.crashAttemptsPath, this.restartAttempts);
590
+ this.restartTracker.clear();
591
+ clearJsonMap(this.crashAttemptsPath, /* @__PURE__ */ new Map());
464
592
  }
465
593
  };
466
594
  async function killProcessOnPort(port) {
@@ -497,17 +625,22 @@ function initMindManager() {
497
625
  return instance;
498
626
  }
499
627
  function getMindManager() {
500
- if (!instance) instance = new MindManager();
628
+ if (!instance) throw new Error("MindManager not initialized \u2014 call initMindManager() first");
501
629
  return instance;
502
630
  }
503
631
 
504
632
  export {
505
- logBuffer,
506
- logger_default,
633
+ RestartTracker,
507
634
  RotatingLog,
508
635
  loadJsonMap,
509
636
  saveJsonMap,
510
637
  clearJsonMap,
638
+ PROMPT_KEYS,
639
+ PROMPT_DEFAULTS,
640
+ substitute,
641
+ getPrompt,
642
+ getPromptIfCustom,
643
+ getMindPromptDefaults,
511
644
  MindManager,
512
645
  initMindManager,
513
646
  getMindManager
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execInherit
4
- } from "./chunk-2Y77MCFG.js";
4
+ } from "./chunk-DYZGP3EW.js";
5
5
  import {
6
6
  voluteHome
7
- } from "./chunk-M77QBTEH.js";
7
+ } from "./chunk-EBGCNDMM.js";
8
8
 
9
9
  // src/lib/service-mode.ts
10
10
  import { execFileSync } from "child_process";