@askexenow/exe-os 0.9.61 → 0.9.63

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 (103) hide show
  1. package/deploy/stack-manifests/v0.9.json +62 -13
  2. package/dist/bin/backfill-conversations.js +210 -2
  3. package/dist/bin/backfill-responses.js +210 -2
  4. package/dist/bin/backfill-vectors.js +47 -2
  5. package/dist/bin/cleanup-stale-review-tasks.js +210 -2
  6. package/dist/bin/cli.js +488 -25
  7. package/dist/bin/exe-agent-config.js +2 -2
  8. package/dist/bin/exe-agent.js +2 -2
  9. package/dist/bin/exe-assign.js +210 -2
  10. package/dist/bin/exe-boot.js +47 -2
  11. package/dist/bin/exe-call.js +2 -2
  12. package/dist/bin/exe-cloud.js +2 -2
  13. package/dist/bin/exe-dispatch.js +210 -2
  14. package/dist/bin/exe-doctor.js +2691 -1212
  15. package/dist/bin/exe-export-behaviors.js +210 -2
  16. package/dist/bin/exe-forget.js +264 -2
  17. package/dist/bin/exe-gateway.js +210 -2
  18. package/dist/bin/exe-heartbeat.js +212 -4
  19. package/dist/bin/exe-kill.js +210 -2
  20. package/dist/bin/exe-launch-agent.js +210 -2
  21. package/dist/bin/exe-link.js +47 -2
  22. package/dist/bin/exe-new-employee.js +113 -3
  23. package/dist/bin/exe-pending-messages.js +210 -2
  24. package/dist/bin/exe-pending-notifications.js +210 -2
  25. package/dist/bin/exe-pending-reviews.js +210 -2
  26. package/dist/bin/exe-rename.js +210 -2
  27. package/dist/bin/exe-review.js +210 -2
  28. package/dist/bin/exe-search.js +234 -3
  29. package/dist/bin/exe-session-cleanup.js +210 -2
  30. package/dist/bin/exe-settings.js +2 -2
  31. package/dist/bin/exe-start-codex.js +322 -4
  32. package/dist/bin/exe-start-opencode.js +336 -4
  33. package/dist/bin/exe-status.js +210 -2
  34. package/dist/bin/exe-team.js +210 -2
  35. package/dist/bin/git-sweep.js +210 -2
  36. package/dist/bin/graph-backfill.js +210 -2
  37. package/dist/bin/graph-export.js +210 -2
  38. package/dist/bin/install.js +113 -3
  39. package/dist/bin/intercom-check.js +210 -2
  40. package/dist/bin/scan-tasks.js +210 -2
  41. package/dist/bin/setup.js +47 -2
  42. package/dist/bin/shard-migrate.js +210 -2
  43. package/dist/bin/stack-update.js +158 -13
  44. package/dist/bin/update.js +2 -2
  45. package/dist/gateway/index.js +210 -2
  46. package/dist/hooks/bug-report-worker.js +321 -44
  47. package/dist/hooks/codex-stop-task-finalizer.js +210 -2
  48. package/dist/hooks/commit-complete.js +210 -2
  49. package/dist/hooks/error-recall.js +234 -3
  50. package/dist/hooks/exe-heartbeat-hook.js +2 -2
  51. package/dist/hooks/ingest-worker.js +2 -2
  52. package/dist/hooks/ingest.js +210 -2
  53. package/dist/hooks/instructions-loaded.js +210 -2
  54. package/dist/hooks/notification.js +210 -2
  55. package/dist/hooks/post-compact.js +210 -2
  56. package/dist/hooks/post-tool-combined.js +234 -3
  57. package/dist/hooks/pre-compact.js +446 -111
  58. package/dist/hooks/pre-tool-use.js +210 -2
  59. package/dist/hooks/prompt-submit.js +234 -3
  60. package/dist/hooks/session-end.js +347 -124
  61. package/dist/hooks/session-start.js +236 -5
  62. package/dist/hooks/stop.js +210 -2
  63. package/dist/hooks/subagent-stop.js +210 -2
  64. package/dist/hooks/summary-worker.js +171 -74
  65. package/dist/index.js +210 -2
  66. package/dist/lib/agent-config.js +2 -2
  67. package/dist/lib/cloud-sync.js +47 -2
  68. package/dist/lib/config.js +2 -2
  69. package/dist/lib/consolidation.js +2 -2
  70. package/dist/lib/database.js +47 -2
  71. package/dist/lib/db-daemon-client.js +2 -2
  72. package/dist/lib/db.js +47 -2
  73. package/dist/lib/device-registry.js +47 -2
  74. package/dist/lib/embedder.js +2 -2
  75. package/dist/lib/employee-templates.js +2 -2
  76. package/dist/lib/employees.js +2 -2
  77. package/dist/lib/exe-daemon-client.js +2 -2
  78. package/dist/lib/exe-daemon.js +592 -10
  79. package/dist/lib/hybrid-search.js +234 -3
  80. package/dist/lib/identity.js +2 -2
  81. package/dist/lib/license.js +2 -2
  82. package/dist/lib/messaging.js +2 -2
  83. package/dist/lib/reminders.js +2 -2
  84. package/dist/lib/schedules.js +47 -2
  85. package/dist/lib/skill-learning.js +2 -2
  86. package/dist/lib/store.js +210 -2
  87. package/dist/lib/task-router.js +2 -2
  88. package/dist/lib/tasks.js +2 -2
  89. package/dist/lib/tmux-routing.js +2 -2
  90. package/dist/lib/token-spend.js +2 -2
  91. package/dist/mcp/server.js +608 -10
  92. package/dist/mcp/tools/complete-reminder.js +2 -2
  93. package/dist/mcp/tools/create-reminder.js +2 -2
  94. package/dist/mcp/tools/create-task.js +2 -2
  95. package/dist/mcp/tools/deactivate-behavior.js +2 -2
  96. package/dist/mcp/tools/list-reminders.js +2 -2
  97. package/dist/mcp/tools/list-tasks.js +2 -2
  98. package/dist/mcp/tools/send-message.js +2 -2
  99. package/dist/mcp/tools/update-task.js +2 -2
  100. package/dist/runtime/index.js +210 -2
  101. package/dist/tui/App.js +210 -2
  102. package/package.json +3 -2
  103. package/stack.release.json +23 -7
@@ -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";
@@ -256,8 +192,8 @@ var init_config = __esm({
256
192
  rerankerAutoTrigger: {
257
193
  enabled: true,
258
194
  broadQueryMinCardinality: 5e4,
259
- fetchTopK: 150,
260
- returnTopK: 5
195
+ fetchTopK: 200,
196
+ returnTopK: 20
261
197
  }
262
198
  },
263
199
  graphRagEnabled: true,
@@ -302,162 +238,742 @@ var init_config = __esm({
302
238
  }
303
239
  });
304
240
 
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";
241
+ // src/lib/shard-manager.ts
242
+ var shard_manager_exports = {};
243
+ __export(shard_manager_exports, {
244
+ auditShardHealth: () => auditShardHealth,
245
+ disposeShards: () => disposeShards,
246
+ ensureShardSchema: () => ensureShardSchema,
247
+ getOpenShardCount: () => getOpenShardCount,
248
+ getReadyShardClient: () => getReadyShardClient,
249
+ getShardClient: () => getShardClient,
250
+ getShardsDir: () => getShardsDir,
251
+ initShardManager: () => initShardManager,
252
+ isShardingEnabled: () => isShardingEnabled,
253
+ listShards: () => listShards,
254
+ shardExists: () => shardExists
255
+ });
309
256
  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");
257
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync2, statSync } from "fs";
258
+ import { createClient } from "@libsql/client";
259
+ function initShardManager(encryptionKey) {
260
+ _encryptionKey = encryptionKey;
261
+ if (!existsSync3(SHARDS_DIR)) {
262
+ mkdirSync2(SHARDS_DIR, { recursive: true });
340
263
  }
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, '""')}"`;
264
+ _shardingEnabled = true;
265
+ if (_evictionTimer) clearInterval(_evictionTimer);
266
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
267
+ _evictionTimer.unref();
350
268
  }
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();
269
+ function isShardingEnabled() {
270
+ return _shardingEnabled;
355
271
  }
