@askexenow/exe-os 0.9.64 → 0.9.66

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 (105) hide show
  1. package/deploy/stack-manifests/v0.9.json +4 -4
  2. package/dist/bin/backfill-conversations.js +22 -0
  3. package/dist/bin/backfill-responses.js +22 -0
  4. package/dist/bin/backfill-vectors.js +22 -0
  5. package/dist/bin/cleanup-stale-review-tasks.js +22 -0
  6. package/dist/bin/cli.js +2280 -1199
  7. package/dist/bin/exe-agent-config.js +4 -0
  8. package/dist/bin/exe-agent.js +16 -0
  9. package/dist/bin/exe-assign.js +22 -0
  10. package/dist/bin/exe-boot.js +116 -7
  11. package/dist/bin/exe-call.js +16 -0
  12. package/dist/bin/exe-cloud.js +6671 -464
  13. package/dist/bin/exe-dispatch.js +24 -0
  14. package/dist/bin/exe-doctor.js +2845 -1223
  15. package/dist/bin/exe-export-behaviors.js +24 -0
  16. package/dist/bin/exe-forget.js +22 -0
  17. package/dist/bin/exe-gateway.js +24 -0
  18. package/dist/bin/exe-heartbeat.js +23 -0
  19. package/dist/bin/exe-kill.js +22 -0
  20. package/dist/bin/exe-launch-agent.js +24 -0
  21. package/dist/bin/exe-link.js +310 -178
  22. package/dist/bin/exe-new-employee.js +127 -1
  23. package/dist/bin/exe-pending-messages.js +22 -0
  24. package/dist/bin/exe-pending-notifications.js +22 -0
  25. package/dist/bin/exe-pending-reviews.js +22 -0
  26. package/dist/bin/exe-rename.js +22 -0
  27. package/dist/bin/exe-review.js +22 -0
  28. package/dist/bin/exe-search.js +24 -0
  29. package/dist/bin/exe-session-cleanup.js +24 -0
  30. package/dist/bin/exe-settings.js +10 -0
  31. package/dist/bin/exe-start-codex.js +135 -1
  32. package/dist/bin/exe-start-opencode.js +149 -1
  33. package/dist/bin/exe-status.js +22 -0
  34. package/dist/bin/exe-team.js +22 -0
  35. package/dist/bin/git-sweep.js +24 -0
  36. package/dist/bin/graph-backfill.js +22 -0
  37. package/dist/bin/graph-export.js +22 -0
  38. package/dist/bin/install.js +115 -1
  39. package/dist/bin/intercom-check.js +24 -0
  40. package/dist/bin/scan-tasks.js +24 -0
  41. package/dist/bin/setup.js +412 -157
  42. package/dist/bin/shard-migrate.js +22 -0
  43. package/dist/bin/update.js +4 -0
  44. package/dist/gateway/index.js +24 -0
  45. package/dist/hooks/bug-report-worker.js +135 -42
  46. package/dist/hooks/codex-stop-task-finalizer.js +24 -0
  47. package/dist/hooks/commit-complete.js +24 -0
  48. package/dist/hooks/error-recall.js +24 -0
  49. package/dist/hooks/exe-heartbeat-hook.js +4 -0
  50. package/dist/hooks/ingest-worker.js +4 -0
  51. package/dist/hooks/ingest.js +23 -0
  52. package/dist/hooks/instructions-loaded.js +22 -0
  53. package/dist/hooks/notification.js +22 -0
  54. package/dist/hooks/post-compact.js +22 -0
  55. package/dist/hooks/post-tool-combined.js +24 -0
  56. package/dist/hooks/pre-compact.js +260 -109
  57. package/dist/hooks/pre-tool-use.js +22 -0
  58. package/dist/hooks/prompt-submit.js +24 -0
  59. package/dist/hooks/session-end.js +161 -122
  60. package/dist/hooks/session-start.js +142 -0
  61. package/dist/hooks/stop.js +23 -0
  62. package/dist/hooks/subagent-stop.js +22 -0
  63. package/dist/hooks/summary-worker.js +195 -79
  64. package/dist/index.js +24 -0
  65. package/dist/lib/agent-config.js +4 -0
  66. package/dist/lib/cloud-sync.js +50 -6
  67. package/dist/lib/config.js +12 -0
  68. package/dist/lib/consolidation.js +4 -0
  69. package/dist/lib/database.js +4 -0
  70. package/dist/lib/db-daemon-client.js +4 -0
  71. package/dist/lib/db.js +4 -0
  72. package/dist/lib/device-registry.js +4 -0
  73. package/dist/lib/embedder.js +12 -0
  74. package/dist/lib/employee-templates.js +16 -0
  75. package/dist/lib/employees.js +4 -0
  76. package/dist/lib/exe-daemon-client.js +4 -0
  77. package/dist/lib/exe-daemon.js +1144 -480
  78. package/dist/lib/hybrid-search.js +24 -0
  79. package/dist/lib/identity.js +4 -0
  80. package/dist/lib/license.js +4 -0
  81. package/dist/lib/messaging.js +4 -0
  82. package/dist/lib/reminders.js +4 -0
  83. package/dist/lib/schedules.js +22 -0
  84. package/dist/lib/skill-learning.js +12 -0
  85. package/dist/lib/status-brief.js +39 -0
  86. package/dist/lib/store.js +22 -0
  87. package/dist/lib/task-router.js +4 -0
  88. package/dist/lib/tasks.js +12 -0
  89. package/dist/lib/tmux-routing.js +12 -0
  90. package/dist/lib/token-spend.js +4 -0
  91. package/dist/mcp/server.js +1045 -427
  92. package/dist/mcp/tools/complete-reminder.js +4 -0
  93. package/dist/mcp/tools/create-reminder.js +4 -0
  94. package/dist/mcp/tools/create-task.js +12 -0
  95. package/dist/mcp/tools/deactivate-behavior.js +4 -0
  96. package/dist/mcp/tools/list-reminders.js +4 -0
  97. package/dist/mcp/tools/list-tasks.js +4 -0
  98. package/dist/mcp/tools/send-message.js +4 -0
  99. package/dist/mcp/tools/update-task.js +12 -0
  100. package/dist/runtime/index.js +24 -0
  101. package/dist/tui/App.js +24 -0
  102. package/package.json +3 -2
  103. package/src/commands/exe/cloud.md +15 -8
  104. package/src/commands/exe/link.md +7 -6
  105. package/stack.release.json +2 -2
@@ -15,70 +15,6 @@ var __export = (target, all) => {
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
17
 
18
- // src/types/memory.ts
19
- var EMBEDDING_DIM;
20
- var init_memory = __esm({
21
- "src/types/memory.ts"() {
22
- "use strict";
23
- EMBEDDING_DIM = 1024;
24
- }
25
- });
26
-
27
- // src/lib/db-retry.ts
28
- function isBusyError(err) {
29
- if (err instanceof Error) {
30
- const msg = err.message.toLowerCase();
31
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
32
- }
33
- return false;
34
- }
35
- function delay(ms) {
36
- return new Promise((resolve) => setTimeout(resolve, ms));
37
- }
38
- async function retryOnBusy(fn, label) {
39
- let lastError;
40
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
41
- try {
42
- return await fn();
43
- } catch (err) {
44
- lastError = err;
45
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
46
- throw err;
47
- }
48
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
49
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
50
- process.stderr.write(
51
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
52
- `
53
- );
54
- await delay(backoff + jitter);
55
- }
56
- }
57
- throw lastError;
58
- }
59
- function wrapWithRetry(client) {
60
- return new Proxy(client, {
61
- get(target, prop, receiver) {
62
- if (prop === "execute") {
63
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
64
- }
65
- if (prop === "batch") {
66
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
67
- }
68
- return Reflect.get(target, prop, receiver);
69
- }
70
- });
71
- }
72
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
73
- var init_db_retry = __esm({
74
- "src/lib/db-retry.ts"() {
75
- "use strict";
76
- MAX_RETRIES = 5;
77
- BASE_DELAY_MS = 250;
78
- MAX_JITTER_MS = 400;
79
- }
80
- });
81
-
82
18
  // src/lib/secure-files.ts
83
19
  import { chmodSync, existsSync, mkdirSync } from "fs";
84
20
  import { chmod, mkdir } from "fs/promises";
@@ -188,6 +124,11 @@ function normalizeAutoUpdate(raw) {
188
124
  const userAU = raw.autoUpdate ?? {};
189
125
  raw.autoUpdate = { ...defaultAU, ...userAU };
190
126
  }
127
+ function normalizeOrchestration(raw) {
128
+ const defaultOrg = DEFAULT_CONFIG.orchestration;
129
+ const userOrg = raw.orchestration ?? {};
130
+ raw.orchestration = { ...defaultOrg, ...userOrg };
131
+ }
191
132
  async function loadConfig() {
192
133
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
193
134
  await ensurePrivateDir(dir);
@@ -212,6 +153,7 @@ async function loadConfig() {
212
153
  normalizeScalingRoadmap(migratedCfg);
213
154
  normalizeSessionLifecycle(migratedCfg);
214
155
  normalizeAutoUpdate(migratedCfg);
156
+ normalizeOrchestration(migratedCfg);
215
157
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
216
158
  if (config.dbPath.startsWith("~")) {
217
159
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -287,6 +229,10 @@ var init_config = __esm({
287
229
  checkOnBoot: true,
288
230
  autoInstall: false,
289
231
  checkIntervalMs: 24 * 60 * 60 * 1e3
232
+ },
233
+ orchestration: {
234
+ phase: "phase_1_coo",
235
+ phaseSetBy: "default"
290
236
  }
291
237
  };
292
238
  CONFIG_MIGRATIONS = [
@@ -302,162 +248,1104 @@ var init_config = __esm({
302
248
  }
303
249
  });
304
250
 
305
- // src/lib/employees.ts
306
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
307
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
308
- import { execSync } from "child_process";
251
+ // src/lib/shard-manager.ts
252
+ var shard_manager_exports = {};
253
+ __export(shard_manager_exports, {
254
+ auditShardHealth: () => auditShardHealth,
255
+ disposeShards: () => disposeShards,
256
+ ensureShardSchema: () => ensureShardSchema,
257
+ getOpenShardCount: () => getOpenShardCount,
258
+ getReadyShardClient: () => getReadyShardClient,
259
+ getShardClient: () => getShardClient,
260
+ getShardsDir: () => getShardsDir,
261
+ initShardManager: () => initShardManager,
262
+ isShardingEnabled: () => isShardingEnabled,
263
+ listShards: () => listShards,
264
+ shardExists: () => shardExists
265
+ });
309
266
  import path2 from "path";
310
- import os2 from "os";
311
- function normalizeRole(role) {
312
- return (role ?? "").trim().toLowerCase();
313
- }
314
- function isCoordinatorRole(role) {
315
- return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
316
- }
317
- function getCoordinatorEmployee(employees) {
318
- return employees.find((e) => isCoordinatorRole(e.role));
319
- }
320
- function getCoordinatorName(employees = loadEmployeesSync()) {
321
- return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
322
- }
323
- function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
324
- if (!existsSync3(employeesPath)) return [];
325
- try {
326
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
327
- } catch {
328
- return [];
329
- }
330
- }
331
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
332
- var init_employees = __esm({
333
- "src/lib/employees.ts"() {
334
- "use strict";
335
- init_config();
336
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
337
- DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
338
- COORDINATOR_ROLE = "COO";
339
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
267
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync2, statSync } from "fs";
268
+ import { createClient } from "@libsql/client";
269
+ function initShardManager(encryptionKey) {
270
+ _encryptionKey = encryptionKey;
271
+ if (!existsSync3(SHARDS_DIR)) {
272
+ mkdirSync2(SHARDS_DIR, { recursive: true });
340
273
  }
341
- });
342
-
343
- // src/lib/database-adapter.ts
344
- import os3 from "os";
345
- import path3 from "path";
346
- import { createRequire } from "module";
347
- import { pathToFileURL } from "url";
348
- function quotedIdentifier(identifier) {
349
- return `"${identifier.replace(/"/g, '""')}"`;
274
+ _shardingEnabled = true;
275
+ if (_evictionTimer) clearInterval(_evictionTimer);
276
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
277
+ _evictionTimer.unref();
350
278
  }
