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.
- package/README.md +1 -1
- package/dist/archive-ZCFOSTKB.js +15 -0
- package/dist/{channel-SLURLIRV.js → channel-PUQKGSQM.js} +60 -7
- package/dist/{chunk-AYB7XAWO.js → chunk-2TJGRJ4O.js} +114 -279
- package/dist/{chunk-6BDNWYKG.js → chunk-32VR2EOH.js} +2 -2
- package/dist/chunk-4KPUF5JD.js +214 -0
- package/dist/{chunk-QJIIHU32.js → chunk-7NO7EV5Z.js} +2 -2
- package/dist/chunk-AW7P4EVV.js +159 -0
- package/dist/{chunk-2Y77MCFG.js → chunk-DYZGP3EW.js} +2 -2
- package/dist/{chunk-M77QBTEH.js → chunk-EBGCNDMM.js} +24 -14
- package/dist/{chunk-GSPWIM5E.js → chunk-EMQSAY3B.js} +77 -6
- package/dist/{chunk-37X7ECMF.js → chunk-FCDU5BFX.js} +1 -1
- package/dist/chunk-FGV2H4TX.js +803 -0
- package/dist/{chunk-ZCEYUUID.js → chunk-OGXOMR65.js} +2 -1
- package/dist/chunk-OTWLI7F4.js +375 -0
- package/dist/{chunk-GK4E7LM7.js → chunk-RHEGSQFJ.js} +1 -1
- package/dist/{chunk-MVSXRMJJ.js → chunk-SCUDS4US.js} +1 -1
- package/dist/{chunk-FW5API7X.js → chunk-UJ6GHNR7.js} +2 -2
- package/dist/{chunk-OYSZNX5I.js → chunk-VDWCHYTS.js} +1 -1
- package/dist/{chunk-6DVBMLVN.js → chunk-VE4D3GOP.js} +2 -2
- package/dist/chunk-VQWDC6UK.js +142 -0
- package/dist/{chunk-OJQ47SCA.js → chunk-WC6ZHVRL.js} +1 -1
- package/dist/chunk-YUIHSKR6.js +72 -0
- package/dist/chunk-Z524RFCJ.js +36 -0
- package/dist/cli.js +33 -25
- package/dist/{connector-3ELFMI2R.js → connector-JBVNZ7VK.js} +6 -6
- package/dist/connectors/discord.js +2 -2
- package/dist/connectors/slack.js +2 -2
- package/dist/connectors/telegram.js +2 -2
- package/dist/{create-ZWHCRT5F.js → create-HP4OVVHF.js} +6 -4
- package/dist/{daemon-client-ODKDUYDE.js → daemon-client-ITWUCNFO.js} +2 -2
- package/dist/{daemon-restart-2HVTHZAT.js → daemon-restart-JMZM3QY4.js} +8 -8
- package/dist/daemon.js +1144 -1108
- package/dist/db-5ZVC6MQF.js +10 -0
- package/dist/{delete-6G6WEX4F.js → delete-BSU7K3RY.js} +1 -1
- package/dist/delivery-manager-ISTJMZDW.js +16 -0
- package/dist/down-ZY35KMHR.js +14 -0
- package/dist/{env-6IDWGBUH.js → env-A3LMO777.js} +6 -6
- package/dist/export-GCDNQCF3.js +100 -0
- package/dist/{history-YUEKTJ2N.js → history-WNK3DFUM.js} +6 -6
- package/dist/{import-EDGRLIGO.js → import-M63VIUJ5.js} +3 -3
- package/dist/log-PPPZDVEF.js +39 -0
- package/dist/{login-ORQDXLBM.js → login-HNH3EUQV.js} +2 -2
- package/dist/{logout-XC5AUO5I.js → logout-I5CB5UZS.js} +2 -2
- package/dist/{logs-GYOR3L2L.js → logs-SF2IMJN4.js} +6 -6
- package/dist/merge-33C237A4.js +46 -0
- package/dist/{mind-OJN6RBZW.js → mind-PQ5NCPSU.js} +14 -10
- package/dist/mind-manager-RVCFROAY.js +18 -0
- package/dist/{package-OKLFO7UY.js → package-MYE2ZJLV.js} +5 -3
- package/dist/{pages-6IV4VQTU.js → pages-AXCOSY3P.js} +2 -2
- package/dist/{publish-Q4RPSJLL.js → publish-YB377JB7.js} +18 -4
- package/dist/pull-XAEWQJ47.js +39 -0
- package/dist/{register-LDE6LRXY.js → register-VSPCMHKX.js} +2 -2
- package/dist/{restart-YFAWFS5T.js → restart-IQKMCK5M.js} +6 -6
- package/dist/{schedule-AGYLDMNS.js → schedule-LMX7GAQZ.js} +6 -6
- package/dist/schema-5BW7DFZI.js +24 -0
- package/dist/{seed-AP4Q7RZ7.js → seed-J43YDKXG.js} +7 -4
- package/dist/{send-BNDTLUPM.js → send-KVIZIGCE.js} +8 -8
- package/dist/{service-U7MZ2H7F.js → service-LUR7WDO7.js} +6 -6
- package/dist/{setup-DJKIZKGW.js → setup-OH3PJUJO.js} +7 -7
- package/dist/shared-KO35ZM44.js +39 -0
- package/dist/{skill-2Y42P4JY.js → skill-BCVNI6TV.js} +6 -6
- package/{templates/_base/_skills → dist/skills}/orientation/SKILL.md +1 -1
- package/{templates/_base/_skills → dist/skills}/sessions/SKILL.md +2 -2
- package/{templates/_base/_skills → dist/skills}/volute-mind/SKILL.md +19 -1
- package/dist/{sprout-TJ3BHVOG.js → sprout-VBEX63LX.js} +38 -20
- package/dist/{start-3YYRXBKP.js → start-I5JYB65M.js} +6 -6
- package/dist/{status-VSFZYX7S.js → status-4ESFLGH4.js} +5 -5
- package/dist/status-D7E5HHBV.js +35 -0
- package/dist/{status-OKNA6AR3.js → status-JCJAOXTW.js} +2 -2
- package/dist/{stop-AA5K5LYG.js → stop-NBVKEFQQ.js} +6 -6
- package/dist/{up-7B3BWF2U.js → up-WG65SWJU.js} +5 -5
- package/dist/{update-YAGN5ODG.js → update-FJIHDJKM.js} +5 -5
- package/dist/{update-check-APLTH4IN.js → update-check-MWE5AH4U.js} +2 -2
- package/dist/{upgrade-KXZCQSZN.js → upgrade-AIT24B5I.js} +1 -1
- package/dist/{variant-X5QFG6KK.js → variant-63ZWO2W7.js} +4 -4
- package/dist/variants-JAGWGBXG.js +26 -0
- package/dist/web-assets/assets/index-BAbuRsVF.css +1 -0
- package/dist/web-assets/assets/index-CiQhSKi_.js +63 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0010_delivery_queue.sql +12 -0
- package/drizzle/0011_rename_human_to_brain.sql +1 -0
- package/drizzle/meta/0010_snapshot.json +7 -0
- package/drizzle/meta/0011_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +5 -3
- package/templates/_base/.init/.config/hooks/startup-context.sh +1 -1
- package/templates/_base/.init/.config/scripts/session-reader.ts +3 -3
- package/templates/_base/home/VOLUTE.md +16 -1
- package/templates/_base/src/lib/auto-commit.ts +51 -14
- package/templates/_base/src/lib/router.ts +123 -1
- package/templates/_base/src/lib/types.ts +4 -0
- package/templates/_base/src/lib/volute-server.ts +91 -2
- package/templates/claude/src/server.ts +2 -2
- package/templates/claude/volute-template.json +1 -2
- package/templates/pi/src/agent.ts +1 -1
- package/templates/pi/src/lib/session-context-extension.ts +2 -2
- package/templates/pi/volute-template.json +1 -2
- package/dist/chunk-PO5Q2AYN.js +0 -121
- package/dist/down-A56B5JLK.js +0 -14
- package/dist/mind-manager-Z7O7PN2O.js +0 -15
- package/dist/web-assets/assets/index-CtiimdWK.css +0 -1
- package/dist/web-assets/assets/index-kt1_EcuO.js +0 -63
- /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-
|
|
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-
|
|
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-
|
|
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
|
|
26
|
-
import { resolve
|
|
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
|
|
376
|
-
const row = await
|
|
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
|
|
387
|
-
const row = await
|
|
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
|
|
402
|
-
const rows = await
|
|
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
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
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(
|
|
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 =
|
|
408
|
+
env.HOME = resolve(dir, "home");
|
|
563
409
|
}
|
|
564
|
-
const tsxBin =
|
|
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
|
|
574
|
-
this.minds.set(name, { child
|
|
575
|
-
|
|
576
|
-
|
|
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((
|
|
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
|
-
|
|
431
|
+
resolve2();
|
|
586
432
|
}
|
|
587
433
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
448
|
+
child.kill();
|
|
603
449
|
} catch {
|
|
604
450
|
}
|
|
605
451
|
throw err;
|
|
606
452
|
}
|
|
607
|
-
if (
|
|
453
|
+
if (child.pid) {
|
|
608
454
|
try {
|
|
609
|
-
writeFileSync2(pidFile, String(
|
|
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.
|
|
615
|
-
this.setupCrashRecovery(name,
|
|
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,
|
|
658
|
-
|
|
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
|
|
663
|
-
|
|
664
|
-
|
|
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 ${
|
|
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
|
|
535
|
+
const { child } = tracked;
|
|
692
536
|
this.minds.delete(name);
|
|
693
|
-
await new Promise((
|
|
694
|
-
|
|
537
|
+
await new Promise((resolve2) => {
|
|
538
|
+
child.on("exit", () => resolve2());
|
|
695
539
|
try {
|
|
696
|
-
process.kill(-
|
|
540
|
+
process.kill(-child.pid, "SIGTERM");
|
|
697
541
|
} catch {
|
|
698
|
-
|
|
542
|
+
resolve2();
|
|
699
543
|
}
|
|
700
544
|
setTimeout(() => {
|
|
701
545
|
try {
|
|
702
|
-
process.kill(-
|
|
546
|
+
process.kill(-child.pid, "SIGKILL");
|
|
703
547
|
} catch {
|
|
704
548
|
}
|
|
705
|
-
|
|
549
|
+
resolve2();
|
|
706
550
|
}, 5e3);
|
|
707
551
|
});
|
|
708
552
|
this.stopping.delete(name);
|
|
709
|
-
if (this.
|
|
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
|
|
581
|
+
return resolve(voluteHome(), "crash-attempts.json");
|
|
738
582
|
}
|
|
739
583
|
loadCrashAttempts() {
|
|
740
|
-
this.
|
|
584
|
+
this.restartTracker.load(loadJsonMap(this.crashAttemptsPath));
|
|
741
585
|
}
|
|
742
586
|
saveCrashAttempts() {
|
|
743
|
-
saveJsonMap(this.crashAttemptsPath, this.
|
|
587
|
+
saveJsonMap(this.crashAttemptsPath, this.restartTracker.save());
|
|
744
588
|
}
|
|
745
589
|
clearCrashAttempts() {
|
|
746
|
-
|
|
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)
|
|
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
|
-
|
|
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-
|
|
4
|
+
} from "./chunk-DYZGP3EW.js";
|
|
5
5
|
import {
|
|
6
6
|
voluteHome
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EBGCNDMM.js";
|
|
8
8
|
|
|
9
9
|
// src/lib/service-mode.ts
|
|
10
10
|
import { execFileSync } from "child_process";
|