volute 0.18.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 (104) 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-AYB7XAWO.js → chunk-2TJGRJ4O.js} +114 -279
  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-GK4E7LM7.js → chunk-RHEGSQFJ.js} +1 -1
  17. package/dist/{chunk-MVSXRMJJ.js → chunk-SCUDS4US.js} +1 -1
  18. package/dist/{chunk-FW5API7X.js → chunk-UJ6GHNR7.js} +2 -2
  19. package/dist/{chunk-OYSZNX5I.js → chunk-VDWCHYTS.js} +1 -1
  20. package/dist/{chunk-6DVBMLVN.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 +33 -25
  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-2HVTHZAT.js → daemon-restart-JMZM3QY4.js} +8 -8
  33. package/dist/daemon.js +1144 -1108
  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-YUEKTJ2N.js → history-WNK3DFUM.js} +6 -6
  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-OKLFO7UY.js → package-MYE2ZJLV.js} +5 -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-BNDTLUPM.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-2Y42P4JY.js → skill-BCVNI6TV.js} +6 -6
  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 +19 -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-7B3BWF2U.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/0010_delivery_queue.sql +12 -0
  82. package/drizzle/0011_rename_human_to_brain.sql +1 -0
  83. package/drizzle/meta/0010_snapshot.json +7 -0
  84. package/drizzle/meta/0011_snapshot.json +7 -0
  85. package/drizzle/meta/_journal.json +14 -0
  86. package/package.json +5 -3
  87. package/templates/_base/.init/.config/hooks/startup-context.sh +1 -1
  88. package/templates/_base/.init/.config/scripts/session-reader.ts +3 -3
  89. package/templates/_base/home/VOLUTE.md +16 -1
  90. package/templates/_base/src/lib/auto-commit.ts +51 -14
  91. package/templates/_base/src/lib/router.ts +123 -1
  92. package/templates/_base/src/lib/types.ts +4 -0
  93. package/templates/_base/src/lib/volute-server.ts +91 -2
  94. package/templates/claude/src/server.ts +2 -2
  95. package/templates/claude/volute-template.json +1 -2
  96. package/templates/pi/src/agent.ts +1 -1
  97. package/templates/pi/src/lib/session-context-extension.ts +2 -2
  98. package/templates/pi/volute-template.json +1 -2
  99. package/dist/chunk-PO5Q2AYN.js +0 -121
  100. package/dist/down-A56B5JLK.js +0 -14
  101. package/dist/mind-manager-Z7O7PN2O.js +0 -15
  102. package/dist/web-assets/assets/index-CtiimdWK.css +0 -1
  103. package/dist/web-assets/assets/index-kt1_EcuO.js +0 -63
  104. /package/{templates/_base/_skills → dist/skills}/memory/SKILL.md +0 -0
@@ -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,15 +24,12 @@ import {
15
24
  setVariantRunning,
16
25
  stateDir,
17
26
  voluteHome
18
- } from "./chunk-M77QBTEH.js";
19
- import {
20
- __export
21
- } from "./chunk-K3NQKI34.js";
27
+ } from "./chunk-EBGCNDMM.js";
22
28
 
23
29
  // src/lib/mind-manager.ts
24
30
  import { execFile, spawn } from "child_process";
25
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
26
- import { resolve as resolve2 } from "path";
31
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
32
+ import { resolve } from "path";
27
33
  import { promisify } from "util";
28
34
 
29
35
  // src/lib/json-state.ts
@@ -63,210 +69,8 @@ function clearJsonMap(path, map) {
63
69
  }
64
70
  }
65
71
 