356
- function stripTrailingSemicolon(sql) {
357
- return sql.trim().replace(/;+\s*$/u, "");
272
+ function getShardsDir() {
273
+ return SHARDS_DIR;
358
274
  }
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}`;
275
+ function getShardClient(projectName) {
276
+ if (!_encryptionKey) {
277
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
364
278
  }
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: [] };
279
+ const safeName = safeShardName(projectName);
280
+ if (!safeName || safeName === "unknown") {
281
+ throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
371
282
  }
372
- const sql = stmt.sql;
373
- if (Array.isArray(stmt.args) || stmt.args === void 0) {
374
- return { kind: "positional", sql, args: stmt.args ?? [] };
283
+ const cached = _shards.get(safeName);
284
+ if (cached) {
285
+ _shardLastAccess.set(safeName, Date.now());
286
+ return cached;
375
287
  }
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");
288
+ while (_shards.size >= MAX_OPEN_SHARDS) {
289
+ evictLRU();
388
290
  }
389
- return out;
291
+ const dbPath = path2.join(SHARDS_DIR, `${safeName}.db`);
292
+ const client = createClient({
293
+ url: `file:${dbPath}`,
294
+ encryptionKey: _encryptionKey
295
+ });
296
+ _shards.set(safeName, client);
297
+ _shardLastAccess.set(safeName, Date.now());
298
+ return client;
390
299
  }
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");
300
+ function shardExists(projectName) {
301
+ const safeName = safeShardName(projectName);
302
+ return existsSync3(path2.join(SHARDS_DIR, `${safeName}.db`));
397
303
  }
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}`)}`;
304
+ function safeShardName(projectName) {
305
+ return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
416
306
  }
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);
307
+ function listShards() {
308
+ if (!existsSync3(SHARDS_DIR)) return [];
309
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
425
310
  }
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;
311
+ async function auditShardHealth(options = {}) {
312
+ if (!_encryptionKey) {
313
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
435
314
  }
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;
315
+ const repair = options.repair === true;
316
+ const dryRun = options.dryRun === true;
317
+ const names = listShards();
318
+ const shards = [];
319
+ for (const name of names) {
320
+ const dbPath = path2.join(SHARDS_DIR, `${name}.db`);
321
+ const stat = statSync(dbPath);
322
+ const item = {
323
+ name,
324
+ path: dbPath,
325
+ ok: false,
326
+ unreadable: false,
327
+ error: null,
328
+ size: stat.size,
329
+ mtime: stat.mtime.toISOString(),
330
+ memoryCount: null
331
+ };
332
+ const client = createClient({
333
+ url: `file:${dbPath}`,
334
+ encryptionKey: _encryptionKey
335
+ });
336
+ try {
337
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
338
+ const hasMemories = await client.execute(
339
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
340
+ );
341
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
342
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
343
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
344
+ }
345
+ item.ok = true;
346
+ } catch (err) {
347
+ const message = err instanceof Error ? err.message : String(err);
348
+ item.error = message;
349
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
350
+ if (item.unreadable && repair && !dryRun) {
351
+ client.close();
352
+ _shards.delete(name);
353
+ _shardLastAccess.delete(name);
354
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
355
+ const archivedPath = path2.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
356
+ renameSync2(dbPath, archivedPath);
357
+ item.archivedPath = archivedPath;
358
+ }
359
+ } finally {
360
+ try {
361
+ client.close();
362
+ } catch {
363
+ }
364
+ }
365
+ shards.push(item);
366
+ }
367
+ return {
368
+ total: shards.length,
369
+ ok: shards.filter((s) => s.ok).length,
370
+ unreadable: shards.filter((s) => s.unreadable).length,
371
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
372
+ shards
373
+ };
374
+ }
375
+ async function ensureShardSchema(client) {
376
+ await client.execute("PRAGMA journal_mode = WAL");
377
+ await client.execute("PRAGMA busy_timeout = 30000");
378
+ try {
379
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
380
+ } catch {
381
+ }
382
+ await client.executeMultiple(`
383
+ CREATE TABLE IF NOT EXISTS memories (
384
+ id TEXT PRIMARY KEY,
385
+ agent_id TEXT NOT NULL,
386
+ agent_role TEXT NOT NULL,
387
+ session_id TEXT NOT NULL,
388
+ timestamp TEXT NOT NULL,
389
+ tool_name TEXT NOT NULL,
390
+ project_name TEXT NOT NULL,
391
+ has_error INTEGER NOT NULL DEFAULT 0,
392
+ raw_text TEXT NOT NULL,
393
+ vector F32_BLOB(1024),
394
+ version INTEGER NOT NULL DEFAULT 0
395
+ );
396
+
397
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
398
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
399
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
400
+ `);
401
+ await client.executeMultiple(`
402
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
403
+ raw_text,
404
+ content='memories',
405
+ content_rowid='rowid'
406
+ );
407
+
408
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
409
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
410
+ END;
411
+
412
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
413
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
414
+ END;
415
+
416
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
417
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
418
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
419
+ END;
420
+ `);
421
+ for (const col of [
422
+ "ALTER TABLE memories ADD COLUMN task_id TEXT",
423
+ "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
424
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
425
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
426
+ "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
427
+ "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
428
+ "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
429
+ "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
430
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
431
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
432
+ "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
433
+ "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
434
+ // Wiki linkage columns (must match database.ts)
435
+ "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
436
+ "ALTER TABLE memories ADD COLUMN document_id TEXT",
437
+ "ALTER TABLE memories ADD COLUMN user_id TEXT",
438
+ "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
439
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
440
+ // Source provenance columns (must match database.ts)
441
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
442
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
443
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
444
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
445
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
446
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
447
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
448
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
449
+ // Metadata enrichment columns (must match database.ts)
450
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
451
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
452
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
453
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
454
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
455
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
456
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
457
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
458
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
459
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
460
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
461
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
462
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
463
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
464
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
465
+ "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
466
+ ]) {
467
+ try {
468
+ await client.execute(col);
469
+ } catch {
470
+ }
471
+ }
472
+ for (const idx of [
473
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
474
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
475
+ "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"
476
+ ]) {
477
+ try {
478
+ await client.execute(idx);
479
+ } catch {
480
+ }
481
+ }
482
+ try {
483
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
484
+ } catch {
485
+ }
486
+ for (const idx of [
487
+ "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
488
+ "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
489
+ "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
490
+ ]) {
491
+ try {
492
+ await client.execute(idx);
493
+ } catch {
494
+ }
495
+ }
496
+ await client.executeMultiple(`
497
+ CREATE TABLE IF NOT EXISTS entities (
498
+ id TEXT PRIMARY KEY,
499
+ name TEXT NOT NULL,
500
+ type TEXT NOT NULL,
501
+ first_seen TEXT NOT NULL,
502
+ last_seen TEXT NOT NULL,
503
+ properties TEXT DEFAULT '{}',
504
+ UNIQUE(name, type)
505
+ );
506
+
507
+ CREATE TABLE IF NOT EXISTS relationships (
508
+ id TEXT PRIMARY KEY,
509
+ source_entity_id TEXT NOT NULL,
510
+ target_entity_id TEXT NOT NULL,
511
+ type TEXT NOT NULL,
512
+ weight REAL DEFAULT 1.0,
513
+ timestamp TEXT NOT NULL,
514
+ properties TEXT DEFAULT '{}',
515
+ UNIQUE(source_entity_id, target_entity_id, type)
516
+ );
517
+
518
+ CREATE TABLE IF NOT EXISTS entity_memories (
519
+ entity_id TEXT NOT NULL,
520
+ memory_id TEXT NOT NULL,
521
+ PRIMARY KEY (entity_id, memory_id)
522
+ );
523
+
524
+ CREATE TABLE IF NOT EXISTS relationship_memories (
525
+ relationship_id TEXT NOT NULL,
526
+ memory_id TEXT NOT NULL,
527
+ PRIMARY KEY (relationship_id, memory_id)
528
+ );
529
+
530
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
531
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
532
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
533
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
534
+ CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
535
+
536
+ CREATE TABLE IF NOT EXISTS hyperedges (
537
+ id TEXT PRIMARY KEY,
538
+ label TEXT NOT NULL,
539
+ relation TEXT NOT NULL,
540
+ confidence REAL DEFAULT 1.0,
541
+ timestamp TEXT NOT NULL
542
+ );
543
+
544
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
545
+ hyperedge_id TEXT NOT NULL,
546
+ entity_id TEXT NOT NULL,
547
+ PRIMARY KEY (hyperedge_id, entity_id)
548
+ );
549
+ `);
550
+ for (const col of [
551
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
552
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
553
+ ]) {
554
+ try {
555
+ await client.execute(col);
556
+ } catch {
557
+ }
558
+ }
559
+ }
560
+ async function getReadyShardClient(projectName) {
561
+ const safeName = safeShardName(projectName);
562
+ let client = getShardClient(projectName);
563
+ try {
564
+ await ensureShardSchema(client);
565
+ return client;
566
+ } catch (err) {
567
+ const message = err instanceof Error ? err.message : String(err);
568
+ if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
569
+ client.close();
570
+ _shards.delete(safeName);
571
+ _shardLastAccess.delete(safeName);
572
+ const dbPath = path2.join(SHARDS_DIR, `${safeName}.db`);
573
+ if (existsSync3(dbPath)) {
574
+ const stat = statSync(dbPath);
575
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
576
+ const archivedPath = path2.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
577
+ renameSync2(dbPath, archivedPath);
578
+ process.stderr.write(
579
+ `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
580
+ `
581
+ );
582
+ }
583
+ client = getShardClient(projectName);
584
+ await ensureShardSchema(client);
585
+ return client;
586
+ }
587
+ }
588
+ function evictLRU() {
589
+ let oldest = null;
590
+ let oldestTime = Infinity;
591
+ for (const [name, time] of _shardLastAccess) {
592
+ if (time < oldestTime) {
593
+ oldestTime = time;
594
+ oldest = name;
595
+ }
596
+ }
597
+ if (oldest) {
598
+ const client = _shards.get(oldest);
599
+ if (client) {
600
+ client.close();
601
+ }
602
+ _shards.delete(oldest);
603
+ _shardLastAccess.delete(oldest);
604
+ }
605
+ }
606
+ function evictIdleShards() {
607
+ const now = Date.now();
608
+ const toEvict = [];
609
+ for (const [name, lastAccess] of _shardLastAccess) {
610
+ if (now - lastAccess > SHARD_IDLE_MS) {
611
+ toEvict.push(name);
612
+ }
613
+ }
614
+ for (const name of toEvict) {
615
+ const client = _shards.get(name);
616
+ if (client) {
617
+ client.close();
618
+ }
619
+ _shards.delete(name);
620
+ _shardLastAccess.delete(name);
621
+ }
622
+ }
623
+ function getOpenShardCount() {
624
+ return _shards.size;
625
+ }
626
+ function disposeShards() {
627
+ if (_evictionTimer) {
628
+ clearInterval(_evictionTimer);
629
+ _evictionTimer = null;
630
+ }
631
+ for (const [, client] of _shards) {
632
+ client.close();
633
+ }
634
+ _shards.clear();
635
+ _shardLastAccess.clear();
636
+ _shardingEnabled = false;
637
+ _encryptionKey = null;
638
+ }
639
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
640
+ var init_shard_manager = __esm({
641
+ "src/lib/shard-manager.ts"() {
642
+ "use strict";
643
+ init_config();
644
+ SHARDS_DIR = path2.join(EXE_AI_DIR, "shards");
645
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
646
+ MAX_OPEN_SHARDS = 10;
647
+ EVICTION_INTERVAL_MS = 60 * 1e3;
648
+ _shards = /* @__PURE__ */ new Map();
649
+ _shardLastAccess = /* @__PURE__ */ new Map();
650
+ _evictionTimer = null;
651
+ _encryptionKey = null;
652
+ _shardingEnabled = false;
653
+ }
654
+ });
655
+
656
+ // src/lib/worker-gate.ts
657
+ var worker_gate_exports = {};
658
+ __export(worker_gate_exports, {
659
+ MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
660
+ cleanupWorkerPid: () => cleanupWorkerPid,
661
+ registerWorkerPid: () => registerWorkerPid,
662
+ releaseBackfillLock: () => releaseBackfillLock,
663
+ tryAcquireBackfillLock: () => tryAcquireBackfillLock,
664
+ tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
665
+ });
666
+ import { readdirSync as readdirSync2, writeFileSync, unlinkSync, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
667
+ import path3 from "path";
668
+ function tryAcquireWorkerSlot() {
669
+ try {
670
+ mkdirSync3(WORKER_PID_DIR, { recursive: true });
671
+ const reservationId = `res-${process.pid}-${Date.now()}`;
672
+ const reservationPath = path3.join(WORKER_PID_DIR, `${reservationId}.pid`);
673
+ writeFileSync(reservationPath, String(process.pid));
674
+ const files = readdirSync2(WORKER_PID_DIR);
675
+ let alive = 0;
676
+ for (const f of files) {
677
+ if (!f.endsWith(".pid")) continue;
678
+ if (f.startsWith("res-")) {
679
+ alive++;
680
+ continue;
681
+ }
682
+ const dashIdx = f.lastIndexOf("-");
683
+ const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
684
+ if (isNaN(pid)) continue;
685
+ try {
686
+ process.kill(pid, 0);
687
+ alive++;
688
+ } catch {
689
+ try {
690
+ unlinkSync(path3.join(WORKER_PID_DIR, f));
691
+ } catch {
692
+ }
693
+ }
694
+ }
695
+ if (alive > MAX_CONCURRENT_WORKERS) {
696
+ try {
697
+ unlinkSync(reservationPath);
698
+ } catch {
699
+ }
700
+ return false;
701
+ }
702
+ try {
703
+ unlinkSync(reservationPath);
704
+ } catch {
705
+ }
706
+ return true;
707
+ } catch {
708
+ return true;
709
+ }
710
+ }
711
+ function registerWorkerPid(pid) {
712
+ try {
713
+ mkdirSync3(WORKER_PID_DIR, { recursive: true });
714
+ writeFileSync(path3.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
715
+ } catch {
716
+ }
717
+ }
718
+ function cleanupWorkerPid() {
719
+ try {
720
+ unlinkSync(path3.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
721
+ } catch {
722
+ }
723
+ }
724
+ function tryAcquireBackfillLock() {
725
+ try {
726
+ mkdirSync3(WORKER_PID_DIR, { recursive: true });
727
+ if (existsSync4(BACKFILL_LOCK)) {
728
+ try {
729
+ const pid = parseInt(
730
+ __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
731
+ 10
732
+ );
733
+ if (!isNaN(pid) && pid > 0) {
734
+ try {
735
+ process.kill(pid, 0);
736
+ return false;
737
+ } catch {
738
+ }
739
+ }
740
+ } catch {
741
+ }
742
+ }
743
+ writeFileSync(BACKFILL_LOCK, String(process.pid));
744
+ return true;
745
+ } catch {
746
+ return true;
747
+ }
748
+ }
749
+ function releaseBackfillLock() {
750
+ try {
751
+ unlinkSync(BACKFILL_LOCK);
752
+ } catch {
753
+ }
754
+ }
755
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
756
+ var init_worker_gate = __esm({
757
+ "src/lib/worker-gate.ts"() {
758
+ "use strict";
759
+ init_config();
760
+ WORKER_PID_DIR = path3.join(EXE_AI_DIR, "worker-pids");
761
+ MAX_CONCURRENT_WORKERS = 3;
762
+ BACKFILL_LOCK = path3.join(WORKER_PID_DIR, "backfill.lock");
763
+ }
764
+ });
765
+
766
+ // src/lib/db-retry.ts
767
+ function isBusyError(err) {
768
+ if (err instanceof Error) {
769
+ const msg = err.message.toLowerCase();
770
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
771
+ }
772
+ return false;
773
+ }
774
+ function delay(ms) {
775
+ return new Promise((resolve) => setTimeout(resolve, ms));
776
+ }
777
+ async function retryOnBusy(fn, label) {
778
+ let lastError;
779
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
780
+ try {
781
+ return await fn();
782
+ } catch (err) {
783
+ lastError = err;
784
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
785
+ throw err;
786
+ }
787
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
788
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
789
+ process.stderr.write(
790
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
791
+ `
792
+ );
793
+ await delay(backoff + jitter);
794
+ }
795
+ }
796
+ throw lastError;
797
+ }
798
+ function wrapWithRetry(client) {
799
+ return new Proxy(client, {
800
+ get(target, prop, receiver) {
801
+ if (prop === "execute") {
802
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
803
+ }
804
+ if (prop === "batch") {
805
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
806
+ }
807
+ return Reflect.get(target, prop, receiver);
808
+ }
809
+ });
810
+ }
811
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
812
+ var init_db_retry = __esm({
813
+ "src/lib/db-retry.ts"() {
814
+ "use strict";
815
+ MAX_RETRIES = 5;
816
+ BASE_DELAY_MS = 250;
817
+ MAX_JITTER_MS = 400;
818
+ }
819
+ });
820
+
821
+ // src/lib/employees.ts
822
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
823
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
824
+ import { execSync } from "child_process";
825
+ import path4 from "path";
826
+ import os2 from "os";
827
+ function normalizeRole(role) {
828
+ return (role ?? "").trim().toLowerCase();
829
+ }
830
+ function isCoordinatorRole(role) {
831
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
832
+ }
833
+ function getCoordinatorEmployee(employees) {
834
+ return employees.find((e) => isCoordinatorRole(e.role));
835
+ }
836
+ function getCoordinatorName(employees = loadEmployeesSync()) {
837
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
838
+ }
839
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
840
+ if (!existsSync5(employeesPath)) return [];
841
+ try {
842
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
843
+ } catch {
844
+ return [];
845
+ }
846
+ }
847
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
848
+ var init_employees = __esm({
849
+ "src/lib/employees.ts"() {
850
+ "use strict";
851
+ init_config();
852
+ EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
853
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
854
+ COORDINATOR_ROLE = "COO";
855
+ IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
856
+ }
857
+ });
858
+
859
+ // src/lib/database-adapter.ts
860
+ import os3 from "os";
861
+ import path5 from "path";
862
+ import { createRequire } from "module";
863
+ import { pathToFileURL } from "url";
864
+ function quotedIdentifier(identifier) {
865
+ return `"${identifier.replace(/"/g, '""')}"`;
866
+ }
867
+ function unqualifiedTableName(name) {
868
+ const raw = name.trim().replace(/^"|"$/g, "");
869
+ const parts = raw.split(".");
870
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
871
+ }
872
+ function stripTrailingSemicolon(sql) {
873
+ return sql.trim().replace(/;+\s*$/u, "");
874
+ }
875
+ function appendClause(sql, clause) {
876
+ const trimmed = stripTrailingSemicolon(sql);
877
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
878
+ if (!returningMatch) {
879
+ return `${trimmed}${clause}`;
880
+ }
881
+ const idx = returningMatch.index;
882
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
883
+ }
884
+ function normalizeStatement(stmt) {
885
+ if (typeof stmt === "string") {
886
+ return { kind: "positional", sql: stmt, args: [] };
887
+ }
888
+ const sql = stmt.sql;
889
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
890
+ return { kind: "positional", sql, args: stmt.args ?? [] };
891
+ }
892
+ return { kind: "named", sql, args: stmt.args };
893
+ }
894
+ function rewriteBooleanLiterals(sql) {
895
+ let out = sql;
896
+ for (const column of BOOLEAN_COLUMN_NAMES) {
897
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
898
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
899
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
900
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
901
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
902
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
903
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
904
+ }
905
+ return out;
906
+ }
907
+ function rewriteInsertOrIgnore(sql) {
908
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
909
+ return sql;
910
+ }
911
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
912
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
913
+ }
914
+ function rewriteInsertOrReplace(sql) {
915
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
916
+ if (!match) {
917
+ return sql;
918
+ }
919
+ const rawTable = match[1];
920
+ const rawColumns = match[2];
921
+ const remainder = match[3];
922
+ const tableName = unqualifiedTableName(rawTable);
923
+ const conflictKeys = UPSERT_KEYS[tableName];
924
+ if (!conflictKeys?.length) {
925
+ return sql;
926
+ }
927
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
928
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
929
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
930
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
931
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
932
+ }
933
+ function rewriteSql(sql) {
934
+ let out = sql;
935
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
936
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
937
+ out = rewriteBooleanLiterals(out);
938
+ out = rewriteInsertOrReplace(out);
939
+ out = rewriteInsertOrIgnore(out);
940
+ return stripTrailingSemicolon(out);
941
+ }
942
+ function toBoolean(value) {
943
+ if (value === null || value === void 0) return value;
944
+ if (typeof value === "boolean") return value;
945
+ if (typeof value === "number") return value !== 0;
946
+ if (typeof value === "bigint") return value !== 0n;
947
+ if (typeof value === "string") {
948
+ const normalized = value.trim().toLowerCase();
949
+ if (normalized === "0" || normalized === "false") return false;
950
+ if (normalized === "1" || normalized === "true") return true;
951
+ }
952
+ return Boolean(value);
953
+ }
954
+ function countQuestionMarks(sql, end) {
955
+ let count = 0;
956
+ let inSingle = false;
957
+ let inDouble = false;
958
+ let inLineComment = false;
959
+ let inBlockComment = false;
960
+ for (let i = 0; i < end; i++) {
961
+ const ch = sql[i];
962
+ const next = sql[i + 1];
963
+ if (inLineComment) {
964
+ if (ch === "\n") inLineComment = false;
965
+ continue;
966
+ }
967
+ if (inBlockComment) {
968
+ if (ch === "*" && next === "/") {
969
+ inBlockComment = false;
970
+ i += 1;
971
+ }
972
+ continue;
973
+ }
974
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
975
+ inLineComment = true;
976
+ i += 1;
461
977
  continue;
462
978
  }
463
979
  if (!inSingle && !inDouble && ch === "/" && next === "*") {
@@ -653,8 +1169,8 @@ async function loadPrismaClient() {
653
1169
  }
654
1170
  return new PrismaClient2();
655
1171
  }
656
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
657
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1172
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os3.homedir(), "exe-db");
1173
+ const requireFromExeDb = createRequire(path5.join(exeDbRoot, "package.json"));
658
1174
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
659
1175
  const module = await import(pathToFileURL(prismaEntry).href);
660
1176
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -924,10 +1440,19 @@ var init_database_adapter = __esm({
924
1440
  }
925
1441
  });
926
1442
 
1443
+ // src/types/memory.ts
1444
+ var EMBEDDING_DIM;
1445
+ var init_memory = __esm({
1446
+ "src/types/memory.ts"() {
1447
+ "use strict";
1448
+ EMBEDDING_DIM = 1024;
1449
+ }
1450
+ });
1451
+
927
1452
  // src/lib/daemon-auth.ts
928
1453
  import crypto from "crypto";
