volute 0.18.0 → 0.20.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 (117) hide show
  1. package/README.md +67 -67
  2. package/dist/activity-events-OMXKXD5N.js +16 -0
  3. package/dist/archive-ZCFOSTKB.js +15 -0
  4. package/dist/{channel-SLURLIRV.js → channel-PUQKGSQM.js} +60 -7
  5. package/dist/{chunk-6BDNWYKG.js → chunk-32VR2EOH.js} +2 -2
  6. package/dist/chunk-5XNT2472.js +36 -0
  7. package/dist/{chunk-QJIIHU32.js → chunk-7NO7EV5Z.js} +2 -2
  8. package/dist/{chunk-6DVBMLVN.js → chunk-7UFKREVW.js} +2 -2
  9. package/dist/chunk-AW7P4EVV.js +159 -0
  10. package/dist/{chunk-2Y77MCFG.js → chunk-DYZGP3EW.js} +2 -2
  11. package/dist/{chunk-M77QBTEH.js → chunk-EBGCNDMM.js} +24 -14
  12. package/dist/{chunk-37X7ECMF.js → chunk-FCDU5BFX.js} +1 -1
  13. package/dist/chunk-FGSYHIS3.js +891 -0
  14. package/dist/chunk-GZ7DW4YL.js +97 -0
  15. package/dist/chunk-IKMY5X76.js +375 -0
  16. package/dist/chunk-NSE7VJQA.js +159 -0
  17. package/dist/{chunk-GSPWIM5E.js → chunk-O6ASDHFO.js} +79 -7
  18. package/dist/{chunk-ZCEYUUID.js → chunk-OGXOMR65.js} +2 -1
  19. package/dist/{chunk-AYB7XAWO.js → chunk-PUVXOZ6T.js} +120 -279
  20. package/dist/{chunk-GK4E7LM7.js → chunk-RHEGSQFJ.js} +1 -1
  21. package/dist/{chunk-MVSXRMJJ.js → chunk-SCUDS4US.js} +1 -1
  22. package/dist/chunk-TIWH32HP.js +227 -0
  23. package/dist/{chunk-FW5API7X.js → chunk-UJ6GHNR7.js} +2 -2
  24. package/dist/chunk-UU7A7KLB.js +58 -0
  25. package/dist/{chunk-OYSZNX5I.js → chunk-VDWCHYTS.js} +1 -1
  26. package/dist/{chunk-OJQ47SCA.js → chunk-WC6ZHVRL.js} +1 -1
  27. package/dist/chunk-YUIHSKR6.js +72 -0
  28. package/dist/cli.js +43 -25
  29. package/dist/{connector-3ELFMI2R.js → connector-JBVNZ7VK.js} +6 -6
  30. package/dist/connectors/discord.js +2 -2
  31. package/dist/connectors/slack.js +2 -2
  32. package/dist/connectors/telegram.js +2 -2
  33. package/dist/{create-ZWHCRT5F.js → create-HP4OVVHF.js} +6 -4
  34. package/dist/{daemon-client-ODKDUYDE.js → daemon-client-ITWUCNFO.js} +2 -2
  35. package/dist/{daemon-restart-2HVTHZAT.js → daemon-restart-KPSWNYTH.js} +6 -6
  36. package/dist/daemon.js +2463 -1707
  37. package/dist/db-C2CJ46ZU.js +10 -0
  38. package/dist/{delete-6G6WEX4F.js → delete-BSU7K3RY.js} +1 -1
  39. package/dist/delivery-manager-CSG7LXA4.js +16 -0
  40. package/dist/down-ZY35KMHR.js +14 -0
  41. package/dist/{env-6IDWGBUH.js → env-A3LMO777.js} +6 -6
  42. package/dist/export-6QBUOQGC.js +100 -0
  43. package/dist/file-C57SK5DK.js +204 -0
  44. package/dist/{history-YUEKTJ2N.js → history-WNK3DFUM.js} +6 -6
  45. package/dist/{import-EDGRLIGO.js → import-XEC34Y4Z.js} +3 -3
  46. package/dist/log-PPPZDVEF.js +39 -0
  47. package/dist/{login-ORQDXLBM.js → login-HNH3EUQV.js} +2 -2
  48. package/dist/{logout-XC5AUO5I.js → logout-I5CB5UZS.js} +2 -2
  49. package/dist/{logs-GYOR3L2L.js → logs-SF2IMJN4.js} +6 -6
  50. package/dist/merge-33C237A4.js +46 -0
  51. package/dist/{mind-OJN6RBZW.js → mind-Z7CKD6DG.js} +14 -10
  52. package/dist/mind-activity-tracker-624QLQLC.js +19 -0
  53. package/dist/mind-manager-3DMYKZPB.js +18 -0
  54. package/dist/{package-OKLFO7UY.js → package-4NHAVUUI.js} +5 -3
  55. package/dist/{pages-6IV4VQTU.js → pages-4DGQT7ZA.js} +2 -2
  56. package/dist/{publish-Q4RPSJLL.js → publish-TAJUET4I.js} +22 -5
  57. package/dist/pull-XAEWQJ47.js +39 -0
  58. package/dist/{register-LDE6LRXY.js → register-VSPCMHKX.js} +2 -2
  59. package/dist/{restart-YFAWFS5T.js → restart-IQKMCK5M.js} +6 -6
  60. package/dist/{schedule-AGYLDMNS.js → schedule-FFZG23IW.js} +31 -11
  61. package/dist/schema-GFH6RV3W.js +26 -0
  62. package/dist/{seed-AP4Q7RZ7.js → seed-J43YDKXG.js} +7 -4
  63. package/dist/{send-BNDTLUPM.js → send-KVIZIGCE.js} +8 -8
  64. package/dist/{service-U7MZ2H7F.js → service-LUR7WDO7.js} +6 -6
  65. package/dist/{setup-DJKIZKGW.js → setup-52YRV7VP.js} +23 -7
  66. package/dist/shared-KO35ZM44.js +39 -0
  67. package/dist/{skill-2Y42P4JY.js → skill-BCVNI6TV.js} +6 -6
  68. package/{templates/_base/_skills → dist/skills}/orientation/SKILL.md +1 -1
  69. package/{templates/_base/_skills → dist/skills}/sessions/SKILL.md +2 -2
  70. package/{templates/_base/_skills → dist/skills}/volute-mind/SKILL.md +51 -3
  71. package/dist/{sprout-TJ3BHVOG.js → sprout-QN7Y4VVO.js} +38 -20
  72. package/dist/{start-3YYRXBKP.js → start-I5JYB65M.js} +6 -6
  73. package/dist/{status-VSFZYX7S.js → status-4ESFLGH4.js} +5 -5
  74. package/dist/status-D7E5HHBV.js +35 -0
  75. package/dist/{status-OKNA6AR3.js → status-FU2PFVVF.js} +5 -4
  76. package/dist/{stop-AA5K5LYG.js → stop-NBVKEFQQ.js} +6 -6
  77. package/dist/{up-7B3BWF2U.js → up-FS7CKM6V.js} +5 -5
  78. package/dist/{update-YAGN5ODG.js → update-FJIHDJKM.js} +5 -5
  79. package/dist/{update-check-APLTH4IN.js → update-check-MWE5AH4U.js} +2 -2
  80. package/dist/{upgrade-KXZCQSZN.js → upgrade-AIT24B5I.js} +1 -1
  81. package/dist/{variant-X5QFG6KK.js → variant-63ZWO2W7.js} +4 -4
  82. package/dist/variants-JAGWGBXG.js +26 -0
  83. package/dist/web-assets/assets/index-CUZTZzaW.js +64 -0
  84. package/dist/web-assets/assets/index-adVuCkqy.css +1 -0
  85. package/dist/web-assets/index.html +2 -2
  86. package/drizzle/0010_delivery_queue.sql +12 -0
  87. package/drizzle/0011_rename_human_to_brain.sql +1 -0
  88. package/drizzle/0012_activity.sql +11 -0
  89. package/drizzle/meta/0010_snapshot.json +7 -0
  90. package/drizzle/meta/0011_snapshot.json +7 -0
  91. package/drizzle/meta/0012_snapshot.json +7 -0
  92. package/drizzle/meta/_journal.json +21 -0
  93. package/package.json +5 -3
  94. package/templates/_base/.init/.config/hooks/startup-context.sh +1 -1
  95. package/templates/_base/.init/.config/scripts/session-reader.ts +3 -3
  96. package/templates/_base/home/.config/routes.json +2 -2
  97. package/templates/_base/home/VOLUTE.md +16 -1
  98. package/templates/_base/src/lib/auto-commit.ts +51 -14
  99. package/templates/_base/src/lib/daemon-client.ts +22 -0
  100. package/templates/_base/src/lib/router.ts +123 -1
  101. package/templates/_base/src/lib/transparency.ts +1 -1
  102. package/templates/_base/src/lib/types.ts +4 -0
  103. package/templates/_base/src/lib/volute-server.ts +91 -2
  104. package/templates/claude/.init/.config/routes.json +7 -1
  105. package/templates/claude/src/server.ts +2 -2
  106. package/templates/claude/volute-template.json +1 -2
  107. package/templates/pi/.init/.config/routes.json +7 -1
  108. package/templates/pi/src/agent.ts +12 -6
  109. package/templates/pi/src/lib/session-context-extension.ts +6 -4
  110. package/templates/pi/src/server.ts +2 -0
  111. package/templates/pi/volute-template.json +1 -2
  112. package/dist/chunk-PO5Q2AYN.js +0 -121
  113. package/dist/down-A56B5JLK.js +0 -14
  114. package/dist/mind-manager-Z7O7PN2O.js +0 -15
  115. package/dist/web-assets/assets/index-CtiimdWK.css +0 -1
  116. package/dist/web-assets/assets/index-kt1_EcuO.js +0 -63
  117. /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-5XNT2472.js";