66
- // src/lib/log-buffer.ts
67
- var LogBuffer = class {
68
- entries = [];
69
- maxSize = 1e3;
70
- subscribers = /* @__PURE__ */ new Set();
71
- append(entry) {
72
- this.entries.push(entry);
73
- if (this.entries.length > this.maxSize) {
74
- this.entries.shift();
75
- }
76
- for (const sub of this.subscribers) {
77
- sub(entry);
78
- }
79
- }
80
- getEntries() {
81
- return [...this.entries];
82
- }
83
- subscribe(fn) {
84
- this.subscribers.add(fn);
85
- return () => this.subscribers.delete(fn);
86
- }
87
- };
88
- var logBuffer = new LogBuffer();
89
-
90
- // src/lib/logger.ts
91
- var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
92
- var minLevel = LEVELS[process.env.VOLUTE_LOG_LEVEL || "info"] ?? LEVELS.info;
93
- var output = (line) => process.stderr.write(`${line}
94
- `);
95
- function write(level, cat, msg, data) {
96
- if (LEVELS[level] < minLevel) return;
97
- const entry = {
98
- level,
99
- cat,
100
- msg,
101
- ts: (/* @__PURE__ */ new Date()).toISOString(),
102
- ...data ? { data } : {}
103
- };
104
- output(JSON.stringify(entry));
105
- logBuffer.append(entry);
106
- }
107
- function child(cat) {
108
- return {
109
- debug: (msg, data) => write("debug", cat, msg, data),
110
- info: (msg, data) => write("info", cat, msg, data),
111
- warn: (msg, data) => write("warn", cat, msg, data),
112
- error: (msg, data) => write("error", cat, msg, data)
113
- };
114
- }
115
- function errorData(err) {
116
- if (err instanceof Error) return { error: err.stack ?? err.message };
117
- return { error: String(err) };
118
- }
119
- var log = {
120
- ...child("system"),
121
- child,
122
- errorData,
123
- setLevel(level) {
124
- minLevel = LEVELS[level];
125
- },
126
- setOutput(fn) {
127
- output = fn;
128
- }
129
- };
130
- var logger_default = log;
131
-
132
72
  // src/lib/prompts.ts
133
73
  import { eq } from "drizzle-orm";