929
- import path4 from "path";
930
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1454
+ import path6 from "path";
1455
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
931
1456
  function normalizeToken(token) {
932
1457
  if (!token) return null;
933
1458
  const trimmed = token.trim();
@@ -935,7 +1460,7 @@ function normalizeToken(token) {
935
1460
  }
936
1461
  function readDaemonToken() {
937
1462
  try {
938
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1463
+ if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
939
1464
  return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
940
1465
  } catch {
941
1466
  return null;
@@ -946,7 +1471,7 @@ function ensureDaemonToken(seed) {
946
1471
  if (existing) return existing;
947
1472
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
948
1473
  ensurePrivateDirSync(EXE_AI_DIR);
949
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1474
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
950
1475
  `, "utf8");
951
1476
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
952
1477
  return token;
@@ -957,18 +1482,29 @@ var init_daemon_auth = __esm({
957
1482
  "use strict";
958
1483
  init_config();
959
1484
  init_secure_files();
960
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1485
+ DAEMON_TOKEN_PATH = path6.join(EXE_AI_DIR, "exed.token");
961
1486
  }
962
1487
  });
963
1488
 
964
1489
  // src/lib/exe-daemon-client.ts
1490
+ var exe_daemon_client_exports = {};
1491
+ __export(exe_daemon_client_exports, {
1492
+ connectEmbedDaemon: () => connectEmbedDaemon,
1493
+ disconnectClient: () => disconnectClient,
1494
+ embedBatchViaClient: () => embedBatchViaClient,
1495
+ embedViaClient: () => embedViaClient,
1496
+ isClientConnected: () => isClientConnected,
1497
+ pingDaemon: () => pingDaemon,
1498
+ sendDaemonRequest: () => sendDaemonRequest,
1499
+ sendIngestRequest: () => sendIngestRequest
1500
+ });
965
1501
  import net from "net";
966
1502
  import os4 from "os";
967
1503
  import { spawn } from "child_process";
968
1504
  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";
1505
+ import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync4, openSync, closeSync, statSync as statSync2 } from "fs";
1506
+ import path7 from "path";
1507
+ import { fileURLToPath as fileURLToPath2 } from "url";
972
1508
  function handleData(chunk) {
973
1509
  _buffer += chunk.toString();
974
1510
  if (_buffer.length > MAX_BUFFER) {
@@ -995,7 +1531,7 @@ function handleData(chunk) {
995
1531
  }
996
1532
  }
997
1533
  function cleanupStaleFiles() {
998
- if (existsSync5(PID_PATH)) {
1534
+ if (existsSync7(PID_PATH)) {
999
1535
  try {
1000
1536
  const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1001
1537
  if (pid > 0) {
@@ -1008,21 +1544,21 @@ function cleanupStaleFiles() {
1008
1544
  } catch {
1009
1545
  }
1010
1546
  try {
1011
- unlinkSync2(PID_PATH);
1547
+ unlinkSync3(PID_PATH);
1012
1548
  } catch {
1013
1549
  }
1014
1550
  try {
1015
- unlinkSync2(SOCKET_PATH);
1551
+ unlinkSync3(SOCKET_PATH);
1016
1552
  } catch {
1017
1553
  }
1018
1554
  }
1019
1555
  }
1020
1556
  function findPackageRoot() {
1021
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1022
- const { root } = path5.parse(dir);
1557
+ let dir = path7.dirname(fileURLToPath2(import.meta.url));
1558
+ const { root } = path7.parse(dir);
1023
1559
  while (dir !== root) {
1024
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1025
- dir = path5.dirname(dir);
1560
+ if (existsSync7(path7.join(dir, "package.json"))) return dir;
1561
+ dir = path7.dirname(dir);
1026
1562
  }
1027
1563
  return null;
1028
1564
  }
@@ -1069,8 +1605,8 @@ function spawnDaemon() {
1069
1605
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1070
1606
  return;
1071
1607
  }
1072
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1073
- if (!existsSync5(daemonPath)) {
1608
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1609
+ if (!existsSync7(daemonPath)) {
1074
1610
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1075
1611
  `);
1076
1612
  return;
@@ -1079,7 +1615,7 @@ function spawnDaemon() {
1079
1615
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1080
1616
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1081
1617
  `);
1082
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1618
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1083
1619
  let stderrFd = "ignore";
1084
1620
  try {
1085
1621
  stderrFd = openSync(logPath, "a");
@@ -1116,10 +1652,10 @@ function acquireSpawnLock() {
1116
1652
  return true;
1117
1653
  } catch {
1118
1654
  try {
1119
- const stat = statSync(SPAWN_LOCK_PATH);
1655
+ const stat = statSync2(SPAWN_LOCK_PATH);
1120
1656
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1121
1657
  try {
1122
- unlinkSync2(SPAWN_LOCK_PATH);
1658
+ unlinkSync3(SPAWN_LOCK_PATH);
1123
1659
  } catch {
1124
1660
  }
1125
1661
  try {
@@ -1136,7 +1672,7 @@ function acquireSpawnLock() {
1136
1672
  }
1137
1673
  function releaseSpawnLock() {
1138
1674
  try {
1139
- unlinkSync2(SPAWN_LOCK_PATH);
1675
+ unlinkSync3(SPAWN_LOCK_PATH);
1140
1676
  } catch {
1141
1677
  }
1142
1678
  }
@@ -1198,6 +1734,9 @@ async function connectEmbedDaemon() {
1198
1734
  }
1199
1735
  return false;
1200
1736
  }
1737
+ function sendRequest(texts, priority) {
1738
+ return sendDaemonRequest({ texts, priority });
1739
+ }
1201
1740
  function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1202
1741
  return new Promise((resolve) => {
1203
1742
  if (!_socket || !_connected) {
@@ -1220,18 +1759,178 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1220
1759
  }
1221
1760
  });
1222
1761
  }
1762
+ async function pingDaemon() {
1763
+ if (!_socket || !_connected) return null;
1764
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
1765
+ if (response.health) {
1766
+ return response.health;
1767
+ }
1768
+ return null;
1769
+ }
1770
+ function killAndRespawnDaemon() {
1771
+ if (!acquireSpawnLock()) {
1772
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
1773
+ if (_socket) {
1774
+ _socket.destroy();
1775
+ _socket = null;
1776
+ }
1777
+ _connected = false;
1778
+ _buffer = "";
1779
+ return;
1780
+ }
1781
+ try {
1782
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
1783
+ if (existsSync7(PID_PATH)) {
1784
+ try {
1785
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1786
+ if (pid > 0) {
1787
+ try {
1788
+ process.kill(pid, "SIGKILL");
1789
+ } catch {
1790
+ }
1791
+ }
1792
+ } catch {
1793
+ }
1794
+ }
1795
+ if (_socket) {
1796
+ _socket.destroy();
1797
+ _socket = null;
1798
+ }
1799
+ _connected = false;
1800
+ _buffer = "";
1801
+ try {
1802
+ unlinkSync3(PID_PATH);
1803
+ } catch {
1804
+ }
1805
+ try {
1806
+ unlinkSync3(SOCKET_PATH);
1807
+ } catch {
1808
+ }
1809
+ spawnDaemon();
1810
+ } finally {
1811
+ releaseSpawnLock();
1812
+ }
1813
+ }
1814
+ function isDaemonTooYoung() {
1815
+ try {
1816
+ const stat = statSync2(PID_PATH);
1817
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
1818
+ } catch {
1819
+ return false;
1820
+ }
1821
+ }
1822
+ async function retryThenRestart(doRequest, label) {
1823
+ const result = await doRequest();
1824
+ if (!result.error) {
1825
+ _consecutiveFailures = 0;
1826
+ return result;
1827
+ }
1828
+ _consecutiveFailures++;
1829
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
1830
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
1831
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
1832
+ `);
1833
+ await new Promise((r) => setTimeout(r, delayMs));
1834
+ if (!_connected) {
1835
+ if (!await connectToSocket()) continue;
1836
+ }
1837
+ const retry = await doRequest();
1838
+ if (!retry.error) {
1839
+ _consecutiveFailures = 0;
1840
+ return retry;
1841
+ }
1842
+ _consecutiveFailures++;
1843
+ }
1844
+ if (isDaemonTooYoung()) {
1845
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
1846
+ `);
1847
+ return { error: result.error };
1848
+ }
1849
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
1850
+ `);
1851
+ killAndRespawnDaemon();
1852
+ const start = Date.now();
1853
+ let delay2 = 200;
1854
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1855
+ await new Promise((r) => setTimeout(r, delay2));
1856
+ if (await connectToSocket()) break;
1857
+ delay2 = Math.min(delay2 * 2, 3e3);
1858
+ }
1859
+ if (!_connected) return { error: "Daemon restart failed" };
1860
+ const final = await doRequest();
1861
+ if (!final.error) _consecutiveFailures = 0;
1862
+ return final;
1863
+ }
1864
+ async function embedViaClient(text, priority = "high") {
1865
+ if (!_connected && !await connectEmbedDaemon()) return null;
1866
+ _requestCount++;
1867
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1868
+ const health = await pingDaemon();
1869
+ if (!health && !isDaemonTooYoung()) {
1870
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1871
+ `);
1872
+ killAndRespawnDaemon();
1873
+ const start = Date.now();
1874
+ let d = 200;
1875
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1876
+ await new Promise((r) => setTimeout(r, d));
1877
+ if (await connectToSocket()) break;
1878
+ d = Math.min(d * 2, 3e3);
1879
+ }
1880
+ if (!_connected) return null;
1881
+ }
1882
+ }
1883
+ const result = await retryThenRestart(
1884
+ () => sendRequest([text], priority),
1885
+ "Embed"
1886
+ );
1887
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
1888
+ }
1889
+ async function embedBatchViaClient(texts, priority = "high") {
1890
+ if (!_connected && !await connectEmbedDaemon()) return null;
1891
+ _requestCount++;
1892
+ const result = await retryThenRestart(
1893
+ () => sendRequest(texts, priority),
1894
+ "Batch embed"
1895
+ );
1896
+ return !result.error && result.vectors ? result.vectors : null;
1897
+ }
1898
+ function disconnectClient() {
1899
+ if (_socket) {
1900
+ _socket.destroy();
1901
+ _socket = null;
1902
+ }
1903
+ _connected = false;
1904
+ _buffer = "";
1905
+ for (const [id, entry] of _pending) {
1906
+ clearTimeout(entry.timer);
1907
+ _pending.delete(id);
1908
+ entry.resolve({ error: "Client disconnected" });
1909
+ }
1910
+ }
1223
1911
  function isClientConnected() {
1224
1912
  return _connected;
1225
1913
  }
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;
1914
+ function sendIngestRequest(payload) {
1915
+ if (!_socket || !_connected) return false;
1916
+ try {
1917
+ const id = randomUUID();
1918
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1919
+ _socket.write(JSON.stringify({ id, token, type: "ingest", ...payload }) + "\n");
1920
+ return true;
1921
+ } catch {
1922
+ return false;
1923
+ }
1924
+ }
1925
+ 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
1926
  var init_exe_daemon_client = __esm({
1228
1927
  "src/lib/exe-daemon-client.ts"() {
1229
1928
  "use strict";
1230
1929
  init_config();
1231
1930
  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");
1931
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1932
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1933
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1235
1934
  SPAWN_LOCK_STALE_MS = 3e4;
1236
1935
  CONNECT_TIMEOUT_MS = 15e3;
1237
1936
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1239,12 +1938,27 @@ var init_exe_daemon_client = __esm({
1239
1938
  _socket = null;
1240
1939
  _connected = false;
1241
1940
  _buffer = "";
1941
+ _requestCount = 0;
1942
+ _consecutiveFailures = 0;
1943
+ HEALTH_CHECK_INTERVAL = 100;
1944
+ MAX_RETRIES_BEFORE_RESTART = 3;
1945
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
1946
+ MIN_DAEMON_AGE_MS = 3e4;
1242
1947
  _pending = /* @__PURE__ */ new Map();
1243
1948
  MAX_BUFFER = 1e7;
1244
1949
  }
1245
1950
  });
1246
1951
 
1247
1952
  // src/lib/daemon-protocol.ts
1953
+ var daemon_protocol_exports = {};
1954
+ __export(daemon_protocol_exports, {
1955
+ deserializeArgs: () => deserializeArgs,
1956
+ deserializeResultSet: () => deserializeResultSet,
1957
+ deserializeValue: () => deserializeValue,
1958
+ serializeArgs: () => serializeArgs,
1959
+ serializeResultSet: () => serializeResultSet,
1960
+ serializeValue: () => serializeValue
1961
+ });
1248
1962
  function serializeValue(v) {
1249
1963
  if (v === null || v === void 0) return null;
1250
1964
  if (typeof v === "bigint") return Number(v);
@@ -1269,6 +1983,32 @@ function deserializeValue(v) {
1269
1983
  }
1270
1984
  return v;
1271
1985
  }
1986
+ function serializeArgs(args) {
1987
+ return args.map(serializeValue);
1988
+ }
1989
+ function deserializeArgs(args) {
1990
+ return args.map(deserializeValue);
1991
+ }
1992
+ function serializeResultSet(rs) {
1993
+ const rows = [];
1994
+ for (const row of rs.rows) {
1995
+ const obj = {};
1996
+ for (let i = 0; i < rs.columns.length; i++) {
1997
+ const col = rs.columns[i];
1998
+ if (col !== void 0) {
1999
+ obj[col] = serializeValue(row[i]);
2000
+ }
2001
+ }
2002
+ rows.push(obj);
2003
+ }
2004
+ return {
2005
+ columns: [...rs.columns],
2006
+ columnTypes: [...rs.columnTypes ?? []],
2007
+ rows,
2008
+ rowsAffected: typeof rs.rowsAffected === "bigint" ? Number(rs.rowsAffected) : rs.rowsAffected ?? 0,
2009
+ lastInsertRowid: rs.lastInsertRowid != null ? typeof rs.lastInsertRowid === "bigint" ? Number(rs.lastInsertRowid) : rs.lastInsertRowid : null
2010
+ };
2011
+ }
1272
2012
  function deserializeResultSet(srs) {
1273
2013
  const rows = srs.rows.map((obj) => {
1274
2014
  const values = srs.columns.map(
@@ -1458,7 +2198,7 @@ __export(database_exports, {
1458
2198
  isInitialized: () => isInitialized,
1459
2199
  setExternalClient: () => setExternalClient
1460
2200
  });
1461
- import { createClient } from "@libsql/client";
2201
+ import { createClient as createClient2 } from "@libsql/client";
1462
2202
  async function initDatabase(config) {
1463
2203
  if (_walCheckpointTimer) {
1464
2204
  clearInterval(_walCheckpointTimer);
@@ -1483,7 +2223,7 @@ async function initDatabase(config) {
1483
2223
  if (config.encryptionKey) {
1484
2224
  opts.encryptionKey = config.encryptionKey;
1485
2225
  }
1486
- _client = createClient(opts);
2226
+ _client = createClient2(opts);
1487
2227
  _resilientClient = wrapWithRetry(_client);
1488
2228
  _adapterClient = _resilientClient;
1489
2229
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
@@ -2506,6 +3246,51 @@ async function ensureSchema() {
2506
3246
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
2507
3247
  END;
2508
3248
  `);
3249
+ await client.executeMultiple(`
3250
+ CREATE TABLE IF NOT EXISTS memory_cards (
3251
+ id TEXT PRIMARY KEY,
3252
+ memory_id TEXT NOT NULL,
3253
+ agent_id TEXT NOT NULL,
3254
+ session_id TEXT NOT NULL,
3255
+ project_name TEXT,
3256
+ timestamp TEXT NOT NULL,
3257
+ card_type TEXT NOT NULL,
3258
+ subject TEXT,
3259
+ predicate TEXT,
3260
+ object TEXT,
3261
+ content TEXT NOT NULL,
3262
+ source_ref TEXT,
3263
+ confidence REAL DEFAULT 0.6,
3264
+ active INTEGER DEFAULT 1,
3265
+ created_at TEXT NOT NULL
3266
+ );
3267
+
3268
+ CREATE INDEX IF NOT EXISTS idx_memory_cards_agent
3269
+ ON memory_cards(agent_id, active, timestamp);
3270
+
3271
+ CREATE INDEX IF NOT EXISTS idx_memory_cards_memory
3272
+ ON memory_cards(memory_id);
3273
+
3274
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_cards_fts
3275
+ USING fts5(content, subject, predicate, object, content='memory_cards', content_rowid='rowid');
3276
+
3277
+ CREATE TRIGGER IF NOT EXISTS memory_cards_fts_ai AFTER INSERT ON memory_cards BEGIN
3278
+ INSERT INTO memory_cards_fts(rowid, content, subject, predicate, object)
3279
+ VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
3280
+ END;
3281
+
3282
+ CREATE TRIGGER IF NOT EXISTS memory_cards_fts_ad AFTER DELETE ON memory_cards BEGIN
3283
+ INSERT INTO memory_cards_fts(memory_cards_fts, rowid, content, subject, predicate, object)
3284
+ VALUES('delete', old.rowid, old.content, old.subject, old.predicate, old.object);
3285
+ END;
3286
+
3287
+ CREATE TRIGGER IF NOT EXISTS memory_cards_fts_au AFTER UPDATE ON memory_cards BEGIN
3288
+ INSERT INTO memory_cards_fts(memory_cards_fts, rowid, content, subject, predicate, object)
3289
+ VALUES('delete', old.rowid, old.content, old.subject, old.predicate, old.object);
3290
+ INSERT INTO memory_cards_fts(rowid, content, subject, predicate, object)
3291
+ VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
3292
+ END;
3293
+ `);
2509
3294
  try {
2510
3295
  await client.execute({
2511
3296
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
@@ -2559,309 +3344,33 @@ async function ensureSchema() {
2559
3344
  });
2560
3345
  } catch {
2561
3346
  }
2562
- try {
2563
- await client.execute(
2564
- `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
2565
- );
2566
- } catch {
2567
- }
2568
- try {
2569
- await client.execute({
2570
- sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
2571
- args: []
2572
- });
2573
- } catch {
2574
- }
2575
- try {
2576
- await client.execute(
2577
- `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
2578
- );
2579
- } catch {
2580
- }
2581
- try {
2582
- await client.execute({
2583
- sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
2584
- args: []
2585
- });
2586
- } catch {
2587
- }
2588
- for (const col of [
2589
- "ALTER TABLE memories ADD COLUMN intent TEXT",
2590
- "ALTER TABLE memories ADD COLUMN outcome TEXT",
2591
- "ALTER TABLE memories ADD COLUMN domain TEXT",
2592
- "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2593
- "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2594
- "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2595
- "ALTER TABLE memories ADD COLUMN review_status TEXT",
2596
- "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2597
- "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2598
- "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2599
- "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2600
- "ALTER TABLE memories ADD COLUMN token_cost REAL",
2601
- "ALTER TABLE memories ADD COLUMN audience TEXT",
2602
- "ALTER TABLE memories ADD COLUMN language_type TEXT",
2603
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2604
- ]) {
2605
- try {
2606
- await client.execute(col);
2607
- } catch {
2608
- }
2609
- }
2610
- try {
2611
- await client.execute({
2612
- sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2613
- args: []
2614
- });
2615
- } catch {
2616
- }
2617
- }
2618
- async function disposeDatabase() {
2619
- if (_walCheckpointTimer) {
2620
- clearInterval(_walCheckpointTimer);
2621
- _walCheckpointTimer = null;
2622
- }
2623
- if (_daemonClient) {
2624
- _daemonClient.close();
2625
- _daemonClient = null;
2626
- }
2627
- if (_adapterClient && _adapterClient !== _resilientClient) {
2628
- _adapterClient.close();
2629
- }
2630
- _adapterClient = null;
2631
- if (_client) {
2632
- _client.close();
2633
- _client = null;
2634
- _resilientClient = null;
2635
- }
2636
- }
2637
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
2638
- var init_database = __esm({
2639
- "src/lib/database.ts"() {
2640
- "use strict";
2641
- init_db_retry();
2642
- init_employees();
2643
- init_database_adapter();
2644
- init_memory();
2645
- _client = null;
2646
- _resilientClient = null;
2647
- _walCheckpointTimer = null;
2648
- _daemonClient = null;
2649
- _adapterClient = null;
2650
- initTurso = initDatabase;
2651
- SOFT_DELETE_RETENTION_DAYS = 7;
2652
- disposeTurso = disposeDatabase;
2653
- }
2654
- });
2655
-
2656
- // src/lib/shard-manager.ts
2657
- var shard_manager_exports = {};
2658
- __export(shard_manager_exports, {
2659
- auditShardHealth: () => auditShardHealth,
2660
- disposeShards: () => disposeShards,
2661
- ensureShardSchema: () => ensureShardSchema,
2662
- getOpenShardCount: () => getOpenShardCount,
2663
- getReadyShardClient: () => getReadyShardClient,
2664
- getShardClient: () => getShardClient,
2665
- getShardsDir: () => getShardsDir,
2666
- initShardManager: () => initShardManager,
2667
- isShardingEnabled: () => isShardingEnabled,
2668
- listShards: () => listShards,
2669
- shardExists: () => shardExists
2670
- });
2671
- import path7 from "path";
2672
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync2 } from "fs";
2673
- import { createClient as createClient2 } from "@libsql/client";
2674
- function initShardManager(encryptionKey) {
2675
- _encryptionKey = encryptionKey;
2676
- if (!existsSync7(SHARDS_DIR)) {
2677
- mkdirSync2(SHARDS_DIR, { recursive: true });
2678
- }
2679
- _shardingEnabled = true;
2680
- if (_evictionTimer) clearInterval(_evictionTimer);
2681
- _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2682
- _evictionTimer.unref();
2683
- }
2684
- function isShardingEnabled() {
2685
- return _shardingEnabled;
2686
- }
2687
- function getShardsDir() {
2688
- return SHARDS_DIR;
2689
- }
2690
- function getShardClient(projectName) {
2691
- if (!_encryptionKey) {
2692
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
2693
- }
2694
- const safeName = safeShardName(projectName);
2695
- if (!safeName || safeName === "unknown") {
2696
- throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
2697
- }
2698
- const cached = _shards.get(safeName);
2699
- if (cached) {
2700
- _shardLastAccess.set(safeName, Date.now());
2701
- return cached;
2702
- }
2703
- while (_shards.size >= MAX_OPEN_SHARDS) {
2704
- evictLRU();
2705
- }
2706
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2707
- const client = createClient2({
2708
- url: `file:${dbPath}`,
2709
- encryptionKey: _encryptionKey
2710
- });
2711
- _shards.set(safeName, client);
2712
- _shardLastAccess.set(safeName, Date.now());
2713
- return client;
2714
- }
2715
- function shardExists(projectName) {
2716
- const safeName = safeShardName(projectName);
2717
- return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2718
- }
2719
- function safeShardName(projectName) {
2720
- return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2721
- }
2722
- function listShards() {
2723
- if (!existsSync7(SHARDS_DIR)) return [];
2724
- return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2725
- }
2726
- async function auditShardHealth(options = {}) {
2727
- if (!_encryptionKey) {
2728
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
2729
- }
2730
- const repair = options.repair === true;
2731
- const dryRun = options.dryRun === true;
2732
- const names = listShards();
2733
- const shards = [];
2734
- for (const name of names) {
2735
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2736
- const stat = statSync2(dbPath);
2737
- const item = {
2738
- name,
2739
- path: dbPath,
2740
- ok: false,
2741
- unreadable: false,
2742
- error: null,
2743
- size: stat.size,
2744
- mtime: stat.mtime.toISOString(),
2745
- memoryCount: null
2746
- };
2747
- const client = createClient2({
2748
- url: `file:${dbPath}`,
2749
- encryptionKey: _encryptionKey
2750
- });
2751
- try {
2752
- await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2753
- const hasMemories = await client.execute(
2754
- "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2755
- );
2756
- if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2757
- const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2758
- item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2759
- }
2760
- item.ok = true;
2761
- } catch (err) {
2762
- const message = err instanceof Error ? err.message : String(err);
2763
- item.error = message;
2764
- item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2765
- if (item.unreadable && repair && !dryRun) {
2766
- client.close();
2767
- _shards.delete(name);
2768
- _shardLastAccess.delete(name);
2769
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2770
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2771
- renameSync3(dbPath, archivedPath);
2772
- item.archivedPath = archivedPath;
2773
- }
2774
- } finally {
2775
- try {
2776
- client.close();
2777
- } catch {
2778
- }
2779
- }
2780
- shards.push(item);
2781
- }
2782
- return {
2783
- total: shards.length,
2784
- ok: shards.filter((s) => s.ok).length,
2785
- unreadable: shards.filter((s) => s.unreadable).length,
2786
- archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2787
- shards
2788
- };
2789
- }
2790
- async function ensureShardSchema(client) {
2791
- await client.execute("PRAGMA journal_mode = WAL");
2792
- await client.execute("PRAGMA busy_timeout = 30000");
2793
- try {
2794
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
2795
- } catch {
2796
- }
2797
- await client.executeMultiple(`
2798
- CREATE TABLE IF NOT EXISTS memories (
2799
- id TEXT PRIMARY KEY,
2800
- agent_id TEXT NOT NULL,
2801
- agent_role TEXT NOT NULL,
2802
- session_id TEXT NOT NULL,
2803
- timestamp TEXT NOT NULL,
2804
- tool_name TEXT NOT NULL,
2805
- project_name TEXT NOT NULL,
2806
- has_error INTEGER NOT NULL DEFAULT 0,
2807
- raw_text TEXT NOT NULL,
2808
- vector F32_BLOB(1024),
2809
- version INTEGER NOT NULL DEFAULT 0
3347
+ try {
3348
+ await client.execute(
3349
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
2810
3350
  );
2811
-
2812
- CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
2813
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
2814
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
2815
- `);
2816
- await client.executeMultiple(`
2817
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
2818
- raw_text,
2819
- content='memories',
2820
- content_rowid='rowid'
3351
+ } catch {
3352
+ }
3353
+ try {
3354
+ await client.execute({
3355
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
3356
+ args: []
3357
+ });
3358
+ } catch {
3359
+ }
3360
+ try {
3361
+ await client.execute(
3362
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
2821
3363
  );
2822
-
2823
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
2824
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2825
- END;
2826
-
2827
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
2828
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2829
- END;
2830
-
2831
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
2832
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2833
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2834
- END;
2835
- `);
3364
+ } catch {
3365
+ }
3366
+ try {
3367
+ await client.execute({
3368
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
3369
+ args: []
3370
+ });
3371
+ } catch {
3372
+ }
2836
3373
  for (const col of [
2837
- "ALTER TABLE memories ADD COLUMN task_id TEXT",
2838
- "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2839
- "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2840
- "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2841
- "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2842
- "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2843
- "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
2844
- "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
2845
- "ALTER TABLE memories ADD COLUMN content_hash TEXT",
2846
- "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
2847
- "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
2848
- "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
2849
- // Wiki linkage columns (must match database.ts)
2850
- "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
2851
- "ALTER TABLE memories ADD COLUMN document_id TEXT",
2852
- "ALTER TABLE memories ADD COLUMN user_id TEXT",
2853
- "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
2854
- "ALTER TABLE memories ADD COLUMN page_number INTEGER",
2855
- // Source provenance columns (must match database.ts)
2856
- "ALTER TABLE memories ADD COLUMN source_path TEXT",
2857
- "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
2858
- "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
2859
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
2860
- // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2861
- "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2862
- "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2863
- "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2864
- // Metadata enrichment columns (must match database.ts)
2865
3374
  "ALTER TABLE memories ADD COLUMN intent TEXT",
2866
3375
  "ALTER TABLE memories ADD COLUMN outcome TEXT",
2867
3376
  "ALTER TABLE memories ADD COLUMN domain TEXT",
@@ -2876,195 +3385,528 @@ async function ensureShardSchema(client) {
2876
3385
  "ALTER TABLE memories ADD COLUMN token_cost REAL",
2877
3386
  "ALTER TABLE memories ADD COLUMN audience TEXT",
2878
3387
  "ALTER TABLE memories ADD COLUMN language_type TEXT",
2879
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
2880
- "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
3388
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2881
3389
  ]) {
2882
3390
  try {
2883
3391
  await client.execute(col);
2884
3392
  } catch {
2885
3393
  }
2886
3394
  }
2887
- for (const idx of [
2888
- "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2889
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2890
- "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"
2891
- ]) {
2892
- try {
2893
- await client.execute(idx);
2894
- } catch {
2895
- }
3395
+ try {
3396
+ await client.execute({
3397
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3398
+ args: []
3399
+ });
3400
+ } catch {
3401
+ }
3402
+ }
3403
+ async function disposeDatabase() {
3404
+ if (_walCheckpointTimer) {
3405
+ clearInterval(_walCheckpointTimer);
3406
+ _walCheckpointTimer = null;
3407
+ }
3408
+ if (_daemonClient) {
3409
+ _daemonClient.close();
3410
+ _daemonClient = null;
3411
+ }
3412
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3413
+ _adapterClient.close();
3414
+ }
3415
+ _adapterClient = null;
3416
+ if (_client) {
3417
+ _client.close();
3418
+ _client = null;
3419
+ _resilientClient = null;
3420
+ }
3421
+ }
3422
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
3423
+ var init_database = __esm({
3424
+ "src/lib/database.ts"() {
3425
+ "use strict";
3426
+ init_db_retry();
3427
+ init_employees();
3428
+ init_database_adapter();
3429
+ init_memory();
3430
+ _client = null;
3431
+ _resilientClient = null;
3432
+ _walCheckpointTimer = null;
3433
+ _daemonClient = null;
3434
+ _adapterClient = null;
3435
+ initTurso = initDatabase;
3436
+ SOFT_DELETE_RETENTION_DAYS = 7;
3437
+ disposeTurso = disposeDatabase;
3438
+ }
3439
+ });
3440
+
3441
+ // src/lib/keychain.ts
3442
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3443
+ import { existsSync as existsSync8 } from "fs";
3444
+ import { execSync as execSync2 } from "child_process";
3445
+ import path8 from "path";
3446
+ import os5 from "os";
3447
+ function getKeyDir() {
3448
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path8.join(os5.homedir(), ".exe-os");
3449
+ }
3450
+ function getKeyPath() {
3451
+ return path8.join(getKeyDir(), "master.key");
3452
+ }
3453
+ function macKeychainGet() {
3454
+ if (process.platform !== "darwin") return null;
3455
+ try {
3456
+ return execSync2(
3457
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3458
+ { encoding: "utf-8", timeout: 5e3 }
3459
+ ).trim();
3460
+ } catch {
3461
+ return null;
3462
+ }
3463
+ }
3464
+ function macKeychainSet(value) {
3465
+ if (process.platform !== "darwin") return false;
3466
+ try {
3467
+ try {
3468
+ execSync2(
3469
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3470
+ { timeout: 5e3 }
3471
+ );
3472
+ } catch {
3473
+ }
3474
+ execSync2(
3475
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3476
+ { timeout: 5e3 }
3477
+ );
3478
+ return true;
3479
+ } catch {
3480
+ return false;
3481
+ }
3482
+ }
3483
+ function linuxSecretGet() {
3484
+ if (process.platform !== "linux") return null;
3485
+ try {
3486
+ return execSync2(
3487
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3488
+ { encoding: "utf-8", timeout: 5e3 }
3489
+ ).trim();
3490
+ } catch {
3491
+ return null;
3492
+ }
3493
+ }
3494
+ function linuxSecretSet(value) {
3495
+ if (process.platform !== "linux") return false;
3496
+ try {
3497
+ execSync2(
3498
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3499
+ { timeout: 5e3 }
3500
+ );
3501
+ return true;
3502
+ } catch {
3503
+ return false;
3504
+ }
3505
+ }
3506
+ async function tryKeytar() {
3507
+ try {
3508
+ return await import("keytar");
3509
+ } catch {
3510
+ return null;
3511
+ }
3512
+ }
3513
+ function deriveMachineKey() {
3514
+ try {
3515
+ const crypto2 = __require("crypto");
3516
+ const material = [
3517
+ os5.hostname(),
3518
+ os5.userInfo().username,
3519
+ os5.arch(),
3520
+ os5.platform(),
3521
+ // Machine ID on Linux (stable across reboots)
3522
+ process.platform === "linux" ? readMachineId() : ""
3523
+ ].join("|");
3524
+ return crypto2.createHash("sha256").update(material).digest();
3525
+ } catch {
3526
+ return null;
3527
+ }
3528
+ }
3529
+ function readMachineId() {
3530
+ try {
3531
+ const { readFileSync: readFileSync6 } = __require("fs");
3532
+ return readFileSync6("/etc/machine-id", "utf-8").trim();
3533
+ } catch {
3534
+ return "";
3535
+ }
3536
+ }
3537
+ function encryptWithMachineKey(plaintext, machineKey) {
3538
+ const crypto2 = __require("crypto");
3539
+ const iv = crypto2.randomBytes(12);
3540
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3541
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3542
+ encrypted += cipher.final("base64");
3543
+ const authTag = cipher.getAuthTag().toString("base64");
3544
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3545
+ }
3546
+ function decryptWithMachineKey(encrypted, machineKey) {
3547
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3548
+ try {
3549
+ const crypto2 = __require("crypto");
3550
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3551
+ if (parts.length !== 3) return null;
3552
+ const [ivB64, tagB64, cipherB64] = parts;
3553
+ const iv = Buffer.from(ivB64, "base64");
3554
+ const authTag = Buffer.from(tagB64, "base64");
3555
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
3556
+ decipher.setAuthTag(authTag);
3557
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3558
+ decrypted += decipher.final("utf-8");
3559
+ return decrypted;
3560
+ } catch {
3561
+ return null;
3562
+ }
3563
+ }
3564
+ async function writeMachineBoundFileFallback(b64) {
3565
+ const dir = getKeyDir();
3566
+ await mkdir3(dir, { recursive: true });
3567
+ const keyPath = getKeyPath();
3568
+ const machineKey = deriveMachineKey();
3569
+ if (machineKey) {
3570
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3571
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3572
+ await chmod2(keyPath, 384);
3573
+ return "encrypted";
2896
3574
  }
2897
- try {
2898
- await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
2899
- } catch {
3575
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3576
+ await chmod2(keyPath, 384);
3577
+ return "plaintext";
3578
+ }
3579
+ async function getMasterKey() {
3580
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3581
+ if (nativeValue) {
3582
+ return Buffer.from(nativeValue, "base64");
2900
3583
  }
2901
- for (const idx of [
2902
- "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
2903
- "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
2904
- "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
2905
- ]) {
3584
+ const keytar = await tryKeytar();
3585
+ if (keytar) {
2906
3586
  try {
2907
- await client.execute(idx);
3587
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3588
+ if (keytarValue) {
3589
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3590
+ if (migrated) {
3591
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3592
+ }
3593
+ return Buffer.from(keytarValue, "base64");
3594
+ }
2908
3595
  } catch {
2909
3596
  }
2910
3597
  }
2911
- await client.executeMultiple(`
2912
- CREATE TABLE IF NOT EXISTS entities (
2913
- id TEXT PRIMARY KEY,
2914
- name TEXT NOT NULL,
2915
- type TEXT NOT NULL,
2916
- first_seen TEXT NOT NULL,
2917
- last_seen TEXT NOT NULL,
2918
- properties TEXT DEFAULT '{}',
2919
- UNIQUE(name, type)
2920
- );
2921
-
2922
- CREATE TABLE IF NOT EXISTS relationships (
2923
- id TEXT PRIMARY KEY,
2924
- source_entity_id TEXT NOT NULL,
2925
- target_entity_id TEXT NOT NULL,
2926
- type TEXT NOT NULL,
2927
- weight REAL DEFAULT 1.0,
2928
- timestamp TEXT NOT NULL,
2929
- properties TEXT DEFAULT '{}',
2930
- UNIQUE(source_entity_id, target_entity_id, type)
2931
- );
2932
-
2933
- CREATE TABLE IF NOT EXISTS entity_memories (
2934
- entity_id TEXT NOT NULL,
2935
- memory_id TEXT NOT NULL,
2936
- PRIMARY KEY (entity_id, memory_id)
2937
- );
2938
-
2939
- CREATE TABLE IF NOT EXISTS relationship_memories (
2940
- relationship_id TEXT NOT NULL,
2941
- memory_id TEXT NOT NULL,
2942
- PRIMARY KEY (relationship_id, memory_id)
2943
- );
2944
-
2945
- CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
2946
- CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
2947
- CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
2948
- CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
2949
- CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
2950
-
2951
- CREATE TABLE IF NOT EXISTS hyperedges (
2952
- id TEXT PRIMARY KEY,
2953
- label TEXT NOT NULL,
2954
- relation TEXT NOT NULL,
2955
- confidence REAL DEFAULT 1.0,
2956
- timestamp TEXT NOT NULL
2957
- );
2958
-
2959
- CREATE TABLE IF NOT EXISTS hyperedge_nodes (
2960
- hyperedge_id TEXT NOT NULL,
2961
- entity_id TEXT NOT NULL,
2962
- PRIMARY KEY (hyperedge_id, entity_id)
3598
+ const keyPath = getKeyPath();
3599
+ if (!existsSync8(keyPath)) {
3600
+ process.stderr.write(
3601
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3602
+ `
2963
3603
  );
2964
- `);
2965
- for (const col of [
2966
- "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
2967
- "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
2968
- ]) {
2969
- try {
2970
- await client.execute(col);
2971
- } catch {
2972
- }
3604
+ return null;
2973
3605
  }
2974
- }
2975
- async function getReadyShardClient(projectName) {
2976
- const safeName = safeShardName(projectName);
2977
- let client = getShardClient(projectName);
2978
3606
  try {
2979
- await ensureShardSchema(client);
2980
- return client;
3607
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3608
+ let b64Value;
3609
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3610
+ const machineKey = deriveMachineKey();
3611
+ if (!machineKey) {
3612
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3613
+ return null;
3614
+ }
3615
+ const decrypted = decryptWithMachineKey(content, machineKey);
3616
+ if (!decrypted) {
3617
+ process.stderr.write(
3618
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3619
+ );
3620
+ return null;
3621
+ }
3622
+ b64Value = decrypted;
3623
+ } else {
3624
+ b64Value = content;
3625
+ }
3626
+ const key = Buffer.from(b64Value, "base64");
3627
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3628
+ if (migrated) {
3629
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3630
+ try {
3631
+ await unlink(keyPath);
3632
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3633
+ } catch {
3634
+ }
3635
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3636
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3637
+ if (fallback === "encrypted") {
3638
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3639
+ } else {
3640
+ process.stderr.write(
3641
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3642
+ );
3643
+ }
3644
+ }
3645
+ return key;
2981
3646
  } catch (err) {
2982
- const message = err instanceof Error ? err.message : String(err);
2983
- if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
2984
- client.close();
2985
- _shards.delete(safeName);
2986
- _shardLastAccess.delete(safeName);
2987
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2988
- if (existsSync7(dbPath)) {
2989
- const stat = statSync2(dbPath);
2990
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2991
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
2992
- renameSync3(dbPath, archivedPath);
2993
- process.stderr.write(
2994
- `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
3647
+ process.stderr.write(
3648
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
2995
3649
  `
2996
- );
2997
- }
2998
- client = getShardClient(projectName);
2999
- await ensureShardSchema(client);
3000
- return client;
3650
+ );
3651
+ return null;
3001
3652
  }
3002
3653
  }
3003
- function evictLRU() {
3004
- let oldest = null;
3005
- let oldestTime = Infinity;
3006
- for (const [name, time] of _shardLastAccess) {
3007
- if (time < oldestTime) {
3008
- oldestTime = time;
3009
- oldest = name;
3010
- }
3654
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3655
+ var init_keychain = __esm({
3656
+ "src/lib/keychain.ts"() {
3657
+ "use strict";
3658
+ SERVICE = "exe-mem";
3659
+ ACCOUNT = "master-key";
3660
+ ENCRYPTED_PREFIX = "enc:";
3011
3661
  }
3012
- if (oldest) {
3013
- const client = _shards.get(oldest);
3014
- if (client) {
3015
- client.close();
3016
- }
3017
- _shards.delete(oldest);
3018
- _shardLastAccess.delete(oldest);
3662
+ });
3663
+
3664
+ // src/lib/state-bus.ts
3665
+ var StateBus, orgBus;
3666
+ var init_state_bus = __esm({
3667
+ "src/lib/state-bus.ts"() {
3668
+ "use strict";
3669
+ StateBus = class {
3670
+ handlers = /* @__PURE__ */ new Map();
3671
+ globalHandlers = /* @__PURE__ */ new Set();
3672
+ /** Emit an event to all subscribers */
3673
+ emit(event) {
3674
+ const typeHandlers = this.handlers.get(event.type);
3675
+ if (typeHandlers) {
3676
+ for (const handler of typeHandlers) {
3677
+ try {
3678
+ handler(event);
3679
+ } catch {
3680
+ }
3681
+ }
3682
+ }
3683
+ for (const handler of this.globalHandlers) {
3684
+ try {
3685
+ handler(event);
3686
+ } catch {
3687
+ }
3688
+ }
3689
+ }
3690
+ /** Subscribe to a specific event type */
3691
+ on(type, handler) {
3692
+ if (!this.handlers.has(type)) {
3693
+ this.handlers.set(type, /* @__PURE__ */ new Set());
3694
+ }
3695
+ this.handlers.get(type).add(handler);
3696
+ }
3697
+ /** Subscribe to ALL events */
3698
+ onAny(handler) {
3699
+ this.globalHandlers.add(handler);
3700
+ }
3701
+ /** Unsubscribe from a specific event type */
3702
+ off(type, handler) {
3703
+ this.handlers.get(type)?.delete(handler);
3704
+ }
3705
+ /** Unsubscribe from ALL events */
3706
+ offAny(handler) {
3707
+ this.globalHandlers.delete(handler);
3708
+ }
3709
+ /** Remove all listeners */
3710
+ clear() {
3711
+ this.handlers.clear();
3712
+ this.globalHandlers.clear();
3713
+ }
3714
+ };
3715
+ orgBus = new StateBus();
3019
3716
  }
3717
+ });
3718
+
3719
+ // src/lib/memory-write-governor.ts
3720
+ import { createHash } from "crypto";
3721
+ function normalizeMemoryText(text) {
3722
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3723
+ }
3724
+ function classifyMemoryType(input) {
3725
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3726
+ const tool = input.tool_name.toLowerCase();
3727
+ const text = input.raw_text.toLowerCase();
3728
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3729
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3730
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3731
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3732
+ if (tool.includes("checkpoint") || text.startsWith("context checkpoint")) return "checkpoint";
3733
+ if (tool.includes("sessionsummary") || tool.includes("session-summary")) return "summary";
3734
+ if (tool.includes("sessionend") || text.startsWith("session ended")) return "summary";
3735
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3736
+ if (tool === "store_memory" || tool === "manual") return "observation";
3737
+ return "raw";
3738
+ }
3739
+ function shouldDropMemory(text) {
3740
+ const normalized = normalizeMemoryText(text);
3741
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3742
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3743
+ return { drop: true, reason: "known_boilerplate_noise" };
3744
+ }
3745
+ return { drop: false };
3746
+ }
3747
+ function shouldSkipEmbedding(input) {
3748
+ const type = classifyMemoryType(input);
3749
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3750
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3751
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3752
+ return false;
3020
3753
  }
3021
- function evictIdleShards() {
3022
- const now = Date.now();
3023
- const toEvict = [];
3024
- for (const [name, lastAccess] of _shardLastAccess) {
3025
- if (now - lastAccess > SHARD_IDLE_MS) {
3026
- toEvict.push(name);
3027
- }
3028
- }
3029
- for (const name of toEvict) {
3030
- const client = _shards.get(name);
3031
- if (client) {
3032
- client.close();
3033
- }
3034
- _shards.delete(name);
3035
- _shardLastAccess.delete(name);
3036
- }
3754
+ function hashMemoryContent(text) {
3755
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3037
3756
  }
3038
- function getOpenShardCount() {
3039
- return _shards.size;
3757
+ function scopedDedupArgs(input) {
3758
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3040
3759
  }
3041
- function disposeShards() {
3042
- if (_evictionTimer) {
3043
- clearInterval(_evictionTimer);
3044
- _evictionTimer = null;
3045
- }
3046
- for (const [, client] of _shards) {
3047
- client.close();
3760
+ function governMemoryRecord(record) {
3761
+ const normalized = normalizeMemoryText(record.raw_text);
3762
+ const memoryType = classifyMemoryType({
3763
+ raw_text: normalized,
3764
+ agent_id: record.agent_id,
3765
+ project_name: record.project_name,
3766
+ tool_name: record.tool_name,
3767
+ memory_type: record.memory_type
3768
+ });
3769
+ const drop = shouldDropMemory(normalized);
3770
+ const skipEmbedding = shouldSkipEmbedding({
3771
+ raw_text: normalized,
3772
+ agent_id: record.agent_id,
3773
+ project_name: record.project_name,
3774
+ tool_name: record.tool_name,
3775
+ memory_type: memoryType
3776
+ });
3777
+ return {
3778
+ record: {
3779
+ ...record,
3780
+ raw_text: normalized,
3781
+ memory_type: memoryType,
3782
+ vector: skipEmbedding ? null : record.vector
3783
+ },
3784
+ contentHash: hashMemoryContent(normalized),
3785
+ shouldDrop: drop.drop,
3786
+ dropReason: drop.reason,
3787
+ skipEmbedding,
3788
+ hygiene: {
3789
+ dedup: true,
3790
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3791
+ }
3792
+ };
3793
+ }
3794
+ async function findScopedDuplicate(input) {
3795
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3796
+ const client = getClient2();
3797
+ const args = scopedDedupArgs(input);
3798
+ let sql = `SELECT id FROM memories
3799
+ WHERE content_hash = ?
3800
+ AND agent_id = ?
3801
+ AND project_name = ?
3802
+ AND COALESCE(memory_type, 'raw') = ?
3803
+ AND COALESCE(status, 'active') != 'deleted'`;
3804
+ if (input.excludeId) {
3805
+ sql += " AND id != ?";
3806
+ args.push(input.excludeId);
3807
+ }
3808
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3809
+ const result = await client.execute({ sql, args });
3810
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3811
+ }
3812
+ async function runPostWriteMemoryHygiene(memoryId) {
3813
+ try {
3814
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3815
+ const client = getClient2();
3816
+ const current = await client.execute({
3817
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3818
+ importance, timestamp
3819
+ FROM memories
3820
+ WHERE id = ?
3821
+ LIMIT 1`,
3822
+ args: [memoryId]
3823
+ });
3824
+ const row = current.rows[0];
3825
+ if (!row) return;
3826
+ const memoryType = String(row.memory_type ?? "raw");
3827
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3828
+ const agentId = String(row.agent_id);
3829
+ const projectName = String(row.project_name);
3830
+ if (contentHash) {
3831
+ await client.execute({
3832
+ sql: `UPDATE memories
3833
+ SET status = 'deleted',
3834
+ outcome = COALESCE(outcome, 'superseded')
3835
+ WHERE id != ?
3836
+ AND content_hash = ?
3837
+ AND agent_id = ?
3838
+ AND project_name = ?
3839
+ AND COALESCE(memory_type, 'raw') = ?
3840
+ AND COALESCE(status, 'active') = 'active'`,
3841
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3842
+ });
3843
+ }
3844
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3845
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3846
+ const old = await client.execute({
3847
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3848
+ args: [supersedesId]
3849
+ });
3850
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3851
+ const newImportance = Number(row.importance ?? 0);
3852
+ await client.batch([
3853
+ {
3854
+ sql: `UPDATE memories
3855
+ SET status = 'archived',
3856
+ outcome = COALESCE(outcome, 'superseded')
3857
+ WHERE id = ?`,
3858
+ args: [supersedesId]
3859
+ },
3860
+ {
3861
+ sql: `UPDATE memories
3862
+ SET importance = MAX(COALESCE(importance, 5), ?),
3863
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3864
+ WHERE id = ?`,
3865
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3866
+ }
3867
+ ], "write");
3868
+ }
3869
+ } catch (err) {
3870
+ process.stderr.write(
3871
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3872
+ `
3873
+ );
3048
3874
  }
3049
- _shards.clear();
3050
- _shardLastAccess.clear();
3051
- _shardingEnabled = false;
3052
- _encryptionKey = null;
3053
3875
  }
3054
- var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3055
- var init_shard_manager = __esm({
3056
- "src/lib/shard-manager.ts"() {
3876
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3877
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3878
+ if (memoryIds.length === 0) return;
3879
+ const run = () => {
3880
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3881
+ };
3882
+ if (typeof setImmediate === "function") setImmediate(run);
3883
+ else setTimeout(run, 0);
3884
+ }
3885
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3886
+ var init_memory_write_governor = __esm({
3887
+ "src/lib/memory-write-governor.ts"() {
3057
3888
  "use strict";
3058
- init_config();
3059
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3060
- SHARD_IDLE_MS = 5 * 60 * 1e3;
3061
- MAX_OPEN_SHARDS = 10;
3062
- EVICTION_INTERVAL_MS = 60 * 1e3;
3063
- _shards = /* @__PURE__ */ new Map();
3064
- _shardLastAccess = /* @__PURE__ */ new Map();
3065
- _evictionTimer = null;
3066
- _encryptionKey = null;
3067
- _shardingEnabled = false;
3889
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3890
+ "decision",
3891
+ "adr",
3892
+ "behavior",
3893
+ "procedure"
3894
+ ]);
3895
+ NOISE_DROP_PATTERNS = [
3896
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3897
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3898
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3899
+ /^\s*Intercom is a speedup, not delivery/im,
3900
+ /^\s*Context bar reads as USAGE not remaining/im
3901
+ ];
3902
+ SKIP_EMBED_PATTERNS = [
3903
+ /tmux capture-pane\b/i,
3904
+ /docker ps\b/i,
3905
+ /docker images\b/i,
3906
+ /git status\b/i,
3907
+ /grep .*node_modules/i,
3908
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3909
+ ];
3068
3910
  }
3069
3911
  });
3070
3912
 
@@ -3323,595 +4165,969 @@ ${p.content}`).join("\n\n");
3323
4165
  }