11
+ import {
12
+ systemPrompts
13
+ } from "./chunk-NSE7VJQA.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,21 @@ 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
+ import("./mind-activity-tracker-624QLQLC.js").then(({ markIdle }) => markIdle(name)).catch(() => {
509
+ });
510
+ import("./activity-events-OMXKXD5N.js").then(
511
+ ({ publish }) => publish({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
512
+ ).catch(() => {
513
+ });
514
+ const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
515
+ this.saveCrashAttempts();
516
+ if (!shouldRestart) {
517
+ mlog.error(`${name} crashed ${attempt} times \u2014 giving up on restart`);
665
518
  const [base, variant] = name.split("@", 2);
666
519
  if (variant) {
667
520
  setVariantRunning(base, variant, false);
@@ -670,11 +523,8 @@ var MindManager = class {
670
523
  }
671
524
  return;
672
525
  }
673
- const delay = Math.min(BASE_RESTART_DELAY * 2 ** attempts, MAX_RESTART_DELAY);
674
- this.restartAttempts.set(name, attempts + 1);
675
- this.saveCrashAttempts();
676
526
  mlog.info(
677
- `crash recovery for ${name} \u2014 attempt ${attempts + 1}/${MAX_RESTART_ATTEMPTS}, restarting in ${delay}ms`
527
+ `crash recovery for ${name} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, restarting in ${delay}ms`
678
528
  );
679
529
  setTimeout(() => {
680
530
  if (this.shuttingDown) return;
@@ -688,25 +538,25 @@ var MindManager = class {
688
538
  const tracked = this.minds.get(name);
689
539
  if (!tracked) return;
690
540
  this.stopping.add(name);
691
- const { child: child2 } = tracked;
541
+ const { child } = tracked;
692
542
  this.minds.delete(name);
693
- await new Promise((resolve3) => {
694
- child2.on("exit", () => resolve3());
543
+ await new Promise((resolve2) => {
544
+ child.on("exit", () => resolve2());
695
545
  try {
696
- process.kill(-child2.pid, "SIGTERM");
546
+ process.kill(-child.pid, "SIGTERM");
697
547
  } catch {
698
- resolve3();
548
+ resolve2();
699
549
  }
700
550
  setTimeout(() => {
701
551
  try {
702
- process.kill(-child2.pid, "SIGKILL");
552
+ process.kill(-child.pid, "SIGKILL");
703
553
  } catch {
704
554
  }
705
- resolve3();
555
+ resolve2();
706
556
  }, 5e3);
707
557
  });
708
558
  this.stopping.delete(name);
709
- if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
559
+ if (this.restartTracker.reset(name)) this.saveCrashAttempts();
710
560
  rmSync2(mindPidPath(name), { force: true });
711
561
  if (!this.shuttingDown) {
712
562
  const [baseName, variantName] = name.split("@", 2);
@@ -734,16 +584,17 @@ var MindManager = class {
734
584
  return [...this.minds.keys()];
735
585
  }
736
586
  get crashAttemptsPath() {
737
- return resolve2(voluteHome(), "crash-attempts.json");
587
+ return resolve(voluteHome(), "crash-attempts.json");
738
588
  }
739
589
  loadCrashAttempts() {
740
- this.restartAttempts = loadJsonMap(this.crashAttemptsPath);
590
+ this.restartTracker.load(loadJsonMap(this.crashAttemptsPath));
741
591
  }
742
592
  saveCrashAttempts() {
743
- saveJsonMap(this.crashAttemptsPath, this.restartAttempts);
593
+ saveJsonMap(this.crashAttemptsPath, this.restartTracker.save());
744
594
  }
745
595
  clearCrashAttempts() {
746
- clearJsonMap(this.crashAttemptsPath, this.restartAttempts);
596
+ this.restartTracker.clear();
597
+ clearJsonMap(this.crashAttemptsPath, /* @__PURE__ */ new Map());
747
598
  }
748
599
  };
749
600
  async function killProcessOnPort(port) {
@@ -780,26 +631,16 @@ function initMindManager() {
780
631
  return instance;
781
632
  }
782
633
  function getMindManager() {
783
- if (!instance) instance = new MindManager();
634
+ if (!instance) throw new Error("MindManager not initialized \u2014 call initMindManager() first");
784
635
  return instance;
785
636
  }
786
637
 
787
638
  export {
788
- logBuffer,
789
- logger_default,
639
+ RestartTracker,
790
640
  RotatingLog,
791
641
  loadJsonMap,
792
642
  saveJsonMap,
793
643
  clearJsonMap,
794
- users,
795
- conversations,
796
- mindHistory,
797
- conversationParticipants,
798
- sessions,
799
- systemPrompts,
800
- sharedSkills,
801
- messages,
802
- getDb,
803
644
  PROMPT_KEYS,
804
645
  PROMPT_DEFAULTS,
805
646
  substitute,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  stateDir
4
- } from "./chunk-M77QBTEH.js";
4
+ } from "./chunk-EBGCNDMM.js";
5
5
 
6
6
  // src/lib/slugify.ts
7
7
  function slugify(text) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteHome
4
- } from "./chunk-M77QBTEH.js";
4
+ } from "./chunk-EBGCNDMM.js";
5
5
 
6
6
  // src/lib/update-check.ts
7
7
  import { existsSync, readFileSync, writeFileSync } from "fs";