134
-
135
- // src/lib/db.ts
136
- import { chmodSync, existsSync as existsSync2 } from "fs";
137
- import { dirname, resolve } from "path";
138
- import { fileURLToPath } from "url";
139
- import { drizzle } from "drizzle-orm/libsql";
140
- import { migrate } from "drizzle-orm/libsql/migrator";
141
-
142
- // src/lib/schema.ts
143
- var schema_exports = {};
144
- __export(schema_exports, {
145
- conversationParticipants: () => conversationParticipants,
146
- conversations: () => conversations,
147
- messages: () => messages,
148
- mindHistory: () => mindHistory,
149
- sessions: () => sessions,
150
- sharedSkills: () => sharedSkills,
151
- systemPrompts: () => systemPrompts,
152
- users: () => users
153
- });
154
- import { sql } from "drizzle-orm";
155
- import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
156
- var users = sqliteTable("users", {
157
- id: integer("id").primaryKey({ autoIncrement: true }),
158
- username: text("username").unique().notNull(),
159
- password_hash: text("password_hash").notNull(),
160
- role: text("role").notNull().default("pending"),
161
- user_type: text("user_type").notNull().default("human"),
162
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
163
- });
164
- var conversations = sqliteTable(
165
- "conversations",
166
- {
167
- id: text("id").primaryKey(),
168
- mind_name: text("mind_name"),
169
- channel: text("channel").notNull(),
170
- type: text("type").notNull().default("dm"),
171
- name: text("name"),
172
- user_id: integer("user_id").references(() => users.id),
173
- title: text("title"),
174
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
175
- updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
176
- },
177
- (table) => [
178
- index("idx_conversations_mind_name").on(table.mind_name),
179
- index("idx_conversations_user_id").on(table.user_id),
180
- index("idx_conversations_updated_at").on(table.updated_at),
181
- uniqueIndex("idx_conversations_name").on(table.name)
182
- ]
183
- );
184
- var mindHistory = sqliteTable(
185
- "mind_history",
186
- {
187
- id: integer("id").primaryKey({ autoIncrement: true }),
188
- mind: text("mind").notNull(),
189
- channel: text("channel"),
190
- session: text("session"),
191
- sender: text("sender"),
192
- message_id: text("message_id"),
193
- type: text("type").notNull(),
194
- content: text("content"),
195
- metadata: text("metadata"),
196
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
197
- },
198
- (table) => [
199
- index("idx_mind_history_mind").on(table.mind),
200
- index("idx_mind_history_mind_channel").on(table.mind, table.channel),
201
- index("idx_mind_history_mind_type").on(table.mind, table.type)
202
- ]
203
- );
204
- var conversationParticipants = sqliteTable(
205
- "conversation_participants",
206
- {
207
- conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
208
- user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
209
- role: text("role").notNull().default("member"),
210
- joined_at: text("joined_at").notNull().default(sql`(datetime('now'))`)
211
- },
212
- (table) => [
213
- uniqueIndex("idx_cp_unique").on(table.conversation_id, table.user_id),
214
- index("idx_cp_user_id").on(table.user_id)
215
- ]
216
- );
217
- var sessions = sqliteTable("sessions", {
218
- id: text("id").primaryKey(),
219
- userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
220
- createdAt: integer("created_at").notNull()
221
- });
222
- var systemPrompts = sqliteTable("system_prompts", {
223
- key: text("key").primaryKey(),
224
- content: text("content").notNull(),
225
- updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
226
- });
227
- var sharedSkills = sqliteTable("shared_skills", {
228
- id: text("id").primaryKey(),
229
- name: text("name").notNull(),
230
- description: text("description").notNull().default(""),
231
- author: text("author").notNull(),
232
- version: integer("version").notNull().default(1),
233
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
234
- updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
235
- });
236
- var messages = sqliteTable(
237
- "messages",
238
- {
239
- id: integer("id").primaryKey({ autoIncrement: true }),
240
- conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
241
- role: text("role").notNull(),
242
- sender_name: text("sender_name"),
243
- content: text("content").notNull(),
244
- created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
245
- },
246
- (table) => [index("idx_messages_conversation_id").on(table.conversation_id)]
247
- );
248
-
249
- // src/lib/db.ts
250
- var __dirname = dirname(fileURLToPath(import.meta.url));
251
- var migrationsFolder = existsSync2(resolve(__dirname, "../drizzle")) ? resolve(__dirname, "../drizzle") : resolve(__dirname, "../../drizzle");
252
- var db = null;
253
- async function getDb() {
254
- if (db) return db;
255
- const dbPath = process.env.VOLUTE_DB_PATH || resolve(voluteHome(), "volute.db");
256
- db = drizzle({ connection: { url: `file:${dbPath}` }, schema: schema_exports });
257
- await migrate(db, { migrationsFolder });
258
- try {
259
- chmodSync(dbPath, 384);
260
- } catch (err) {
261
- console.error(
262
- `[volute] WARNING: Failed to restrict database file permissions on ${dbPath}:`,
263
- err
264
- );
265
- }
266
- return db;
267
- }
268
-
269
- // src/lib/prompts.ts
270
74
  var PROMPT_KEYS = [
271
75
  "seed_soul",
272
76
  "default_soul",
@@ -372,8 +176,8 @@ async function getPrompt(key, vars) {
372
176
  if (!isValidKey(key)) return "";
373
177
  let content = PROMPT_DEFAULTS[key].content;
374
178
  try {
375
- const db2 = await getDb();
376
- const row = await db2.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
179
+ const db = await getDb();
180
+ const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
377
181
  if (row) content = row.content;
378
182
  } catch (err) {
379
183
  console.error(`[prompts] failed to read DB override for "${key}":`, err);
@@ -383,8 +187,8 @@ async function getPrompt(key, vars) {
383
187
  async function getPromptIfCustom(key) {
384
188
  if (!isValidKey(key)) return null;
385
189
  try {
386
- const db2 = await getDb();
387
- const row = await db2.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
190
+ const db = await getDb();
191
+ const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
388
192
  return row?.content ?? null;
389
193
  } catch (err) {
390
194
  console.error(`[prompts] failed to check DB customization for "${key}":`, err);
@@ -398,8 +202,8 @@ async function getMindPromptDefaults() {
398
202
  result[key] = PROMPT_DEFAULTS[key].content;
399
203
  }
400
204
  try {
401
- const db2 = await getDb();
402
- const rows = await db2.select().from(systemPrompts).all();
205
+ const db = await getDb();
206
+ const rows = await db.select().from(systemPrompts).all();
403
207
  for (const row of rows) {
404
208
  if (MIND_PROMPT_KEYS.includes(row.key)) {
405
209
  result[row.key] = row.content;
@@ -411,10 +215,55 @@ async function getMindPromptDefaults() {
411
215
  return result;
412
216
  }
413
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();
260
+ }
261
+ };
262
+
414
263
  // src/lib/rotating-log.ts
415
264
  import {
416
265
  createWriteStream,
417
- existsSync as existsSync3,
266
+ existsSync as existsSync2,
418
267
  renameSync,
419
268
  rmSync,
420
269
  statSync
@@ -430,7 +279,7 @@ var RotatingLog = class extends Writable {
430
279
  this.on("error", () => {
431
280
  });
432
281
  try {
433
- this.size = existsSync3(path) ? statSync(path).size : 0;
282
+ this.size = existsSync2(path) ? statSync(path).size : 0;
434
283
  } catch {
435
284
  this.size = 0;
436
285
  }
@@ -443,11 +292,11 @@ var RotatingLog = class extends Writable {
443
292
  if (this.size > this.maxSize) {
444
293
  try {
445
294
  const oldest = `${this.path}.${this.maxFiles}`;
446
- if (existsSync3(oldest)) rmSync(oldest);
295
+ if (existsSync2(oldest)) rmSync(oldest);
447
296
  for (let i = this.maxFiles - 1; i >= 1; i--) {
448
297
  const from = `${this.path}.${i}`;
449
298
  const to = `${this.path}.${i + 1}`;
450
- if (existsSync3(from)) renameSync(from, to);
299
+ if (existsSync2(from)) renameSync(from, to);
451
300
  }
452
301
  renameSync(this.path, `${this.path}.1`);
453
302
  const oldStream = this.stream;
@@ -468,16 +317,13 @@ var RotatingLog = class extends Writable {
468
317
  var mlog = logger_default.child("minds");
469
318
  var execFileAsync = promisify(execFile);
470
319
  function mindPidPath(name) {
471
- return resolve2(stateDir(name), "mind.pid");
320
+ return resolve(stateDir(name), "mind.pid");
472
321
  }
473
- var MAX_RESTART_ATTEMPTS = 5;
474
- var BASE_RESTART_DELAY = 3e3;
475
- var MAX_RESTART_DELAY = 6e4;
476
322
  var MindManager = class {
477
323
  minds = /* @__PURE__ */ new Map();
478
324
  stopping = /* @__PURE__ */ new Set();
479
325
  shuttingDown = false;
480
- restartAttempts = /* @__PURE__ */ new Map();
326
+ restartTracker = new RestartTracker();
481
327
  pendingContext = /* @__PURE__ */ new Map();
482
328
  resolveTarget(name) {
483
329
  const [baseName, variantName] = name.split("@", 2);
@@ -489,7 +335,7 @@ var MindManager = class {
489
335
  return { dir: variant.path, port: variant.port, isVariant: true, baseName, variantName };
490
336
  }
491
337
  const dir = mindDir(baseName);
492
- if (!existsSync4(dir)) throw new Error(`Mind directory missing: ${dir}`);
338
+ if (!existsSync3(dir)) throw new Error(`Mind directory missing: ${dir}`);
493
339
  return { dir, port: entry.port, isVariant: false, baseName };
494
340
  }
495
341
  async startMind(name) {
@@ -501,7 +347,7 @@ var MindManager = class {
501
347
  const port = target.port;
502
348
  const pidFile = mindPidPath(name);
503
349
  try {
504
- if (existsSync4(pidFile)) {
350
+ if (existsSync3(pidFile)) {
505
351
  const stalePid = parseInt(readFileSync2(pidFile, "utf-8").trim(), 10);
506
352
  if (stalePid > 0) {
507
353
  try {
@@ -535,7 +381,7 @@ var MindManager = class {
535
381
  } catch {
536
382
  }
537
383
  const mindStateDir = stateDir(name);
538
- const logsDir = resolve2(mindStateDir, "logs");
384
+ const logsDir = resolve(mindStateDir, "logs");
539
385
  mkdirSync(logsDir, { recursive: true });
540
386
  if (isIsolationEnabled()) {
541
387
  try {
@@ -546,7 +392,7 @@ var MindManager = class {
546
392
  );
547
393
  }
548
394
  }
549
- const logStream = new RotatingLog(resolve2(logsDir, "mind.log"));
395
+ const logStream = new RotatingLog(resolve(logsDir, "mind.log"));
550
396
  const mindEnv = loadMergedEnv(name);
551
397
  const env = {
552
398
  ...process.env,
@@ -559,9 +405,9 @@ var MindManager = class {
559
405
  CLAUDECODE: void 0
560
406
  };
561
407
  if (isIsolationEnabled()) {
562
- env.HOME = resolve2(dir, "home");
408
+ env.HOME = resolve(dir, "home");
563
409
  }
564
- const tsxBin = resolve2(dir, "node_modules", ".bin", "tsx");
410
+ const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
565
411
  const tsxArgs = ["src/server.ts", "--port", String(port)];
566
412
  const [spawnCmd, spawnArgs] = wrapForIsolation(tsxBin, tsxArgs, name);
567
413
  const spawnOpts = {
@@ -570,28 +416,28 @@ var MindManager = class {
570
416
  detached: true,
571
417
  env
572
418
  };
573
- const child2 = spawn(spawnCmd, spawnArgs, spawnOpts);
574
- this.minds.set(name, { child: child2, port });
575
- child2.stdout?.pipe(logStream);
576
- 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);
577
423
  try {
578
- await new Promise((resolve3, reject) => {
424
+ await new Promise((resolve2, reject) => {
579
425
  const timeout = setTimeout(() => {
580
426
  reject(new Error(`Mind ${name} did not start within 30s`));
581
427
  }, 3e4);
582
428
  function checkOutput(data) {
583
429
  if (data.toString().match(/listening on :\d+/)) {
584
430
  clearTimeout(timeout);
585
- resolve3();
431
+ resolve2();
586
432
  }
587
433
  }
588
- child2.stdout?.on("data", checkOutput);
589
- child2.stderr?.on("data", checkOutput);
590
- child2.on("error", (err) => {
434
+ child.stdout?.on("data", checkOutput);
435
+ child.stderr?.on("data", checkOutput);
436
+ child.on("error", (err) => {
591
437
  clearTimeout(timeout);
592
438
  reject(err);
593
439
  });
594
- child2.on("exit", (code) => {
440
+ child.on("exit", (code) => {
595
441
  clearTimeout(timeout);
596
442
  reject(new Error(`Mind ${name} exited with code ${code} during startup`));
597
443
  });
@@ -599,20 +445,20 @@ var MindManager = class {
599
445
  } catch (err) {
600
446
  this.minds.delete(name);
601
447
  try {
602
- child2.kill();
448
+ child.kill();
603
449
  } catch {
604
450
  }
605
451
  throw err;
606
452
  }
607
- if (child2.pid) {
453
+ if (child.pid) {
608
454
  try {
609
- writeFileSync2(pidFile, String(child2.pid));
455
+ writeFileSync2(pidFile, String(child.pid));
610
456
  } catch (err) {
611
457
  mlog.warn(`failed to write PID file for ${name}`, logger_default.errorData(err));
612
458
  }
613
459
  }
614
- if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
615
- this.setupCrashRecovery(name, child2);
460
+ if (this.restartTracker.reset(name)) this.saveCrashAttempts();
461
+ this.setupCrashRecovery(name, child);
616
462
  if (isVariant) {
617
463
  setVariantRunning(baseName, variantName, true);
618
464
  } else {
@@ -654,14 +500,15 @@ var MindManager = class {
654
500
  mlog.warn(`failed to deliver pending context to ${name}`, logger_default.errorData(err));
655
501
  }
656
502
  }
657
- setupCrashRecovery(name, child2) {
658
- child2.on("exit", async (code) => {
503
+ setupCrashRecovery(name, child) {
504
+ child.on("exit", async (code) => {
659
505
  this.minds.delete(name);
660
506
  if (this.shuttingDown || this.stopping.has(name)) return;
661
507
  mlog.error(`mind ${name} exited with code ${code}`);
662
- const attempts = this.restartAttempts.get(name) ?? 0;
663
- if (attempts >= MAX_RESTART_ATTEMPTS) {
664
- 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`);
665
512
  const [base, variant] = name.split("@", 2);
666
513
  if (variant) {
667
514
  setVariantRunning(base, variant, false);
@@ -670,11 +517,8 @@ var MindManager = class {
670
517
  }
671
518
  return;
672
519
  }
673
- const delay = Math.min(BASE_RESTART_DELAY * 2 ** attempts, MAX_RESTART_DELAY);
674
- this.restartAttempts.set(name, attempts + 1);
675
- this.saveCrashAttempts();
676
520
  mlog.info(
677
- `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`
678
522
  );
679
523
  setTimeout(() => {
680
524
  if (this.shuttingDown) return;
@@ -688,25 +532,25 @@ var MindManager = class {
688
532
  const tracked = this.minds.get(name);
689
533
  if (!tracked) return;
690
534
  this.stopping.add(name);
691
- const { child: child2 } = tracked;
535
+ const { child } = tracked;
692
536
  this.minds.delete(name);
693
- await new Promise((resolve3) => {
694
- child2.on("exit", () => resolve3());
537
+ await new Promise((resolve2) => {
538
+ child.on("exit", () => resolve2());
695
539
  try {
696
- process.kill(-child2.pid, "SIGTERM");
540
+ process.kill(-child.pid, "SIGTERM");
697
541
  } catch {
698
- resolve3();
542
+ resolve2();
699
543
  }
700
544
  setTimeout(() => {
701
545
  try {
702
- process.kill(-child2.pid, "SIGKILL");
546
+ process.kill(-child.pid, "SIGKILL");
703
547
  } catch {
704
548
  }
705
- resolve3();
549
+ resolve2();
706
550
  }, 5e3);
707
551
  });
708
552
  this.stopping.delete(name);
709
- if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
553
+ if (this.restartTracker.reset(name)) this.saveCrashAttempts();
710
554
  rmSync2(mindPidPath(name), { force: true });
711
555
  if (!this.shuttingDown) {
712
556
  const [baseName, variantName] = name.split("@", 2);
@@ -734,16 +578,17 @@ var MindManager = class {
734
578
  return [...this.minds.keys()];
735
579
  }
736
580
  get crashAttemptsPath() {
737
- return resolve2(voluteHome(), "crash-attempts.json");
581
+ return resolve(voluteHome(), "crash-attempts.json");
738
582
  }
739
583
  loadCrashAttempts() {
740
- this.restartAttempts = loadJsonMap(this.crashAttemptsPath);
584
+ this.restartTracker.load(loadJsonMap(this.crashAttemptsPath));
741
585
  }
742
586
  saveCrashAttempts() {
743
- saveJsonMap(this.crashAttemptsPath, this.restartAttempts);
587
+ saveJsonMap(this.crashAttemptsPath, this.restartTracker.save());
744
588
  }
745
589
  clearCrashAttempts() {
746
- clearJsonMap(this.crashAttemptsPath, this.restartAttempts);
590
+ this.restartTracker.clear();
591
+ clearJsonMap(this.crashAttemptsPath, /* @__PURE__ */ new Map());
747
592
  }
748
593
  };
749
594
  async function killProcessOnPort(port) {
@@ -780,26 +625,16 @@ function initMindManager() {
780
625
  return instance;
781
626
  }
782
627
  function getMindManager() {
783
- if (!instance) instance = new MindManager();
628
+ if (!instance) throw new Error("MindManager not initialized \u2014 call initMindManager() first");
784
629
  return instance;
785
630
  }
786
631
 
787
632
  export {
788
- logBuffer,
789
- logger_default,
633
+ RestartTracker,
790
634
  RotatingLog,
791
635
  loadJsonMap,
792
636
  saveJsonMap,
793
637
  clearJsonMap,
794
- users,
795
- conversations,
796
- mindHistory,
797
- conversationParticipants,
798
- sessions,
799
- systemPrompts,
800
- sharedSkills,
801
- messages,
802
- getDb,
803
638
  PROMPT_KEYS,
804
639
  PROMPT_DEFAULTS,
805
640
  substitute,
@@ -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";