3324
4166
  });
3325
4167
 
3326
- // src/lib/worker-gate.ts
3327
- var worker_gate_exports = {};
3328
- __export(worker_gate_exports, {
3329
- MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
3330
- cleanupWorkerPid: () => cleanupWorkerPid,
3331
- registerWorkerPid: () => registerWorkerPid,
3332
- releaseBackfillLock: () => releaseBackfillLock,
3333
- tryAcquireBackfillLock: () => tryAcquireBackfillLock,
3334
- tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
4168
+ // src/lib/memory-cards.ts
4169
+ var memory_cards_exports = {};
4170
+ __export(memory_cards_exports, {
4171
+ extractMemoryCards: () => extractMemoryCards,
4172
+ insertMemoryCardsForBatch: () => insertMemoryCardsForBatch,
4173
+ searchMemoryCards: () => searchMemoryCards
3335
4174
  });
3336
- import { readdirSync as readdirSync2, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
3337
- import path8 from "path";
3338
- function tryAcquireWorkerSlot() {
3339
- try {
3340
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3341
- const reservationId = `res-${process.pid}-${Date.now()}`;
3342
- const reservationPath = path8.join(WORKER_PID_DIR, `${reservationId}.pid`);
3343
- writeFileSync3(reservationPath, String(process.pid));
3344
- const files = readdirSync2(WORKER_PID_DIR);
3345
- let alive = 0;
3346
- for (const f of files) {
3347
- if (!f.endsWith(".pid")) continue;
3348
- if (f.startsWith("res-")) {
3349
- alive++;
3350
- continue;
3351
- }
3352
- const dashIdx = f.lastIndexOf("-");
3353
- const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
3354
- if (isNaN(pid)) continue;
3355
- try {
3356
- process.kill(pid, 0);
3357
- alive++;
3358
- } catch {
3359
- try {
3360
- unlinkSync3(path8.join(WORKER_PID_DIR, f));
3361
- } catch {
3362
- }
3363
- }
3364
- }
3365
- if (alive > MAX_CONCURRENT_WORKERS) {
3366
- try {
3367
- unlinkSync3(reservationPath);
3368
- } catch {
3369
- }
3370
- return false;
3371
- }
3372
- try {
3373
- unlinkSync3(reservationPath);
3374
- } catch {
3375
- }
3376
- return true;
3377
- } catch {
3378
- return true;
3379
- }
3380
- }
3381
- function registerWorkerPid(pid) {
3382
- try {
3383
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3384
- writeFileSync3(path8.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3385
- } catch {
3386
- }
3387
- }
3388
- function cleanupWorkerPid() {
3389
- try {
3390
- unlinkSync3(path8.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3391
- } catch {
3392
- }
3393
- }
3394
- function tryAcquireBackfillLock() {
3395
- try {
3396
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3397
- if (existsSync8(BACKFILL_LOCK)) {
3398
- try {
3399
- const pid = parseInt(
3400
- __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
3401
- 10
3402
- );
3403
- if (!isNaN(pid) && pid > 0) {
3404
- try {
3405
- process.kill(pid, 0);
3406
- return false;
3407
- } catch {
3408
- }
3409
- }
3410
- } catch {
3411
- }
3412
- }
3413
- writeFileSync3(BACKFILL_LOCK, String(process.pid));
3414
- return true;
3415
- } catch {
3416
- return true;
4175
+ import { createHash as createHash2 } from "crypto";
4176
+ function stableId(memoryId, type, content) {
4177
+ return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4178
+ }
4179
+ function cleanText(text) {
4180
+ return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
4181
+ }
4182
+ function splitSentences(text) {
4183
+ return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
4184
+ }
4185
+ function inferCardType(sentence, toolName) {
4186
+ const lower = sentence.toLowerCase();
4187
+ if (toolName === "store_decision" || /\b(decided|decision|adr|approved|rejected)\b/.test(lower)) return "decision";
4188
+ if (/\b(prefers|preference|likes|dislikes|wants|doesn't want|does not want)\b/.test(lower)) return "preference";
4189
+ if (/\b(changed|updated|replaced|now|no longer|instead|supersedes)\b/.test(lower)) return "belief_update";
4190
+ if (toolName && ["Read", "Write", "Edit", "Bash"].includes(toolName)) return "code";
4191
+ if (/\b(meeting|deadline|shipped|launched|completed|failed|blocked|assigned|created)\b/.test(lower)) return "event";
4192
+ return "fact";
4193
+ }
4194
+ function extractSubject(sentence, agentId) {
4195
+ const explicit = sentence.match(/\b([A-Z][a-zA-Z0-9_-]{2,}(?:\s+[A-Z][a-zA-Z0-9_-]{2,})?)\b/);
4196
+ return explicit?.[1] ?? agentId;
4197
+ }
4198
+ function predicateFor(type) {
4199
+ switch (type) {
4200
+ case "preference":
4201
+ return "prefers";
4202
+ case "belief_update":
4203
+ return "updated";
4204
+ case "decision":
4205
+ return "decided";
4206
+ case "event":
4207
+ return "happened";
4208
+ case "code":
4209
+ return "implemented";
4210
+ default:
4211
+ return "states";
4212
+ }
4213
+ }
4214
+ function extractMemoryCards(row) {
4215
+ const sentences = splitSentences(row.raw_text);
4216
+ const cards = [];
4217
+ for (const sentence of sentences) {
4218
+ const type = inferCardType(sentence, row.tool_name);
4219
+ const subject = extractSubject(sentence, row.agent_id);
4220
+ const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
4221
+ cards.push({
4222
+ id: stableId(row.id, type, content),
4223
+ memory_id: row.id,
4224
+ agent_id: row.agent_id,
4225
+ session_id: row.session_id,
4226
+ project_name: row.project_name ?? null,
4227
+ timestamp: row.timestamp,
4228
+ card_type: type,
4229
+ subject,
4230
+ predicate: predicateFor(type),
4231
+ object: content,
4232
+ content,
4233
+ source_ref: row.id,
4234
+ confidence: type === "fact" ? 0.55 : 0.65
4235
+ });
4236
+ if (cards.length >= MAX_CARDS_PER_MEMORY) break;
3417
4237
  }
4238
+ return cards;
3418
4239
  }
3419
- function releaseBackfillLock() {
3420
- try {
3421
- unlinkSync3(BACKFILL_LOCK);
3422
- } catch {
3423
- }
4240
+ async function insertMemoryCardsForBatch(rows) {
4241
+ const cards = rows.flatMap(extractMemoryCards);
4242
+ if (cards.length === 0) return 0;
4243
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4244
+ const client = getClient();
4245
+ const stmts = cards.map((card) => ({
4246
+ sql: `INSERT OR IGNORE INTO memory_cards
4247
+ (id, memory_id, agent_id, session_id, project_name, timestamp, card_type,
4248
+ subject, predicate, object, content, source_ref, confidence, active, created_at)
4249
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)`,
4250
+ args: [
4251
+ card.id,
4252
+ card.memory_id,
4253
+ card.agent_id,
4254
+ card.session_id,
4255
+ card.project_name,
4256
+ card.timestamp,
4257
+ card.card_type,
4258
+ card.subject,
4259
+ card.predicate,
4260
+ card.object,
4261
+ card.content,
4262
+ card.source_ref,
4263
+ card.confidence,
4264
+ now
4265
+ ]
4266
+ }));
4267
+ await client.batch(stmts, "write");
4268
+ return cards.length;
4269
+ }
4270
+ function buildMatchExpr(queryText) {
4271
+ 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);
4272
+ if (terms.length === 0) return null;
4273
+ return terms.map((t) => `${t}*`).join(terms.length >= 3 ? " AND " : " OR ");
4274
+ }
4275
+ async function searchMemoryCards(queryText, agentId, options) {
4276
+ const limit = options?.limit ?? 10;
4277
+ const matchExpr = buildMatchExpr(queryText);
4278
+ if (!matchExpr) return [];
4279
+ let sql = `SELECT c.id, c.memory_id, c.agent_id, c.session_id, c.project_name,
4280
+ c.timestamp, c.card_type, c.content, c.source_ref, c.confidence
4281
+ FROM memory_cards c
4282
+ JOIN memory_cards_fts fts ON c.rowid = fts.rowid
4283
+ WHERE memory_cards_fts MATCH ?
4284
+ AND c.agent_id = ?
4285
+ AND COALESCE(c.active, 1) = 1`;
4286
+ const args = [matchExpr, agentId];
4287
+ if (options?.projectName) {
4288
+ sql += ` AND c.project_name = ?`;
4289
+ args.push(options.projectName);
4290
+ }
4291
+ if (options?.since) {
4292
+ sql += ` AND c.timestamp >= ?`;
4293
+ args.push(options.since);
4294
+ }
4295
+ sql += ` ORDER BY rank LIMIT ?`;
4296
+ args.push(limit);
4297
+ const result = await getClient().execute({ sql, args });
4298
+ return result.rows.map((row) => ({
4299
+ id: `card:${String(row.id)}`,
4300
+ agent_id: String(row.agent_id),
4301
+ agent_role: "memory_card",
4302
+ session_id: String(row.session_id),
4303
+ timestamp: String(row.timestamp),
4304
+ tool_name: `memory_card:${String(row.card_type)}`,
4305
+ project_name: row.project_name == null ? "" : String(row.project_name),
4306
+ has_error: false,
4307
+ raw_text: `[${String(row.card_type)}] ${String(row.content)}
4308
+ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4309
+ vector: [],
4310
+ importance: 6,
4311
+ status: "active",
4312
+ confidence: Number(row.confidence ?? 0.6),
4313
+ last_accessed: String(row.timestamp)
4314
+ }));
3424
4315
  }
3425
- var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
3426
- var init_worker_gate = __esm({
3427
- "src/lib/worker-gate.ts"() {
4316
+ var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
4317
+ var init_memory_cards = __esm({
4318
+ "src/lib/memory-cards.ts"() {
3428
4319
  "use strict";
3429
- init_config();
3430
- WORKER_PID_DIR = path8.join(EXE_AI_DIR, "worker-pids");
3431
- MAX_CONCURRENT_WORKERS = 3;
3432
- BACKFILL_LOCK = path8.join(WORKER_PID_DIR, "backfill.lock");
4320
+ init_database();
4321
+ MAX_CARDS_PER_MEMORY = 6;
4322
+ MAX_SENTENCE_CHARS = 360;
3433
4323
  }
3434
4324
  });
3435
4325
 
3436
- // src/lib/db-backup.ts
3437
- var db_backup_exports = {};
3438
- __export(db_backup_exports, {
3439
- createBackup: () => createBackup,
3440
- findActiveDb: () => findActiveDb,
3441
- getBackupDir: () => getBackupDir,
3442
- getLatestBackup: () => getLatestBackup,
3443
- hasBackupToday: () => hasBackupToday,
3444
- listBackups: () => listBackups,
3445
- rotateBackups: () => rotateBackups
4326
+ // src/lib/store.ts
4327
+ var store_exports = {};
4328
+ __export(store_exports, {
4329
+ attachDocumentMetadata: () => attachDocumentMetadata,
4330
+ buildRawVisibilityFilter: () => buildRawVisibilityFilter,
4331
+ buildWikiScopeFilter: () => buildWikiScopeFilter,
4332
+ classifyTier: () => classifyTier,
4333
+ disposeStore: () => disposeStore,
4334
+ flushBatch: () => flushBatch,
4335
+ flushTier3: () => flushTier3,
4336
+ getMemoryCardinality: () => getMemoryCardinality,
4337
+ initStore: () => initStore,
4338
+ reserveVersions: () => reserveVersions,
4339
+ searchMemories: () => searchMemories,
4340
+ updateMemoryStatus: () => updateMemoryStatus,
4341
+ vectorToBlob: () => vectorToBlob,
4342
+ writeMemory: () => writeMemory
3446
4343
  });
3447
- import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync as statSync3 } from "fs";
3448
- import path9 from "path";
3449
- function findActiveDb() {
3450
- for (const name of DB_NAMES) {
3451
- const p = path9.join(EXE_AI_DIR, name);
3452
- if (existsSync9(p)) return p;
4344
+ function isBusyError2(err) {
4345
+ if (err instanceof Error) {
4346
+ const msg = err.message.toLowerCase();
4347
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
3453
4348
  }
3454
- return null;
4349
+ return false;
3455
4350
  }
3456
- function createBackup(reason = "manual") {
3457
- const dbPath = findActiveDb();
3458
- if (!dbPath) return null;
3459
- mkdirSync4(BACKUP_DIR, { recursive: true });
3460
- const dbName = path9.basename(dbPath, ".db");
3461
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
3462
- const backupName = `${dbName}-${reason}-${timestamp}.db`;
3463
- const backupPath = path9.join(BACKUP_DIR, backupName);
3464
- copyFileSync(dbPath, backupPath);
3465
- const walPath = dbPath + "-wal";
3466
- if (existsSync9(walPath)) {
3467
- try {
3468
- copyFileSync(walPath, backupPath + "-wal");
3469
- } catch {
3470
- }
3471
- }
3472
- const shmPath = dbPath + "-shm";
3473
- if (existsSync9(shmPath)) {
4351
+ async function retryOnBusy2(fn, label) {
4352
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
3474
4353
  try {
3475
- copyFileSync(shmPath, backupPath + "-shm");
3476
- } catch {
3477
- }
3478
- }
3479
- return backupPath;
3480
- }
3481
- function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
3482
- if (!existsSync9(BACKUP_DIR)) return 0;
3483
- const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
3484
- let deleted = 0;
3485
- try {
3486
- const files = readdirSync3(BACKUP_DIR);
3487
- for (const file of files) {
3488
- if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
3489
- const filePath = path9.join(BACKUP_DIR, file);
3490
- try {
3491
- const stat = statSync3(filePath);
3492
- if (stat.mtimeMs < cutoff) {
3493
- unlinkSync4(filePath);
3494
- deleted++;
3495
- }
3496
- } catch {
3497
- }
4354
+ return await fn();
4355
+ } catch (err) {
4356
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
4357
+ process.stderr.write(
4358
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
4359
+ `
4360
+ );
4361
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
3498
4362
  }
3499
- } catch {
3500
- }
3501
- return deleted;
3502
- }
3503
- function listBackups() {
3504
- if (!existsSync9(BACKUP_DIR)) return [];
3505
- try {
3506
- const files = readdirSync3(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
3507
- return files.map((name) => {
3508
- const p = path9.join(BACKUP_DIR, name);
3509
- const stat = statSync3(p);
3510
- return { path: p, name, size: stat.size, date: stat.mtime };
3511
- }).sort((a, b) => b.date.getTime() - a.date.getTime());
3512
- } catch {
3513
- return [];
3514
4363
  }
4364
+ throw new Error("unreachable");
3515
4365
  }
3516
- function hasBackupToday(reason) {
3517
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3518
- const backups = listBackups();
3519
- return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3520
- }
3521
- function getLatestBackup() {
3522
- const backups = listBackups();
3523
- return backups.length > 0 ? backups[0].path : null;
3524
- }
3525
- function getBackupDir() {
3526
- return BACKUP_DIR;
3527
- }
3528
- var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
3529
- var init_db_backup = __esm({
3530
- "src/lib/db-backup.ts"() {
3531
- "use strict";
3532
- init_config();
3533
- BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
3534
- DEFAULT_KEEP_DAYS = 3;
3535
- DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
4366
+ async function initStore(options) {
4367
+ if (_flushTimer !== null) {
4368
+ clearInterval(_flushTimer);
4369
+ _flushTimer = null;
3536
4370
  }
3537
- });
3538
-
3539
- // src/bin/exe-doctor.ts
3540
- import os6 from "os";
3541
-
3542
- // src/lib/store.ts
3543
- init_memory();
3544
- init_database();
3545
-
3546
- // src/lib/keychain.ts
3547
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3548
- import { existsSync as existsSync6 } from "fs";
3549
- import { execSync as execSync2 } from "child_process";
3550
- import path6 from "path";
3551
- import os5 from "os";
3552
- var SERVICE = "exe-mem";
3553
- var ACCOUNT = "master-key";
3554
- function getKeyDir() {
3555
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3556
- }
3557
- function getKeyPath() {
3558
- return path6.join(getKeyDir(), "master.key");
3559
- }
3560
- function macKeychainGet() {
3561
- if (process.platform !== "darwin") return null;
4371
+ _pendingRecords = [];
4372
+ _flushing = false;
4373
+ _batchSize = options?.batchSize ?? 20;
4374
+ _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
4375
+ let dbPath = options?.dbPath;
4376
+ if (!dbPath) {
4377
+ const config = await loadConfig();
4378
+ dbPath = config.dbPath;
4379
+ }
4380
+ let masterKey = options?.masterKey ?? null;
4381
+ if (!masterKey) {
4382
+ masterKey = await getMasterKey();
4383
+ if (!masterKey) {
4384
+ throw new Error(
4385
+ "No encryption key found. Run /exe-setup to generate one."
4386
+ );
4387
+ }
4388
+ }
4389
+ const hexKey = masterKey.toString("hex");
4390
+ await initTurso({
4391
+ dbPath,
4392
+ encryptionKey: hexKey
4393
+ });
4394
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
3562
4395
  try {
3563
- return execSync2(
3564
- `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3565
- { encoding: "utf-8", timeout: 5e3 }
3566
- ).trim();
4396
+ const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4397
+ await initDaemonClient2();
3567
4398
  } catch {
3568
- return null;
3569
4399
  }
3570
- }
3571
- function macKeychainSet(value) {
3572
- if (process.platform !== "darwin") return false;
3573
- try {
4400
+ if (!options?.lightweight) {
3574
4401
  try {
3575
- execSync2(
3576
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3577
- { timeout: 5e3 }
3578
- );
4402
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4403
+ initShardManager2(hexKey);
3579
4404
  } catch {
3580
4405
  }
3581
- execSync2(
3582
- `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3583
- { timeout: 5e3 }
4406
+ const client = getClient();
4407
+ const vResult = await retryOnBusy2(
4408
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
4409
+ "version-query"
3584
4410
  );
3585
- return true;
3586
- } catch {
3587
- return false;
4411
+ _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4412
+ try {
4413
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
4414
+ await loadGlobalProcedures2();
4415
+ } catch {
4416
+ }
3588
4417
  }
3589
4418
  }
3590
- function linuxSecretGet() {
3591
- if (process.platform !== "linux") return null;
3592
- try {
3593
- return execSync2(
3594
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3595
- { encoding: "utf-8", timeout: 5e3 }
3596
- ).trim();
3597
- } catch {
3598
- return null;
3599
- }
4419
+ function classifyTier(record) {
4420
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
4421
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
4422
+ return 3;
4423
+ }
4424
+ function inferFilePaths(record) {
4425
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
4426
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
4427
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
4428
+ return match ? JSON.stringify([match[1]]) : null;
4429
+ }
4430
+ function inferCommitHash(record) {
4431
+ if (record.tool_name !== "Bash") return null;
4432
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
4433
+ return match ? match[1] : null;
4434
+ }
4435
+ function inferLanguageType(record) {
4436
+ const text = record.raw_text;
4437
+ if (!text || text.length < 10) return null;
4438
+ const trimmed = text.trimStart();
4439
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
4440
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
4441
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
4442
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
4443
+ return "mixed";
4444
+ }
4445
+ function inferDomain(record) {
4446
+ const proj = (record.project_name ?? "").toLowerCase();
4447
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
4448
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
4449
+ return null;
3600
4450
  }
3601
- function linuxSecretSet(value) {
3602
- if (process.platform !== "linux") return false;
3603
- try {
3604
- execSync2(
3605
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3606
- { timeout: 5e3 }
4451
+ async function writeMemory(record) {
4452
+ if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
4453
+ throw new Error(
4454
+ `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3607
4455
  );
3608
- return true;
3609
- } catch {
3610
- return false;
3611
- }
3612
- }
3613
- async function tryKeytar() {
3614
- try {
3615
- return await import("keytar");
3616
- } catch {
3617
- return null;
3618
4456
  }
3619
- }
3620
- var ENCRYPTED_PREFIX = "enc:";
3621
- function deriveMachineKey() {
3622
- try {
3623
- const crypto2 = __require("crypto");
3624
- const material = [
3625
- os5.hostname(),
3626
- os5.userInfo().username,
3627
- os5.arch(),
3628
- os5.platform(),
3629
- // Machine ID on Linux (stable across reboots)
3630
- process.platform === "linux" ? readMachineId() : ""
3631
- ].join("|");
3632
- return crypto2.createHash("sha256").update(material).digest();
3633
- } catch {
3634
- return null;
4457
+ const governed = governMemoryRecord(record);
4458
+ if (governed.shouldDrop) return;
4459
+ record = governed.record;
4460
+ const contentHash = governed.contentHash;
4461
+ const memoryType = record.memory_type ?? "raw";
4462
+ if (_pendingRecords.some(
4463
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4464
+ )) {
4465
+ return;
3635
4466
  }
3636
- }
3637
- function readMachineId() {
3638
4467
  try {
3639
- const { readFileSync: readFileSync6 } = __require("fs");
3640
- return readFileSync6("/etc/machine-id", "utf-8").trim();
4468
+ const existing = await findScopedDuplicate({
4469
+ contentHash,
4470
+ agentId: record.agent_id,
4471
+ projectName: record.project_name,
4472
+ memoryType
4473
+ });
4474
+ if (existing) return;
3641
4475
  } catch {
3642
- return "";
3643
4476
  }
3644
- }
3645
- function encryptWithMachineKey(plaintext, machineKey) {
3646
- const crypto2 = __require("crypto");
3647
- const iv = crypto2.randomBytes(12);
3648
- const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3649
- let encrypted = cipher.update(plaintext, "utf-8", "base64");
3650
- encrypted += cipher.final("base64");
3651
- const authTag = cipher.getAuthTag().toString("base64");
3652
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3653
- }
3654
- function decryptWithMachineKey(encrypted, machineKey) {
3655
- if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3656
- try {
3657
- const crypto2 = __require("crypto");
3658
- const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3659
- if (parts.length !== 3) return null;
3660
- const [ivB64, tagB64, cipherB64] = parts;
3661
- const iv = Buffer.from(ivB64, "base64");
3662
- const authTag = Buffer.from(tagB64, "base64");
3663
- const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
3664
- decipher.setAuthTag(authTag);
3665
- let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3666
- decrypted += decipher.final("utf-8");
3667
- return decrypted;
3668
- } catch {
3669
- return null;
4477
+ const dbRow = {
4478
+ id: record.id,
4479
+ agent_id: record.agent_id,
4480
+ agent_role: record.agent_role,
4481
+ session_id: record.session_id,
4482
+ timestamp: record.timestamp,
4483
+ tool_name: record.tool_name,
4484
+ project_name: record.project_name,
4485
+ has_error: record.has_error ? 1 : 0,
4486
+ raw_text: record.raw_text,
4487
+ vector: record.vector,
4488
+ version: 0,
4489
+ // Placeholder — assigned atomically at flush time
4490
+ task_id: record.task_id ?? null,
4491
+ importance: record.importance ?? 5,
4492
+ status: record.status ?? "active",
4493
+ confidence: record.confidence ?? 0.7,
4494
+ last_accessed: record.last_accessed ?? record.timestamp,
4495
+ workspace_id: record.workspace_id ?? null,
4496
+ document_id: record.document_id ?? null,
4497
+ user_id: record.user_id ?? null,
4498
+ char_offset: record.char_offset ?? null,
4499
+ page_number: record.page_number ?? null,
4500
+ source_path: record.source_path ?? null,
4501
+ source_type: record.source_type ?? null,
4502
+ tier: record.tier ?? classifyTier(record),
4503
+ supersedes_id: record.supersedes_id ?? null,
4504
+ draft: record.draft ? 1 : 0,
4505
+ memory_type: memoryType,
4506
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4507
+ content_hash: contentHash,
4508
+ intent: record.intent ?? null,
4509
+ outcome: record.outcome ?? null,
4510
+ domain: record.domain ?? inferDomain(record),
4511
+ referenced_entities: record.referenced_entities ?? null,
4512
+ retrieval_count: record.retrieval_count ?? 0,
4513
+ chain_position: record.chain_position ?? null,
4514
+ review_status: record.review_status ?? null,
4515
+ context_window_pct: record.context_window_pct ?? null,
4516
+ file_paths: record.file_paths ?? inferFilePaths(record),
4517
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
4518
+ duration_ms: record.duration_ms ?? null,
4519
+ token_cost: record.token_cost ?? null,
4520
+ audience: record.audience ?? null,
4521
+ language_type: record.language_type ?? inferLanguageType(record),
4522
+ parent_memory_id: record.parent_memory_id ?? null
4523
+ };
4524
+ _pendingRecords.push(dbRow);
4525
+ orgBus.emit({
4526
+ type: "memory_stored",
4527
+ agentId: record.agent_id,
4528
+ project: record.project_name,
4529
+ timestamp: record.timestamp
4530
+ });
4531
+ const MAX_PENDING = 1e3;
4532
+ if (_pendingRecords.length > MAX_PENDING) {
4533
+ const dropped = _pendingRecords.length - MAX_PENDING;
4534
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
4535
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
4536
+ }
4537
+ if (_flushTimer === null) {
4538
+ _flushTimer = setInterval(() => {
4539
+ void flushBatch();
4540
+ }, _flushIntervalMs);
4541
+ if (_flushTimer && typeof _flushTimer === "object" && "unref" in _flushTimer) {
4542
+ _flushTimer.unref();
4543
+ }
3670
4544
  }
3671
- }
3672
- async function writeMachineBoundFileFallback(b64) {
3673
- const dir = getKeyDir();
3674
- await mkdir3(dir, { recursive: true });
3675
- const keyPath = getKeyPath();
3676
- const machineKey = deriveMachineKey();
3677
- if (machineKey) {
3678
- const encrypted = encryptWithMachineKey(b64, machineKey);
3679
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3680
- await chmod2(keyPath, 384);
3681
- return "encrypted";
4545
+ if (_pendingRecords.length >= _batchSize) {
4546
+ await flushBatch();
3682
4547
  }
3683
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3684
- await chmod2(keyPath, 384);
3685
- return "plaintext";
3686
4548
  }
3687
- async function getMasterKey() {
3688
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
3689
- if (nativeValue) {
3690
- return Buffer.from(nativeValue, "base64");
3691
- }
3692
- const keytar = await tryKeytar();
3693
- if (keytar) {
4549
+ async function flushBatch() {
4550
+ if (_flushing || _pendingRecords.length === 0) return 0;
4551
+ _flushing = true;
4552
+ try {
4553
+ const batch = _pendingRecords.slice(0);
4554
+ const client = getClient();
4555
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
4556
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4557
+ for (const row of batch) {
4558
+ row.version = baseVersion++;
4559
+ }
4560
+ _nextVersion = baseVersion;
4561
+ const buildStmt = (row) => {
4562
+ const hasVector = row.vector !== null;
4563
+ const taskId = row.task_id ?? null;
4564
+ const importance = row.importance ?? 5;
4565
+ const status = row.status ?? "active";
4566
+ const confidence = row.confidence ?? 0.7;
4567
+ const lastAccessed = row.last_accessed ?? row.timestamp;
4568
+ const workspaceId = row.workspace_id ?? null;
4569
+ const documentId = row.document_id ?? null;
4570
+ const userId = row.user_id ?? null;
4571
+ const charOffset = row.char_offset ?? null;
4572
+ const pageNumber = row.page_number ?? null;
4573
+ const sourcePath = row.source_path ?? null;
4574
+ const sourceType = row.source_type ?? null;
4575
+ const tier = row.tier ?? 3;
4576
+ const supersedesId = row.supersedes_id ?? null;
4577
+ const draft = row.draft ? 1 : 0;
4578
+ const memoryType = row.memory_type ?? "raw";
4579
+ const trajectory = row.trajectory ?? null;
4580
+ const contentHash = row.content_hash ?? null;
4581
+ const intent = row.intent ?? null;
4582
+ const outcome = row.outcome ?? null;
4583
+ const domain = row.domain ?? null;
4584
+ const referencedEntities = row.referenced_entities ?? null;
4585
+ const retrievalCount = row.retrieval_count ?? 0;
4586
+ const chainPosition = row.chain_position ?? null;
4587
+ const reviewStatus = row.review_status ?? null;
4588
+ const contextWindowPct = row.context_window_pct ?? null;
4589
+ const filePaths = row.file_paths ?? null;
4590
+ const commitHash = row.commit_hash ?? null;
4591
+ const durationMs = row.duration_ms ?? null;
4592
+ const tokenCost = row.token_cost ?? null;
4593
+ const audience = row.audience ?? null;
4594
+ const languageType = row.language_type ?? null;
4595
+ const parentMemoryId = row.parent_memory_id ?? null;
4596
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
4597
+ tool_name, project_name,
4598
+ has_error, raw_text, vector, version, task_id, importance, status,
4599
+ confidence, last_accessed,
4600
+ workspace_id, document_id, user_id, char_offset, page_number,
4601
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
4602
+ intent, outcome, domain, referenced_entities, retrieval_count,
4603
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
4604
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
4605
+ const metaArgs = [
4606
+ intent,
4607
+ outcome,
4608
+ domain,
4609
+ referencedEntities,
4610
+ retrievalCount,
4611
+ chainPosition,
4612
+ reviewStatus,
4613
+ contextWindowPct,
4614
+ filePaths,
4615
+ commitHash,
4616
+ durationMs,
4617
+ tokenCost,
4618
+ audience,
4619
+ languageType,
4620
+ parentMemoryId
4621
+ ];
4622
+ const baseArgs = [
4623
+ row.id,
4624
+ row.agent_id,
4625
+ row.agent_role,
4626
+ row.session_id,
4627
+ row.timestamp,
4628
+ row.tool_name,
4629
+ row.project_name,
4630
+ row.has_error,
4631
+ row.raw_text
4632
+ ];
4633
+ const sharedArgs = [
4634
+ row.version,
4635
+ taskId,
4636
+ importance,
4637
+ status,
4638
+ confidence,
4639
+ lastAccessed,
4640
+ workspaceId,
4641
+ documentId,
4642
+ userId,
4643
+ charOffset,
4644
+ pageNumber,
4645
+ sourcePath,
4646
+ sourceType,
4647
+ tier,
4648
+ supersedesId,
4649
+ draft,
4650
+ memoryType,
4651
+ trajectory,
4652
+ contentHash
4653
+ ];
4654
+ return {
4655
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
4656
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
4657
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4658
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
4659
+ };
4660
+ };
4661
+ const globalClient = getClient();
4662
+ const globalStmts = batch.map(buildStmt);
4663
+ await globalClient.batch(globalStmts, "write");
3694
4664
  try {
3695
- const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3696
- if (keytarValue) {
3697
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3698
- if (migrated) {
3699
- process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
4665
+ const { insertMemoryCardsForBatch: insertMemoryCardsForBatch2 } = await Promise.resolve().then(() => (init_memory_cards(), memory_cards_exports));
4666
+ await insertMemoryCardsForBatch2(batch);
4667
+ } catch {
4668
+ }
4669
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4670
+ _pendingRecords.splice(0, batch.length);
4671
+ try {
4672
+ const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4673
+ if (isShardingEnabled2()) {
4674
+ const byProject = /* @__PURE__ */ new Map();
4675
+ let skippedUnknown = 0;
4676
+ for (const row of batch) {
4677
+ const proj = row.project_name?.trim();
4678
+ if (!proj) {
4679
+ skippedUnknown++;
4680
+ continue;
4681
+ }
4682
+ if (!byProject.has(proj)) byProject.set(proj, []);
4683
+ byProject.get(proj).push(row);
4684
+ }
4685
+ if (skippedUnknown > 0) {
4686
+ process.stderr.write(
4687
+ `[store] Shard skip: ${skippedUnknown} record(s) with empty project_name (kept in main DB only)
4688
+ `
4689
+ );
4690
+ }
4691
+ for (const [project, rows] of byProject) {
4692
+ try {
4693
+ const shardClient = await getReadyShardClient2(project);
4694
+ const shardStmts = rows.map(buildStmt);
4695
+ await shardClient.batch(shardStmts, "write");
4696
+ } catch (err) {
4697
+ const fullError = err instanceof Error ? `${err.name}: ${err.message}${err.stack ? `
4698
+ ${err.stack.split("\n").slice(1, 3).join("\n")}` : ""}` : String(err);
4699
+ process.stderr.write(
4700
+ `[store] Shard write failed for ${project} (${rows.length} records): ${fullError}
4701
+ `
4702
+ );
4703
+ }
3700
4704
  }
3701
- return Buffer.from(keytarValue, "base64");
3702
4705
  }
3703
4706
  } catch {
3704
4707
  }
4708
+ return batch.length;
4709
+ } finally {
4710
+ _flushing = false;
3705
4711
  }
3706
- const keyPath = getKeyPath();
3707
- if (!existsSync6(keyPath)) {
3708
- process.stderr.write(
3709
- `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3710
- `
3711
- );
3712
- return null;
4712
+ }
4713
+ function buildWikiScopeFilter(options, columnPrefix) {
4714
+ const args = [];
4715
+ let clause = "";
4716
+ if (options?.workspaceId !== void 0) {
4717
+ clause += ` AND ${columnPrefix}workspace_id = ?`;
4718
+ args.push(options.workspaceId);
4719
+ }
4720
+ if (options?.userId === void 0) {
4721
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
4722
+ } else if (options.userId === null) {
4723
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
4724
+ } else {
4725
+ clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
4726
+ args.push(options.userId);
3713
4727
  }
3714
- try {
3715
- const content = (await readFile3(keyPath, "utf-8")).trim();
3716
- let b64Value;
3717
- if (content.startsWith(ENCRYPTED_PREFIX)) {
3718
- const machineKey = deriveMachineKey();
3719
- if (!machineKey) {
3720
- process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3721
- return null;
3722
- }
3723
- const decrypted = decryptWithMachineKey(content, machineKey);
3724
- if (!decrypted) {
3725
- process.stderr.write(
3726
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3727
- );
3728
- return null;
3729
- }
3730
- b64Value = decrypted;
3731
- } else {
3732
- b64Value = content;
3733
- }
3734
- const key = Buffer.from(b64Value, "base64");
3735
- const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3736
- if (migrated) {
3737
- process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3738
- try {
3739
- await unlink(keyPath);
3740
- process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3741
- } catch {
3742
- }
3743
- } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3744
- const fallback = await writeMachineBoundFileFallback(b64Value);
3745
- if (fallback === "encrypted") {
3746
- process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3747
- } else {
3748
- process.stderr.write(
3749
- "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3750
- );
3751
- }
3752
- }
3753
- return key;
3754
- } catch (err) {
3755
- process.stderr.write(
3756
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
3757
- `
3758
- );
3759
- return null;
4728
+ return { clause, args };
4729
+ }
4730
+ function buildRawVisibilityFilter(options, columnPrefix) {
4731
+ if (options?.includeRaw === false) {
4732
+ return {
4733
+ clause: ` AND COALESCE(${columnPrefix}memory_type, 'raw') != 'raw'`,
4734
+ args: []
4735
+ };
3760
4736
  }
4737
+ return { clause: "", args: [] };
3761
4738
  }
3762
-
3763
- // src/lib/store.ts
3764
- init_config();
3765
-
3766
- // src/lib/state-bus.ts
3767
- var StateBus = class {
3768
- handlers = /* @__PURE__ */ new Map();
3769
- globalHandlers = /* @__PURE__ */ new Set();
3770
- /** Emit an event to all subscribers */
3771
- emit(event) {
3772
- const typeHandlers = this.handlers.get(event.type);
3773
- if (typeHandlers) {
3774
- for (const handler of typeHandlers) {
3775
- try {
3776
- handler(event);
3777
- } catch {
3778
- }
3779
- }
4739
+ async function searchMemories(queryVector, agentId, options) {
4740
+ let client;
4741
+ try {
4742
+ const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4743
+ if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
4744
+ client = await getReadyShardClient2(options.projectName);
4745
+ } else {
4746
+ client = getClient();
3780
4747
  }
3781
- for (const handler of this.globalHandlers) {
3782
- try {
3783
- handler(event);
3784
- } catch {
3785
- }
4748
+ } catch {
4749
+ client = getClient();
4750
+ }
4751
+ const limit = options?.limit ?? 10;
4752
+ const statusFilter = options?.includeArchived ? "" : `
4753
+ AND COALESCE(status, 'active') = 'active'`;
4754
+ const draftFilter = options?.includeDrafts ? "" : `
4755
+ AND (draft = 0 OR draft IS NULL)`;
4756
+ let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4757
+ tool_name, project_name,
4758
+ has_error, raw_text, vector, importance, status,
4759
+ confidence, last_accessed,
4760
+ workspace_id, document_id, user_id,
4761
+ char_offset, page_number,
4762
+ source_path, source_type
4763
+ FROM memories
4764
+ WHERE agent_id = ?
4765
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
4766
+ AND COALESCE(confidence, 0.7) >= 0.3`;
4767
+ const args = [agentId];
4768
+ const scope = buildWikiScopeFilter(options, "");
4769
+ sql += scope.clause;
4770
+ args.push(...scope.args);
4771
+ const rawVisibility = buildRawVisibilityFilter(options, "");
4772
+ sql += rawVisibility.clause;
4773
+ args.push(...rawVisibility.args);
4774
+ if (options?.projectName) {
4775
+ sql += ` AND project_name = ?`;
4776
+ args.push(options.projectName);
4777
+ }
4778
+ if (options?.toolName) {
4779
+ sql += ` AND tool_name = ?`;
4780
+ args.push(options.toolName);
4781
+ }
4782
+ if (options?.hasError !== void 0) {
4783
+ sql += ` AND has_error = ?`;
4784
+ args.push(options.hasError ? 1 : 0);
4785
+ }
4786
+ if (options?.since) {
4787
+ sql += ` AND timestamp >= ?`;
4788
+ args.push(options.since);
4789
+ }
4790
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4791
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4792
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4793
+ args.push(...uniqueTypes);
4794
+ } else if (options?.memoryType) {
4795
+ sql += ` AND memory_type = ?`;
4796
+ args.push(options.memoryType);
4797
+ }
4798
+ sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
4799
+ args.push(vectorToBlob(queryVector));
4800
+ sql += ` LIMIT ?`;
4801
+ args.push(limit);
4802
+ const result = await client.execute({ sql, args });
4803
+ return result.rows.map((row) => ({
4804
+ id: row.id,
4805
+ agent_id: row.agent_id,
4806
+ agent_role: row.agent_role,
4807
+ session_id: row.session_id,
4808
+ timestamp: row.timestamp,
4809
+ tool_name: row.tool_name,
4810
+ project_name: row.project_name,
4811
+ has_error: row.has_error === 1,
4812
+ raw_text: row.raw_text,
4813
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
4814
+ importance: row.importance ?? 5,
4815
+ status: row.status ?? "active",
4816
+ confidence: row.confidence ?? 0.7,
4817
+ last_accessed: row.last_accessed ?? row.timestamp,
4818
+ workspace_id: row.workspace_id ?? null,
4819
+ document_id: row.document_id ?? null,
4820
+ user_id: row.user_id ?? null,
4821
+ char_offset: row.char_offset ?? null,
4822
+ page_number: row.page_number ?? null,
4823
+ source_path: row.source_path ?? null,
4824
+ source_type: row.source_type ?? null
4825
+ }));
4826
+ }
4827
+ async function attachDocumentMetadata(records) {
4828
+ const docIds = [
4829
+ ...new Set(
4830
+ records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
4831
+ )
4832
+ ];
4833
+ if (docIds.length === 0) return records;
4834
+ try {
4835
+ const client = getClient();
4836
+ const placeholders = docIds.map(() => "?").join(",");
4837
+ const result = await client.execute({
4838
+ sql: `SELECT id, filename, mime, source_type, uploaded_at
4839
+ FROM documents
4840
+ WHERE id IN (${placeholders})`,
4841
+ args: docIds
4842
+ });
4843
+ const byId = /* @__PURE__ */ new Map();
4844
+ for (const row of result.rows) {
4845
+ const id = row.id;
4846
+ byId.set(id, {
4847
+ document_id: id,
4848
+ filename: row.filename,
4849
+ mime: row.mime ?? null,
4850
+ source_type: row.source_type ?? null,
4851
+ uploaded_at: row.uploaded_at
4852
+ });
3786
4853
  }
3787
- }
3788
- /** Subscribe to a specific event type */
3789
- on(type, handler) {
3790
- if (!this.handlers.has(type)) {
3791
- this.handlers.set(type, /* @__PURE__ */ new Set());
4854
+ for (const record of records) {
4855
+ if (!record.document_id) continue;
4856
+ record.document_metadata = byId.get(record.document_id) ?? null;
3792
4857
  }
3793
- this.handlers.get(type).add(handler);
3794
- }
3795
- /** Subscribe to ALL events */
3796
- onAny(handler) {
3797
- this.globalHandlers.add(handler);
3798
- }
3799
- /** Unsubscribe from a specific event type */
3800
- off(type, handler) {
3801
- this.handlers.get(type)?.delete(handler);
3802
- }
3803
- /** Unsubscribe from ALL events */
3804
- offAny(handler) {
3805
- this.globalHandlers.delete(handler);
3806
- }
3807
- /** Remove all listeners */
3808
- clear() {
3809
- this.handlers.clear();
3810
- this.globalHandlers.clear();
3811
- }
3812
- };
3813
- var orgBus = new StateBus();
3814
-
3815
- // src/lib/memory-write-governor.ts
3816
- import { createHash } from "crypto";
3817
-
3818
- // src/lib/store.ts
3819
- var INIT_MAX_RETRIES = 3;
3820
- var INIT_RETRY_DELAY_MS = 1e3;
3821
- function isBusyError2(err) {
3822
- if (err instanceof Error) {
3823
- const msg = err.message.toLowerCase();
3824
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
4858
+ } catch {
3825
4859
  }
3826
- return false;
4860
+ return records;
3827
4861
  }
3828
- async function retryOnBusy2(fn, label) {
3829
- for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
3830
- try {
3831
- return await fn();
3832
- } catch (err) {
3833
- if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
3834
- process.stderr.write(
3835
- `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
3836
- `
3837
- );
3838
- await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
3839
- }
4862
+ async function flushTier3(agentId, options) {
4863
+ const client = getClient();
4864
+ const maxAge = options?.maxAgeHours ?? 72;
4865
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
4866
+ if (options?.dryRun) {
4867
+ const result2 = await client.execute({
4868
+ sql: `SELECT COUNT(*) as cnt FROM memories
4869
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
4870
+ args: [agentId, cutoff]
4871
+ });
4872
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
3840
4873
  }
3841
- throw new Error("unreachable");
4874
+ const result = await client.execute({
4875
+ sql: `UPDATE memories SET status = 'archived'
4876
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
4877
+ args: [agentId, cutoff]
4878
+ });
4879
+ return { archived: result.rowsAffected };
3842
4880
  }
3843
- var _pendingRecords = [];
3844
- var _batchSize = 20;
3845
- var _flushIntervalMs = 1e4;
3846
- var _flushTimer = null;
3847
- var _flushing = false;
3848
- var _nextVersion = 1;
3849
- async function initStore(options) {
4881
+ async function disposeStore() {
3850
4882
  if (_flushTimer !== null) {
3851
4883
  clearInterval(_flushTimer);
3852
4884
  _flushTimer = null;
3853
4885
  }
4886
+ if (_pendingRecords.length > 0) {
4887
+ await flushBatch();
4888
+ }
4889
+ await disposeTurso();
3854
4890
  _pendingRecords = [];
3855
- _flushing = false;
3856
- _batchSize = options?.batchSize ?? 20;
3857
- _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
3858
- let dbPath = options?.dbPath;
3859
- if (!dbPath) {
3860
- const config = await loadConfig();
3861
- dbPath = config.dbPath;
4891
+ _nextVersion = 1;
4892
+ }
4893
+ function vectorToBlob(vector) {
4894
+ const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
4895
+ return JSON.stringify(Array.from(f32));
4896
+ }
4897
+ async function updateMemoryStatus(id, status) {
4898
+ const client = getClient();
4899
+ await client.execute({
4900
+ sql: `UPDATE memories SET status = ? WHERE id = ?`,
4901
+ args: [status, id]
4902
+ });
4903
+ }
4904
+ function reserveVersions(count) {
4905
+ const reserved = [];
4906
+ for (let i = 0; i < count; i++) {
4907
+ reserved.push(_nextVersion++);
3862
4908
  }
3863
- let masterKey = options?.masterKey ?? null;
3864
- if (!masterKey) {
3865
- masterKey = await getMasterKey();
3866
- if (!masterKey) {
3867
- throw new Error(
3868
- "No encryption key found. Run /exe-setup to generate one."
3869
- );
3870
- }
4909
+ return reserved;
4910
+ }
4911
+ async function getMemoryCardinality(agentId) {
4912
+ try {
4913
+ const client = getClient();
4914
+ const result = await client.execute({
4915
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
4916
+ args: [agentId]
4917
+ });
4918
+ return Number(result.rows[0]?.cnt) || 0;
4919
+ } catch {
4920
+ return 0;
4921
+ }
4922
+ }
4923
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
4924
+ var init_store = __esm({
4925
+ "src/lib/store.ts"() {
4926
+ "use strict";
4927
+ init_memory();
4928
+ init_database();
4929
+ init_keychain();
4930
+ init_config();
4931
+ init_state_bus();
4932
+ init_memory_write_governor();
4933
+ INIT_MAX_RETRIES = 3;
4934
+ INIT_RETRY_DELAY_MS = 1e3;
4935
+ _pendingRecords = [];
4936
+ _batchSize = 20;
4937
+ _flushIntervalMs = 1e4;
4938
+ _flushTimer = null;
4939
+ _flushing = false;
4940
+ _nextVersion = 1;
4941
+ }
4942
+ });
4943
+
4944
+ // src/bin/fast-db-init.ts
4945
+ var fast_db_init_exports = {};
4946
+ __export(fast_db_init_exports, {
4947
+ fastDbInit: () => fastDbInit
4948
+ });
4949
+ async function fastDbInit() {
4950
+ const { isInitialized: isInitialized2, getClient: getClient2, setExternalClient: setExternalClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4951
+ if (isInitialized2()) {
4952
+ return getClient2();
3871
4953
  }
3872
- const hexKey = masterKey.toString("hex");
3873
- await initTurso({
3874
- dbPath,
3875
- encryptionKey: hexKey
3876
- });
3877
- await retryOnBusy2(() => ensureSchema(), "ensureSchema");
3878
4954
  try {
3879
- const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3880
- await initDaemonClient2();
4955
+ const { connectEmbedDaemon: connectEmbedDaemon2, sendDaemonRequest: sendDaemonRequest2, isClientConnected: isClientConnected2 } = await Promise.resolve().then(() => (init_exe_daemon_client(), exe_daemon_client_exports));
4956
+ const { deserializeResultSet: deserializeResultSet2 } = await Promise.resolve().then(() => (init_daemon_protocol(), daemon_protocol_exports));
4957
+ await connectEmbedDaemon2();
4958
+ if (isClientConnected2()) {
4959
+ const daemonClient = {
4960
+ async execute(stmt) {
4961
+ const sql = typeof stmt === "string" ? stmt : stmt.sql;
4962
+ const args = typeof stmt === "string" ? [] : Array.isArray(stmt.args) ? stmt.args : [];
4963
+ const resp = await sendDaemonRequest2({ type: "db-execute", sql, args });
4964
+ if (resp.error) throw new Error(String(resp.error));
4965
+ if (resp.db) return deserializeResultSet2(resp.db);
4966
+ throw new Error("Unexpected daemon response");
4967
+ },
4968
+ async batch(stmts, mode) {
4969
+ const statements = stmts.map((s) => {
4970
+ const sql = typeof s === "string" ? s : s.sql;
4971
+ const args = typeof s === "string" ? [] : Array.isArray(s.args) ? s.args : [];
4972
+ return { sql, args };
4973
+ });
4974
+ const resp = await sendDaemonRequest2({ type: "db-batch", statements, mode: mode ?? "deferred" });
4975
+ if (resp.error) throw new Error(String(resp.error));
4976
+ const batchResults = resp["db-batch"];
4977
+ if (batchResults) return batchResults.map(deserializeResultSet2);
4978
+ throw new Error("Unexpected daemon batch response");
4979
+ },
4980
+ async transaction(_mode) {
4981
+ throw new Error("Transactions not supported via daemon socket");
4982
+ },
4983
+ async executeMultiple(_sql) {
4984
+ throw new Error("executeMultiple not supported via daemon socket");
4985
+ },
4986
+ async migrate(_stmts) {
4987
+ throw new Error("migrate not supported via daemon socket");
4988
+ },
4989
+ sync() {
4990
+ return Promise.resolve(void 0);
4991
+ },
4992
+ close() {
4993
+ },
4994
+ get closed() {
4995
+ return false;
4996
+ },
4997
+ get protocol() {
4998
+ return "file";
4999
+ }
5000
+ };
5001
+ setExternalClient2(daemonClient);
5002
+ return daemonClient;
5003
+ }
3881
5004
  } catch {
3882
5005
  }
3883
- if (!options?.lightweight) {
5006
+ const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
5007
+ await initStore2({ lightweight: true });
5008
+ return getClient2();
5009
+ }
5010
+ var init_fast_db_init = __esm({
5011
+ "src/bin/fast-db-init.ts"() {
5012
+ "use strict";
5013
+ }
5014
+ });
5015
+
5016
+ // src/lib/db-backup.ts
5017
+ var db_backup_exports = {};
5018
+ __export(db_backup_exports, {
5019
+ createBackup: () => createBackup,
5020
+ findActiveDb: () => findActiveDb,
5021
+ getBackupDir: () => getBackupDir,
5022
+ getLatestBackup: () => getLatestBackup,
5023
+ hasBackupToday: () => hasBackupToday,
5024
+ listBackups: () => listBackups,
5025
+ rotateBackups: () => rotateBackups
5026
+ });
5027
+ import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync as statSync3 } from "fs";
5028
+ import path9 from "path";
5029
+ function findActiveDb() {
5030
+ for (const name of DB_NAMES) {
5031
+ const p = path9.join(EXE_AI_DIR, name);
5032
+ if (existsSync9(p)) return p;
5033
+ }
5034
+ return null;
5035
+ }
5036
+ function createBackup(reason = "manual") {
5037
+ const dbPath = findActiveDb();
5038
+ if (!dbPath) return null;
5039
+ mkdirSync4(BACKUP_DIR, { recursive: true });
5040
+ const dbName = path9.basename(dbPath, ".db");
5041
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
5042
+ const backupName = `${dbName}-${reason}-${timestamp}.db`;
5043
+ const backupPath = path9.join(BACKUP_DIR, backupName);
5044
+ copyFileSync(dbPath, backupPath);
5045
+ const walPath = dbPath + "-wal";
5046
+ if (existsSync9(walPath)) {
3884
5047
  try {
3885
- const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3886
- initShardManager2(hexKey);
5048
+ copyFileSync(walPath, backupPath + "-wal");
3887
5049
  } catch {
3888
5050
  }
3889
- const client = getClient();
3890
- const vResult = await retryOnBusy2(
3891
- () => client.execute("SELECT MAX(version) as max_v FROM memories"),
3892
- "version-query"
3893
- );
3894
- _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
5051
+ }
5052
+ const shmPath = dbPath + "-shm";
5053
+ if (existsSync9(shmPath)) {
3895
5054
  try {
3896
- const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
3897
- await loadGlobalProcedures2();
5055
+ copyFileSync(shmPath, backupPath + "-shm");
3898
5056
  } catch {
3899
5057
  }
3900
5058
  }
5059
+ return backupPath;
5060
+ }
5061
+ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
5062
+ if (!existsSync9(BACKUP_DIR)) return 0;
5063
+ const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
5064
+ let deleted = 0;
5065
+ try {
5066
+ const files = readdirSync3(BACKUP_DIR);
5067
+ for (const file of files) {
5068
+ if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
5069
+ const filePath = path9.join(BACKUP_DIR, file);
5070
+ try {
5071
+ const stat = statSync3(filePath);
5072
+ if (stat.mtimeMs < cutoff) {
5073
+ unlinkSync4(filePath);
5074
+ deleted++;
5075
+ }
5076
+ } catch {
5077
+ }
5078
+ }
5079
+ } catch {
5080
+ }
5081
+ return deleted;
5082
+ }
5083
+ function listBackups() {
5084
+ if (!existsSync9(BACKUP_DIR)) return [];
5085
+ try {
5086
+ const files = readdirSync3(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
5087
+ return files.map((name) => {
5088
+ const p = path9.join(BACKUP_DIR, name);
5089
+ const stat = statSync3(p);
5090
+ return { path: p, name, size: stat.size, date: stat.mtime };
5091
+ }).sort((a, b) => b.date.getTime() - a.date.getTime());
5092
+ } catch {
5093
+ return [];
5094
+ }
5095
+ }
5096
+ function hasBackupToday(reason) {
5097
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5098
+ const backups = listBackups();
5099
+ return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3901
5100
  }
5101
+ function getLatestBackup() {
5102
+ const backups = listBackups();
5103
+ return backups.length > 0 ? backups[0].path : null;
5104
+ }
5105
+ function getBackupDir() {
5106
+ return BACKUP_DIR;
5107
+ }
5108
+ var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
5109
+ var init_db_backup = __esm({
5110
+ "src/lib/db-backup.ts"() {
5111
+ "use strict";
5112
+ init_config();
5113
+ BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
5114
+ DEFAULT_KEEP_DAYS = 3;
5115
+ DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
5116
+ }
5117
+ });
3902
5118
 
3903
5119
  // src/bin/exe-doctor.ts
3904
- init_database();
5120
+ import os6 from "os";
3905
5121
 
3906
5122
  // src/lib/is-main.ts
3907
5123
  import { realpathSync } from "fs";
3908
- import { fileURLToPath as fileURLToPath2 } from "url";
5124
+ import { fileURLToPath } from "url";
3909
5125
  function isMainModule(importMetaUrl) {
3910
5126
  if (process.argv[1] == null) return false;
3911
5127
  if (process.argv[1].includes("mcp/server")) return false;
3912
5128
  try {
3913
5129
  const scriptPath = realpathSync(process.argv[1]);
3914
- const modulePath = realpathSync(fileURLToPath2(importMetaUrl));
5130
+ const modulePath = realpathSync(fileURLToPath(importMetaUrl));
3915
5131
  return scriptPath === modulePath;
3916
5132
  } catch {
3917
5133
  return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
@@ -4184,6 +5400,140 @@ async function detectConflicts(client, projectFilter, agentFilter2) {
4184
5400
  };
4185
5401
  }
4186
5402
 
5403
+ // src/adapters/runtime-hook-manifest.ts
5404
+ var EXE_HOOKS = {
5405
+ postToolCombined: "dist/hooks/post-tool-combined.js",
5406
+ sessionStart: "dist/hooks/session-start.js",
5407
+ promptSubmit: "dist/hooks/prompt-submit.js",
5408
+ heartbeat: "dist/hooks/exe-heartbeat-hook.js",
5409
+ stop: "dist/hooks/stop.js",
5410
+ preToolUse: "dist/hooks/pre-tool-use.js",
5411
+ subagentStop: "dist/hooks/subagent-stop.js",
5412
+ preCompact: "dist/hooks/pre-compact.js",
5413
+ postCompact: "dist/hooks/post-compact.js",
5414
+ sessionEnd: "dist/hooks/session-end.js",
5415
+ notification: "dist/hooks/notification.js",
5416
+ instructionsLoaded: "dist/hooks/instructions-loaded.js"
5417
+ };
5418
+ var EXE_HOOK_MANIFEST = [
5419
+ {
5420
+ key: "postToolCombined",
5421
+ event: "PostToolUse",
5422
+ commandMarker: EXE_HOOKS.postToolCombined,
5423
+ owner: "exe-os",
5424
+ purpose: "Single PostToolUse entrypoint for ingestion, error recall, summaries, and bug detection.",
5425
+ runtimes: ["claude", "codex", "opencode"],
5426
+ checkpointRole: "none"
5427
+ },
5428
+ {
5429
+ key: "sessionStart",
5430
+ event: "SessionStart",
5431
+ commandMarker: EXE_HOOKS.sessionStart,
5432
+ owner: "exe-os",
5433
+ purpose: "Loads agent identity, procedures, and boot context.",
5434
+ runtimes: ["claude", "codex", "opencode"],
5435
+ checkpointRole: "none"
5436
+ },
5437
+ {
5438
+ key: "promptSubmit",
5439
+ event: "UserPromptSubmit",
5440
+ commandMarker: EXE_HOOKS.promptSubmit,
5441
+ owner: "exe-os",
5442
+ purpose: "Injects current tasks, pending reviews, and local context before each prompt.",
5443
+ runtimes: ["claude", "codex", "opencode"],
5444
+ checkpointRole: "none"
5445
+ },
5446
+ {
5447
+ key: "heartbeat",
5448
+ event: "UserPromptSubmit",
5449
+ commandMarker: EXE_HOOKS.heartbeat,
5450
+ owner: "exe-os",
5451
+ purpose: "Lightweight heartbeat/status sidecar for Claude Code sessions.",
5452
+ runtimes: ["claude"],
5453
+ checkpointRole: "none"
5454
+ },
5455
+ {
5456
+ key: "stop",
5457
+ event: "Stop",
5458
+ commandMarker: EXE_HOOKS.stop,
5459
+ owner: "exe-os",
5460
+ purpose: "Finalizes task state, capacity signals, and emergency checkpointing.",
5461
+ runtimes: ["claude", "codex", "opencode"],
5462
+ checkpointRole: "capacity_checkpoint"
5463
+ },
5464
+ {
5465
+ key: "preToolUse",
5466
+ event: "PreToolUse",
5467
+ commandMarker: EXE_HOOKS.preToolUse,
5468
+ owner: "exe-os",
5469
+ purpose: "Preflight guardrails before shell/tool execution.",
5470
+ runtimes: ["claude", "codex", "opencode"],
5471
+ checkpointRole: "none"
5472
+ },
5473
+ {
5474
+ key: "subagentStop",
5475
+ event: "SubagentStop",
5476
+ commandMarker: EXE_HOOKS.subagentStop,
5477
+ owner: "exe-os",
5478
+ purpose: "Captures subagent completion context.",
5479
+ runtimes: ["claude"],
5480
+ checkpointRole: "none"
5481
+ },
5482
+ {
5483
+ key: "preCompact",
5484
+ event: "PreCompact",
5485
+ commandMarker: EXE_HOOKS.preCompact,
5486
+ owner: "exe-os",
5487
+ purpose: "Writes active-task snapshot and compaction recovery context.",
5488
+ runtimes: ["claude"],
5489
+ checkpointRole: "recovery_context"
5490
+ },
5491
+ {
5492
+ key: "postCompact",
5493
+ event: "PostCompact",
5494
+ commandMarker: EXE_HOOKS.postCompact,
5495
+ owner: "exe-os",
5496
+ purpose: "Rehydrates recovery context after compaction.",
5497
+ runtimes: ["claude"],
5498
+ checkpointRole: "recovery_context"
5499
+ },
5500
+ {
5501
+ key: "sessionEnd",
5502
+ event: "SessionEnd",
5503
+ commandMarker: EXE_HOOKS.sessionEnd,
5504
+ owner: "exe-os",
5505
+ purpose: "Stores session-end checkpoint and triages orphaned in-progress tasks.",
5506
+ runtimes: ["claude"],
5507
+ checkpointRole: "session_summary"
5508
+ },
5509
+ {
5510
+ key: "notification",
5511
+ event: "Notification",
5512
+ commandMarker: EXE_HOOKS.notification,
5513
+ owner: "exe-os",
5514
+ purpose: "Captures runtime notifications and nudges.",
5515
+ runtimes: ["claude"],
5516
+ checkpointRole: "none"
5517
+ },
5518
+ {
5519
+ key: "instructionsLoaded",
5520
+ event: "InstructionsLoaded",
5521
+ commandMarker: EXE_HOOKS.instructionsLoaded,
5522
+ owner: "exe-os",
5523
+ purpose: "Applies runtime instruction post-processing.",
5524
+ runtimes: ["claude"],
5525
+ checkpointRole: "none"
5526
+ }
5527
+ ];
5528
+ var LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS = [
5529
+ "dist/hooks/ingest.js",
5530
+ "dist/hooks/error-recall.js",
5531
+ "dist/hooks/ingest-worker.js"
5532
+ ];
5533
+ function manifestEntryForCommand(command) {
5534
+ return EXE_HOOK_MANIFEST.find((entry) => command.includes(entry.commandMarker));
5535
+ }
5536
+
4187
5537
  // src/bin/exe-doctor.ts
4188
5538
  function parseFlags(argv) {
4189
5539
  const flags = { fix: false, dryRun: false, verbose: false, conflicts: false };
@@ -4382,6 +5732,121 @@ function auditHookHealth() {
4382
5732
  const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
4383
5733
  return { logExists: true, totalLines, errorsLastHour, topPatterns };
4384
5734
  }
5735
+ function safeReadJson(filePath) {
5736
+ if (!existsSync10(filePath)) return null;
5737
+ try {
5738
+ return JSON.parse(readFileSync5(filePath, "utf-8"));
5739
+ } catch {
5740
+ return null;
5741
+ }
5742
+ }
5743
+ function collectHookCommandsFromClaudeSettings(settings) {
5744
+ const hooks = settings?.hooks;
5745
+ if (!hooks || typeof hooks !== "object") return [];
5746
+ const commands = [];
5747
+ for (const [event, groups] of Object.entries(hooks)) {
5748
+ if (!Array.isArray(groups)) continue;
5749
+ for (const group of groups) {
5750
+ const hooksForGroup = group?.hooks;
5751
+ if (!Array.isArray(hooksForGroup)) continue;
5752
+ for (const hook of hooksForGroup) {
5753
+ const command = hook?.command;
5754
+ if (typeof command === "string") commands.push({ event, command });
5755
+ }
5756
+ }
5757
+ }
5758
+ return commands;
5759
+ }
5760
+ function collectHookCommandsFromCodexHooks(config) {
5761
+ const commands = [];
5762
+ if (!config || typeof config !== "object") return commands;
5763
+ const root = config;
5764
+ for (const [event, value] of Object.entries(root)) {
5765
+ const entries = Array.isArray(value) ? value : value && typeof value === "object" ? Object.values(value) : [];
5766
+ for (const entry of entries) {
5767
+ if (typeof entry === "string") commands.push({ event, command: entry });
5768
+ const command = entry?.command;
5769
+ if (typeof command === "string") commands.push({ event, command });
5770
+ }
5771
+ }
5772
+ return commands;
5773
+ }
5774
+ function buildHookOwnershipIssues(runtime, commands) {
5775
+ const issues = [];
5776
+ for (const marker of LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS) {
5777
+ const count = commands.filter((cmd) => cmd.command.includes(marker)).length;
5778
+ if (count > 0) {
5779
+ issues.push({
5780
+ runtime,
5781
+ event: "PostToolUse",
5782
+ marker,
5783
+ count,
5784
+ message: `Legacy split PostToolUse hook still installed: ${marker}`
5785
+ });
5786
+ }
5787
+ }
5788
+ for (const entry of EXE_HOOK_MANIFEST.filter((hook) => hook.runtimes.includes(runtime))) {
5789
+ const matching = commands.filter((cmd) => cmd.command.includes(entry.commandMarker));
5790
+ if (matching.length > 1) {
5791
+ issues.push({
5792
+ runtime,
5793
+ event: entry.event,
5794
+ marker: entry.commandMarker,
5795
+ count: matching.length,
5796
+ message: `Duplicate exe-os hook owner for ${entry.event}: ${entry.commandMarker}`
5797
+ });
5798
+ }
5799
+ for (const cmd of matching) {
5800
+ if (cmd.event !== entry.event) {
5801
+ issues.push({
5802
+ runtime,
5803
+ event: cmd.event,
5804
+ marker: entry.commandMarker,
5805
+ count: 1,
5806
+ message: `exe-os hook ${entry.commandMarker} is registered under ${cmd.event}, expected ${entry.event}`
5807
+ });
5808
+ }
5809
+ }
5810
+ }
5811
+ for (const cmd of commands) {
5812
+ if (!cmd.command.includes("dist/hooks/")) continue;
5813
+ if (!cmd.command.includes("exe-os")) continue;
5814
+ const entry = manifestEntryForCommand(cmd.command);
5815
+ const isLegacy = LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS.some((marker) => cmd.command.includes(marker));
5816
+ if (!entry && !isLegacy) {
5817
+ issues.push({
5818
+ runtime,
5819
+ event: cmd.event,
5820
+ marker: "dist/hooks/",
5821
+ count: 1,
5822
+ message: `Unknown exe-os hook command not present in ownership manifest: ${cmd.command.slice(0, 160)}`
5823
+ });
5824
+ }
5825
+ }
5826
+ return issues;
5827
+ }
5828
+ function auditHookOwnership() {
5829
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
5830
+ const checkedFiles = [];
5831
+ const issues = [];
5832
+ const claudeSettingsPath = path10.join(home, ".claude", "settings.json");
5833
+ const claudeSettings = safeReadJson(claudeSettingsPath);
5834
+ if (claudeSettings) {
5835
+ checkedFiles.push(claudeSettingsPath);
5836
+ issues.push(...buildHookOwnershipIssues("claude", collectHookCommandsFromClaudeSettings(claudeSettings)));
5837
+ }
5838
+ const codexHooksPath = path10.join(home, ".codex", "hooks.json");
5839
+ const codexHooks = safeReadJson(codexHooksPath);
5840
+ if (codexHooks) {
5841
+ checkedFiles.push(codexHooksPath);
5842
+ issues.push(...buildHookOwnershipIssues("codex", collectHookCommandsFromCodexHooks(codexHooks)));
5843
+ }
5844
+ return {
5845
+ checkedFiles,
5846
+ issues,
5847
+ staleLegacyHooks: issues.filter((issue) => issue.message.includes("Legacy split"))
5848
+ };
5849
+ }
4385
5850
  async function auditShards() {
4386
5851
  try {
4387
5852
  const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4426,7 +5891,8 @@ async function runAudit(client, flags) {
4426
5891
  }
4427
5892
  const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
4428
5893
  const hookHealth = auditHookHealth();
4429
- return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, shards };
5894
+ const hookOwnership = auditHookOwnership();
5895
+ return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, hookOwnership, shards };
4430
5896
  }
4431
5897
  function indicator(value, warn) {
4432
5898
  if (value === 0) return "\u{1F7E2}";
@@ -4505,6 +5971,15 @@ function formatReport(report, flags) {
4505
5971
  lines.push(` ${p.count}x: ${p.pattern}`);
4506
5972
  }
4507
5973
  }
5974
+ const ho = report.hookOwnership;
5975
+ if (ho.issues.length === 0) {
5976
+ lines.push(`\u{1F7E2} Hook ownership: ${ho.checkedFiles.length > 0 ? "manifest clean" : "no local hook config found"}`);
5977
+ } else {
5978
+ lines.push(`\u{1F534} Hook ownership: ${fmtNum(ho.issues.length)} issue(s)`);
5979
+ for (const issue of ho.issues.slice(0, 5)) {
5980
+ lines.push(` [${issue.runtime}/${issue.event}] ${issue.message}`);
5981
+ }
5982
+ }
4508
5983
  const sh = report.shards;
4509
5984
  if (sh.total > 0) {
4510
5985
  if (sh.unreadable === 0) {
@@ -4574,6 +6049,9 @@ function formatReport(report, flags) {
4574
6049
  if (report.conflicts.superseded > 0) {
4575
6050
  recs.push(`${fmtNum(report.conflicts.superseded)} superseded memories can be deactivated`);
4576
6051
  }
6052
+ if (report.hookOwnership.issues.length > 0) {
6053
+ recs.push(`Run exe-os install to refresh hook config; remove stale exe-os hook commands if they remain`);
6054
+ }
4577
6055
  if (recs.length > 0) {
4578
6056
  lines.push("Recommendations:");
4579
6057
  for (const r of recs) {
@@ -4707,8 +6185,8 @@ function splitAtSentences(text, maxChunkSize) {
4707
6185
  }
4708
6186
  async function main(argv = process.argv.slice(2)) {
4709
6187
  const flags = parseFlags(argv);
4710
- await initStore();
4711
- const client = getClient();
6188
+ const { fastDbInit: fastDbInit2 } = await Promise.resolve().then(() => (init_fast_db_init(), fast_db_init_exports));
6189
+ const client = await fastDbInit2();
4712
6190
  const report = await runAudit(client, flags);
4713
6191
  console.log(formatReport(report, flags));
4714
6192
  if (flags.fix || flags.dryRun) {
@@ -4776,6 +6254,7 @@ export {
4776
6254
  auditDuplicates,
4777
6255
  auditFts,
4778
6256
  auditHookHealth,
6257
+ auditHookOwnership,
4779
6258
  auditNullVectors,
4780
6259
  auditOrphanedProjects,
4781
6260
  auditShards,