351
- function unqualifiedTableName(name) {
352
- const raw = name.trim().replace(/^"|"$/g, "");
353
- const parts = raw.split(".");
354
- return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
279
+ function isShardingEnabled() {
280
+ return _shardingEnabled;
355
281
  }
356
- function stripTrailingSemicolon(sql) {
357
- return sql.trim().replace(/;+\s*$/u, "");
282
+ function getShardsDir() {
283
+ return SHARDS_DIR;
358
284
  }
359
- function appendClause(sql, clause) {
360
- const trimmed = stripTrailingSemicolon(sql);
361
- const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
362
- if (!returningMatch) {
363
- return `${trimmed}${clause}`;
285
+ function getShardClient(projectName) {
286
+ if (!_encryptionKey) {
287
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
364
288
  }
365
- const idx = returningMatch.index;
366
- return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
367
- }
368
- function normalizeStatement(stmt) {
369
- if (typeof stmt === "string") {
370
- return { kind: "positional", sql: stmt, args: [] };
289
+ const safeName = safeShardName(projectName);
290
+ if (!safeName || safeName === "unknown") {
291
+ throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
371
292
  }
372
- const sql = stmt.sql;
373
- if (Array.isArray(stmt.args) || stmt.args === void 0) {
374
- return { kind: "positional", sql, args: stmt.args ?? [] };
293
+ const cached = _shards.get(safeName);
294
+ if (cached) {
295
+ _shardLastAccess.set(safeName, Date.now());
296
+ return cached;
375
297
  }
376
- return { kind: "named", sql, args: stmt.args };
377
- }
378
- function rewriteBooleanLiterals(sql) {
379
- let out = sql;
380
- for (const column of BOOLEAN_COLUMN_NAMES) {
381
- const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
382
- out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
383
- out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
384
- out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
385
- out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
386
- out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
387
- out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
298
+ while (_shards.size >= MAX_OPEN_SHARDS) {
299
+ evictLRU();
388
300
  }
389
- return out;
301
+ const dbPath = path2.join(SHARDS_DIR, `${safeName}.db`);
302
+ const client = createClient({
303
+ url: `file:${dbPath}`,
304
+ encryptionKey: _encryptionKey
305
+ });
306
+ _shards.set(safeName, client);
307
+ _shardLastAccess.set(safeName, Date.now());
308
+ return client;
390
309
  }
391
- function rewriteInsertOrIgnore(sql) {
392
- if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
393
- return sql;
394
- }
395
- const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
396
- return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
310
+ function shardExists(projectName) {
311
+ const safeName = safeShardName(projectName);
312
+ return existsSync3(path2.join(SHARDS_DIR, `${safeName}.db`));
397
313
  }
398
- function rewriteInsertOrReplace(sql) {
399
- const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
400
- if (!match) {
401
- return sql;
402
- }
403
- const rawTable = match[1];
404
- const rawColumns = match[2];
405
- const remainder = match[3];
406
- const tableName = unqualifiedTableName(rawTable);
407
- const conflictKeys = UPSERT_KEYS[tableName];
408
- if (!conflictKeys?.length) {
409
- return sql;
410
- }
411
- const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
412
- const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
413
- const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
414
- const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
415
- return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
314
+ function safeShardName(projectName) {
315
+ return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
416
316
  }
417
- function rewriteSql(sql) {
418
- let out = sql;
419
- out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
420
- out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
421
- out = rewriteBooleanLiterals(out);
422
- out = rewriteInsertOrReplace(out);
423
- out = rewriteInsertOrIgnore(out);
424
- return stripTrailingSemicolon(out);
317
+ function listShards() {
318
+ if (!existsSync3(SHARDS_DIR)) return [];
319
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
425
320
  }
426
- function toBoolean(value) {
427
- if (value === null || value === void 0) return value;
428
- if (typeof value === "boolean") return value;
429
- if (typeof value === "number") return value !== 0;
430
- if (typeof value === "bigint") return value !== 0n;
431
- if (typeof value === "string") {
432
- const normalized = value.trim().toLowerCase();
433
- if (normalized === "0" || normalized === "false") return false;
434
- if (normalized === "1" || normalized === "true") return true;
321
+ async function auditShardHealth(options = {}) {
322
+ if (!_encryptionKey) {
323
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
435
324
  }
436
- return Boolean(value);
437
- }
438
- function countQuestionMarks(sql, end) {
439
- let count = 0;
440
- let inSingle = false;
441
- let inDouble = false;
442
- let inLineComment = false;
443
- let inBlockComment = false;
444
- for (let i = 0; i < end; i++) {
445
- const ch = sql[i];
446
- const next = sql[i + 1];
447
- if (inLineComment) {
448
- if (ch === "\n") inLineComment = false;
449
- continue;
450
- }
451
- if (inBlockComment) {
452
- if (ch === "*" && next === "/") {
453
- inBlockComment = false;
454
- i += 1;
455
- }
456
- continue;
457
- }
458
- if (!inSingle && !inDouble && ch === "-" && next === "-") {
459
- inLineComment = true;
460
- i += 1;
325
+ const repair = options.repair === true;
326
+ const dryRun = options.dryRun === true;
327
+ const names = listShards();
328
+ const shards = [];
329
+ for (const name of names) {
330
+ const dbPath = path2.join(SHARDS_DIR, `${name}.db`);
331
+ const stat = statSync(dbPath);
332
+ const item = {
333
+ name,
334
+ path: dbPath,
335
+ ok: false,
336
+ unreadable: false,
337
+ error: null,
338
+ size: stat.size,
339
+ mtime: stat.mtime.toISOString(),
340
+ memoryCount: null
341
+ };
342
+ const client = createClient({
343
+ url: `file:${dbPath}`,
344
+ encryptionKey: _encryptionKey
345
+ });
346
+ try {
347
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
348
+ const hasMemories = await client.execute(
349
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
350
+ );
351
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
352
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
353
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
354
+ }
355
+ item.ok = true;
356
+ } catch (err) {
357
+ const message = err instanceof Error ? err.message : String(err);
358
+ item.error = message;
359
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
360
+ if (item.unreadable && repair && !dryRun) {
361
+ client.close();
362
+ _shards.delete(name);
363
+ _shardLastAccess.delete(name);
364
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
365
+ const archivedPath = path2.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
366
+ renameSync2(dbPath, archivedPath);
367
+ item.archivedPath = archivedPath;
368
+ }
369
+ } finally {
370
+ try {
371
+ client.close();
372
+ } catch {
373
+ }
374
+ }
375
+ shards.push(item);
376
+ }
377
+ return {
378
+ total: shards.length,
379
+ ok: shards.filter((s) => s.ok).length,
380
+ unreadable: shards.filter((s) => s.unreadable).length,
381
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
382
+ shards
383
+ };
384
+ }
385
+ async function ensureShardSchema(client) {
386
+ await client.execute("PRAGMA journal_mode = WAL");
387
+ await client.execute("PRAGMA busy_timeout = 30000");
388
+ try {
389
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
390
+ } catch {
391
+ }
392
+ await client.executeMultiple(`
393
+ CREATE TABLE IF NOT EXISTS memories (
394
+ id TEXT PRIMARY KEY,
395
+ agent_id TEXT NOT NULL,
396
+ agent_role TEXT NOT NULL,
397
+ session_id TEXT NOT NULL,
398
+ timestamp TEXT NOT NULL,
399
+ tool_name TEXT NOT NULL,
400
+ project_name TEXT NOT NULL,
401
+ has_error INTEGER NOT NULL DEFAULT 0,
402
+ raw_text TEXT NOT NULL,
403
+ vector F32_BLOB(1024),
404
+ version INTEGER NOT NULL DEFAULT 0
405
+ );
406
+
407
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
408
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
409
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
410
+ `);
411
+ await client.executeMultiple(`
412
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
413
+ raw_text,
414
+ content='memories',
415
+ content_rowid='rowid'
416
+ );
417
+
418
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
419
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
420
+ END;
421
+
422
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
423
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
424
+ END;
425
+
426
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
427
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
428
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
429
+ END;
430
+ `);
431
+ for (const col of [
432
+ "ALTER TABLE memories ADD COLUMN task_id TEXT",
433
+ "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
434
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
435
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
436
+ "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
437
+ "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
438
+ "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
439
+ "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
440
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
441
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
442
+ "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
443
+ "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
444
+ // Wiki linkage columns (must match database.ts)
445
+ "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
446
+ "ALTER TABLE memories ADD COLUMN document_id TEXT",
447
+ "ALTER TABLE memories ADD COLUMN user_id TEXT",
448
+ "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
449
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
450
+ // Source provenance columns (must match database.ts)
451
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
452
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
453
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
454
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
455
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
456
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
457
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
458
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
459
+ // Metadata enrichment columns (must match database.ts)
460
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
461
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
462
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
463
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
464
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
465
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
466
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
467
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
468
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
469
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
470
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
471
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
472
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
473
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
474
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
475
+ "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
476
+ ]) {
477
+ try {
478
+ await client.execute(col);
479
+ } catch {
480
+ }
481
+ }
482
+ for (const idx of [
483
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
484
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
485
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
486
+ ]) {
487
+ try {
488
+ await client.execute(idx);
489
+ } catch {
490
+ }
491
+ }
492
+ try {
493
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
494
+ } catch {
495
+ }
496
+ for (const idx of [
497
+ "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
498
+ "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
499
+ "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
500
+ ]) {
501
+ try {
502
+ await client.execute(idx);
503
+ } catch {
504
+ }
505
+ }
506
+ await client.executeMultiple(`
507
+ CREATE TABLE IF NOT EXISTS entities (
508
+ id TEXT PRIMARY KEY,
509
+ name TEXT NOT NULL,
510
+ type TEXT NOT NULL,
511
+ first_seen TEXT NOT NULL,
512
+ last_seen TEXT NOT NULL,
513
+ properties TEXT DEFAULT '{}',
514
+ UNIQUE(name, type)
515
+ );
516
+
517
+ CREATE TABLE IF NOT EXISTS relationships (
518
+ id TEXT PRIMARY KEY,
519
+ source_entity_id TEXT NOT NULL,
520
+ target_entity_id TEXT NOT NULL,
521
+ type TEXT NOT NULL,
522
+ weight REAL DEFAULT 1.0,
523
+ timestamp TEXT NOT NULL,
524
+ properties TEXT DEFAULT '{}',
525
+ UNIQUE(source_entity_id, target_entity_id, type)
526
+ );
527
+
528
+ CREATE TABLE IF NOT EXISTS entity_memories (
529
+ entity_id TEXT NOT NULL,
530
+ memory_id TEXT NOT NULL,
531
+ PRIMARY KEY (entity_id, memory_id)
532
+ );
533
+
534
+ CREATE TABLE IF NOT EXISTS relationship_memories (
535
+ relationship_id TEXT NOT NULL,
536
+ memory_id TEXT NOT NULL,
537
+ PRIMARY KEY (relationship_id, memory_id)
538
+ );
539
+
540
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
541
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
542
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
543
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
544
+ CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
545
+
546
+ CREATE TABLE IF NOT EXISTS hyperedges (
547
+ id TEXT PRIMARY KEY,
548
+ label TEXT NOT NULL,
549
+ relation TEXT NOT NULL,
550
+ confidence REAL DEFAULT 1.0,
551
+ timestamp TEXT NOT NULL
552
+ );
553
+
554
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
555
+ hyperedge_id TEXT NOT NULL,
556
+ entity_id TEXT NOT NULL,
557
+ PRIMARY KEY (hyperedge_id, entity_id)
558
+ );
559
+ `);
560
+ for (const col of [
561
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
562
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
563
+ ]) {
564
+ try {
565
+ await client.execute(col);
566
+ } catch {
567
+ }
568
+ }
569
+ }
570
+ async function getReadyShardClient(projectName) {
571
+ const safeName = safeShardName(projectName);
572
+ let client = getShardClient(projectName);
573
+ try {
574
+ await ensureShardSchema(client);
575
+ return client;
576
+ } catch (err) {
577
+ const message = err instanceof Error ? err.message : String(err);
578
+ if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
579
+ client.close();
580
+ _shards.delete(safeName);
581
+ _shardLastAccess.delete(safeName);
582
+ const dbPath = path2.join(SHARDS_DIR, `${safeName}.db`);
583
+ if (existsSync3(dbPath)) {
584
+ const stat = statSync(dbPath);
585
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
586
+ const archivedPath = path2.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
587
+ renameSync2(dbPath, archivedPath);
588
+ process.stderr.write(
589
+ `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
590
+ `
591
+ );
592
+ }
593
+ client = getShardClient(projectName);
594
+ await ensureShardSchema(client);
595
+ return client;
596
+ }
597
+ }
598
+ function evictLRU() {
599
+ let oldest = null;
600
+ let oldestTime = Infinity;
601
+ for (const [name, time] of _shardLastAccess) {
602
+ if (time < oldestTime) {
603
+ oldestTime = time;
604
+ oldest = name;
605
+ }
606
+ }
607
+ if (oldest) {
608
+ const client = _shards.get(oldest);
609
+ if (client) {
610
+ client.close();
611
+ }
612
+ _shards.delete(oldest);
613
+ _shardLastAccess.delete(oldest);
614
+ }
615
+ }
616
+ function evictIdleShards() {
617
+ const now = Date.now();
618
+ const toEvict = [];
619
+ for (const [name, lastAccess] of _shardLastAccess) {
620
+ if (now - lastAccess > SHARD_IDLE_MS) {
621
+ toEvict.push(name);
622
+ }
623
+ }
624
+ for (const name of toEvict) {
625
+ const client = _shards.get(name);
626
+ if (client) {
627
+ client.close();
628
+ }
629
+ _shards.delete(name);
630
+ _shardLastAccess.delete(name);
631
+ }
632
+ }
633
+ function getOpenShardCount() {
634
+ return _shards.size;
635
+ }
636
+ function disposeShards() {
637
+ if (_evictionTimer) {
638
+ clearInterval(_evictionTimer);
639
+ _evictionTimer = null;
640
+ }
641
+ for (const [, client] of _shards) {
642
+ client.close();
643
+ }
644
+ _shards.clear();
645
+ _shardLastAccess.clear();
646
+ _shardingEnabled = false;
647
+ _encryptionKey = null;
648
+ }
649
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
650
+ var init_shard_manager = __esm({
651
+ "src/lib/shard-manager.ts"() {
652
+ "use strict";
653
+ init_config();
654
+ SHARDS_DIR = path2.join(EXE_AI_DIR, "shards");
655
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
656
+ MAX_OPEN_SHARDS = 10;
657
+ EVICTION_INTERVAL_MS = 60 * 1e3;
658
+ _shards = /* @__PURE__ */ new Map();
659
+ _shardLastAccess = /* @__PURE__ */ new Map();
660
+ _evictionTimer = null;
661
+ _encryptionKey = null;
662
+ _shardingEnabled = false;
663
+ }
664
+ });
665
+
666
+ // src/lib/keychain.ts
667
+ var keychain_exports = {};
668
+ __export(keychain_exports, {
669
+ deleteMasterKey: () => deleteMasterKey,
670
+ exportMnemonic: () => exportMnemonic,
671
+ getMasterKey: () => getMasterKey,
672
+ importMnemonic: () => importMnemonic,
673
+ setMasterKey: () => setMasterKey
674
+ });
675
+ import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
676
+ import { existsSync as existsSync4 } from "fs";
677
+ import { execSync } from "child_process";
678
+ import path3 from "path";
679
+ import os2 from "os";
680
+ function getKeyDir() {
681
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os2.homedir(), ".exe-os");
682
+ }
683
+ function getKeyPath() {
684
+ return path3.join(getKeyDir(), "master.key");
685
+ }
686
+ function macKeychainGet() {
687
+ if (process.platform !== "darwin") return null;
688
+ try {
689
+ return execSync(
690
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
691
+ { encoding: "utf-8", timeout: 5e3 }
692
+ ).trim();
693
+ } catch {
694
+ return null;
695
+ }
696
+ }
697
+ function macKeychainSet(value) {
698
+ if (process.platform !== "darwin") return false;
699
+ try {
700
+ try {
701
+ execSync(
702
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
703
+ { timeout: 5e3 }
704
+ );
705
+ } catch {
706
+ }
707
+ execSync(
708
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
709
+ { timeout: 5e3 }
710
+ );
711
+ return true;
712
+ } catch {
713
+ return false;
714
+ }
715
+ }
716
+ function macKeychainDelete() {
717
+ if (process.platform !== "darwin") return false;
718
+ try {
719
+ execSync(
720
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
721
+ { timeout: 5e3 }
722
+ );
723
+ return true;
724
+ } catch {
725
+ return false;
726
+ }
727
+ }
728
+ function linuxSecretGet() {
729
+ if (process.platform !== "linux") return null;
730
+ try {
731
+ return execSync(
732
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
733
+ { encoding: "utf-8", timeout: 5e3 }
734
+ ).trim();
735
+ } catch {
736
+ return null;
737
+ }
738
+ }
739
+ function linuxSecretSet(value) {
740
+ if (process.platform !== "linux") return false;
741
+ try {
742
+ execSync(
743
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
744
+ { timeout: 5e3 }
745
+ );
746
+ return true;
747
+ } catch {
748
+ return false;
749
+ }
750
+ }
751
+ function linuxSecretDelete() {
752
+ if (process.platform !== "linux") return false;
753
+ try {
754
+ execSync(
755
+ `secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
756
+ { timeout: 5e3 }
757
+ );
758
+ return true;
759
+ } catch {
760
+ return false;
761
+ }
762
+ }
763
+ async function tryKeytar() {
764
+ try {
765
+ return await import("keytar");
766
+ } catch {
767
+ return null;
768
+ }
769
+ }
770
+ function deriveMachineKey() {
771
+ try {
772
+ const crypto2 = __require("crypto");
773
+ const material = [
774
+ os2.hostname(),
775
+ os2.userInfo().username,
776
+ os2.arch(),
777
+ os2.platform(),
778
+ // Machine ID on Linux (stable across reboots)
779
+ process.platform === "linux" ? readMachineId() : ""
780
+ ].join("|");
781
+ return crypto2.createHash("sha256").update(material).digest();
782
+ } catch {
783
+ return null;
784
+ }
785
+ }
786
+ function readMachineId() {
787
+ try {
788
+ const { readFileSync: readFileSync7 } = __require("fs");
789
+ return readFileSync7("/etc/machine-id", "utf-8").trim();
790
+ } catch {
791
+ return "";
792
+ }
793
+ }
794
+ function encryptWithMachineKey(plaintext, machineKey) {
795
+ const crypto2 = __require("crypto");
796
+ const iv = crypto2.randomBytes(12);
797
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
798
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
799
+ encrypted += cipher.final("base64");
800
+ const authTag = cipher.getAuthTag().toString("base64");
801
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
802
+ }
803
+ function decryptWithMachineKey(encrypted, machineKey) {
804
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
805
+ try {
806
+ const crypto2 = __require("crypto");
807
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
808
+ if (parts.length !== 3) return null;
809
+ const [ivB64, tagB64, cipherB64] = parts;
810
+ const iv = Buffer.from(ivB64, "base64");
811
+ const authTag = Buffer.from(tagB64, "base64");
812
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
813
+ decipher.setAuthTag(authTag);
814
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
815
+ decrypted += decipher.final("utf-8");
816
+ return decrypted;
817
+ } catch {
818
+ return null;
819
+ }
820
+ }
821
+ async function writeMachineBoundFileFallback(b64) {
822
+ const dir = getKeyDir();
823
+ await mkdir2(dir, { recursive: true });
824
+ const keyPath = getKeyPath();
825
+ const machineKey = deriveMachineKey();
826
+ if (machineKey) {
827
+ const encrypted = encryptWithMachineKey(b64, machineKey);
828
+ await writeFile2(keyPath, encrypted + "\n", "utf-8");
829
+ await chmod2(keyPath, 384);
830
+ return "encrypted";
831
+ }
832
+ await writeFile2(keyPath, b64 + "\n", "utf-8");
833
+ await chmod2(keyPath, 384);
834
+ return "plaintext";
835
+ }
836
+ async function getMasterKey() {
837
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
838
+ if (nativeValue) {
839
+ return Buffer.from(nativeValue, "base64");
840
+ }
841
+ const keytar = await tryKeytar();
842
+ if (keytar) {
843
+ try {
844
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
845
+ if (keytarValue) {
846
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
847
+ if (migrated) {
848
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
849
+ }
850
+ return Buffer.from(keytarValue, "base64");
851
+ }
852
+ } catch {
853
+ }
854
+ }
855
+ const keyPath = getKeyPath();
856
+ if (!existsSync4(keyPath)) {
857
+ process.stderr.write(
858
+ `[keychain] Key not found at ${keyPath} (HOME=${os2.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
859
+ `
860
+ );
861
+ return null;
862
+ }
863
+ try {
864
+ const content = (await readFile2(keyPath, "utf-8")).trim();
865
+ let b64Value;
866
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
867
+ const machineKey = deriveMachineKey();
868
+ if (!machineKey) {
869
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
870
+ return null;
871
+ }
872
+ const decrypted = decryptWithMachineKey(content, machineKey);
873
+ if (!decrypted) {
874
+ process.stderr.write(
875
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
876
+ );
877
+ return null;
878
+ }
879
+ b64Value = decrypted;
880
+ } else {
881
+ b64Value = content;
882
+ }
883
+ const key = Buffer.from(b64Value, "base64");
884
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
885
+ if (migrated) {
886
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
887
+ try {
888
+ await unlink(keyPath);
889
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
890
+ } catch {
891
+ }
892
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
893
+ const fallback = await writeMachineBoundFileFallback(b64Value);
894
+ if (fallback === "encrypted") {
895
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
896
+ } else {
897
+ process.stderr.write(
898
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
899
+ );
900
+ }
901
+ }
902
+ return key;
903
+ } catch (err) {
904
+ process.stderr.write(
905
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
906
+ `
907
+ );
908
+ return null;
909
+ }
910
+ }
911
+ async function setMasterKey(key) {
912
+ const b64 = key.toString("base64");
913
+ if (macKeychainSet(b64) || linuxSecretSet(b64)) {
914
+ return;
915
+ }
916
+ const keytar = await tryKeytar();
917
+ if (keytar) {
918
+ try {
919
+ await keytar.setPassword(SERVICE, ACCOUNT, b64);
920
+ return;
921
+ } catch {
922
+ }
923
+ }
924
+ const fallback = await writeMachineBoundFileFallback(b64);
925
+ if (fallback === "encrypted") {
926
+ process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
927
+ } else {
928
+ process.stderr.write(
929
+ "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
930
+ );
931
+ }
932
+ }
933
+ async function deleteMasterKey() {
934
+ macKeychainDelete();
935
+ linuxSecretDelete();
936
+ const keytar = await tryKeytar();
937
+ if (keytar) {
938
+ try {
939
+ await keytar.deletePassword(SERVICE, ACCOUNT);
940
+ } catch {
941
+ }
942
+ }
943
+ const keyPath = getKeyPath();
944
+ if (existsSync4(keyPath)) {
945
+ await unlink(keyPath);
946
+ }
947
+ }
948
+ async function loadBip39() {
949
+ try {
950
+ return await import("bip39");
951
+ } catch {
952
+ throw new Error(
953
+ "bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
954
+ );
955
+ }
956
+ }
957
+ async function exportMnemonic(key) {
958
+ if (key.length !== 32) {
959
+ throw new Error(`Key must be 32 bytes, got ${key.length}`);
960
+ }
961
+ const { entropyToMnemonic } = await loadBip39();
962
+ return entropyToMnemonic(key.toString("hex"));
963
+ }
964
+ async function importMnemonic(mnemonic) {
965
+ const trimmed = mnemonic.trim();
966
+ const words = trimmed.split(/\s+/);
967
+ if (words.length !== 24) {
968
+ throw new Error(`Expected 24 words, got ${words.length}`);
969
+ }
970
+ const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
971
+ if (!validateMnemonic(trimmed)) {
972
+ throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
973
+ }
974
+ const entropy = mnemonicToEntropy(trimmed);
975
+ return Buffer.from(entropy, "hex");
976
+ }
977
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
978
+ var init_keychain = __esm({
979
+ "src/lib/keychain.ts"() {
980
+ "use strict";
981
+ SERVICE = "exe-mem";
982
+ ACCOUNT = "master-key";
983
+ ENCRYPTED_PREFIX = "enc:";
984
+ }
985
+ });
986
+
987
+ // src/lib/key-backup-status.ts
988
+ var key_backup_status_exports = {};
989
+ __export(key_backup_status_exports, {
990
+ getKeyBackupStatus: () => getKeyBackupStatus,
991
+ keyBackupMarkerPath: () => keyBackupMarkerPath,
992
+ markKeyBackupConfirmed: () => markKeyBackupConfirmed
993
+ });
994
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
995
+ import path4 from "path";
996
+ function keyBackupMarkerPath() {
997
+ return path4.join(EXE_AI_DIR, "key-backup-confirmed.json");
998
+ }
999
+ function getKeyBackupStatus() {
1000
+ const marker = keyBackupMarkerPath();
1001
+ if (!existsSync5(marker)) return { exists: false };
1002
+ try {
1003
+ const parsed = JSON.parse(readFileSync2(marker, "utf8"));
1004
+ return {
1005
+ exists: true,
1006
+ confirmedAt: parsed.confirmedAt,
1007
+ source: parsed.source
1008
+ };
1009
+ } catch {
1010
+ return { exists: true };
1011
+ }
1012
+ }
1013
+ function markKeyBackupConfirmed(source) {
1014
+ mkdirSync3(EXE_AI_DIR, { recursive: true, mode: 448 });
1015
+ writeFileSync(
1016
+ keyBackupMarkerPath(),
1017
+ JSON.stringify({ confirmedAt: (/* @__PURE__ */ new Date()).toISOString(), source }, null, 2) + "\n",
1018
+ { mode: 384 }
1019
+ );
1020
+ }
1021
+ var init_key_backup_status = __esm({
1022
+ "src/lib/key-backup-status.ts"() {
1023
+ "use strict";
1024
+ init_config();
1025
+ }
1026
+ });
1027
+
1028
+ // src/lib/worker-gate.ts
1029
+ var worker_gate_exports = {};
1030
+ __export(worker_gate_exports, {
1031
+ MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
1032
+ cleanupWorkerPid: () => cleanupWorkerPid,
1033
+ registerWorkerPid: () => registerWorkerPid,
1034
+ releaseBackfillLock: () => releaseBackfillLock,
1035
+ tryAcquireBackfillLock: () => tryAcquireBackfillLock,
1036
+ tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
1037
+ });
1038
+ import { readdirSync as readdirSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
1039
+ import path5 from "path";
1040
+ function tryAcquireWorkerSlot() {
1041
+ try {
1042
+ mkdirSync4(WORKER_PID_DIR, { recursive: true });
1043
+ const reservationId = `res-${process.pid}-${Date.now()}`;
1044
+ const reservationPath = path5.join(WORKER_PID_DIR, `${reservationId}.pid`);
1045
+ writeFileSync2(reservationPath, String(process.pid));
1046
+ const files = readdirSync2(WORKER_PID_DIR);
1047
+ let alive = 0;
1048
+ for (const f of files) {
1049
+ if (!f.endsWith(".pid")) continue;
1050
+ if (f.startsWith("res-")) {
1051
+ alive++;
1052
+ continue;
1053
+ }
1054
+ const dashIdx = f.lastIndexOf("-");
1055
+ const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
1056
+ if (isNaN(pid)) continue;
1057
+ try {
1058
+ process.kill(pid, 0);
1059
+ alive++;
1060
+ } catch {
1061
+ try {
1062
+ unlinkSync(path5.join(WORKER_PID_DIR, f));
1063
+ } catch {
1064
+ }
1065
+ }
1066
+ }
1067
+ if (alive > MAX_CONCURRENT_WORKERS) {
1068
+ try {
1069
+ unlinkSync(reservationPath);
1070
+ } catch {
1071
+ }
1072
+ return false;
1073
+ }
1074
+ try {
1075
+ unlinkSync(reservationPath);
1076
+ } catch {
1077
+ }
1078
+ return true;
1079
+ } catch {
1080
+ return true;
1081
+ }
1082
+ }
1083
+ function registerWorkerPid(pid) {
1084
+ try {
1085
+ mkdirSync4(WORKER_PID_DIR, { recursive: true });
1086
+ writeFileSync2(path5.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
1087
+ } catch {
1088
+ }
1089
+ }
1090
+ function cleanupWorkerPid() {
1091
+ try {
1092
+ unlinkSync(path5.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
1093
+ } catch {
1094
+ }
1095
+ }
1096
+ function tryAcquireBackfillLock() {
1097
+ try {
1098
+ mkdirSync4(WORKER_PID_DIR, { recursive: true });
1099
+ if (existsSync6(BACKFILL_LOCK)) {
1100
+ try {
1101
+ const pid = parseInt(
1102
+ __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
1103
+ 10
1104
+ );
1105
+ if (!isNaN(pid) && pid > 0) {
1106
+ try {
1107
+ process.kill(pid, 0);
1108
+ return false;
1109
+ } catch {
1110
+ }
1111
+ }
1112
+ } catch {
1113
+ }
1114
+ }
1115
+ writeFileSync2(BACKFILL_LOCK, String(process.pid));
1116
+ return true;
1117
+ } catch {
1118
+ return true;
1119
+ }
1120
+ }
1121
+ function releaseBackfillLock() {
1122
+ try {
1123
+ unlinkSync(BACKFILL_LOCK);
1124
+ } catch {
1125
+ }
1126
+ }
1127
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
1128
+ var init_worker_gate = __esm({
1129
+ "src/lib/worker-gate.ts"() {
1130
+ "use strict";
1131
+ init_config();
1132
+ WORKER_PID_DIR = path5.join(EXE_AI_DIR, "worker-pids");
1133
+ MAX_CONCURRENT_WORKERS = 3;
1134
+ BACKFILL_LOCK = path5.join(WORKER_PID_DIR, "backfill.lock");
1135
+ }
1136
+ });
1137
+
1138
+ // src/lib/db-retry.ts
1139
+ function isBusyError(err) {
1140
+ if (err instanceof Error) {
1141
+ const msg = err.message.toLowerCase();
1142
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1143
+ }
1144
+ return false;
1145
+ }
1146
+ function delay(ms) {
1147
+ return new Promise((resolve) => setTimeout(resolve, ms));
1148
+ }
1149
+ async function retryOnBusy(fn, label) {
1150
+ let lastError;
1151
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
1152
+ try {
1153
+ return await fn();
1154
+ } catch (err) {
1155
+ lastError = err;
1156
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
1157
+ throw err;
1158
+ }
1159
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
1160
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
1161
+ process.stderr.write(
1162
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
1163
+ `
1164
+ );
1165
+ await delay(backoff + jitter);
1166
+ }
1167
+ }
1168
+ throw lastError;
1169
+ }
1170
+ function wrapWithRetry(client) {
1171
+ return new Proxy(client, {
1172
+ get(target, prop, receiver) {
1173
+ if (prop === "execute") {
1174
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
1175
+ }
1176
+ if (prop === "batch") {
1177
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
1178
+ }
1179
+ return Reflect.get(target, prop, receiver);
1180
+ }
1181
+ });
1182
+ }
1183
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
1184
+ var init_db_retry = __esm({
1185
+ "src/lib/db-retry.ts"() {
1186
+ "use strict";
1187
+ MAX_RETRIES = 5;
1188
+ BASE_DELAY_MS = 250;
1189
+ MAX_JITTER_MS = 400;
1190
+ }
1191
+ });
1192
+
1193
+ // src/lib/employees.ts
1194
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1195
+ import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
1196
+ import { execSync as execSync2 } from "child_process";
1197
+ import path6 from "path";
1198
+ import os3 from "os";
1199
+ function normalizeRole(role) {
1200
+ return (role ?? "").trim().toLowerCase();
1201
+ }
1202
+ function isCoordinatorRole(role) {
1203
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
1204
+ }
1205
+ function getCoordinatorEmployee(employees) {
1206
+ return employees.find((e) => isCoordinatorRole(e.role));
1207
+ }
1208
+ function getCoordinatorName(employees = loadEmployeesSync()) {
1209
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
1210
+ }
1211
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1212
+ if (!existsSync7(employeesPath)) return [];
1213
+ try {
1214
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
1215
+ } catch {
1216
+ return [];
1217
+ }
1218
+ }
1219
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
1220
+ var init_employees = __esm({
1221
+ "src/lib/employees.ts"() {
1222
+ "use strict";
1223
+ init_config();
1224
+ EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
1225
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
1226
+ COORDINATOR_ROLE = "COO";
1227
+ IDENTITY_DIR = path6.join(EXE_AI_DIR, "identity");
1228
+ }
1229
+ });
1230
+
1231
+ // src/lib/database-adapter.ts
1232
+ import os4 from "os";
1233
+ import path7 from "path";
1234
+ import { createRequire } from "module";
1235
+ import { pathToFileURL } from "url";
1236
+ function quotedIdentifier(identifier) {
1237
+ return `"${identifier.replace(/"/g, '""')}"`;
1238
+ }
1239
+ function unqualifiedTableName(name) {
1240
+ const raw = name.trim().replace(/^"|"$/g, "");
1241
+ const parts = raw.split(".");
1242
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
1243
+ }
1244
+ function stripTrailingSemicolon(sql) {
1245
+ return sql.trim().replace(/;+\s*$/u, "");
1246
+ }
1247
+ function appendClause(sql, clause) {
1248
+ const trimmed = stripTrailingSemicolon(sql);
1249
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
1250
+ if (!returningMatch) {
1251
+ return `${trimmed}${clause}`;
1252
+ }
1253
+ const idx = returningMatch.index;
1254
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
1255
+ }
1256
+ function normalizeStatement(stmt) {
1257
+ if (typeof stmt === "string") {
1258
+ return { kind: "positional", sql: stmt, args: [] };
1259
+ }
1260
+ const sql = stmt.sql;
1261
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
1262
+ return { kind: "positional", sql, args: stmt.args ?? [] };
1263
+ }
1264
+ return { kind: "named", sql, args: stmt.args };
1265
+ }
1266
+ function rewriteBooleanLiterals(sql) {
1267
+ let out = sql;
1268
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1269
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
1270
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
1271
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
1272
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
1273
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
1274
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
1275
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
1276
+ }
1277
+ return out;
1278
+ }
1279
+ function rewriteInsertOrIgnore(sql) {
1280
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
1281
+ return sql;
1282
+ }
1283
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
1284
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
1285
+ }
1286
+ function rewriteInsertOrReplace(sql) {
1287
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
1288
+ if (!match) {
1289
+ return sql;
1290
+ }
1291
+ const rawTable = match[1];
1292
+ const rawColumns = match[2];
1293
+ const remainder = match[3];
1294
+ const tableName = unqualifiedTableName(rawTable);
1295
+ const conflictKeys = UPSERT_KEYS[tableName];
1296
+ if (!conflictKeys?.length) {
1297
+ return sql;
1298
+ }
1299
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1300
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
1301
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
1302
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
1303
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
1304
+ }
1305
+ function rewriteSql(sql) {
1306
+ let out = sql;
1307
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
1308
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
1309
+ out = rewriteBooleanLiterals(out);
1310
+ out = rewriteInsertOrReplace(out);
1311
+ out = rewriteInsertOrIgnore(out);
1312
+ return stripTrailingSemicolon(out);
1313
+ }
1314
+ function toBoolean(value) {
1315
+ if (value === null || value === void 0) return value;
1316
+ if (typeof value === "boolean") return value;
1317
+ if (typeof value === "number") return value !== 0;
1318
+ if (typeof value === "bigint") return value !== 0n;
1319
+ if (typeof value === "string") {
1320
+ const normalized = value.trim().toLowerCase();
1321
+ if (normalized === "0" || normalized === "false") return false;
1322
+ if (normalized === "1" || normalized === "true") return true;
1323
+ }
1324
+ return Boolean(value);
1325
+ }
1326
+ function countQuestionMarks(sql, end) {
1327
+ let count = 0;
1328
+ let inSingle = false;
1329
+ let inDouble = false;
1330
+ let inLineComment = false;
1331
+ let inBlockComment = false;
1332
+ for (let i = 0; i < end; i++) {
1333
+ const ch = sql[i];
1334
+ const next = sql[i + 1];
1335
+ if (inLineComment) {
1336
+ if (ch === "\n") inLineComment = false;
1337
+ continue;
1338
+ }
1339
+ if (inBlockComment) {
1340
+ if (ch === "*" && next === "/") {
1341
+ inBlockComment = false;
1342
+ i += 1;
1343
+ }
1344
+ continue;
1345
+ }
1346
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1347
+ inLineComment = true;
1348
+ i += 1;
461
1349
  continue;
462
1350
  }
463
1351
  if (!inSingle && !inDouble && ch === "/" && next === "*") {
@@ -653,8 +1541,8 @@ async function loadPrismaClient() {
653
1541
  }
654
1542
  return new PrismaClient2();
655
1543
  }
656
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
657
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1544
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os4.homedir(), "exe-db");
1545
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
658
1546
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
659
1547
  const module = await import(pathToFileURL(prismaEntry).href);
660
1548
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -924,10 +1812,19 @@ var init_database_adapter = __esm({
924
1812
  }
925
1813
  });
926
1814
 
1815
+ // src/types/memory.ts
1816
+ var EMBEDDING_DIM;
1817
+ var init_memory = __esm({
1818
+ "src/types/memory.ts"() {
1819
+ "use strict";
1820
+ EMBEDDING_DIM = 1024;
1821
+ }
1822
+ });
1823
+
927
1824
  // src/lib/daemon-auth.ts
928
1825
  import crypto from "crypto";
929
- import path4 from "path";
930
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1826
+ import path8 from "path";
1827
+ import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
931
1828
  function normalizeToken(token) {
932
1829
  if (!token) return null;
933
1830
  const trimmed = token.trim();
@@ -935,8 +1832,8 @@ function normalizeToken(token) {
935
1832
  }
936
1833
  function readDaemonToken() {
937
1834
  try {
938
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
939
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1835
+ if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
1836
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
940
1837
  } catch {
941
1838
  return null;
942
1839
  }
@@ -946,7 +1843,7 @@ function ensureDaemonToken(seed) {
946
1843
  if (existing) return existing;
947
1844
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
948
1845
  ensurePrivateDirSync(EXE_AI_DIR);
949
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1846
+ writeFileSync4(DAEMON_TOKEN_PATH, `${token}
950
1847
  `, "utf8");
951
1848
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
952
1849
  return token;
@@ -957,18 +1854,29 @@ var init_daemon_auth = __esm({
957
1854
  "use strict";
958
1855
  init_config();
959
1856
  init_secure_files();
960
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1857
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
961
1858
  }
962
1859
  });
963
1860
 
964
1861
  // src/lib/exe-daemon-client.ts
1862
+ var exe_daemon_client_exports = {};
1863
+ __export(exe_daemon_client_exports, {
1864
+ connectEmbedDaemon: () => connectEmbedDaemon,
1865
+ disconnectClient: () => disconnectClient,
1866
+ embedBatchViaClient: () => embedBatchViaClient,
1867
+ embedViaClient: () => embedViaClient,
1868
+ isClientConnected: () => isClientConnected,
1869
+ pingDaemon: () => pingDaemon,
1870
+ sendDaemonRequest: () => sendDaemonRequest,
1871
+ sendIngestRequest: () => sendIngestRequest
1872
+ });
965
1873
  import net from "net";
966
- import os4 from "os";
1874
+ import os5 from "os";
967
1875
  import { spawn } from "child_process";
968
1876
  import { randomUUID } from "crypto";
969
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
970
- import path5 from "path";
971
- import { fileURLToPath } from "url";
1877
+ import { existsSync as existsSync9, unlinkSync as unlinkSync3, readFileSync as readFileSync5, openSync, closeSync, statSync as statSync2 } from "fs";
1878
+ import path9 from "path";
1879
+ import { fileURLToPath as fileURLToPath2 } from "url";
972
1880
  function handleData(chunk) {
973
1881
  _buffer += chunk.toString();
974
1882
  if (_buffer.length > MAX_BUFFER) {
@@ -995,9 +1903,9 @@ function handleData(chunk) {
995
1903
  }
996
1904
  }
997
1905
  function cleanupStaleFiles() {
998
- if (existsSync5(PID_PATH)) {
1906
+ if (existsSync9(PID_PATH)) {
999
1907
  try {
1000
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1908
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1001
1909
  if (pid > 0) {
1002
1910
  try {
1003
1911
  process.kill(pid, 0);
@@ -1008,21 +1916,21 @@ function cleanupStaleFiles() {
1008
1916
  } catch {
1009
1917
  }
1010
1918
  try {
1011
- unlinkSync2(PID_PATH);
1919
+ unlinkSync3(PID_PATH);
1012
1920
  } catch {
1013
1921
  }
1014
1922
  try {
1015
- unlinkSync2(SOCKET_PATH);
1923
+ unlinkSync3(SOCKET_PATH);
1016
1924
  } catch {
1017
1925
  }
1018
1926
  }
1019
1927
  }
1020
1928
  function findPackageRoot() {
1021
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1022
- const { root } = path5.parse(dir);
1929
+ let dir = path9.dirname(fileURLToPath2(import.meta.url));
1930
+ const { root } = path9.parse(dir);
1023
1931
  while (dir !== root) {
1024
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1025
- dir = path5.dirname(dir);
1932
+ if (existsSync9(path9.join(dir, "package.json"))) return dir;
1933
+ dir = path9.dirname(dir);
1026
1934
  }
1027
1935
  return null;
1028
1936
  }
@@ -1042,14 +1950,14 @@ function getAvailableMemoryGB() {
1042
1950
  const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1043
1951
  return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1044
1952
  } catch {
1045
- return os4.freemem() / (1024 * 1024 * 1024);
1953
+ return os5.freemem() / (1024 * 1024 * 1024);
1046
1954
  }
1047
1955
  }
1048
- return os4.freemem() / (1024 * 1024 * 1024);
1956
+ return os5.freemem() / (1024 * 1024 * 1024);
1049
1957
  }
1050
1958
  function spawnDaemon() {
1051
1959
  const freeGB = getAvailableMemoryGB();
1052
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1960
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1053
1961
  if (totalGB <= 8) {
1054
1962
  process.stderr.write(
1055
1963
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1069,8 +1977,8 @@ function spawnDaemon() {
1069
1977
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1070
1978
  return;
1071
1979
  }
1072
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1073
- if (!existsSync5(daemonPath)) {
1980
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1981
+ if (!existsSync9(daemonPath)) {
1074
1982
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1075
1983
  `);
1076
1984
  return;
@@ -1079,7 +1987,7 @@ function spawnDaemon() {
1079
1987
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1080
1988
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1081
1989
  `);
1082
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1990
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1083
1991
  let stderrFd = "ignore";
1084
1992
  try {
1085
1993
  stderrFd = openSync(logPath, "a");
@@ -1116,10 +2024,10 @@ function acquireSpawnLock() {
1116
2024
  return true;
1117
2025
  } catch {
1118
2026
  try {
1119
- const stat = statSync(SPAWN_LOCK_PATH);
2027
+ const stat = statSync2(SPAWN_LOCK_PATH);
1120
2028
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1121
2029
  try {
1122
- unlinkSync2(SPAWN_LOCK_PATH);
2030
+ unlinkSync3(SPAWN_LOCK_PATH);
1123
2031
  } catch {
1124
2032
  }
1125
2033
  try {
@@ -1136,7 +2044,7 @@ function acquireSpawnLock() {
1136
2044
  }
1137
2045
  function releaseSpawnLock() {
1138
2046
  try {
1139
- unlinkSync2(SPAWN_LOCK_PATH);
2047
+ unlinkSync3(SPAWN_LOCK_PATH);
1140
2048
  } catch {
1141
2049
  }
1142
2050
  }
@@ -1198,6 +2106,9 @@ async function connectEmbedDaemon() {
1198
2106
  }
1199
2107
  return false;
1200
2108
  }
2109
+ function sendRequest(texts, priority) {
2110
+ return sendDaemonRequest({ texts, priority });
2111
+ }
1201
2112
  function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1202
2113
  return new Promise((resolve) => {
1203
2114
  if (!_socket || !_connected) {
@@ -1220,18 +2131,178 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1220
2131
  }
1221
2132
  });
1222
2133
  }
2134
+ async function pingDaemon() {
2135
+ if (!_socket || !_connected) return null;
2136
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
2137
+ if (response.health) {
2138
+ return response.health;
2139
+ }
2140
+ return null;
2141
+ }
2142
+ function killAndRespawnDaemon() {
2143
+ if (!acquireSpawnLock()) {
2144
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
2145
+ if (_socket) {
2146
+ _socket.destroy();
2147
+ _socket = null;
2148
+ }
2149
+ _connected = false;
2150
+ _buffer = "";
2151
+ return;
2152
+ }
2153
+ try {
2154
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
2155
+ if (existsSync9(PID_PATH)) {
2156
+ try {
2157
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2158
+ if (pid > 0) {
2159
+ try {
2160
+ process.kill(pid, "SIGKILL");
2161
+ } catch {
2162
+ }
2163
+ }
2164
+ } catch {
2165
+ }
2166
+ }
2167
+ if (_socket) {
2168
+ _socket.destroy();
2169
+ _socket = null;
2170
+ }
2171
+ _connected = false;
2172
+ _buffer = "";
2173
+ try {
2174
+ unlinkSync3(PID_PATH);
2175
+ } catch {
2176
+ }
2177
+ try {
2178
+ unlinkSync3(SOCKET_PATH);
2179
+ } catch {
2180
+ }
2181
+ spawnDaemon();
2182
+ } finally {
2183
+ releaseSpawnLock();
2184
+ }
2185
+ }
2186
+ function isDaemonTooYoung() {
2187
+ try {
2188
+ const stat = statSync2(PID_PATH);
2189
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
2190
+ } catch {
2191
+ return false;
2192
+ }
2193
+ }
2194
+ async function retryThenRestart(doRequest, label) {
2195
+ const result = await doRequest();
2196
+ if (!result.error) {
2197
+ _consecutiveFailures = 0;
2198
+ return result;
2199
+ }
2200
+ _consecutiveFailures++;
2201
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
2202
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
2203
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
2204
+ `);
2205
+ await new Promise((r) => setTimeout(r, delayMs));
2206
+ if (!_connected) {
2207
+ if (!await connectToSocket()) continue;
2208
+ }
2209
+ const retry = await doRequest();
2210
+ if (!retry.error) {
2211
+ _consecutiveFailures = 0;
2212
+ return retry;
2213
+ }
2214
+ _consecutiveFailures++;
2215
+ }
2216
+ if (isDaemonTooYoung()) {
2217
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
2218
+ `);
2219
+ return { error: result.error };
2220
+ }
2221
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
2222
+ `);
2223
+ killAndRespawnDaemon();
2224
+ const start = Date.now();
2225
+ let delay2 = 200;
2226
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2227
+ await new Promise((r) => setTimeout(r, delay2));
2228
+ if (await connectToSocket()) break;
2229
+ delay2 = Math.min(delay2 * 2, 3e3);
2230
+ }
2231
+ if (!_connected) return { error: "Daemon restart failed" };
2232
+ const final = await doRequest();
2233
+ if (!final.error) _consecutiveFailures = 0;
2234
+ return final;
2235
+ }
2236
+ async function embedViaClient(text, priority = "high") {
2237
+ if (!_connected && !await connectEmbedDaemon()) return null;
2238
+ _requestCount++;
2239
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
2240
+ const health = await pingDaemon();
2241
+ if (!health && !isDaemonTooYoung()) {
2242
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
2243
+ `);
2244
+ killAndRespawnDaemon();
2245
+ const start = Date.now();
2246
+ let d = 200;
2247
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2248
+ await new Promise((r) => setTimeout(r, d));
2249
+ if (await connectToSocket()) break;
2250
+ d = Math.min(d * 2, 3e3);
2251
+ }
2252
+ if (!_connected) return null;
2253
+ }
2254
+ }
2255
+ const result = await retryThenRestart(
2256
+ () => sendRequest([text], priority),
2257
+ "Embed"
2258
+ );
2259
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
2260
+ }
2261
+ async function embedBatchViaClient(texts, priority = "high") {
2262
+ if (!_connected && !await connectEmbedDaemon()) return null;
2263
+ _requestCount++;
2264
+ const result = await retryThenRestart(
2265
+ () => sendRequest(texts, priority),
2266
+ "Batch embed"
2267
+ );
2268
+ return !result.error && result.vectors ? result.vectors : null;
2269
+ }
2270
+ function disconnectClient() {
2271
+ if (_socket) {
2272
+ _socket.destroy();
2273
+ _socket = null;
2274
+ }
2275
+ _connected = false;
2276
+ _buffer = "";
2277
+ for (const [id, entry] of _pending) {
2278
+ clearTimeout(entry.timer);
2279
+ _pending.delete(id);
2280
+ entry.resolve({ error: "Client disconnected" });
2281
+ }
2282
+ }
1223
2283
  function isClientConnected() {
1224
2284
  return _connected;
1225
2285
  }
1226
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
2286
+ function sendIngestRequest(payload) {
2287
+ if (!_socket || !_connected) return false;
2288
+ try {
2289
+ const id = randomUUID();
2290
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
2291
+ _socket.write(JSON.stringify({ id, token, type: "ingest", ...payload }) + "\n");
2292
+ return true;
2293
+ } catch {
2294
+ return false;
2295
+ }
2296
+ }
2297
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
1227
2298
  var init_exe_daemon_client = __esm({
1228
2299
  "src/lib/exe-daemon-client.ts"() {
1229
2300
  "use strict";
1230
2301
  init_config();
1231
2302
  init_daemon_auth();
1232
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1233
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1234
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
2303
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
2304
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
2305
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1235
2306
  SPAWN_LOCK_STALE_MS = 3e4;
1236
2307
  CONNECT_TIMEOUT_MS = 15e3;
1237
2308
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1239,12 +2310,27 @@ var init_exe_daemon_client = __esm({
1239
2310
  _socket = null;
1240
2311
  _connected = false;
1241
2312
  _buffer = "";
2313
+ _requestCount = 0;
2314
+ _consecutiveFailures = 0;
2315
+ HEALTH_CHECK_INTERVAL = 100;
2316
+ MAX_RETRIES_BEFORE_RESTART = 3;
2317
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
2318
+ MIN_DAEMON_AGE_MS = 3e4;
1242
2319
  _pending = /* @__PURE__ */ new Map();
1243
2320
  MAX_BUFFER = 1e7;
1244
2321
  }
1245
2322
  });
1246
2323
 
1247
2324
  // src/lib/daemon-protocol.ts
2325
+ var daemon_protocol_exports = {};
2326
+ __export(daemon_protocol_exports, {
2327
+ deserializeArgs: () => deserializeArgs,
2328
+ deserializeResultSet: () => deserializeResultSet,
2329
+ deserializeValue: () => deserializeValue,
2330
+ serializeArgs: () => serializeArgs,
2331
+ serializeResultSet: () => serializeResultSet,
2332
+ serializeValue: () => serializeValue
2333
+ });
1248
2334
  function serializeValue(v) {
1249
2335
  if (v === null || v === void 0) return null;
1250
2336
  if (typeof v === "bigint") return Number(v);
@@ -1269,6 +2355,32 @@ function deserializeValue(v) {
1269
2355
  }
1270
2356
  return v;
1271
2357
  }
2358
+ function serializeArgs(args) {
2359
+ return args.map(serializeValue);
2360
+ }
2361
+ function deserializeArgs(args) {
2362
+ return args.map(deserializeValue);
2363
+ }
2364
+ function serializeResultSet(rs) {
2365
+ const rows = [];
2366
+ for (const row of rs.rows) {
2367
+ const obj = {};
2368
+ for (let i = 0; i < rs.columns.length; i++) {
2369
+ const col = rs.columns[i];
2370
+ if (col !== void 0) {
2371
+ obj[col] = serializeValue(row[i]);
2372
+ }
2373
+ }
2374
+ rows.push(obj);
2375
+ }
2376
+ return {
2377
+ columns: [...rs.columns],
2378
+ columnTypes: [...rs.columnTypes ?? []],
2379
+ rows,
2380
+ rowsAffected: typeof rs.rowsAffected === "bigint" ? Number(rs.rowsAffected) : rs.rowsAffected ?? 0,
2381
+ lastInsertRowid: rs.lastInsertRowid != null ? typeof rs.lastInsertRowid === "bigint" ? Number(rs.lastInsertRowid) : rs.lastInsertRowid : null
2382
+ };
2383
+ }
1272
2384
  function deserializeResultSet(srs) {
1273
2385
  const rows = srs.rows.map((obj) => {
1274
2386
  const values = srs.columns.map(
@@ -1458,7 +2570,7 @@ __export(database_exports, {
1458
2570
  isInitialized: () => isInitialized,
1459
2571
  setExternalClient: () => setExternalClient
1460
2572
  });
1461
- import { createClient } from "@libsql/client";
2573
+ import { createClient as createClient2 } from "@libsql/client";
1462
2574
  async function initDatabase(config) {
1463
2575
  if (_walCheckpointTimer) {
1464
2576
  clearInterval(_walCheckpointTimer);
@@ -1483,7 +2595,7 @@ async function initDatabase(config) {
1483
2595
  if (config.encryptionKey) {
1484
2596
  opts.encryptionKey = config.encryptionKey;
1485
2597
  }
1486
- _client = createClient(opts);
2598
+ _client = createClient2(opts);
1487
2599
  _resilientClient = wrapWithRetry(_client);
1488
2600
  _adapterClient = _resilientClient;
1489
2601
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
@@ -2618,295 +3730,19 @@ async function ensureSchema() {
2618
3730
  } catch {
2619
3731
  }
2620
3732
  try {
2621
- await client.execute(
2622
- `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
2623
- );
2624
- } catch {
2625
- }
2626
- try {
2627
- await client.execute({
2628
- sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
2629
- args: []
2630
- });
2631
- } catch {
2632
- }
2633
- for (const col of [
2634
- "ALTER TABLE memories ADD COLUMN intent TEXT",
2635
- "ALTER TABLE memories ADD COLUMN outcome TEXT",
2636
- "ALTER TABLE memories ADD COLUMN domain TEXT",
2637
- "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2638
- "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2639
- "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2640
- "ALTER TABLE memories ADD COLUMN review_status TEXT",
2641
- "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2642
- "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2643
- "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2644
- "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2645
- "ALTER TABLE memories ADD COLUMN token_cost REAL",
2646
- "ALTER TABLE memories ADD COLUMN audience TEXT",
2647
- "ALTER TABLE memories ADD COLUMN language_type TEXT",
2648
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2649
- ]) {
2650
- try {
2651
- await client.execute(col);
2652
- } catch {
2653
- }
2654
- }
2655
- try {
2656
- await client.execute({
2657
- sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2658
- args: []
2659
- });
2660
- } catch {
2661
- }
2662
- }
2663
- async function disposeDatabase() {
2664
- if (_walCheckpointTimer) {
2665
- clearInterval(_walCheckpointTimer);
2666
- _walCheckpointTimer = null;
2667
- }
2668
- if (_daemonClient) {
2669
- _daemonClient.close();
2670
- _daemonClient = null;
2671
- }
2672
- if (_adapterClient && _adapterClient !== _resilientClient) {
2673
- _adapterClient.close();
2674
- }
2675
- _adapterClient = null;
2676
- if (_client) {
2677
- _client.close();
2678
- _client = null;
2679
- _resilientClient = null;
2680
- }
2681
- }
2682
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
2683
- var init_database = __esm({
2684
- "src/lib/database.ts"() {
2685
- "use strict";
2686
- init_db_retry();
2687
- init_employees();
2688
- init_database_adapter();
2689
- init_memory();
2690
- _client = null;
2691
- _resilientClient = null;
2692
- _walCheckpointTimer = null;
2693
- _daemonClient = null;
2694
- _adapterClient = null;
2695
- initTurso = initDatabase;
2696
- SOFT_DELETE_RETENTION_DAYS = 7;
2697
- disposeTurso = disposeDatabase;
2698
- }
2699
- });
2700
-
2701
- // src/lib/shard-manager.ts
2702
- var shard_manager_exports = {};
2703
- __export(shard_manager_exports, {
2704
- auditShardHealth: () => auditShardHealth,
2705
- disposeShards: () => disposeShards,
2706
- ensureShardSchema: () => ensureShardSchema,
2707
- getOpenShardCount: () => getOpenShardCount,
2708
- getReadyShardClient: () => getReadyShardClient,
2709
- getShardClient: () => getShardClient,
2710
- getShardsDir: () => getShardsDir,
2711
- initShardManager: () => initShardManager,
2712
- isShardingEnabled: () => isShardingEnabled,
2713
- listShards: () => listShards,
2714
- shardExists: () => shardExists
2715
- });
2716
- import path7 from "path";
2717
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync2 } from "fs";
2718
- import { createClient as createClient2 } from "@libsql/client";
2719
- function initShardManager(encryptionKey) {
2720
- _encryptionKey = encryptionKey;
2721
- if (!existsSync7(SHARDS_DIR)) {
2722
- mkdirSync2(SHARDS_DIR, { recursive: true });
2723
- }
2724
- _shardingEnabled = true;
2725
- if (_evictionTimer) clearInterval(_evictionTimer);
2726
- _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2727
- _evictionTimer.unref();
2728
- }
2729
- function isShardingEnabled() {
2730
- return _shardingEnabled;
2731
- }
2732
- function getShardsDir() {
2733
- return SHARDS_DIR;
2734
- }
2735
- function getShardClient(projectName) {
2736
- if (!_encryptionKey) {
2737
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
2738
- }
2739
- const safeName = safeShardName(projectName);
2740
- if (!safeName || safeName === "unknown") {
2741
- throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
2742
- }
2743
- const cached = _shards.get(safeName);
2744
- if (cached) {
2745
- _shardLastAccess.set(safeName, Date.now());
2746
- return cached;
2747
- }
2748
- while (_shards.size >= MAX_OPEN_SHARDS) {
2749
- evictLRU();
2750
- }
2751
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2752
- const client = createClient2({
2753
- url: `file:${dbPath}`,
2754
- encryptionKey: _encryptionKey
2755
- });
2756
- _shards.set(safeName, client);
2757
- _shardLastAccess.set(safeName, Date.now());
2758
- return client;
2759
- }
2760
- function shardExists(projectName) {
2761
- const safeName = safeShardName(projectName);
2762
- return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2763
- }
2764
- function safeShardName(projectName) {
2765
- return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2766
- }
2767
- function listShards() {
2768
- if (!existsSync7(SHARDS_DIR)) return [];
2769
- return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2770
- }
2771
- async function auditShardHealth(options = {}) {
2772
- if (!_encryptionKey) {
2773
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
2774
- }
2775
- const repair = options.repair === true;
2776
- const dryRun = options.dryRun === true;
2777
- const names = listShards();
2778
- const shards = [];
2779
- for (const name of names) {
2780
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2781
- const stat = statSync2(dbPath);
2782
- const item = {
2783
- name,
2784
- path: dbPath,
2785
- ok: false,
2786
- unreadable: false,
2787
- error: null,
2788
- size: stat.size,
2789
- mtime: stat.mtime.toISOString(),
2790
- memoryCount: null
2791
- };
2792
- const client = createClient2({
2793
- url: `file:${dbPath}`,
2794
- encryptionKey: _encryptionKey
2795
- });
2796
- try {
2797
- await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2798
- const hasMemories = await client.execute(
2799
- "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2800
- );
2801
- if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2802
- const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2803
- item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2804
- }
2805
- item.ok = true;
2806
- } catch (err) {
2807
- const message = err instanceof Error ? err.message : String(err);
2808
- item.error = message;
2809
- item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2810
- if (item.unreadable && repair && !dryRun) {
2811
- client.close();
2812
- _shards.delete(name);
2813
- _shardLastAccess.delete(name);
2814
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2815
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2816
- renameSync3(dbPath, archivedPath);
2817
- item.archivedPath = archivedPath;
2818
- }
2819
- } finally {
2820
- try {
2821
- client.close();
2822
- } catch {
2823
- }
2824
- }
2825
- shards.push(item);
2826
- }
2827
- return {
2828
- total: shards.length,
2829
- ok: shards.filter((s) => s.ok).length,
2830
- unreadable: shards.filter((s) => s.unreadable).length,
2831
- archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2832
- shards
2833
- };
2834
- }
2835
- async function ensureShardSchema(client) {
2836
- await client.execute("PRAGMA journal_mode = WAL");
2837
- await client.execute("PRAGMA busy_timeout = 30000");
2838
- try {
2839
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
2840
- } catch {
2841
- }
2842
- await client.executeMultiple(`
2843
- CREATE TABLE IF NOT EXISTS memories (
2844
- id TEXT PRIMARY KEY,
2845
- agent_id TEXT NOT NULL,
2846
- agent_role TEXT NOT NULL,
2847
- session_id TEXT NOT NULL,
2848
- timestamp TEXT NOT NULL,
2849
- tool_name TEXT NOT NULL,
2850
- project_name TEXT NOT NULL,
2851
- has_error INTEGER NOT NULL DEFAULT 0,
2852
- raw_text TEXT NOT NULL,
2853
- vector F32_BLOB(1024),
2854
- version INTEGER NOT NULL DEFAULT 0
2855
- );
2856
-
2857
- CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
2858
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
2859
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
2860
- `);
2861
- await client.executeMultiple(`
2862
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
2863
- raw_text,
2864
- content='memories',
2865
- content_rowid='rowid'
3733
+ await client.execute(
3734
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
2866
3735
  );
2867
-
2868
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
2869
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2870
- END;
2871
-
2872
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
2873
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2874
- END;
2875
-
2876
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
2877
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2878
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2879
- END;
2880
- `);
3736
+ } catch {
3737
+ }
3738
+ try {
3739
+ await client.execute({
3740
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
3741
+ args: []
3742
+ });
3743
+ } catch {
3744
+ }
2881
3745
  for (const col of [
2882
- "ALTER TABLE memories ADD COLUMN task_id TEXT",
2883
- "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2884
- "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2885
- "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2886
- "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2887
- "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2888
- "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
2889
- "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
2890
- "ALTER TABLE memories ADD COLUMN content_hash TEXT",
2891
- "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
2892
- "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
2893
- "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
2894
- // Wiki linkage columns (must match database.ts)
2895
- "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
2896
- "ALTER TABLE memories ADD COLUMN document_id TEXT",
2897
- "ALTER TABLE memories ADD COLUMN user_id TEXT",
2898
- "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
2899
- "ALTER TABLE memories ADD COLUMN page_number INTEGER",
2900
- // Source provenance columns (must match database.ts)
2901
- "ALTER TABLE memories ADD COLUMN source_path TEXT",
2902
- "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
2903
- "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
2904
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
2905
- // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2906
- "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2907
- "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2908
- "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2909
- // Metadata enrichment columns (must match database.ts)
2910
3746
  "ALTER TABLE memories ADD COLUMN intent TEXT",
2911
3747
  "ALTER TABLE memories ADD COLUMN outcome TEXT",
2912
3748
  "ALTER TABLE memories ADD COLUMN domain TEXT",
@@ -2921,195 +3757,305 @@ async function ensureShardSchema(client) {
2921
3757
  "ALTER TABLE memories ADD COLUMN token_cost REAL",
2922
3758
  "ALTER TABLE memories ADD COLUMN audience TEXT",
2923
3759
  "ALTER TABLE memories ADD COLUMN language_type TEXT",
2924
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
2925
- "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
3760
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2926
3761
  ]) {
2927
3762
  try {
2928
3763
  await client.execute(col);
2929
3764
  } catch {
2930
3765
  }
2931
3766
  }
2932
- for (const idx of [
2933
- "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2934
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2935
- "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
2936
- ]) {
2937
- try {
2938
- await client.execute(idx);
2939
- } catch {
2940
- }
2941
- }
2942
3767
  try {
2943
- await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
3768
+ await client.execute({
3769
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3770
+ args: []
3771
+ });
2944
3772
  } catch {
2945
3773
  }
2946
- for (const idx of [
2947
- "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
2948
- "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
2949
- "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
2950
- ]) {
2951
- try {
2952
- await client.execute(idx);
2953
- } catch {
2954
- }
3774
+ }
3775
+ async function disposeDatabase() {
3776
+ if (_walCheckpointTimer) {
3777
+ clearInterval(_walCheckpointTimer);
3778
+ _walCheckpointTimer = null;
2955
3779
  }
2956
- await client.executeMultiple(`
2957
- CREATE TABLE IF NOT EXISTS entities (
2958
- id TEXT PRIMARY KEY,
2959
- name TEXT NOT NULL,
2960
- type TEXT NOT NULL,
2961
- first_seen TEXT NOT NULL,
2962
- last_seen TEXT NOT NULL,
2963
- properties TEXT DEFAULT '{}',
2964
- UNIQUE(name, type)
2965
- );
2966
-
2967
- CREATE TABLE IF NOT EXISTS relationships (
2968
- id TEXT PRIMARY KEY,
2969
- source_entity_id TEXT NOT NULL,
2970
- target_entity_id TEXT NOT NULL,
2971
- type TEXT NOT NULL,
2972
- weight REAL DEFAULT 1.0,
2973
- timestamp TEXT NOT NULL,
2974
- properties TEXT DEFAULT '{}',
2975
- UNIQUE(source_entity_id, target_entity_id, type)
2976
- );
2977
-
2978
- CREATE TABLE IF NOT EXISTS entity_memories (
2979
- entity_id TEXT NOT NULL,
2980
- memory_id TEXT NOT NULL,
2981
- PRIMARY KEY (entity_id, memory_id)
2982
- );
2983
-
2984
- CREATE TABLE IF NOT EXISTS relationship_memories (
2985
- relationship_id TEXT NOT NULL,
2986
- memory_id TEXT NOT NULL,
2987
- PRIMARY KEY (relationship_id, memory_id)
2988
- );
2989
-
2990
- CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
2991
- CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
2992
- CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
2993
- CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
2994
- CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
2995
-
2996
- CREATE TABLE IF NOT EXISTS hyperedges (
2997
- id TEXT PRIMARY KEY,
2998
- label TEXT NOT NULL,
2999
- relation TEXT NOT NULL,
3000
- confidence REAL DEFAULT 1.0,
3001
- timestamp TEXT NOT NULL
3002
- );
3003
-
3004
- CREATE TABLE IF NOT EXISTS hyperedge_nodes (
3005
- hyperedge_id TEXT NOT NULL,
3006
- entity_id TEXT NOT NULL,
3007
- PRIMARY KEY (hyperedge_id, entity_id)
3008
- );
3009
- `);
3010
- for (const col of [
3011
- "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
3012
- "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
3013
- ]) {
3014
- try {
3015
- await client.execute(col);
3016
- } catch {
3017
- }
3780
+ if (_daemonClient) {
3781
+ _daemonClient.close();
3782
+ _daemonClient = null;
3018
3783
  }
3019
- }
3020
- async function getReadyShardClient(projectName) {
3021
- const safeName = safeShardName(projectName);
3022
- let client = getShardClient(projectName);
3023
- try {
3024
- await ensureShardSchema(client);
3025
- return client;
3026
- } catch (err) {
3027
- const message = err instanceof Error ? err.message : String(err);
3028
- if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
3029
- client.close();
3030
- _shards.delete(safeName);
3031
- _shardLastAccess.delete(safeName);
3032
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3033
- if (existsSync7(dbPath)) {
3034
- const stat = statSync2(dbPath);
3035
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3036
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
3037
- renameSync3(dbPath, archivedPath);
3038
- process.stderr.write(
3039
- `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
3040
- `
3041
- );
3042
- }
3043
- client = getShardClient(projectName);
3044
- await ensureShardSchema(client);
3045
- return client;
3784
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3785
+ _adapterClient.close();
3786
+ }
3787
+ _adapterClient = null;
3788
+ if (_client) {
3789
+ _client.close();
3790
+ _client = null;
3791
+ _resilientClient = null;
3046
3792
  }
3047
3793
  }
3048
- function evictLRU() {
3049
- let oldest = null;
3050
- let oldestTime = Infinity;
3051
- for (const [name, time] of _shardLastAccess) {
3052
- if (time < oldestTime) {
3053
- oldestTime = time;
3054
- oldest = name;
3055
- }
3794
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
3795
+ var init_database = __esm({
3796
+ "src/lib/database.ts"() {
3797
+ "use strict";
3798
+ init_db_retry();
3799
+ init_employees();
3800
+ init_database_adapter();
3801
+ init_memory();
3802
+ _client = null;
3803
+ _resilientClient = null;
3804
+ _walCheckpointTimer = null;
3805
+ _daemonClient = null;
3806
+ _adapterClient = null;
3807
+ initTurso = initDatabase;
3808
+ SOFT_DELETE_RETENTION_DAYS = 7;
3809
+ disposeTurso = disposeDatabase;
3056
3810
  }
3057
- if (oldest) {
3058
- const client = _shards.get(oldest);
3059
- if (client) {
3060
- client.close();
3061
- }
3062
- _shards.delete(oldest);
3063
- _shardLastAccess.delete(oldest);
3811
+ });
3812
+
3813
+ // src/lib/state-bus.ts
3814
+ var StateBus, orgBus;
3815
+ var init_state_bus = __esm({
3816
+ "src/lib/state-bus.ts"() {
3817
+ "use strict";
3818
+ StateBus = class {
3819
+ handlers = /* @__PURE__ */ new Map();
3820
+ globalHandlers = /* @__PURE__ */ new Set();
3821
+ /** Emit an event to all subscribers */
3822
+ emit(event) {
3823
+ const typeHandlers = this.handlers.get(event.type);
3824
+ if (typeHandlers) {
3825
+ for (const handler of typeHandlers) {
3826
+ try {
3827
+ handler(event);
3828
+ } catch {
3829
+ }
3830
+ }
3831
+ }
3832
+ for (const handler of this.globalHandlers) {
3833
+ try {
3834
+ handler(event);
3835
+ } catch {
3836
+ }
3837
+ }
3838
+ }
3839
+ /** Subscribe to a specific event type */
3840
+ on(type, handler) {
3841
+ if (!this.handlers.has(type)) {
3842
+ this.handlers.set(type, /* @__PURE__ */ new Set());
3843
+ }
3844
+ this.handlers.get(type).add(handler);
3845
+ }
3846
+ /** Subscribe to ALL events */
3847
+ onAny(handler) {
3848
+ this.globalHandlers.add(handler);
3849
+ }
3850
+ /** Unsubscribe from a specific event type */
3851
+ off(type, handler) {
3852
+ this.handlers.get(type)?.delete(handler);
3853
+ }
3854
+ /** Unsubscribe from ALL events */
3855
+ offAny(handler) {
3856
+ this.globalHandlers.delete(handler);
3857
+ }
3858
+ /** Remove all listeners */
3859
+ clear() {
3860
+ this.handlers.clear();
3861
+ this.globalHandlers.clear();
3862
+ }
3863
+ };
3864
+ orgBus = new StateBus();
3064
3865
  }
3866
+ });
3867
+
3868
+ // src/lib/memory-write-governor.ts
3869
+ import { createHash } from "crypto";
3870
+ function normalizeMemoryText(text) {
3871
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3872
+ }
3873
+ function classifyMemoryType(input) {
3874
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3875
+ const tool = input.tool_name.toLowerCase();
3876
+ const text = input.raw_text.toLowerCase();
3877
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3878
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3879
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3880
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3881
+ if (tool.includes("checkpoint") || text.startsWith("context checkpoint")) return "checkpoint";
3882
+ if (tool.includes("sessionsummary") || tool.includes("session-summary")) return "summary";
3883
+ if (tool.includes("sessionend") || text.startsWith("session ended")) return "summary";
3884
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3885
+ if (tool === "store_memory" || tool === "manual") return "observation";
3886
+ return "raw";
3887
+ }
3888
+ function shouldDropMemory(text) {
3889
+ const normalized = normalizeMemoryText(text);
3890
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3891
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3892
+ return { drop: true, reason: "known_boilerplate_noise" };
3893
+ }
3894
+ return { drop: false };
3895
+ }
3896
+ function shouldSkipEmbedding(input) {
3897
+ const type = classifyMemoryType(input);
3898
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3899
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3900
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3901
+ return false;
3065
3902
  }
3066
- function evictIdleShards() {
3067
- const now = Date.now();
3068
- const toEvict = [];
3069
- for (const [name, lastAccess] of _shardLastAccess) {
3070
- if (now - lastAccess > SHARD_IDLE_MS) {
3071
- toEvict.push(name);
3903
+ function hashMemoryContent(text) {
3904
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3905
+ }
3906
+ function scopedDedupArgs(input) {
3907
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3908
+ }
3909
+ function governMemoryRecord(record) {
3910
+ const normalized = normalizeMemoryText(record.raw_text);
3911
+ const memoryType = classifyMemoryType({
3912
+ raw_text: normalized,
3913
+ agent_id: record.agent_id,
3914
+ project_name: record.project_name,
3915
+ tool_name: record.tool_name,
3916
+ memory_type: record.memory_type
3917
+ });
3918
+ const drop = shouldDropMemory(normalized);
3919
+ const skipEmbedding = shouldSkipEmbedding({
3920
+ raw_text: normalized,
3921
+ agent_id: record.agent_id,
3922
+ project_name: record.project_name,
3923
+ tool_name: record.tool_name,
3924
+ memory_type: memoryType
3925
+ });
3926
+ return {
3927
+ record: {
3928
+ ...record,
3929
+ raw_text: normalized,
3930
+ memory_type: memoryType,
3931
+ vector: skipEmbedding ? null : record.vector
3932
+ },
3933
+ contentHash: hashMemoryContent(normalized),
3934
+ shouldDrop: drop.drop,
3935
+ dropReason: drop.reason,
3936
+ skipEmbedding,
3937
+ hygiene: {
3938
+ dedup: true,
3939
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3072
3940
  }
3073
- }
3074
- for (const name of toEvict) {
3075
- const client = _shards.get(name);
3076
- if (client) {
3077
- client.close();
3941
+ };
3942
+ }
3943
+ async function findScopedDuplicate(input) {
3944
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3945
+ const client = getClient2();
3946
+ const args = scopedDedupArgs(input);
3947
+ let sql = `SELECT id FROM memories
3948
+ WHERE content_hash = ?
3949
+ AND agent_id = ?
3950
+ AND project_name = ?
3951
+ AND COALESCE(memory_type, 'raw') = ?
3952
+ AND COALESCE(status, 'active') != 'deleted'`;
3953
+ if (input.excludeId) {
3954
+ sql += " AND id != ?";
3955
+ args.push(input.excludeId);
3956
+ }
3957
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3958
+ const result = await client.execute({ sql, args });
3959
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3960
+ }
3961
+ async function runPostWriteMemoryHygiene(memoryId) {
3962
+ try {
3963
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3964
+ const client = getClient2();
3965
+ const current = await client.execute({
3966
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3967
+ importance, timestamp
3968
+ FROM memories
3969
+ WHERE id = ?
3970
+ LIMIT 1`,
3971
+ args: [memoryId]
3972
+ });
3973
+ const row = current.rows[0];
3974
+ if (!row) return;
3975
+ const memoryType = String(row.memory_type ?? "raw");
3976
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3977
+ const agentId = String(row.agent_id);
3978
+ const projectName = String(row.project_name);
3979
+ if (contentHash) {
3980
+ await client.execute({
3981
+ sql: `UPDATE memories
3982
+ SET status = 'deleted',
3983
+ outcome = COALESCE(outcome, 'superseded')
3984
+ WHERE id != ?
3985
+ AND content_hash = ?
3986
+ AND agent_id = ?
3987
+ AND project_name = ?
3988
+ AND COALESCE(memory_type, 'raw') = ?
3989
+ AND COALESCE(status, 'active') = 'active'`,
3990
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3991
+ });
3078
3992
  }
3079
- _shards.delete(name);
3080
- _shardLastAccess.delete(name);
3993
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3994
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3995
+ const old = await client.execute({
3996
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3997
+ args: [supersedesId]
3998
+ });
3999
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
4000
+ const newImportance = Number(row.importance ?? 0);
4001
+ await client.batch([
4002
+ {
4003
+ sql: `UPDATE memories
4004
+ SET status = 'archived',
4005
+ outcome = COALESCE(outcome, 'superseded')
4006
+ WHERE id = ?`,
4007
+ args: [supersedesId]
4008
+ },
4009
+ {
4010
+ sql: `UPDATE memories
4011
+ SET importance = MAX(COALESCE(importance, 5), ?),
4012
+ parent_memory_id = COALESCE(parent_memory_id, ?)
4013
+ WHERE id = ?`,
4014
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
4015
+ }
4016
+ ], "write");
4017
+ }
4018
+ } catch (err) {
4019
+ process.stderr.write(
4020
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
4021
+ `
4022
+ );
3081
4023
  }
3082
4024
  }
3083
- function getOpenShardCount() {
3084
- return _shards.size;
3085
- }
3086
- function disposeShards() {
3087
- if (_evictionTimer) {
3088
- clearInterval(_evictionTimer);
3089
- _evictionTimer = null;
3090
- }
3091
- for (const [, client] of _shards) {
3092
- client.close();
3093
- }
3094
- _shards.clear();
3095
- _shardLastAccess.clear();
3096
- _shardingEnabled = false;
3097
- _encryptionKey = null;
4025
+ function schedulePostWriteMemoryHygiene(memoryIds) {
4026
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
4027
+ if (memoryIds.length === 0) return;
4028
+ const run = () => {
4029
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
4030
+ };
4031
+ if (typeof setImmediate === "function") setImmediate(run);
4032
+ else setTimeout(run, 0);
3098
4033
  }
3099
- var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3100
- var init_shard_manager = __esm({
3101
- "src/lib/shard-manager.ts"() {
4034
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
4035
+ var init_memory_write_governor = __esm({
4036
+ "src/lib/memory-write-governor.ts"() {
3102
4037
  "use strict";
3103
- init_config();
3104
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3105
- SHARD_IDLE_MS = 5 * 60 * 1e3;
3106
- MAX_OPEN_SHARDS = 10;
3107
- EVICTION_INTERVAL_MS = 60 * 1e3;
3108
- _shards = /* @__PURE__ */ new Map();
3109
- _shardLastAccess = /* @__PURE__ */ new Map();
3110
- _evictionTimer = null;
3111
- _encryptionKey = null;
3112
- _shardingEnabled = false;
4038
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
4039
+ "decision",
4040
+ "adr",
4041
+ "behavior",
4042
+ "procedure"
4043
+ ]);
4044
+ NOISE_DROP_PATTERNS = [
4045
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
4046
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
4047
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
4048
+ /^\s*Intercom is a speedup, not delivery/im,
4049
+ /^\s*Context bar reads as USAGE not remaining/im
4050
+ ];
4051
+ SKIP_EMBED_PATTERNS = [
4052
+ /tmux capture-pane\b/i,
4053
+ /docker ps\b/i,
4054
+ /docker images\b/i,
4055
+ /git status\b/i,
4056
+ /grep .*node_modules/i,
4057
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
4058
+ ];
3113
4059
  }
3114
4060
  });
3115
4061
 
@@ -3151,6 +4097,12 @@ var init_platform_procedures = __esm({
3151
4097
  priority: "p0",
3152
4098
  content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3153
4099
  },
4100
+ {
4101
+ title: "Customer orchestration maturity \u2014 recommend, never trap",
4102
+ domain: "workflow",
4103
+ priority: "p1",
4104
+ content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
4105
+ },
3154
4106
  {
3155
4107
  title: "Single dispatch path \u2014 create_task only",
3156
4108
  domain: "workflow",
@@ -3209,6 +4161,12 @@ var init_platform_procedures = __esm({
3209
4161
  priority: "p0",
3210
4162
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
3211
4163
  },
4164
+ {
4165
+ title: "Commit discipline \u2014 never leave verified work floating",
4166
+ domain: "workflow",
4167
+ priority: "p1",
4168
+ content: "After any code-change batch passes typecheck/tests/build, run git status, summarize changed files, and commit with a clear message before ending the session. If work must remain uncommitted for review/dogfood, explicitly say so, list the files, and state the blocker. Never imply work is complete while verified changes are still floating locally."
4169
+ },
3212
4170
  {
3213
4171
  title: "Desktop and TUI are the same product",
3214
4172
  domain: "architecture",
@@ -3368,595 +4326,969 @@ ${p.content}`).join("\n\n");
3368
4326
  }
3369
4327
  });
3370
4328
 
3371
- // src/lib/worker-gate.ts
3372
- var worker_gate_exports = {};
3373
- __export(worker_gate_exports, {
3374
- MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
3375
- cleanupWorkerPid: () => cleanupWorkerPid,
3376
- registerWorkerPid: () => registerWorkerPid,
3377
- releaseBackfillLock: () => releaseBackfillLock,
3378
- tryAcquireBackfillLock: () => tryAcquireBackfillLock,
3379
- tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
3380
- });
3381
- import { readdirSync as readdirSync2, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
3382
- import path8 from "path";
3383
- function tryAcquireWorkerSlot() {
3384
- try {
3385
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3386
- const reservationId = `res-${process.pid}-${Date.now()}`;
3387
- const reservationPath = path8.join(WORKER_PID_DIR, `${reservationId}.pid`);
3388
- writeFileSync3(reservationPath, String(process.pid));
3389
- const files = readdirSync2(WORKER_PID_DIR);
3390
- let alive = 0;
3391
- for (const f of files) {
3392
- if (!f.endsWith(".pid")) continue;
3393
- if (f.startsWith("res-")) {
3394
- alive++;
3395
- continue;
3396
- }
3397
- const dashIdx = f.lastIndexOf("-");
3398
- const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
3399
- if (isNaN(pid)) continue;
3400
- try {
3401
- process.kill(pid, 0);
3402
- alive++;
3403
- } catch {
3404
- try {
3405
- unlinkSync3(path8.join(WORKER_PID_DIR, f));
3406
- } catch {
3407
- }
3408
- }
3409
- }
3410
- if (alive > MAX_CONCURRENT_WORKERS) {
3411
- try {
3412
- unlinkSync3(reservationPath);
3413
- } catch {
3414
- }
3415
- return false;
3416
- }
3417
- try {
3418
- unlinkSync3(reservationPath);
3419
- } catch {
3420
- }
3421
- return true;
3422
- } catch {
3423
- return true;
3424
- }
3425
- }
3426
- function registerWorkerPid(pid) {
3427
- try {
3428
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3429
- writeFileSync3(path8.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3430
- } catch {
3431
- }
3432
- }
3433
- function cleanupWorkerPid() {
3434
- try {
3435
- unlinkSync3(path8.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3436
- } catch {
3437
- }
3438
- }
3439
- function tryAcquireBackfillLock() {
3440
- try {
3441
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3442
- if (existsSync8(BACKFILL_LOCK)) {
3443
- try {
3444
- const pid = parseInt(
3445
- __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
3446
- 10
3447
- );
3448
- if (!isNaN(pid) && pid > 0) {
3449
- try {
3450
- process.kill(pid, 0);
3451
- return false;
3452
- } catch {
3453
- }
3454
- }
3455
- } catch {
3456
- }
3457
- }
3458
- writeFileSync3(BACKFILL_LOCK, String(process.pid));
3459
- return true;
3460
- } catch {
3461
- return true;
3462
- }
3463
- }
3464
- function releaseBackfillLock() {
3465
- try {
3466
- unlinkSync3(BACKFILL_LOCK);
3467
- } catch {
3468
- }
3469
- }
3470
- var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
3471
- var init_worker_gate = __esm({
3472
- "src/lib/worker-gate.ts"() {
3473
- "use strict";
3474
- init_config();
3475
- WORKER_PID_DIR = path8.join(EXE_AI_DIR, "worker-pids");
3476
- MAX_CONCURRENT_WORKERS = 3;
3477
- BACKFILL_LOCK = path8.join(WORKER_PID_DIR, "backfill.lock");
3478
- }
3479
- });
3480
-
3481
- // src/lib/db-backup.ts
3482
- var db_backup_exports = {};
3483
- __export(db_backup_exports, {
3484
- createBackup: () => createBackup,
3485
- findActiveDb: () => findActiveDb,
3486
- getBackupDir: () => getBackupDir,
3487
- getLatestBackup: () => getLatestBackup,
3488
- hasBackupToday: () => hasBackupToday,
3489
- listBackups: () => listBackups,
3490
- rotateBackups: () => rotateBackups
4329
+ // src/lib/memory-cards.ts
4330
+ var memory_cards_exports = {};
4331
+ __export(memory_cards_exports, {
4332
+ extractMemoryCards: () => extractMemoryCards,
4333
+ insertMemoryCardsForBatch: () => insertMemoryCardsForBatch,
4334
+ searchMemoryCards: () => searchMemoryCards
3491
4335
  });
3492
- import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync as statSync3 } from "fs";
3493
- import path9 from "path";
3494
- function findActiveDb() {
3495
- for (const name of DB_NAMES) {
3496
- const p = path9.join(EXE_AI_DIR, name);
3497
- if (existsSync9(p)) return p;
3498
- }
3499
- return null;
3500
- }
3501
- function createBackup(reason = "manual") {
3502
- const dbPath = findActiveDb();
3503
- if (!dbPath) return null;
3504
- mkdirSync4(BACKUP_DIR, { recursive: true });
3505
- const dbName = path9.basename(dbPath, ".db");
3506
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
3507
- const backupName = `${dbName}-${reason}-${timestamp}.db`;
3508
- const backupPath = path9.join(BACKUP_DIR, backupName);
3509
- copyFileSync(dbPath, backupPath);
3510
- const walPath = dbPath + "-wal";
3511
- if (existsSync9(walPath)) {
3512
- try {
3513
- copyFileSync(walPath, backupPath + "-wal");
3514
- } catch {
3515
- }
3516
- }
3517
- const shmPath = dbPath + "-shm";
3518
- if (existsSync9(shmPath)) {
3519
- try {
3520
- copyFileSync(shmPath, backupPath + "-shm");
3521
- } catch {
3522
- }
3523
- }
3524
- return backupPath;
3525
- }
3526
- function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
3527
- if (!existsSync9(BACKUP_DIR)) return 0;
3528
- const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
3529
- let deleted = 0;
3530
- try {
3531
- const files = readdirSync3(BACKUP_DIR);
3532
- for (const file of files) {
3533
- if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
3534
- const filePath = path9.join(BACKUP_DIR, file);
3535
- try {
3536
- const stat = statSync3(filePath);
3537
- if (stat.mtimeMs < cutoff) {
3538
- unlinkSync4(filePath);
3539
- deleted++;
3540
- }
3541
- } catch {
3542
- }
3543
- }
3544
- } catch {
3545
- }
3546
- return deleted;
3547
- }
3548
- function listBackups() {
3549
- if (!existsSync9(BACKUP_DIR)) return [];
3550
- try {
3551
- const files = readdirSync3(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
3552
- return files.map((name) => {
3553
- const p = path9.join(BACKUP_DIR, name);
3554
- const stat = statSync3(p);
3555
- return { path: p, name, size: stat.size, date: stat.mtime };
3556
- }).sort((a, b) => b.date.getTime() - a.date.getTime());
3557
- } catch {
3558
- return [];
4336
+ import { createHash as createHash2 } from "crypto";
4337
+ function stableId(memoryId, type, content) {
4338
+ return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4339
+ }
4340
+ function cleanText(text) {
4341
+ return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
4342
+ }
4343
+ function splitSentences(text) {
4344
+ return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
4345
+ }
4346
+ function inferCardType(sentence, toolName) {
4347
+ const lower = sentence.toLowerCase();
4348
+ if (toolName === "store_decision" || /\b(decided|decision|adr|approved|rejected)\b/.test(lower)) return "decision";
4349
+ if (/\b(prefers|preference|likes|dislikes|wants|doesn't want|does not want)\b/.test(lower)) return "preference";
4350
+ if (/\b(changed|updated|replaced|now|no longer|instead|supersedes)\b/.test(lower)) return "belief_update";
4351
+ if (toolName && ["Read", "Write", "Edit", "Bash"].includes(toolName)) return "code";
4352
+ if (/\b(meeting|deadline|shipped|launched|completed|failed|blocked|assigned|created)\b/.test(lower)) return "event";
4353
+ return "fact";
4354
+ }
4355
+ function extractSubject(sentence, agentId) {
4356
+ const explicit = sentence.match(/\b([A-Z][a-zA-Z0-9_-]{2,}(?:\s+[A-Z][a-zA-Z0-9_-]{2,})?)\b/);
4357
+ return explicit?.[1] ?? agentId;
4358
+ }
4359
+ function predicateFor(type) {
4360
+ switch (type) {
4361
+ case "preference":
4362
+ return "prefers";
4363
+ case "belief_update":
4364
+ return "updated";
4365
+ case "decision":
4366
+ return "decided";
4367
+ case "event":
4368
+ return "happened";
4369
+ case "code":
4370
+ return "implemented";
4371
+ default:
4372
+ return "states";
4373
+ }
4374
+ }
4375
+ function extractMemoryCards(row) {
4376
+ const sentences = splitSentences(row.raw_text);
4377
+ const cards = [];
4378
+ for (const sentence of sentences) {
4379
+ const type = inferCardType(sentence, row.tool_name);
4380
+ const subject = extractSubject(sentence, row.agent_id);
4381
+ const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
4382
+ cards.push({
4383
+ id: stableId(row.id, type, content),
4384
+ memory_id: row.id,
4385
+ agent_id: row.agent_id,
4386
+ session_id: row.session_id,
4387
+ project_name: row.project_name ?? null,
4388
+ timestamp: row.timestamp,
4389
+ card_type: type,
4390
+ subject,
4391
+ predicate: predicateFor(type),
4392
+ object: content,
4393
+ content,
4394
+ source_ref: row.id,
4395
+ confidence: type === "fact" ? 0.55 : 0.65
4396
+ });
4397
+ if (cards.length >= MAX_CARDS_PER_MEMORY) break;
3559
4398
  }
4399
+ return cards;
3560
4400
  }
3561
- function hasBackupToday(reason) {
3562
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3563
- const backups = listBackups();
3564
- return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3565
- }
3566
- function getLatestBackup() {
3567
- const backups = listBackups();
3568
- return backups.length > 0 ? backups[0].path : null;
3569
- }
3570
- function getBackupDir() {
3571
- return BACKUP_DIR;
4401
+ async function insertMemoryCardsForBatch(rows) {
4402
+ const cards = rows.flatMap(extractMemoryCards);
4403
+ if (cards.length === 0) return 0;
4404
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4405
+ const client = getClient();
4406
+ const stmts = cards.map((card) => ({
4407
+ sql: `INSERT OR IGNORE INTO memory_cards
4408
+ (id, memory_id, agent_id, session_id, project_name, timestamp, card_type,
4409
+ subject, predicate, object, content, source_ref, confidence, active, created_at)
4410
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)`,
4411
+ args: [
4412
+ card.id,
4413
+ card.memory_id,
4414
+ card.agent_id,
4415
+ card.session_id,
4416
+ card.project_name,
4417
+ card.timestamp,
4418
+ card.card_type,
4419
+ card.subject,
4420
+ card.predicate,
4421
+ card.object,
4422
+ card.content,
4423
+ card.source_ref,
4424
+ card.confidence,
4425
+ now
4426
+ ]
4427
+ }));
4428
+ await client.batch(stmts, "write");
4429
+ return cards.length;
4430
+ }
4431
+ function buildMatchExpr(queryText) {
4432
+ const terms = queryText.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_]/g, "")).filter((t) => t.length >= 3).slice(0, 12);
4433
+ if (terms.length === 0) return null;
4434
+ return terms.map((t) => `${t}*`).join(terms.length >= 3 ? " AND " : " OR ");
4435
+ }
4436
+ async function searchMemoryCards(queryText, agentId, options) {
4437
+ const limit = options?.limit ?? 10;
4438
+ const matchExpr = buildMatchExpr(queryText);
4439
+ if (!matchExpr) return [];
4440
+ let sql = `SELECT c.id, c.memory_id, c.agent_id, c.session_id, c.project_name,
4441
+ c.timestamp, c.card_type, c.content, c.source_ref, c.confidence
4442
+ FROM memory_cards c
4443
+ JOIN memory_cards_fts fts ON c.rowid = fts.rowid
4444
+ WHERE memory_cards_fts MATCH ?
4445
+ AND c.agent_id = ?
4446
+ AND COALESCE(c.active, 1) = 1`;
4447
+ const args = [matchExpr, agentId];
4448
+ if (options?.projectName) {
4449
+ sql += ` AND c.project_name = ?`;
4450
+ args.push(options.projectName);
4451
+ }
4452
+ if (options?.since) {
4453
+ sql += ` AND c.timestamp >= ?`;
4454
+ args.push(options.since);
4455
+ }
4456
+ sql += ` ORDER BY rank LIMIT ?`;
4457
+ args.push(limit);
4458
+ const result = await getClient().execute({ sql, args });
4459
+ return result.rows.map((row) => ({
4460
+ id: `card:${String(row.id)}`,
4461
+ agent_id: String(row.agent_id),
4462
+ agent_role: "memory_card",
4463
+ session_id: String(row.session_id),
4464
+ timestamp: String(row.timestamp),
4465
+ tool_name: `memory_card:${String(row.card_type)}`,
4466
+ project_name: row.project_name == null ? "" : String(row.project_name),
4467
+ has_error: false,
4468
+ raw_text: `[${String(row.card_type)}] ${String(row.content)}
4469
+ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4470
+ vector: [],
4471
+ importance: 6,
4472
+ status: "active",
4473
+ confidence: Number(row.confidence ?? 0.6),
4474
+ last_accessed: String(row.timestamp)
4475
+ }));
3572
4476
  }
3573
- var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
3574
- var init_db_backup = __esm({
3575
- "src/lib/db-backup.ts"() {
4477
+ var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
4478
+ var init_memory_cards = __esm({
4479
+ "src/lib/memory-cards.ts"() {
3576
4480
  "use strict";
3577
- init_config();
3578
- BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
3579
- DEFAULT_KEEP_DAYS = 3;
3580
- DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
4481
+ init_database();
4482
+ MAX_CARDS_PER_MEMORY = 6;
4483
+ MAX_SENTENCE_CHARS = 360;
3581
4484
  }
3582
4485
  });
3583
4486
 
3584
- // src/bin/exe-doctor.ts
3585
- import os6 from "os";
3586
-
3587
4487
  // src/lib/store.ts
3588
- init_memory();
3589
- init_database();
3590
-
3591
- // src/lib/keychain.ts
3592
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3593
- import { existsSync as existsSync6 } from "fs";
3594
- import { execSync as execSync2 } from "child_process";
3595
- import path6 from "path";
3596
- import os5 from "os";
3597
- var SERVICE = "exe-mem";
3598
- var ACCOUNT = "master-key";
3599
- function getKeyDir() {
3600
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3601
- }
3602
- function getKeyPath() {
3603
- return path6.join(getKeyDir(), "master.key");
3604
- }
3605
- function macKeychainGet() {
3606
- if (process.platform !== "darwin") return null;
3607
- try {
3608
- return execSync2(
3609
- `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3610
- { encoding: "utf-8", timeout: 5e3 }
3611
- ).trim();
3612
- } catch {
3613
- return null;
4488
+ var store_exports = {};
4489
+ __export(store_exports, {
4490
+ attachDocumentMetadata: () => attachDocumentMetadata,
4491
+ buildRawVisibilityFilter: () => buildRawVisibilityFilter,
4492
+ buildWikiScopeFilter: () => buildWikiScopeFilter,
4493
+ classifyTier: () => classifyTier,
4494
+ disposeStore: () => disposeStore,
4495
+ flushBatch: () => flushBatch,
4496
+ flushTier3: () => flushTier3,
4497
+ getMemoryCardinality: () => getMemoryCardinality,
4498
+ initStore: () => initStore,
4499
+ reserveVersions: () => reserveVersions,
4500
+ searchMemories: () => searchMemories,
4501
+ updateMemoryStatus: () => updateMemoryStatus,
4502
+ vectorToBlob: () => vectorToBlob,
4503
+ writeMemory: () => writeMemory
4504
+ });
4505
+ function isBusyError2(err) {
4506
+ if (err instanceof Error) {
4507
+ const msg = err.message.toLowerCase();
4508
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
3614
4509
  }
4510
+ return false;
3615
4511
  }
3616
- function macKeychainSet(value) {
3617
- if (process.platform !== "darwin") return false;
3618
- try {
4512
+ async function retryOnBusy2(fn, label) {
4513
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
3619
4514
  try {
3620
- execSync2(
3621
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3622
- { timeout: 5e3 }
4515
+ return await fn();
4516
+ } catch (err) {
4517
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
4518
+ process.stderr.write(
4519
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
4520
+ `
3623
4521
  );
3624
- } catch {
4522
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
3625
4523
  }
3626
- execSync2(
3627
- `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3628
- { timeout: 5e3 }
3629
- );
3630
- return true;
3631
- } catch {
3632
- return false;
3633
4524
  }
4525
+ throw new Error("unreachable");
3634
4526
  }
3635
- function linuxSecretGet() {
3636
- if (process.platform !== "linux") return null;
3637
- try {
3638
- return execSync2(
3639
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3640
- { encoding: "utf-8", timeout: 5e3 }
3641
- ).trim();
3642
- } catch {
3643
- return null;
4527
+ async function initStore(options) {
4528
+ if (_flushTimer !== null) {
4529
+ clearInterval(_flushTimer);
4530
+ _flushTimer = null;
3644
4531
  }
3645
- }
3646
- function linuxSecretSet(value) {
3647
- if (process.platform !== "linux") return false;
3648
- try {
3649
- execSync2(
3650
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3651
- { timeout: 5e3 }
3652
- );
3653
- return true;
3654
- } catch {
3655
- return false;
4532
+ _pendingRecords = [];
4533
+ _flushing = false;
4534
+ _batchSize = options?.batchSize ?? 20;
4535
+ _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
4536
+ let dbPath = options?.dbPath;
4537
+ if (!dbPath) {
4538
+ const config = await loadConfig();
4539
+ dbPath = config.dbPath;
3656
4540
  }
3657
- }
3658
- async function tryKeytar() {
3659
- try {
3660
- return await import("keytar");
3661
- } catch {
3662
- return null;
4541
+ let masterKey = options?.masterKey ?? null;
4542
+ if (!masterKey) {
4543
+ masterKey = await getMasterKey();
4544
+ if (!masterKey) {
4545
+ throw new Error(
4546
+ "No encryption key found. Run /exe-setup to generate one."
4547
+ );
4548
+ }
3663
4549
  }
3664
- }
3665
- var ENCRYPTED_PREFIX = "enc:";
3666
- function deriveMachineKey() {
4550
+ const hexKey = masterKey.toString("hex");
4551
+ await initTurso({
4552
+ dbPath,
4553
+ encryptionKey: hexKey
4554
+ });
4555
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
3667
4556
  try {
3668
- const crypto2 = __require("crypto");
3669
- const material = [
3670
- os5.hostname(),
3671
- os5.userInfo().username,
3672
- os5.arch(),
3673
- os5.platform(),
3674
- // Machine ID on Linux (stable across reboots)
3675
- process.platform === "linux" ? readMachineId() : ""
3676
- ].join("|");
3677
- return crypto2.createHash("sha256").update(material).digest();
4557
+ const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4558
+ await initDaemonClient2();
3678
4559
  } catch {
3679
- return null;
3680
4560
  }
3681
- }
3682
- function readMachineId() {
3683
- try {
3684
- const { readFileSync: readFileSync6 } = __require("fs");
3685
- return readFileSync6("/etc/machine-id", "utf-8").trim();
3686
- } catch {
3687
- return "";
4561
+ if (!options?.lightweight) {
4562
+ try {
4563
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4564
+ initShardManager2(hexKey);
4565
+ } catch {
4566
+ }
4567
+ const client = getClient();
4568
+ const vResult = await retryOnBusy2(
4569
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
4570
+ "version-query"
4571
+ );
4572
+ _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4573
+ try {
4574
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
4575
+ await loadGlobalProcedures2();
4576
+ } catch {
4577
+ }
3688
4578
  }
3689
4579
  }
3690
- function encryptWithMachineKey(plaintext, machineKey) {
3691
- const crypto2 = __require("crypto");
3692
- const iv = crypto2.randomBytes(12);
3693
- const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3694
- let encrypted = cipher.update(plaintext, "utf-8", "base64");
3695
- encrypted += cipher.final("base64");
3696
- const authTag = cipher.getAuthTag().toString("base64");
3697
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
4580
+ function classifyTier(record) {
4581
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
4582
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
4583
+ return 3;
4584
+ }
4585
+ function inferFilePaths(record) {
4586
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
4587
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
4588
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
4589
+ return match ? JSON.stringify([match[1]]) : null;
4590
+ }
4591
+ function inferCommitHash(record) {
4592
+ if (record.tool_name !== "Bash") return null;
4593
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
4594
+ return match ? match[1] : null;
4595
+ }
4596
+ function inferLanguageType(record) {
4597
+ const text = record.raw_text;
4598
+ if (!text || text.length < 10) return null;
4599
+ const trimmed = text.trimStart();
4600
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
4601
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
4602
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
4603
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
4604
+ return "mixed";
4605
+ }
4606
+ function inferDomain(record) {
4607
+ const proj = (record.project_name ?? "").toLowerCase();
4608
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
4609
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
4610
+ return null;
3698
4611
  }
3699
- function decryptWithMachineKey(encrypted, machineKey) {
3700
- if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3701
- try {
3702
- const crypto2 = __require("crypto");
3703
- const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3704
- if (parts.length !== 3) return null;
3705
- const [ivB64, tagB64, cipherB64] = parts;
3706
- const iv = Buffer.from(ivB64, "base64");
3707
- const authTag = Buffer.from(tagB64, "base64");
3708
- const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
3709
- decipher.setAuthTag(authTag);
3710
- let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3711
- decrypted += decipher.final("utf-8");
3712
- return decrypted;
3713
- } catch {
3714
- return null;
4612
+ async function writeMemory(record) {
4613
+ if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
4614
+ throw new Error(
4615
+ `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4616
+ );
3715
4617
  }
3716
- }
3717
- async function writeMachineBoundFileFallback(b64) {
3718
- const dir = getKeyDir();
3719
- await mkdir3(dir, { recursive: true });
3720
- const keyPath = getKeyPath();
3721
- const machineKey = deriveMachineKey();
3722
- if (machineKey) {
3723
- const encrypted = encryptWithMachineKey(b64, machineKey);
3724
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3725
- await chmod2(keyPath, 384);
3726
- return "encrypted";
4618
+ const governed = governMemoryRecord(record);
4619
+ if (governed.shouldDrop) return;
4620
+ record = governed.record;
4621
+ const contentHash = governed.contentHash;
4622
+ const memoryType = record.memory_type ?? "raw";
4623
+ if (_pendingRecords.some(
4624
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4625
+ )) {
4626
+ return;
3727
4627
  }
3728
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3729
- await chmod2(keyPath, 384);
3730
- return "plaintext";
3731
- }
3732
- async function getMasterKey() {
3733
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
3734
- if (nativeValue) {
3735
- return Buffer.from(nativeValue, "base64");
4628
+ try {
4629
+ const existing = await findScopedDuplicate({
4630
+ contentHash,
4631
+ agentId: record.agent_id,
4632
+ projectName: record.project_name,
4633
+ memoryType
4634
+ });
4635
+ if (existing) return;
4636
+ } catch {
3736
4637
  }
3737
- const keytar = await tryKeytar();
3738
- if (keytar) {
3739
- try {
3740
- const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3741
- if (keytarValue) {
3742
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3743
- if (migrated) {
3744
- process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3745
- }
3746
- return Buffer.from(keytarValue, "base64");
3747
- }
3748
- } catch {
4638
+ const dbRow = {
4639
+ id: record.id,
4640
+ agent_id: record.agent_id,
4641
+ agent_role: record.agent_role,
4642
+ session_id: record.session_id,
4643
+ timestamp: record.timestamp,
4644
+ tool_name: record.tool_name,
4645
+ project_name: record.project_name,
4646
+ has_error: record.has_error ? 1 : 0,
4647
+ raw_text: record.raw_text,
4648
+ vector: record.vector,
4649
+ version: 0,
4650
+ // Placeholder — assigned atomically at flush time
4651
+ task_id: record.task_id ?? null,
4652
+ importance: record.importance ?? 5,
4653
+ status: record.status ?? "active",
4654
+ confidence: record.confidence ?? 0.7,
4655
+ last_accessed: record.last_accessed ?? record.timestamp,
4656
+ workspace_id: record.workspace_id ?? null,
4657
+ document_id: record.document_id ?? null,
4658
+ user_id: record.user_id ?? null,
4659
+ char_offset: record.char_offset ?? null,
4660
+ page_number: record.page_number ?? null,
4661
+ source_path: record.source_path ?? null,
4662
+ source_type: record.source_type ?? null,
4663
+ tier: record.tier ?? classifyTier(record),
4664
+ supersedes_id: record.supersedes_id ?? null,
4665
+ draft: record.draft ? 1 : 0,
4666
+ memory_type: memoryType,
4667
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4668
+ content_hash: contentHash,
4669
+ intent: record.intent ?? null,
4670
+ outcome: record.outcome ?? null,
4671
+ domain: record.domain ?? inferDomain(record),
4672
+ referenced_entities: record.referenced_entities ?? null,
4673
+ retrieval_count: record.retrieval_count ?? 0,
4674
+ chain_position: record.chain_position ?? null,
4675
+ review_status: record.review_status ?? null,
4676
+ context_window_pct: record.context_window_pct ?? null,
4677
+ file_paths: record.file_paths ?? inferFilePaths(record),
4678
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
4679
+ duration_ms: record.duration_ms ?? null,
4680
+ token_cost: record.token_cost ?? null,
4681
+ audience: record.audience ?? null,
4682
+ language_type: record.language_type ?? inferLanguageType(record),
4683
+ parent_memory_id: record.parent_memory_id ?? null
4684
+ };
4685
+ _pendingRecords.push(dbRow);
4686
+ orgBus.emit({
4687
+ type: "memory_stored",
4688
+ agentId: record.agent_id,
4689
+ project: record.project_name,
4690
+ timestamp: record.timestamp
4691
+ });
4692
+ const MAX_PENDING = 1e3;
4693
+ if (_pendingRecords.length > MAX_PENDING) {
4694
+ const dropped = _pendingRecords.length - MAX_PENDING;
4695
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
4696
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
4697
+ }
4698
+ if (_flushTimer === null) {
4699
+ _flushTimer = setInterval(() => {
4700
+ void flushBatch();
4701
+ }, _flushIntervalMs);
4702
+ if (_flushTimer && typeof _flushTimer === "object" && "unref" in _flushTimer) {
4703
+ _flushTimer.unref();
3749
4704
  }
3750
4705
  }
3751
- const keyPath = getKeyPath();
3752
- if (!existsSync6(keyPath)) {
3753
- process.stderr.write(
3754
- `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3755
- `
3756
- );
3757
- return null;
4706
+ if (_pendingRecords.length >= _batchSize) {
4707
+ await flushBatch();
3758
4708
  }
4709
+ }
4710
+ async function flushBatch() {
4711
+ if (_flushing || _pendingRecords.length === 0) return 0;
4712
+ _flushing = true;
3759
4713
  try {
3760
- const content = (await readFile3(keyPath, "utf-8")).trim();
3761
- let b64Value;
3762
- if (content.startsWith(ENCRYPTED_PREFIX)) {
3763
- const machineKey = deriveMachineKey();
3764
- if (!machineKey) {
3765
- process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3766
- return null;
3767
- }
3768
- const decrypted = decryptWithMachineKey(content, machineKey);
3769
- if (!decrypted) {
3770
- process.stderr.write(
3771
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3772
- );
3773
- return null;
3774
- }
3775
- b64Value = decrypted;
3776
- } else {
3777
- b64Value = content;
4714
+ const batch = _pendingRecords.slice(0);
4715
+ const client = getClient();
4716
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
4717
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4718
+ for (const row of batch) {
4719
+ row.version = baseVersion++;
3778
4720
  }
3779
- const key = Buffer.from(b64Value, "base64");
3780
- const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3781
- if (migrated) {
3782
- process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3783
- try {
3784
- await unlink(keyPath);
3785
- process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3786
- } catch {
3787
- }
3788
- } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3789
- const fallback = await writeMachineBoundFileFallback(b64Value);
3790
- if (fallback === "encrypted") {
3791
- process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3792
- } else {
3793
- process.stderr.write(
3794
- "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3795
- );
3796
- }
4721
+ _nextVersion = baseVersion;
4722
+ const buildStmt = (row) => {
4723
+ const hasVector = row.vector !== null;
4724
+ const taskId = row.task_id ?? null;
4725
+ const importance = row.importance ?? 5;
4726
+ const status = row.status ?? "active";
4727
+ const confidence = row.confidence ?? 0.7;
4728
+ const lastAccessed = row.last_accessed ?? row.timestamp;
4729
+ const workspaceId = row.workspace_id ?? null;
4730
+ const documentId = row.document_id ?? null;
4731
+ const userId = row.user_id ?? null;
4732
+ const charOffset = row.char_offset ?? null;
4733
+ const pageNumber = row.page_number ?? null;
4734
+ const sourcePath = row.source_path ?? null;
4735
+ const sourceType = row.source_type ?? null;
4736
+ const tier = row.tier ?? 3;
4737
+ const supersedesId = row.supersedes_id ?? null;
4738
+ const draft = row.draft ? 1 : 0;
4739
+ const memoryType = row.memory_type ?? "raw";
4740
+ const trajectory = row.trajectory ?? null;
4741
+ const contentHash = row.content_hash ?? null;
4742
+ const intent = row.intent ?? null;
4743
+ const outcome = row.outcome ?? null;
4744
+ const domain = row.domain ?? null;
4745
+ const referencedEntities = row.referenced_entities ?? null;
4746
+ const retrievalCount = row.retrieval_count ?? 0;
4747
+ const chainPosition = row.chain_position ?? null;
4748
+ const reviewStatus = row.review_status ?? null;
4749
+ const contextWindowPct = row.context_window_pct ?? null;
4750
+ const filePaths = row.file_paths ?? null;
4751
+ const commitHash = row.commit_hash ?? null;
4752
+ const durationMs = row.duration_ms ?? null;
4753
+ const tokenCost = row.token_cost ?? null;
4754
+ const audience = row.audience ?? null;
4755
+ const languageType = row.language_type ?? null;
4756
+ const parentMemoryId = row.parent_memory_id ?? null;
4757
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
4758
+ tool_name, project_name,
4759
+ has_error, raw_text, vector, version, task_id, importance, status,
4760
+ confidence, last_accessed,
4761
+ workspace_id, document_id, user_id, char_offset, page_number,
4762
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
4763
+ intent, outcome, domain, referenced_entities, retrieval_count,
4764
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
4765
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
4766
+ const metaArgs = [
4767
+ intent,
4768
+ outcome,
4769
+ domain,
4770
+ referencedEntities,
4771
+ retrievalCount,
4772
+ chainPosition,
4773
+ reviewStatus,
4774
+ contextWindowPct,
4775
+ filePaths,
4776
+ commitHash,
4777
+ durationMs,
4778
+ tokenCost,
4779
+ audience,
4780
+ languageType,
4781
+ parentMemoryId
4782
+ ];
4783
+ const baseArgs = [
4784
+ row.id,
4785
+ row.agent_id,
4786
+ row.agent_role,
4787
+ row.session_id,
4788
+ row.timestamp,
4789
+ row.tool_name,
4790
+ row.project_name,
4791
+ row.has_error,
4792
+ row.raw_text
4793
+ ];
4794
+ const sharedArgs = [
4795
+ row.version,
4796
+ taskId,
4797
+ importance,
4798
+ status,
4799
+ confidence,
4800
+ lastAccessed,
4801
+ workspaceId,
4802
+ documentId,
4803
+ userId,
4804
+ charOffset,
4805
+ pageNumber,
4806
+ sourcePath,
4807
+ sourceType,
4808
+ tier,
4809
+ supersedesId,
4810
+ draft,
4811
+ memoryType,
4812
+ trajectory,
4813
+ contentHash
4814
+ ];
4815
+ return {
4816
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
4817
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
4818
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4819
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
4820
+ };
4821
+ };
4822
+ const globalClient = getClient();
4823
+ const globalStmts = batch.map(buildStmt);
4824
+ await globalClient.batch(globalStmts, "write");
4825
+ try {
4826
+ const { insertMemoryCardsForBatch: insertMemoryCardsForBatch2 } = await Promise.resolve().then(() => (init_memory_cards(), memory_cards_exports));
4827
+ await insertMemoryCardsForBatch2(batch);
4828
+ } catch {
3797
4829
  }
3798
- return key;
3799
- } catch (err) {
3800
- process.stderr.write(
3801
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
4830
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4831
+ _pendingRecords.splice(0, batch.length);
4832
+ try {
4833
+ const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4834
+ if (isShardingEnabled2()) {
4835
+ const byProject = /* @__PURE__ */ new Map();
4836
+ let skippedUnknown = 0;
4837
+ for (const row of batch) {
4838
+ const proj = row.project_name?.trim();
4839
+ if (!proj) {
4840
+ skippedUnknown++;
4841
+ continue;
4842
+ }
4843
+ if (!byProject.has(proj)) byProject.set(proj, []);
4844
+ byProject.get(proj).push(row);
4845
+ }
4846
+ if (skippedUnknown > 0) {
4847
+ process.stderr.write(
4848
+ `[store] Shard skip: ${skippedUnknown} record(s) with empty project_name (kept in main DB only)
3802
4849
  `
3803
- );
3804
- return null;
3805
- }
3806
- }
3807
-
3808
- // src/lib/store.ts
3809
- init_config();
3810
-
3811
- // src/lib/state-bus.ts
3812
- var StateBus = class {
3813
- handlers = /* @__PURE__ */ new Map();
3814
- globalHandlers = /* @__PURE__ */ new Set();
3815
- /** Emit an event to all subscribers */
3816
- emit(event) {
3817
- const typeHandlers = this.handlers.get(event.type);
3818
- if (typeHandlers) {
3819
- for (const handler of typeHandlers) {
3820
- try {
3821
- handler(event);
3822
- } catch {
4850
+ );
4851
+ }
4852
+ for (const [project, rows] of byProject) {
4853
+ try {
4854
+ const shardClient = await getReadyShardClient2(project);
4855
+ const shardStmts = rows.map(buildStmt);
4856
+ await shardClient.batch(shardStmts, "write");
4857
+ } catch (err) {
4858
+ const fullError = err instanceof Error ? `${err.name}: ${err.message}${err.stack ? `
4859
+ ${err.stack.split("\n").slice(1, 3).join("\n")}` : ""}` : String(err);
4860
+ process.stderr.write(
4861
+ `[store] Shard write failed for ${project} (${rows.length} records): ${fullError}
4862
+ `
4863
+ );
4864
+ }
3823
4865
  }
3824
4866
  }
4867
+ } catch {
3825
4868
  }
3826
- for (const handler of this.globalHandlers) {
3827
- try {
3828
- handler(event);
3829
- } catch {
3830
- }
3831
- }
3832
- }
3833
- /** Subscribe to a specific event type */
3834
- on(type, handler) {
3835
- if (!this.handlers.has(type)) {
3836
- this.handlers.set(type, /* @__PURE__ */ new Set());
3837
- }
3838
- this.handlers.get(type).add(handler);
3839
- }
3840
- /** Subscribe to ALL events */
3841
- onAny(handler) {
3842
- this.globalHandlers.add(handler);
3843
- }
3844
- /** Unsubscribe from a specific event type */
3845
- off(type, handler) {
3846
- this.handlers.get(type)?.delete(handler);
3847
- }
3848
- /** Unsubscribe from ALL events */
3849
- offAny(handler) {
3850
- this.globalHandlers.delete(handler);
4869
+ return batch.length;
4870
+ } finally {
4871
+ _flushing = false;
3851
4872
  }
3852
- /** Remove all listeners */
3853
- clear() {
3854
- this.handlers.clear();
3855
- this.globalHandlers.clear();
4873
+ }
4874
+ function buildWikiScopeFilter(options, columnPrefix) {
4875
+ const args = [];
4876
+ let clause = "";
4877
+ if (options?.workspaceId !== void 0) {
4878
+ clause += ` AND ${columnPrefix}workspace_id = ?`;
4879
+ args.push(options.workspaceId);
4880
+ }
4881
+ if (options?.userId === void 0) {
4882
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
4883
+ } else if (options.userId === null) {
4884
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
4885
+ } else {
4886
+ clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
4887
+ args.push(options.userId);
3856
4888
  }
3857
- };
3858
- var orgBus = new StateBus();
3859
-
3860
- // src/lib/memory-write-governor.ts
3861
- import { createHash } from "crypto";
3862
-
3863
- // src/lib/store.ts
3864
- var INIT_MAX_RETRIES = 3;
3865
- var INIT_RETRY_DELAY_MS = 1e3;
3866
- function isBusyError2(err) {
3867
- if (err instanceof Error) {
3868
- const msg = err.message.toLowerCase();
3869
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
4889
+ return { clause, args };
4890
+ }
4891
+ function buildRawVisibilityFilter(options, columnPrefix) {
4892
+ if (options?.includeRaw === false) {
4893
+ return {
4894
+ clause: ` AND COALESCE(${columnPrefix}memory_type, 'raw') != 'raw'`,
4895
+ args: []
4896
+ };
3870
4897
  }
3871
- return false;
4898
+ return { clause: "", args: [] };
3872
4899
  }
3873
- async function retryOnBusy2(fn, label) {
3874
- for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
3875
- try {
3876
- return await fn();
3877
- } catch (err) {
3878
- if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
3879
- process.stderr.write(
3880
- `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
3881
- `
3882
- );
3883
- await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
4900
+ async function searchMemories(queryVector, agentId, options) {
4901
+ let client;
4902
+ try {
4903
+ const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4904
+ if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
4905
+ client = await getReadyShardClient2(options.projectName);
4906
+ } else {
4907
+ client = getClient();
3884
4908
  }
4909
+ } catch {
4910
+ client = getClient();
4911
+ }
4912
+ const limit = options?.limit ?? 10;
4913
+ const statusFilter = options?.includeArchived ? "" : `
4914
+ AND COALESCE(status, 'active') = 'active'`;
4915
+ const draftFilter = options?.includeDrafts ? "" : `
4916
+ AND (draft = 0 OR draft IS NULL)`;
4917
+ let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4918
+ tool_name, project_name,
4919
+ has_error, raw_text, vector, importance, status,
4920
+ confidence, last_accessed,
4921
+ workspace_id, document_id, user_id,
4922
+ char_offset, page_number,
4923
+ source_path, source_type
4924
+ FROM memories
4925
+ WHERE agent_id = ?
4926
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
4927
+ AND COALESCE(confidence, 0.7) >= 0.3`;
4928
+ const args = [agentId];
4929
+ const scope = buildWikiScopeFilter(options, "");
4930
+ sql += scope.clause;
4931
+ args.push(...scope.args);
4932
+ const rawVisibility = buildRawVisibilityFilter(options, "");
4933
+ sql += rawVisibility.clause;
4934
+ args.push(...rawVisibility.args);
4935
+ if (options?.projectName) {
4936
+ sql += ` AND project_name = ?`;
4937
+ args.push(options.projectName);
4938
+ }
4939
+ if (options?.toolName) {
4940
+ sql += ` AND tool_name = ?`;
4941
+ args.push(options.toolName);
4942
+ }
4943
+ if (options?.hasError !== void 0) {
4944
+ sql += ` AND has_error = ?`;
4945
+ args.push(options.hasError ? 1 : 0);
4946
+ }
4947
+ if (options?.since) {
4948
+ sql += ` AND timestamp >= ?`;
4949
+ args.push(options.since);
4950
+ }
4951
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4952
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4953
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4954
+ args.push(...uniqueTypes);
4955
+ } else if (options?.memoryType) {
4956
+ sql += ` AND memory_type = ?`;
4957
+ args.push(options.memoryType);
4958
+ }
4959
+ sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
4960
+ args.push(vectorToBlob(queryVector));
4961
+ sql += ` LIMIT ?`;
4962
+ args.push(limit);
4963
+ const result = await client.execute({ sql, args });
4964
+ return result.rows.map((row) => ({
4965
+ id: row.id,
4966
+ agent_id: row.agent_id,
4967
+ agent_role: row.agent_role,
4968
+ session_id: row.session_id,
4969
+ timestamp: row.timestamp,
4970
+ tool_name: row.tool_name,
4971
+ project_name: row.project_name,
4972
+ has_error: row.has_error === 1,
4973
+ raw_text: row.raw_text,
4974
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
4975
+ importance: row.importance ?? 5,
4976
+ status: row.status ?? "active",
4977
+ confidence: row.confidence ?? 0.7,
4978
+ last_accessed: row.last_accessed ?? row.timestamp,
4979
+ workspace_id: row.workspace_id ?? null,
4980
+ document_id: row.document_id ?? null,
4981
+ user_id: row.user_id ?? null,
4982
+ char_offset: row.char_offset ?? null,
4983
+ page_number: row.page_number ?? null,
4984
+ source_path: row.source_path ?? null,
4985
+ source_type: row.source_type ?? null
4986
+ }));
4987
+ }
4988
+ async function attachDocumentMetadata(records) {
4989
+ const docIds = [
4990
+ ...new Set(
4991
+ records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
4992
+ )
4993
+ ];
4994
+ if (docIds.length === 0) return records;
4995
+ try {
4996
+ const client = getClient();
4997
+ const placeholders = docIds.map(() => "?").join(",");
4998
+ const result = await client.execute({
4999
+ sql: `SELECT id, filename, mime, source_type, uploaded_at
5000
+ FROM documents
5001
+ WHERE id IN (${placeholders})`,
5002
+ args: docIds
5003
+ });
5004
+ const byId = /* @__PURE__ */ new Map();
5005
+ for (const row of result.rows) {
5006
+ const id = row.id;
5007
+ byId.set(id, {
5008
+ document_id: id,
5009
+ filename: row.filename,
5010
+ mime: row.mime ?? null,
5011
+ source_type: row.source_type ?? null,
5012
+ uploaded_at: row.uploaded_at
5013
+ });
5014
+ }
5015
+ for (const record of records) {
5016
+ if (!record.document_id) continue;
5017
+ record.document_metadata = byId.get(record.document_id) ?? null;
5018
+ }
5019
+ } catch {
3885
5020
  }
3886
- throw new Error("unreachable");
5021
+ return records;
3887
5022
  }
3888
- var _pendingRecords = [];
3889
- var _batchSize = 20;
3890
- var _flushIntervalMs = 1e4;
3891
- var _flushTimer = null;
3892
- var _flushing = false;
3893
- var _nextVersion = 1;
3894
- async function initStore(options) {
5023
+ async function flushTier3(agentId, options) {
5024
+ const client = getClient();
5025
+ const maxAge = options?.maxAgeHours ?? 72;
5026
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
5027
+ if (options?.dryRun) {
5028
+ const result2 = await client.execute({
5029
+ sql: `SELECT COUNT(*) as cnt FROM memories
5030
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
5031
+ args: [agentId, cutoff]
5032
+ });
5033
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
5034
+ }
5035
+ const result = await client.execute({
5036
+ sql: `UPDATE memories SET status = 'archived'
5037
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
5038
+ args: [agentId, cutoff]
5039
+ });
5040
+ return { archived: result.rowsAffected };
5041
+ }
5042
+ async function disposeStore() {
3895
5043
  if (_flushTimer !== null) {
3896
5044
  clearInterval(_flushTimer);
3897
5045
  _flushTimer = null;
3898
5046
  }
5047
+ if (_pendingRecords.length > 0) {
5048
+ await flushBatch();
5049
+ }
5050
+ await disposeTurso();
3899
5051
  _pendingRecords = [];
3900
- _flushing = false;
3901
- _batchSize = options?.batchSize ?? 20;
3902
- _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
3903
- let dbPath = options?.dbPath;
3904
- if (!dbPath) {
3905
- const config = await loadConfig();
3906
- dbPath = config.dbPath;
5052
+ _nextVersion = 1;
5053
+ }
5054
+ function vectorToBlob(vector) {
5055
+ const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
5056
+ return JSON.stringify(Array.from(f32));
5057
+ }
5058
+ async function updateMemoryStatus(id, status) {
5059
+ const client = getClient();
5060
+ await client.execute({
5061
+ sql: `UPDATE memories SET status = ? WHERE id = ?`,
5062
+ args: [status, id]
5063
+ });
5064
+ }
5065
+ function reserveVersions(count) {
5066
+ const reserved = [];
5067
+ for (let i = 0; i < count; i++) {
5068
+ reserved.push(_nextVersion++);
3907
5069
  }
3908
- let masterKey = options?.masterKey ?? null;
3909
- if (!masterKey) {
3910
- masterKey = await getMasterKey();
3911
- if (!masterKey) {
3912
- throw new Error(
3913
- "No encryption key found. Run /exe-setup to generate one."
3914
- );
3915
- }
5070
+ return reserved;
5071
+ }
5072
+ async function getMemoryCardinality(agentId) {
5073
+ try {
5074
+ const client = getClient();
5075
+ const result = await client.execute({
5076
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
5077
+ args: [agentId]
5078
+ });
5079
+ return Number(result.rows[0]?.cnt) || 0;
5080
+ } catch {
5081
+ return 0;
5082
+ }
5083
+ }
5084
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
5085
+ var init_store = __esm({
5086
+ "src/lib/store.ts"() {
5087
+ "use strict";
5088
+ init_memory();
5089
+ init_database();
5090
+ init_keychain();
5091
+ init_config();
5092
+ init_state_bus();
5093
+ init_memory_write_governor();
5094
+ INIT_MAX_RETRIES = 3;
5095
+ INIT_RETRY_DELAY_MS = 1e3;
5096
+ _pendingRecords = [];
5097
+ _batchSize = 20;
5098
+ _flushIntervalMs = 1e4;
5099
+ _flushTimer = null;
5100
+ _flushing = false;
5101
+ _nextVersion = 1;
5102
+ }
5103
+ });
5104
+
5105
+ // src/bin/fast-db-init.ts
5106
+ var fast_db_init_exports = {};
5107
+ __export(fast_db_init_exports, {
5108
+ fastDbInit: () => fastDbInit
5109
+ });
5110
+ async function fastDbInit() {
5111
+ const { isInitialized: isInitialized2, getClient: getClient2, setExternalClient: setExternalClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5112
+ if (isInitialized2()) {
5113
+ return getClient2();
3916
5114
  }
3917
- const hexKey = masterKey.toString("hex");
3918
- await initTurso({
3919
- dbPath,
3920
- encryptionKey: hexKey
3921
- });
3922
- await retryOnBusy2(() => ensureSchema(), "ensureSchema");
3923
5115
  try {
3924
- const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3925
- await initDaemonClient2();
5116
+ const { connectEmbedDaemon: connectEmbedDaemon2, sendDaemonRequest: sendDaemonRequest2, isClientConnected: isClientConnected2 } = await Promise.resolve().then(() => (init_exe_daemon_client(), exe_daemon_client_exports));
5117
+ const { deserializeResultSet: deserializeResultSet2 } = await Promise.resolve().then(() => (init_daemon_protocol(), daemon_protocol_exports));
5118
+ await connectEmbedDaemon2();
5119
+ if (isClientConnected2()) {
5120
+ const daemonClient = {
5121
+ async execute(stmt) {
5122
+ const sql = typeof stmt === "string" ? stmt : stmt.sql;
5123
+ const args = typeof stmt === "string" ? [] : Array.isArray(stmt.args) ? stmt.args : [];
5124
+ const resp = await sendDaemonRequest2({ type: "db-execute", sql, args });
5125
+ if (resp.error) throw new Error(String(resp.error));
5126
+ if (resp.db) return deserializeResultSet2(resp.db);
5127
+ throw new Error("Unexpected daemon response");
5128
+ },
5129
+ async batch(stmts, mode) {
5130
+ const statements = stmts.map((s) => {
5131
+ const sql = typeof s === "string" ? s : s.sql;
5132
+ const args = typeof s === "string" ? [] : Array.isArray(s.args) ? s.args : [];
5133
+ return { sql, args };
5134
+ });
5135
+ const resp = await sendDaemonRequest2({ type: "db-batch", statements, mode: mode ?? "deferred" });
5136
+ if (resp.error) throw new Error(String(resp.error));
5137
+ const batchResults = resp["db-batch"];
5138
+ if (batchResults) return batchResults.map(deserializeResultSet2);
5139
+ throw new Error("Unexpected daemon batch response");
5140
+ },
5141
+ async transaction(_mode) {
5142
+ throw new Error("Transactions not supported via daemon socket");
5143
+ },
5144
+ async executeMultiple(_sql) {
5145
+ throw new Error("executeMultiple not supported via daemon socket");
5146
+ },
5147
+ async migrate(_stmts) {
5148
+ throw new Error("migrate not supported via daemon socket");
5149
+ },
5150
+ sync() {
5151
+ return Promise.resolve(void 0);
5152
+ },
5153
+ close() {
5154
+ },
5155
+ get closed() {
5156
+ return false;
5157
+ },
5158
+ get protocol() {
5159
+ return "file";
5160
+ }
5161
+ };
5162
+ setExternalClient2(daemonClient);
5163
+ return daemonClient;
5164
+ }
3926
5165
  } catch {
3927
5166
  }
3928
- if (!options?.lightweight) {
5167
+ const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
5168
+ await initStore2({ lightweight: true });
5169
+ return getClient2();
5170
+ }
5171
+ var init_fast_db_init = __esm({
5172
+ "src/bin/fast-db-init.ts"() {
5173
+ "use strict";
5174
+ }
5175
+ });
5176
+
5177
+ // src/lib/db-backup.ts
5178
+ var db_backup_exports = {};
5179
+ __export(db_backup_exports, {
5180
+ createBackup: () => createBackup,
5181
+ findActiveDb: () => findActiveDb,
5182
+ getBackupDir: () => getBackupDir,
5183
+ getLatestBackup: () => getLatestBackup,
5184
+ hasBackupToday: () => hasBackupToday,
5185
+ listBackups: () => listBackups,
5186
+ rotateBackups: () => rotateBackups
5187
+ });
5188
+ import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync as statSync3 } from "fs";
5189
+ import path10 from "path";
5190
+ function findActiveDb() {
5191
+ for (const name of DB_NAMES) {
5192
+ const p = path10.join(EXE_AI_DIR, name);
5193
+ if (existsSync10(p)) return p;
5194
+ }
5195
+ return null;
5196
+ }
5197
+ function createBackup(reason = "manual") {
5198
+ const dbPath = findActiveDb();
5199
+ if (!dbPath) return null;
5200
+ mkdirSync5(BACKUP_DIR, { recursive: true });
5201
+ const dbName = path10.basename(dbPath, ".db");
5202
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
5203
+ const backupName = `${dbName}-${reason}-${timestamp}.db`;
5204
+ const backupPath = path10.join(BACKUP_DIR, backupName);
5205
+ copyFileSync(dbPath, backupPath);
5206
+ const walPath = dbPath + "-wal";
5207
+ if (existsSync10(walPath)) {
3929
5208
  try {
3930
- const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3931
- initShardManager2(hexKey);
5209
+ copyFileSync(walPath, backupPath + "-wal");
3932
5210
  } catch {
3933
5211
  }
3934
- const client = getClient();
3935
- const vResult = await retryOnBusy2(
3936
- () => client.execute("SELECT MAX(version) as max_v FROM memories"),
3937
- "version-query"
3938
- );
3939
- _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
5212
+ }
5213
+ const shmPath = dbPath + "-shm";
5214
+ if (existsSync10(shmPath)) {
3940
5215
  try {
3941
- const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
3942
- await loadGlobalProcedures2();
5216
+ copyFileSync(shmPath, backupPath + "-shm");
3943
5217
  } catch {
3944
5218
  }
3945
5219
  }
5220
+ return backupPath;
5221
+ }
5222
+ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
5223
+ if (!existsSync10(BACKUP_DIR)) return 0;
5224
+ const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
5225
+ let deleted = 0;
5226
+ try {
5227
+ const files = readdirSync3(BACKUP_DIR);
5228
+ for (const file of files) {
5229
+ if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
5230
+ const filePath = path10.join(BACKUP_DIR, file);
5231
+ try {
5232
+ const stat = statSync3(filePath);
5233
+ if (stat.mtimeMs < cutoff) {
5234
+ unlinkSync4(filePath);
5235
+ deleted++;
5236
+ }
5237
+ } catch {
5238
+ }
5239
+ }
5240
+ } catch {
5241
+ }
5242
+ return deleted;
5243
+ }
5244
+ function listBackups() {
5245
+ if (!existsSync10(BACKUP_DIR)) return [];
5246
+ try {
5247
+ const files = readdirSync3(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
5248
+ return files.map((name) => {
5249
+ const p = path10.join(BACKUP_DIR, name);
5250
+ const stat = statSync3(p);
5251
+ return { path: p, name, size: stat.size, date: stat.mtime };
5252
+ }).sort((a, b) => b.date.getTime() - a.date.getTime());
5253
+ } catch {
5254
+ return [];
5255
+ }
5256
+ }
5257
+ function hasBackupToday(reason) {
5258
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5259
+ const backups = listBackups();
5260
+ return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3946
5261
  }
5262
+ function getLatestBackup() {
5263
+ const backups = listBackups();
5264
+ return backups.length > 0 ? backups[0].path : null;
5265
+ }
5266
+ function getBackupDir() {
5267
+ return BACKUP_DIR;
5268
+ }
5269
+ var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
5270
+ var init_db_backup = __esm({
5271
+ "src/lib/db-backup.ts"() {
5272
+ "use strict";
5273
+ init_config();
5274
+ BACKUP_DIR = path10.join(EXE_AI_DIR, "backups");
5275
+ DEFAULT_KEEP_DAYS = 3;
5276
+ DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
5277
+ }
5278
+ });
3947
5279
 
3948
5280
  // src/bin/exe-doctor.ts
3949
- init_database();
5281
+ import os6 from "os";
3950
5282
 
3951
5283
  // src/lib/is-main.ts
3952
5284
  import { realpathSync } from "fs";
3953
- import { fileURLToPath as fileURLToPath2 } from "url";
5285
+ import { fileURLToPath } from "url";
3954
5286
  function isMainModule(importMetaUrl) {
3955
5287
  if (process.argv[1] == null) return false;
3956
5288
  if (process.argv[1].includes("mcp/server")) return false;
3957
5289
  try {
3958
5290
  const scriptPath = realpathSync(process.argv[1]);
3959
- const modulePath = realpathSync(fileURLToPath2(importMetaUrl));
5291
+ const modulePath = realpathSync(fileURLToPath(importMetaUrl));
3960
5292
  return scriptPath === modulePath;
3961
5293
  } catch {
3962
5294
  return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
@@ -3964,9 +5296,9 @@ function isMainModule(importMetaUrl) {
3964
5296
  }
3965
5297
 
3966
5298
  // src/bin/exe-doctor.ts
3967
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
5299
+ import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
3968
5300
  import { spawn as spawn2 } from "child_process";
3969
- import path10 from "path";
5301
+ import path11 from "path";
3970
5302
  import { randomUUID as randomUUID3 } from "crypto";
3971
5303
 
3972
5304
  // src/lib/conflict-detector.ts
@@ -4229,6 +5561,140 @@ async function detectConflicts(client, projectFilter, agentFilter2) {
4229
5561
  };
4230
5562
  }
4231
5563
 
5564
+ // src/adapters/runtime-hook-manifest.ts
5565
+ var EXE_HOOKS = {
5566
+ postToolCombined: "dist/hooks/post-tool-combined.js",
5567
+ sessionStart: "dist/hooks/session-start.js",
5568
+ promptSubmit: "dist/hooks/prompt-submit.js",
5569
+ heartbeat: "dist/hooks/exe-heartbeat-hook.js",
5570
+ stop: "dist/hooks/stop.js",
5571
+ preToolUse: "dist/hooks/pre-tool-use.js",
5572
+ subagentStop: "dist/hooks/subagent-stop.js",
5573
+ preCompact: "dist/hooks/pre-compact.js",
5574
+ postCompact: "dist/hooks/post-compact.js",
5575
+ sessionEnd: "dist/hooks/session-end.js",
5576
+ notification: "dist/hooks/notification.js",
5577
+ instructionsLoaded: "dist/hooks/instructions-loaded.js"
5578
+ };
5579
+ var EXE_HOOK_MANIFEST = [
5580
+ {
5581
+ key: "postToolCombined",
5582
+ event: "PostToolUse",
5583
+ commandMarker: EXE_HOOKS.postToolCombined,
5584
+ owner: "exe-os",
5585
+ purpose: "Single PostToolUse entrypoint for ingestion, error recall, summaries, and bug detection.",
5586
+ runtimes: ["claude", "codex", "opencode"],
5587
+ checkpointRole: "none"
5588
+ },
5589
+ {
5590
+ key: "sessionStart",
5591
+ event: "SessionStart",
5592
+ commandMarker: EXE_HOOKS.sessionStart,
5593
+ owner: "exe-os",
5594
+ purpose: "Loads agent identity, procedures, and boot context.",
5595
+ runtimes: ["claude", "codex", "opencode"],
5596
+ checkpointRole: "none"
5597
+ },
5598
+ {
5599
+ key: "promptSubmit",
5600
+ event: "UserPromptSubmit",
5601
+ commandMarker: EXE_HOOKS.promptSubmit,
5602
+ owner: "exe-os",
5603
+ purpose: "Injects current tasks, pending reviews, and local context before each prompt.",
5604
+ runtimes: ["claude", "codex", "opencode"],
5605
+ checkpointRole: "none"
5606
+ },
5607
+ {
5608
+ key: "heartbeat",
5609
+ event: "UserPromptSubmit",
5610
+ commandMarker: EXE_HOOKS.heartbeat,
5611
+ owner: "exe-os",
5612
+ purpose: "Lightweight heartbeat/status sidecar for Claude Code sessions.",
5613
+ runtimes: ["claude"],
5614
+ checkpointRole: "none"
5615
+ },
5616
+ {
5617
+ key: "stop",
5618
+ event: "Stop",
5619
+ commandMarker: EXE_HOOKS.stop,
5620
+ owner: "exe-os",
5621
+ purpose: "Finalizes task state, capacity signals, and emergency checkpointing.",
5622
+ runtimes: ["claude", "codex", "opencode"],
5623
+ checkpointRole: "capacity_checkpoint"
5624
+ },
5625
+ {
5626
+ key: "preToolUse",
5627
+ event: "PreToolUse",
5628
+ commandMarker: EXE_HOOKS.preToolUse,
5629
+ owner: "exe-os",
5630
+ purpose: "Preflight guardrails before shell/tool execution.",
5631
+ runtimes: ["claude", "codex", "opencode"],
5632
+ checkpointRole: "none"
5633
+ },
5634
+ {
5635
+ key: "subagentStop",
5636
+ event: "SubagentStop",
5637
+ commandMarker: EXE_HOOKS.subagentStop,
5638
+ owner: "exe-os",
5639
+ purpose: "Captures subagent completion context.",
5640
+ runtimes: ["claude"],
5641
+ checkpointRole: "none"
5642
+ },
5643
+ {
5644
+ key: "preCompact",
5645
+ event: "PreCompact",
5646
+ commandMarker: EXE_HOOKS.preCompact,
5647
+ owner: "exe-os",
5648
+ purpose: "Writes active-task snapshot and compaction recovery context.",
5649
+ runtimes: ["claude"],
5650
+ checkpointRole: "recovery_context"
5651
+ },
5652
+ {
5653
+ key: "postCompact",
5654
+ event: "PostCompact",
5655
+ commandMarker: EXE_HOOKS.postCompact,
5656
+ owner: "exe-os",
5657
+ purpose: "Rehydrates recovery context after compaction.",
5658
+ runtimes: ["claude"],
5659
+ checkpointRole: "recovery_context"
5660
+ },
5661
+ {
5662
+ key: "sessionEnd",
5663
+ event: "SessionEnd",
5664
+ commandMarker: EXE_HOOKS.sessionEnd,
5665
+ owner: "exe-os",
5666
+ purpose: "Stores session-end checkpoint and triages orphaned in-progress tasks.",
5667
+ runtimes: ["claude"],
5668
+ checkpointRole: "session_summary"
5669
+ },
5670
+ {
5671
+ key: "notification",
5672
+ event: "Notification",
5673
+ commandMarker: EXE_HOOKS.notification,
5674
+ owner: "exe-os",
5675
+ purpose: "Captures runtime notifications and nudges.",
5676
+ runtimes: ["claude"],
5677
+ checkpointRole: "none"
5678
+ },
5679
+ {
5680
+ key: "instructionsLoaded",
5681
+ event: "InstructionsLoaded",
5682
+ commandMarker: EXE_HOOKS.instructionsLoaded,
5683
+ owner: "exe-os",
5684
+ purpose: "Applies runtime instruction post-processing.",
5685
+ runtimes: ["claude"],
5686
+ checkpointRole: "none"
5687
+ }
5688
+ ];
5689
+ var LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS = [
5690
+ "dist/hooks/ingest.js",
5691
+ "dist/hooks/error-recall.js",
5692
+ "dist/hooks/ingest-worker.js"
5693
+ ];
5694
+ function manifestEntryForCommand(command) {
5695
+ return EXE_HOOK_MANIFEST.find((entry) => command.includes(entry.commandMarker));
5696
+ }
5697
+
4232
5698
  // src/bin/exe-doctor.ts
4233
5699
  function parseFlags(argv) {
4234
5700
  const flags = { fix: false, dryRun: false, verbose: false, conflicts: false };
@@ -4380,7 +5846,7 @@ async function auditOrphanedProjects(client) {
4380
5846
  for (const row of result.rows) {
4381
5847
  const name = row.project_name;
4382
5848
  const count = Number(row.cnt);
4383
- const exists = existsSync10(path10.join(home, name)) || existsSync10(path10.join(home, "..", name)) || existsSync10(path10.join(process.cwd(), "..", name));
5849
+ const exists = existsSync11(path11.join(home, name)) || existsSync11(path11.join(home, "..", name)) || existsSync11(path11.join(process.cwd(), "..", name));
4384
5850
  if (!exists) {
4385
5851
  orphans.push({ project_name: name, count });
4386
5852
  }
@@ -4388,18 +5854,18 @@ async function auditOrphanedProjects(client) {
4388
5854
  return orphans;
4389
5855
  }
4390
5856
  function auditHookHealth() {
4391
- const logPath = path10.join(
5857
+ const logPath = path11.join(
4392
5858
  process.env.HOME ?? process.env.USERPROFILE ?? "",
4393
5859
  ".exe-os",
4394
5860
  "logs",
4395
5861
  "hooks.log"
4396
5862
  );
4397
- if (!existsSync10(logPath)) {
5863
+ if (!existsSync11(logPath)) {
4398
5864
  return { logExists: false, totalLines: 0, errorsLastHour: 0, topPatterns: [] };
4399
5865
  }
4400
5866
  let content;
4401
5867
  try {
4402
- content = readFileSync5(logPath, "utf-8");
5868
+ content = readFileSync6(logPath, "utf-8");
4403
5869
  } catch {
4404
5870
  return { logExists: false, totalLines: 0, errorsLastHour: 0, topPatterns: [] };
4405
5871
  }
@@ -4427,6 +5893,121 @@ function auditHookHealth() {
4427
5893
  const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
4428
5894
  return { logExists: true, totalLines, errorsLastHour, topPatterns };
4429
5895
  }
5896
+ function safeReadJson(filePath) {
5897
+ if (!existsSync11(filePath)) return null;
5898
+ try {
5899
+ return JSON.parse(readFileSync6(filePath, "utf-8"));
5900
+ } catch {
5901
+ return null;
5902
+ }
5903
+ }
5904
+ function collectHookCommandsFromClaudeSettings(settings) {
5905
+ const hooks = settings?.hooks;
5906
+ if (!hooks || typeof hooks !== "object") return [];
5907
+ const commands = [];
5908
+ for (const [event, groups] of Object.entries(hooks)) {
5909
+ if (!Array.isArray(groups)) continue;
5910
+ for (const group of groups) {
5911
+ const hooksForGroup = group?.hooks;
5912
+ if (!Array.isArray(hooksForGroup)) continue;
5913
+ for (const hook of hooksForGroup) {
5914
+ const command = hook?.command;
5915
+ if (typeof command === "string") commands.push({ event, command });
5916
+ }
5917
+ }
5918
+ }
5919
+ return commands;
5920
+ }
5921
+ function collectHookCommandsFromCodexHooks(config) {
5922
+ const commands = [];
5923
+ if (!config || typeof config !== "object") return commands;
5924
+ const root = config;
5925
+ for (const [event, value] of Object.entries(root)) {
5926
+ const entries = Array.isArray(value) ? value : value && typeof value === "object" ? Object.values(value) : [];
5927
+ for (const entry of entries) {
5928
+ if (typeof entry === "string") commands.push({ event, command: entry });
5929
+ const command = entry?.command;
5930
+ if (typeof command === "string") commands.push({ event, command });
5931
+ }
5932
+ }
5933
+ return commands;
5934
+ }
5935
+ function buildHookOwnershipIssues(runtime, commands) {
5936
+ const issues = [];
5937
+ for (const marker of LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS) {
5938
+ const count = commands.filter((cmd) => cmd.command.includes(marker)).length;
5939
+ if (count > 0) {
5940
+ issues.push({
5941
+ runtime,
5942
+ event: "PostToolUse",
5943
+ marker,
5944
+ count,
5945
+ message: `Legacy split PostToolUse hook still installed: ${marker}`
5946
+ });
5947
+ }
5948
+ }
5949
+ for (const entry of EXE_HOOK_MANIFEST.filter((hook) => hook.runtimes.includes(runtime))) {
5950
+ const matching = commands.filter((cmd) => cmd.command.includes(entry.commandMarker));
5951
+ if (matching.length > 1) {
5952
+ issues.push({
5953
+ runtime,
5954
+ event: entry.event,
5955
+ marker: entry.commandMarker,
5956
+ count: matching.length,
5957
+ message: `Duplicate exe-os hook owner for ${entry.event}: ${entry.commandMarker}`
5958
+ });
5959
+ }
5960
+ for (const cmd of matching) {
5961
+ if (cmd.event !== entry.event) {
5962
+ issues.push({
5963
+ runtime,
5964
+ event: cmd.event,
5965
+ marker: entry.commandMarker,
5966
+ count: 1,
5967
+ message: `exe-os hook ${entry.commandMarker} is registered under ${cmd.event}, expected ${entry.event}`
5968
+ });
5969
+ }
5970
+ }
5971
+ }
5972
+ for (const cmd of commands) {
5973
+ if (!cmd.command.includes("dist/hooks/")) continue;
5974
+ if (!cmd.command.includes("exe-os")) continue;
5975
+ const entry = manifestEntryForCommand(cmd.command);
5976
+ const isLegacy = LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS.some((marker) => cmd.command.includes(marker));
5977
+ if (!entry && !isLegacy) {
5978
+ issues.push({
5979
+ runtime,
5980
+ event: cmd.event,
5981
+ marker: "dist/hooks/",
5982
+ count: 1,
5983
+ message: `Unknown exe-os hook command not present in ownership manifest: ${cmd.command.slice(0, 160)}`
5984
+ });
5985
+ }
5986
+ }
5987
+ return issues;
5988
+ }
5989
+ function auditHookOwnership() {
5990
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
5991
+ const checkedFiles = [];
5992
+ const issues = [];
5993
+ const claudeSettingsPath = path11.join(home, ".claude", "settings.json");
5994
+ const claudeSettings = safeReadJson(claudeSettingsPath);
5995
+ if (claudeSettings) {
5996
+ checkedFiles.push(claudeSettingsPath);
5997
+ issues.push(...buildHookOwnershipIssues("claude", collectHookCommandsFromClaudeSettings(claudeSettings)));
5998
+ }
5999
+ const codexHooksPath = path11.join(home, ".codex", "hooks.json");
6000
+ const codexHooks = safeReadJson(codexHooksPath);
6001
+ if (codexHooks) {
6002
+ checkedFiles.push(codexHooksPath);
6003
+ issues.push(...buildHookOwnershipIssues("codex", collectHookCommandsFromCodexHooks(codexHooks)));
6004
+ }
6005
+ return {
6006
+ checkedFiles,
6007
+ issues,
6008
+ staleLegacyHooks: issues.filter((issue) => issue.message.includes("Legacy split"))
6009
+ };
6010
+ }
4430
6011
  async function auditShards() {
4431
6012
  try {
4432
6013
  const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4442,16 +6023,33 @@ async function auditShards() {
4442
6023
  return { total: 0, ok: 0, unreadable: 0, archived: 0, unreadableNames: [] };
4443
6024
  }
4444
6025
  }
6026
+ async function auditKeyHealth() {
6027
+ try {
6028
+ const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
6029
+ const { getKeyBackupStatus: getKeyBackupStatus2 } = await Promise.resolve().then(() => (init_key_backup_status(), key_backup_status_exports));
6030
+ const key = await getMasterKey2();
6031
+ const backup = getKeyBackupStatus2();
6032
+ return {
6033
+ masterKeyPresent: Boolean(key),
6034
+ recoveryBackupMarked: backup.exists,
6035
+ recoveryBackupConfirmedAt: backup.confirmedAt,
6036
+ recoveryBackupSource: backup.source
6037
+ };
6038
+ } catch {
6039
+ return { masterKeyPresent: false, recoveryBackupMarked: false };
6040
+ }
6041
+ }
4445
6042
  async function runAudit(client, flags) {
4446
6043
  const runConflicts = flags.conflicts || process.env.EXE_AUDIT_CONFLICTS === "1";
4447
- const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects, shards] = await Promise.all([
6044
+ const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects, shards, keyHealth] = await Promise.all([
4448
6045
  auditStats(client, flags),
4449
6046
  auditNullVectors(client, flags),
4450
6047
  auditDuplicates(client, flags),
4451
6048
  auditBloated(client, flags),
4452
6049
  auditFts(client),
4453
6050
  auditOrphanedProjects(client),
4454
- auditShards()
6051
+ auditShards(),
6052
+ auditKeyHealth()
4455
6053
  ]);
4456
6054
  let conflicts;
4457
6055
  if (runConflicts) {
@@ -4471,7 +6069,8 @@ async function runAudit(client, flags) {
4471
6069
  }
4472
6070
  const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
4473
6071
  const hookHealth = auditHookHealth();
4474
- return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, shards };
6072
+ const hookOwnership = auditHookOwnership();
6073
+ return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, hookOwnership, shards, keyHealth };
4475
6074
  }
4476
6075
  function indicator(value, warn) {
4477
6076
  if (value === 0) return "\u{1F7E2}";
@@ -4519,6 +6118,15 @@ function formatReport(report, flags) {
4519
6118
  lines.push(`${indicator(report.bloated.length, 20)} Bloated (>5KB): ${fmtNum(report.bloated.length)} / ${fmtNum(s.total)} (${pct(report.bloated.length, s.total)})`);
4520
6119
  const ftsIndicator = report.fts.inSync ? "\u{1F7E2}" : "\u{1F534}";
4521
6120
  lines.push(`${ftsIndicator} FTS index: ${report.fts.inSync ? "in sync" : "OUT OF SYNC"} (${fmtNum(report.fts.memoryCount)} / ${fmtNum(report.fts.ftsCount)})`);
6121
+ const kh = report.keyHealth;
6122
+ if (!kh.masterKeyPresent) {
6123
+ lines.push("\u{1F534} Recovery key: master key missing \u2014 import phrase or run setup before syncing");
6124
+ } else if (!kh.recoveryBackupMarked) {
6125
+ lines.push("\u{1F534} Recovery backup: not confirmed \u2014 run `exe-os link export --local-terminal-only` and save the 24-word phrase");
6126
+ } else {
6127
+ const suffix = kh.recoveryBackupConfirmedAt ? ` (${kh.recoveryBackupConfirmedAt.slice(0, 10)}${kh.recoveryBackupSource ? ` via ${kh.recoveryBackupSource}` : ""})` : "";
6128
+ lines.push(`\u{1F7E2} Recovery backup: confirmed${suffix}`);
6129
+ }
4522
6130
  if (report.orphanedProjects.length > 0) {
4523
6131
  const orphanList = report.orphanedProjects.map((o) => `${o.project_name} \u2014 ${o.count} memories`).join(", ");
4524
6132
  lines.push(`\u2139\uFE0F Orphaned projects: ${report.orphanedProjects.length} (${orphanList})`);
@@ -4550,6 +6158,15 @@ function formatReport(report, flags) {
4550
6158
  lines.push(` ${p.count}x: ${p.pattern}`);
4551
6159
  }
4552
6160
  }
6161
+ const ho = report.hookOwnership;
6162
+ if (ho.issues.length === 0) {
6163
+ lines.push(`\u{1F7E2} Hook ownership: ${ho.checkedFiles.length > 0 ? "manifest clean" : "no local hook config found"}`);
6164
+ } else {
6165
+ lines.push(`\u{1F534} Hook ownership: ${fmtNum(ho.issues.length)} issue(s)`);
6166
+ for (const issue of ho.issues.slice(0, 5)) {
6167
+ lines.push(` [${issue.runtime}/${issue.event}] ${issue.message}`);
6168
+ }
6169
+ }
4553
6170
  const sh = report.shards;
4554
6171
  if (sh.total > 0) {
4555
6172
  if (sh.unreadable === 0) {
@@ -4619,6 +6236,9 @@ function formatReport(report, flags) {
4619
6236
  if (report.conflicts.superseded > 0) {
4620
6237
  recs.push(`${fmtNum(report.conflicts.superseded)} superseded memories can be deactivated`);
4621
6238
  }
6239
+ if (report.hookOwnership.issues.length > 0) {
6240
+ recs.push(`Run exe-os install to refresh hook config; remove stale exe-os hook commands if they remain`);
6241
+ }
4622
6242
  if (recs.length > 0) {
4623
6243
  lines.push("Recommendations:");
4624
6244
  for (const r of recs) {
@@ -4640,7 +6260,7 @@ async function fixNullVectors() {
4640
6260
  }
4641
6261
  }
4642
6262
  const npmRoot = (await import("child_process")).execSync("npm root -g", { encoding: "utf8" }).trim();
4643
- const backfillPath = path10.join(npmRoot, "exe-os", "dist", "bin", "backfill-vectors.js");
6263
+ const backfillPath = path11.join(npmRoot, "exe-os", "dist", "bin", "backfill-vectors.js");
4644
6264
  return new Promise((resolve, reject) => {
4645
6265
  const child = spawn2("node", [backfillPath], { stdio: "inherit" });
4646
6266
  if (child.pid) registerWorkerPid2(child.pid);
@@ -4752,8 +6372,8 @@ function splitAtSentences(text, maxChunkSize) {
4752
6372
  }
4753
6373
  async function main(argv = process.argv.slice(2)) {
4754
6374
  const flags = parseFlags(argv);
4755
- await initStore();
4756
- const client = getClient();
6375
+ const { fastDbInit: fastDbInit2 } = await Promise.resolve().then(() => (init_fast_db_init(), fast_db_init_exports));
6376
+ const client = await fastDbInit2();
4757
6377
  const report = await runAudit(client, flags);
4758
6378
  console.log(formatReport(report, flags));
4759
6379
  if (flags.fix || flags.dryRun) {
@@ -4821,6 +6441,8 @@ export {
4821
6441
  auditDuplicates,
4822
6442
  auditFts,
4823
6443
  auditHookHealth,
6444
+ auditHookOwnership,
6445
+ auditKeyHealth,
4824
6446
  auditNullVectors,
4825
6447
  auditOrphanedProjects,
4826
6448
  auditShards,