@askexenow/exe-os 0.9.62 → 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.
@@ -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";
@@ -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) {
@@ -1218,20 +1757,180 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1218
1757
  _pending.delete(id);
1219
1758
  resolve({ error: "Write failed" });
1220
1759
  }
1221
- });
1760
+ });
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
+ }
1222
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(() => {
@@ -2604,309 +3344,33 @@ async function ensureSchema() {
2604
3344
  });
2605
3345
  } catch {
2606
3346
  }
2607
- try {
2608
- await client.execute(
2609
- `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
2610
- );
2611
- } catch {
2612
- }
2613
- try {
2614
- await client.execute({
2615
- sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
2616
- args: []
2617
- });
2618
- } catch {
2619
- }
2620
- try {
2621
- await client.execute(
2622
- `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
2623
- );
2624
- } catch {
2625
- }
2626
- try {
2627
- await client.execute({
2628
- sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
2629
- args: []
2630
- });
2631
- } catch {
2632
- }
2633
- for (const col of [
2634
- "ALTER TABLE memories ADD COLUMN intent TEXT",
2635
- "ALTER TABLE memories ADD COLUMN outcome TEXT",
2636
- "ALTER TABLE memories ADD COLUMN domain TEXT",
2637
- "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2638
- "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2639
- "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2640
- "ALTER TABLE memories ADD COLUMN review_status TEXT",
2641
- "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2642
- "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2643
- "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2644
- "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2645
- "ALTER TABLE memories ADD COLUMN token_cost REAL",
2646
- "ALTER TABLE memories ADD COLUMN audience TEXT",
2647
- "ALTER TABLE memories ADD COLUMN language_type TEXT",
2648
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2649
- ]) {
2650
- try {
2651
- await client.execute(col);
2652
- } catch {
2653
- }
2654
- }
2655
- try {
2656
- await client.execute({
2657
- sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2658
- args: []
2659
- });
2660
- } catch {
2661
- }
2662
- }
2663
- async function disposeDatabase() {
2664
- if (_walCheckpointTimer) {
2665
- clearInterval(_walCheckpointTimer);
2666
- _walCheckpointTimer = null;
2667
- }
2668
- if (_daemonClient) {
2669
- _daemonClient.close();
2670
- _daemonClient = null;
2671
- }
2672
- if (_adapterClient && _adapterClient !== _resilientClient) {
2673
- _adapterClient.close();
2674
- }
2675
- _adapterClient = null;
2676
- if (_client) {
2677
- _client.close();
2678
- _client = null;
2679
- _resilientClient = null;
2680
- }
2681
- }
2682
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
2683
- var init_database = __esm({
2684
- "src/lib/database.ts"() {
2685
- "use strict";
2686
- init_db_retry();
2687
- init_employees();
2688
- init_database_adapter();
2689
- init_memory();
2690
- _client = null;
2691
- _resilientClient = null;
2692
- _walCheckpointTimer = null;
2693
- _daemonClient = null;
2694
- _adapterClient = null;
2695
- initTurso = initDatabase;
2696
- SOFT_DELETE_RETENTION_DAYS = 7;
2697
- disposeTurso = disposeDatabase;
2698
- }
2699
- });
2700
-
2701
- // src/lib/shard-manager.ts
2702
- var shard_manager_exports = {};
2703
- __export(shard_manager_exports, {
2704
- auditShardHealth: () => auditShardHealth,
2705
- disposeShards: () => disposeShards,
2706
- ensureShardSchema: () => ensureShardSchema,
2707
- getOpenShardCount: () => getOpenShardCount,
2708
- getReadyShardClient: () => getReadyShardClient,
2709
- getShardClient: () => getShardClient,
2710
- getShardsDir: () => getShardsDir,
2711
- initShardManager: () => initShardManager,
2712
- isShardingEnabled: () => isShardingEnabled,
2713
- listShards: () => listShards,
2714
- shardExists: () => shardExists
2715
- });
2716
- import path7 from "path";
2717
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync2 } from "fs";
2718
- import { createClient as createClient2 } from "@libsql/client";
2719
- function initShardManager(encryptionKey) {
2720
- _encryptionKey = encryptionKey;
2721
- if (!existsSync7(SHARDS_DIR)) {
2722
- mkdirSync2(SHARDS_DIR, { recursive: true });
2723
- }
2724
- _shardingEnabled = true;
2725
- if (_evictionTimer) clearInterval(_evictionTimer);
2726
- _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2727
- _evictionTimer.unref();
2728
- }
2729
- function isShardingEnabled() {
2730
- return _shardingEnabled;
2731
- }
2732
- function getShardsDir() {
2733
- return SHARDS_DIR;
2734
- }
2735
- function getShardClient(projectName) {
2736
- if (!_encryptionKey) {
2737
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
2738
- }
2739
- const safeName = safeShardName(projectName);
2740
- if (!safeName || safeName === "unknown") {
2741
- throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
2742
- }
2743
- const cached = _shards.get(safeName);
2744
- if (cached) {
2745
- _shardLastAccess.set(safeName, Date.now());
2746
- return cached;
2747
- }
2748
- while (_shards.size >= MAX_OPEN_SHARDS) {
2749
- evictLRU();
2750
- }
2751
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2752
- const client = createClient2({
2753
- url: `file:${dbPath}`,
2754
- encryptionKey: _encryptionKey
2755
- });
2756
- _shards.set(safeName, client);
2757
- _shardLastAccess.set(safeName, Date.now());
2758
- return client;
2759
- }
2760
- function shardExists(projectName) {
2761
- const safeName = safeShardName(projectName);
2762
- return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2763
- }
2764
- function safeShardName(projectName) {
2765
- return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2766
- }
2767
- function listShards() {
2768
- if (!existsSync7(SHARDS_DIR)) return [];
2769
- return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2770
- }
2771
- async function auditShardHealth(options = {}) {
2772
- if (!_encryptionKey) {
2773
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
2774
- }
2775
- const repair = options.repair === true;
2776
- const dryRun = options.dryRun === true;
2777
- const names = listShards();
2778
- const shards = [];
2779
- for (const name of names) {
2780
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2781
- const stat = statSync2(dbPath);
2782
- const item = {
2783
- name,
2784
- path: dbPath,
2785
- ok: false,
2786
- unreadable: false,
2787
- error: null,
2788
- size: stat.size,
2789
- mtime: stat.mtime.toISOString(),
2790
- memoryCount: null
2791
- };
2792
- const client = createClient2({
2793
- url: `file:${dbPath}`,
2794
- encryptionKey: _encryptionKey
2795
- });
2796
- try {
2797
- await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2798
- const hasMemories = await client.execute(
2799
- "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2800
- );
2801
- if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2802
- const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2803
- item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2804
- }
2805
- item.ok = true;
2806
- } catch (err) {
2807
- const message = err instanceof Error ? err.message : String(err);
2808
- item.error = message;
2809
- item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2810
- if (item.unreadable && repair && !dryRun) {
2811
- client.close();
2812
- _shards.delete(name);
2813
- _shardLastAccess.delete(name);
2814
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2815
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2816
- renameSync3(dbPath, archivedPath);
2817
- item.archivedPath = archivedPath;
2818
- }
2819
- } finally {
2820
- try {
2821
- client.close();
2822
- } catch {
2823
- }
2824
- }
2825
- shards.push(item);
2826
- }
2827
- return {
2828
- total: shards.length,
2829
- ok: shards.filter((s) => s.ok).length,
2830
- unreadable: shards.filter((s) => s.unreadable).length,
2831
- archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2832
- shards
2833
- };
2834
- }
2835
- async function ensureShardSchema(client) {
2836
- await client.execute("PRAGMA journal_mode = WAL");
2837
- await client.execute("PRAGMA busy_timeout = 30000");
2838
- try {
2839
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
2840
- } catch {
2841
- }
2842
- await client.executeMultiple(`
2843
- CREATE TABLE IF NOT EXISTS memories (
2844
- id TEXT PRIMARY KEY,
2845
- agent_id TEXT NOT NULL,
2846
- agent_role TEXT NOT NULL,
2847
- session_id TEXT NOT NULL,
2848
- timestamp TEXT NOT NULL,
2849
- tool_name TEXT NOT NULL,
2850
- project_name TEXT NOT NULL,
2851
- has_error INTEGER NOT NULL DEFAULT 0,
2852
- raw_text TEXT NOT NULL,
2853
- vector F32_BLOB(1024),
2854
- version INTEGER NOT NULL DEFAULT 0
3347
+ try {
3348
+ await client.execute(
3349
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
2855
3350
  );
2856
-
2857
- CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
2858
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
2859
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
2860
- `);
2861
- await client.executeMultiple(`
2862
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
2863
- raw_text,
2864
- content='memories',
2865
- content_rowid='rowid'
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)`
2866
3363
  );
2867
-
2868
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
2869
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2870
- END;
2871
-
2872
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
2873
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2874
- END;
2875
-
2876
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
2877
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2878
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2879
- END;
2880
- `);
3364
+ } catch {
3365
+ }
3366
+ try {
3367
+ await client.execute({
3368
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
3369
+ args: []
3370
+ });
3371
+ } catch {
3372
+ }
2881
3373
  for (const col of [
2882
- "ALTER TABLE memories ADD COLUMN task_id TEXT",
2883
- "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2884
- "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2885
- "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2886
- "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2887
- "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2888
- "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
2889
- "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
2890
- "ALTER TABLE memories ADD COLUMN content_hash TEXT",
2891
- "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
2892
- "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
2893
- "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
2894
- // Wiki linkage columns (must match database.ts)
2895
- "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
2896
- "ALTER TABLE memories ADD COLUMN document_id TEXT",
2897
- "ALTER TABLE memories ADD COLUMN user_id TEXT",
2898
- "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
2899
- "ALTER TABLE memories ADD COLUMN page_number INTEGER",
2900
- // Source provenance columns (must match database.ts)
2901
- "ALTER TABLE memories ADD COLUMN source_path TEXT",
2902
- "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
2903
- "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
2904
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
2905
- // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2906
- "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2907
- "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2908
- "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2909
- // Metadata enrichment columns (must match database.ts)
2910
3374
  "ALTER TABLE memories ADD COLUMN intent TEXT",
2911
3375
  "ALTER TABLE memories ADD COLUMN outcome TEXT",
2912
3376
  "ALTER TABLE memories ADD COLUMN domain TEXT",
@@ -2921,195 +3385,528 @@ async function ensureShardSchema(client) {
2921
3385
  "ALTER TABLE memories ADD COLUMN token_cost REAL",
2922
3386
  "ALTER TABLE memories ADD COLUMN audience TEXT",
2923
3387
  "ALTER TABLE memories ADD COLUMN language_type TEXT",
2924
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
2925
- "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
3388
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2926
3389
  ]) {
2927
3390
  try {
2928
3391
  await client.execute(col);
2929
3392
  } catch {
2930
3393
  }
2931
3394
  }
2932
- for (const idx of [
2933
- "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2934
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2935
- "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
2936
- ]) {
2937
- try {
2938
- await client.execute(idx);
2939
- } catch {
2940
- }
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";
2941
3574
  }
2942
- try {
2943
- await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
2944
- } 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");
2945
3583
  }
2946
- for (const idx of [
2947
- "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
2948
- "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
2949
- "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
2950
- ]) {
3584
+ const keytar = await tryKeytar();
3585
+ if (keytar) {
2951
3586
  try {
2952
- 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
+ }
2953
3595
  } catch {
2954
3596
  }
2955
3597
  }
2956
- await client.executeMultiple(`
2957
- CREATE TABLE IF NOT EXISTS entities (
2958
- id TEXT PRIMARY KEY,
2959
- name TEXT NOT NULL,
2960
- type TEXT NOT NULL,
2961
- first_seen TEXT NOT NULL,
2962
- last_seen TEXT NOT NULL,
2963
- properties TEXT DEFAULT '{}',
2964
- UNIQUE(name, type)
2965
- );
2966
-
2967
- CREATE TABLE IF NOT EXISTS relationships (
2968
- id TEXT PRIMARY KEY,
2969
- source_entity_id TEXT NOT NULL,
2970
- target_entity_id TEXT NOT NULL,
2971
- type TEXT NOT NULL,
2972
- weight REAL DEFAULT 1.0,
2973
- timestamp TEXT NOT NULL,
2974
- properties TEXT DEFAULT '{}',
2975
- UNIQUE(source_entity_id, target_entity_id, type)
2976
- );
2977
-
2978
- CREATE TABLE IF NOT EXISTS entity_memories (
2979
- entity_id TEXT NOT NULL,
2980
- memory_id TEXT NOT NULL,
2981
- PRIMARY KEY (entity_id, memory_id)
2982
- );
2983
-
2984
- CREATE TABLE IF NOT EXISTS relationship_memories (
2985
- relationship_id TEXT NOT NULL,
2986
- memory_id TEXT NOT NULL,
2987
- PRIMARY KEY (relationship_id, memory_id)
2988
- );
2989
-
2990
- CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
2991
- CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
2992
- CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
2993
- CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
2994
- CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
2995
-
2996
- CREATE TABLE IF NOT EXISTS hyperedges (
2997
- id TEXT PRIMARY KEY,
2998
- label TEXT NOT NULL,
2999
- relation TEXT NOT NULL,
3000
- confidence REAL DEFAULT 1.0,
3001
- timestamp TEXT NOT NULL
3002
- );
3003
-
3004
- CREATE TABLE IF NOT EXISTS hyperedge_nodes (
3005
- hyperedge_id TEXT NOT NULL,
3006
- entity_id TEXT NOT NULL,
3007
- PRIMARY KEY (hyperedge_id, entity_id)
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
+ `
3008
3603
  );
3009
- `);
3010
- for (const col of [
3011
- "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
3012
- "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
3013
- ]) {
3014
- try {
3015
- await client.execute(col);
3016
- } catch {
3017
- }
3604
+ return null;
3018
3605
  }
3019
- }
3020
- async function getReadyShardClient(projectName) {
3021
- const safeName = safeShardName(projectName);
3022
- let client = getShardClient(projectName);
3023
3606
  try {
3024
- await ensureShardSchema(client);
3025
- 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;
3026
3646
  } catch (err) {
3027
- const message = err instanceof Error ? err.message : String(err);
3028
- if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
3029
- client.close();
3030
- _shards.delete(safeName);
3031
- _shardLastAccess.delete(safeName);
3032
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3033
- if (existsSync7(dbPath)) {
3034
- const stat = statSync2(dbPath);
3035
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3036
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
3037
- renameSync3(dbPath, archivedPath);
3038
- process.stderr.write(
3039
- `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
3647
+ process.stderr.write(
3648
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
3040
3649
  `
3041
- );
3042
- }
3043
- client = getShardClient(projectName);
3044
- await ensureShardSchema(client);
3045
- return client;
3650
+ );
3651
+ return null;
3046
3652
  }
3047
3653
  }
3048
- function evictLRU() {
3049
- let oldest = null;
3050
- let oldestTime = Infinity;
3051
- for (const [name, time] of _shardLastAccess) {
3052
- if (time < oldestTime) {
3053
- oldestTime = time;
3054
- oldest = name;
3055
- }
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:";
3056
3661
  }
3057
- if (oldest) {
3058
- const client = _shards.get(oldest);
3059
- if (client) {
3060
- client.close();
3061
- }
3062
- _shards.delete(oldest);
3063
- _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();
3064
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;
3065
3753
  }
3066
- function evictIdleShards() {
3067
- const now = Date.now();
3068
- const toEvict = [];
3069
- for (const [name, lastAccess] of _shardLastAccess) {
3070
- if (now - lastAccess > SHARD_IDLE_MS) {
3071
- toEvict.push(name);
3072
- }
3073
- }
3074
- for (const name of toEvict) {
3075
- const client = _shards.get(name);
3076
- if (client) {
3077
- client.close();
3078
- }
3079
- _shards.delete(name);
3080
- _shardLastAccess.delete(name);
3081
- }
3754
+ function hashMemoryContent(text) {
3755
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3082
3756
  }
3083
- function getOpenShardCount() {
3084
- return _shards.size;
3757
+ function scopedDedupArgs(input) {
3758
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3085
3759
  }
3086
- function disposeShards() {
3087
- if (_evictionTimer) {
3088
- clearInterval(_evictionTimer);
3089
- _evictionTimer = null;
3090
- }
3091
- for (const [, client] of _shards) {
3092
- 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
+ );
3093
3874
  }
3094
- _shards.clear();
3095
- _shardLastAccess.clear();
3096
- _shardingEnabled = false;
3097
- _encryptionKey = null;
3098
3875
  }
3099
- var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3100
- var init_shard_manager = __esm({
3101
- "src/lib/shard-manager.ts"() {
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"() {
3102
3888
  "use strict";
3103
- init_config();
3104
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3105
- SHARD_IDLE_MS = 5 * 60 * 1e3;
3106
- MAX_OPEN_SHARDS = 10;
3107
- EVICTION_INTERVAL_MS = 60 * 1e3;
3108
- _shards = /* @__PURE__ */ new Map();
3109
- _shardLastAccess = /* @__PURE__ */ new Map();
3110
- _evictionTimer = null;
3111
- _encryptionKey = null;
3112
- _shardingEnabled = false;
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
+ ];
3113
3910
  }
3114
3911
  });
3115
3912
 
@@ -3368,595 +4165,969 @@ ${p.content}`).join("\n\n");
3368
4165
  }
3369
4166
  });
3370
4167
 
3371
- // src/lib/worker-gate.ts
3372
- var worker_gate_exports = {};
3373
- __export(worker_gate_exports, {
3374
- MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
3375
- cleanupWorkerPid: () => cleanupWorkerPid,
3376
- registerWorkerPid: () => registerWorkerPid,
3377
- releaseBackfillLock: () => releaseBackfillLock,
3378
- tryAcquireBackfillLock: () => tryAcquireBackfillLock,
3379
- tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
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
3380
4174
  });
3381
- import { readdirSync as readdirSync2, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
3382
- import path8 from "path";
3383
- function tryAcquireWorkerSlot() {
3384
- try {
3385
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3386
- const reservationId = `res-${process.pid}-${Date.now()}`;
3387
- const reservationPath = path8.join(WORKER_PID_DIR, `${reservationId}.pid`);
3388
- writeFileSync3(reservationPath, String(process.pid));
3389
- const files = readdirSync2(WORKER_PID_DIR);
3390
- let alive = 0;
3391
- for (const f of files) {
3392
- if (!f.endsWith(".pid")) continue;
3393
- if (f.startsWith("res-")) {
3394
- alive++;
3395
- continue;
3396
- }
3397
- const dashIdx = f.lastIndexOf("-");
3398
- const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
3399
- if (isNaN(pid)) continue;
3400
- try {
3401
- process.kill(pid, 0);
3402
- alive++;
3403
- } catch {
3404
- try {
3405
- unlinkSync3(path8.join(WORKER_PID_DIR, f));
3406
- } catch {
3407
- }
3408
- }
3409
- }
3410
- if (alive > MAX_CONCURRENT_WORKERS) {
3411
- try {
3412
- unlinkSync3(reservationPath);
3413
- } catch {
3414
- }
3415
- return false;
3416
- }
3417
- try {
3418
- unlinkSync3(reservationPath);
3419
- } catch {
3420
- }
3421
- return true;
3422
- } catch {
3423
- return true;
3424
- }
3425
- }
3426
- function registerWorkerPid(pid) {
3427
- try {
3428
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3429
- writeFileSync3(path8.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3430
- } catch {
3431
- }
3432
- }
3433
- function cleanupWorkerPid() {
3434
- try {
3435
- unlinkSync3(path8.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3436
- } catch {
3437
- }
3438
- }
3439
- function tryAcquireBackfillLock() {
3440
- try {
3441
- mkdirSync3(WORKER_PID_DIR, { recursive: true });
3442
- if (existsSync8(BACKFILL_LOCK)) {
3443
- try {
3444
- const pid = parseInt(
3445
- __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
3446
- 10
3447
- );
3448
- if (!isNaN(pid) && pid > 0) {
3449
- try {
3450
- process.kill(pid, 0);
3451
- return false;
3452
- } catch {
3453
- }
3454
- }
3455
- } catch {
3456
- }
3457
- }
3458
- writeFileSync3(BACKFILL_LOCK, String(process.pid));
3459
- return true;
3460
- } catch {
3461
- return true;
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;
3462
4237
  }
4238
+ return cards;
3463
4239
  }
3464
- function releaseBackfillLock() {
3465
- try {
3466
- unlinkSync3(BACKFILL_LOCK);
3467
- } catch {
3468
- }
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
+ }));
3469
4315
  }
3470
- var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
3471
- var init_worker_gate = __esm({
3472
- "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"() {
3473
4319
  "use strict";
3474
- init_config();
3475
- WORKER_PID_DIR = path8.join(EXE_AI_DIR, "worker-pids");
3476
- MAX_CONCURRENT_WORKERS = 3;
3477
- BACKFILL_LOCK = path8.join(WORKER_PID_DIR, "backfill.lock");
4320
+ init_database();
4321
+ MAX_CARDS_PER_MEMORY = 6;
4322
+ MAX_SENTENCE_CHARS = 360;
3478
4323
  }
3479
4324
  });
3480
4325
 
3481
- // src/lib/db-backup.ts
3482
- var db_backup_exports = {};
3483
- __export(db_backup_exports, {
3484
- createBackup: () => createBackup,
3485
- findActiveDb: () => findActiveDb,
3486
- getBackupDir: () => getBackupDir,
3487
- getLatestBackup: () => getLatestBackup,
3488
- hasBackupToday: () => hasBackupToday,
3489
- listBackups: () => listBackups,
3490
- rotateBackups: () => rotateBackups
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
3491
4343
  });
3492
- import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync as statSync3 } from "fs";
3493
- import path9 from "path";
3494
- function findActiveDb() {
3495
- for (const name of DB_NAMES) {
3496
- const p = path9.join(EXE_AI_DIR, name);
3497
- if (existsSync9(p)) return p;
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");
3498
4348
  }
3499
- return null;
4349
+ return false;
3500
4350
  }
3501
- function createBackup(reason = "manual") {
3502
- const dbPath = findActiveDb();
3503
- if (!dbPath) return null;
3504
- mkdirSync4(BACKUP_DIR, { recursive: true });
3505
- const dbName = path9.basename(dbPath, ".db");
3506
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
3507
- const backupName = `${dbName}-${reason}-${timestamp}.db`;
3508
- const backupPath = path9.join(BACKUP_DIR, backupName);
3509
- copyFileSync(dbPath, backupPath);
3510
- const walPath = dbPath + "-wal";
3511
- if (existsSync9(walPath)) {
3512
- try {
3513
- copyFileSync(walPath, backupPath + "-wal");
3514
- } catch {
3515
- }
3516
- }
3517
- const shmPath = dbPath + "-shm";
3518
- if (existsSync9(shmPath)) {
4351
+ async function retryOnBusy2(fn, label) {
4352
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
3519
4353
  try {
3520
- copyFileSync(shmPath, backupPath + "-shm");
3521
- } catch {
3522
- }
3523
- }
3524
- return backupPath;
3525
- }
3526
- function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
3527
- if (!existsSync9(BACKUP_DIR)) return 0;
3528
- const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
3529
- let deleted = 0;
3530
- try {
3531
- const files = readdirSync3(BACKUP_DIR);
3532
- for (const file of files) {
3533
- if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
3534
- const filePath = path9.join(BACKUP_DIR, file);
3535
- try {
3536
- const stat = statSync3(filePath);
3537
- if (stat.mtimeMs < cutoff) {
3538
- unlinkSync4(filePath);
3539
- deleted++;
3540
- }
3541
- } catch {
3542
- }
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)));
3543
4362
  }
3544
- } catch {
3545
- }
3546
- return deleted;
3547
- }
3548
- function listBackups() {
3549
- if (!existsSync9(BACKUP_DIR)) return [];
3550
- try {
3551
- const files = readdirSync3(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
3552
- return files.map((name) => {
3553
- const p = path9.join(BACKUP_DIR, name);
3554
- const stat = statSync3(p);
3555
- return { path: p, name, size: stat.size, date: stat.mtime };
3556
- }).sort((a, b) => b.date.getTime() - a.date.getTime());
3557
- } catch {
3558
- return [];
3559
4363
  }
4364
+ throw new Error("unreachable");
3560
4365
  }
3561
- function hasBackupToday(reason) {
3562
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3563
- const backups = listBackups();
3564
- return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3565
- }
3566
- function getLatestBackup() {
3567
- const backups = listBackups();
3568
- return backups.length > 0 ? backups[0].path : null;
3569
- }
3570
- function getBackupDir() {
3571
- return BACKUP_DIR;
3572
- }
3573
- var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
3574
- var init_db_backup = __esm({
3575
- "src/lib/db-backup.ts"() {
3576
- "use strict";
3577
- init_config();
3578
- BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
3579
- DEFAULT_KEEP_DAYS = 3;
3580
- DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
4366
+ async function initStore(options) {
4367
+ if (_flushTimer !== null) {
4368
+ clearInterval(_flushTimer);
4369
+ _flushTimer = null;
3581
4370
  }
3582
- });
3583
-
3584
- // src/bin/exe-doctor.ts
3585
- import os6 from "os";
3586
-
3587
- // src/lib/store.ts
3588
- init_memory();
3589
- init_database();
3590
-
3591
- // src/lib/keychain.ts
3592
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3593
- import { existsSync as existsSync6 } from "fs";
3594
- import { execSync as execSync2 } from "child_process";
3595
- import path6 from "path";
3596
- import os5 from "os";
3597
- var SERVICE = "exe-mem";
3598
- var ACCOUNT = "master-key";
3599
- function getKeyDir() {
3600
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3601
- }
3602
- function getKeyPath() {
3603
- return path6.join(getKeyDir(), "master.key");
3604
- }
3605
- function macKeychainGet() {
3606
- if (process.platform !== "darwin") return null;
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");
3607
4395
  try {
3608
- return execSync2(
3609
- `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3610
- { encoding: "utf-8", timeout: 5e3 }
3611
- ).trim();
4396
+ const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4397
+ await initDaemonClient2();
3612
4398
  } catch {
3613
- return null;
3614
4399
  }
3615
- }
3616
- function macKeychainSet(value) {
3617
- if (process.platform !== "darwin") return false;
3618
- try {
4400
+ if (!options?.lightweight) {
3619
4401
  try {
3620
- execSync2(
3621
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3622
- { timeout: 5e3 }
3623
- );
4402
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4403
+ initShardManager2(hexKey);
3624
4404
  } catch {
3625
4405
  }
3626
- execSync2(
3627
- `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3628
- { 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"
3629
4410
  );
3630
- return true;
3631
- } catch {
3632
- 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
+ }
3633
4417
  }
3634
4418
  }
3635
- function linuxSecretGet() {
3636
- if (process.platform !== "linux") return null;
3637
- try {
3638
- return execSync2(
3639
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3640
- { encoding: "utf-8", timeout: 5e3 }
3641
- ).trim();
3642
- } catch {
3643
- return null;
3644
- }
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;
3645
4450
  }
3646
- function linuxSecretSet(value) {
3647
- if (process.platform !== "linux") return false;
3648
- try {
3649
- execSync2(
3650
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3651
- { timeout: 5e3 }
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}`
3652
4455
  );
3653
- return true;
3654
- } catch {
3655
- return false;
3656
- }
3657
- }
3658
- async function tryKeytar() {
3659
- try {
3660
- return await import("keytar");
3661
- } catch {
3662
- return null;
3663
4456
  }
3664
- }
3665
- var ENCRYPTED_PREFIX = "enc:";
3666
- function deriveMachineKey() {
3667
- try {
3668
- const crypto2 = __require("crypto");
3669
- const material = [
3670
- os5.hostname(),
3671
- os5.userInfo().username,
3672
- os5.arch(),
3673
- os5.platform(),
3674
- // Machine ID on Linux (stable across reboots)
3675
- process.platform === "linux" ? readMachineId() : ""
3676
- ].join("|");
3677
- return crypto2.createHash("sha256").update(material).digest();
3678
- } catch {
3679
- 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;
3680
4466
  }
3681
- }
3682
- function readMachineId() {
3683
4467
  try {
3684
- const { readFileSync: readFileSync6 } = __require("fs");
3685
- 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;
3686
4475
  } catch {
3687
- return "";
3688
4476
  }
3689
- }
3690
- function encryptWithMachineKey(plaintext, machineKey) {
3691
- const crypto2 = __require("crypto");
3692
- const iv = crypto2.randomBytes(12);
3693
- const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3694
- let encrypted = cipher.update(plaintext, "utf-8", "base64");
3695
- encrypted += cipher.final("base64");
3696
- const authTag = cipher.getAuthTag().toString("base64");
3697
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3698
- }
3699
- function decryptWithMachineKey(encrypted, machineKey) {
3700
- if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3701
- try {
3702
- const crypto2 = __require("crypto");
3703
- const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3704
- if (parts.length !== 3) return null;
3705
- const [ivB64, tagB64, cipherB64] = parts;
3706
- const iv = Buffer.from(ivB64, "base64");
3707
- const authTag = Buffer.from(tagB64, "base64");
3708
- const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
3709
- decipher.setAuthTag(authTag);
3710
- let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3711
- decrypted += decipher.final("utf-8");
3712
- return decrypted;
3713
- } catch {
3714
- return null;
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
+ }
3715
4544
  }
3716
- }
3717
- async function writeMachineBoundFileFallback(b64) {
3718
- const dir = getKeyDir();
3719
- await mkdir3(dir, { recursive: true });
3720
- const keyPath = getKeyPath();
3721
- const machineKey = deriveMachineKey();
3722
- if (machineKey) {
3723
- const encrypted = encryptWithMachineKey(b64, machineKey);
3724
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3725
- await chmod2(keyPath, 384);
3726
- return "encrypted";
4545
+ if (_pendingRecords.length >= _batchSize) {
4546
+ await flushBatch();
3727
4547
  }
3728
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3729
- await chmod2(keyPath, 384);
3730
- return "plaintext";
3731
4548
  }
3732
- async function getMasterKey() {
3733
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
3734
- if (nativeValue) {
3735
- return Buffer.from(nativeValue, "base64");
3736
- }
3737
- const keytar = await tryKeytar();
3738
- 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");
3739
4664
  try {
3740
- const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3741
- if (keytarValue) {
3742
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3743
- if (migrated) {
3744
- process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
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
+ }
3745
4704
  }
3746
- return Buffer.from(keytarValue, "base64");
3747
4705
  }
3748
4706
  } catch {
3749
4707
  }
4708
+ return batch.length;
4709
+ } finally {
4710
+ _flushing = false;
3750
4711
  }
3751
- const keyPath = getKeyPath();
3752
- if (!existsSync6(keyPath)) {
3753
- process.stderr.write(
3754
- `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3755
- `
3756
- );
3757
- return null;
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);
3758
4727
  }
3759
- try {
3760
- const content = (await readFile3(keyPath, "utf-8")).trim();
3761
- let b64Value;
3762
- if (content.startsWith(ENCRYPTED_PREFIX)) {
3763
- const machineKey = deriveMachineKey();
3764
- if (!machineKey) {
3765
- process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3766
- return null;
3767
- }
3768
- const decrypted = decryptWithMachineKey(content, machineKey);
3769
- if (!decrypted) {
3770
- process.stderr.write(
3771
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3772
- );
3773
- return null;
3774
- }
3775
- b64Value = decrypted;
3776
- } else {
3777
- b64Value = content;
3778
- }
3779
- const key = Buffer.from(b64Value, "base64");
3780
- const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3781
- if (migrated) {
3782
- process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3783
- try {
3784
- await unlink(keyPath);
3785
- process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3786
- } catch {
3787
- }
3788
- } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3789
- const fallback = await writeMachineBoundFileFallback(b64Value);
3790
- if (fallback === "encrypted") {
3791
- process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3792
- } else {
3793
- process.stderr.write(
3794
- "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3795
- );
3796
- }
3797
- }
3798
- return key;
3799
- } catch (err) {
3800
- process.stderr.write(
3801
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
3802
- `
3803
- );
3804
- 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
+ };
3805
4736
  }
4737
+ return { clause: "", args: [] };
3806
4738
  }
3807
-
3808
- // src/lib/store.ts
3809
- init_config();
3810
-
3811
- // src/lib/state-bus.ts
3812
- var StateBus = class {
3813
- handlers = /* @__PURE__ */ new Map();
3814
- globalHandlers = /* @__PURE__ */ new Set();
3815
- /** Emit an event to all subscribers */
3816
- emit(event) {
3817
- const typeHandlers = this.handlers.get(event.type);
3818
- if (typeHandlers) {
3819
- for (const handler of typeHandlers) {
3820
- try {
3821
- handler(event);
3822
- } catch {
3823
- }
3824
- }
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();
3825
4747
  }
3826
- for (const handler of this.globalHandlers) {
3827
- try {
3828
- handler(event);
3829
- } catch {
3830
- }
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
+ });
3831
4853
  }
3832
- }
3833
- /** Subscribe to a specific event type */
3834
- on(type, handler) {
3835
- if (!this.handlers.has(type)) {
3836
- this.handlers.set(type, /* @__PURE__ */ new Set());
4854
+ for (const record of records) {
4855
+ if (!record.document_id) continue;
4856
+ record.document_metadata = byId.get(record.document_id) ?? null;
3837
4857
  }
3838
- this.handlers.get(type).add(handler);
3839
- }
3840
- /** Subscribe to ALL events */
3841
- onAny(handler) {
3842
- this.globalHandlers.add(handler);
3843
- }
3844
- /** Unsubscribe from a specific event type */
3845
- off(type, handler) {
3846
- this.handlers.get(type)?.delete(handler);
3847
- }
3848
- /** Unsubscribe from ALL events */
3849
- offAny(handler) {
3850
- this.globalHandlers.delete(handler);
3851
- }
3852
- /** Remove all listeners */
3853
- clear() {
3854
- this.handlers.clear();
3855
- this.globalHandlers.clear();
3856
- }
3857
- };
3858
- var orgBus = new StateBus();
3859
-
3860
- // src/lib/memory-write-governor.ts
3861
- import { createHash } from "crypto";
3862
-
3863
- // src/lib/store.ts
3864
- var INIT_MAX_RETRIES = 3;
3865
- var INIT_RETRY_DELAY_MS = 1e3;
3866
- function isBusyError2(err) {
3867
- if (err instanceof Error) {
3868
- const msg = err.message.toLowerCase();
3869
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
4858
+ } catch {
3870
4859
  }
3871
- return false;
4860
+ return records;
3872
4861
  }
3873
- async function retryOnBusy2(fn, label) {
3874
- for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
3875
- try {
3876
- return await fn();
3877
- } catch (err) {
3878
- if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
3879
- process.stderr.write(
3880
- `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
3881
- `
3882
- );
3883
- await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
3884
- }
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) };
3885
4873
  }
3886
- 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 };
3887
4880
  }
3888
- var _pendingRecords = [];
3889
- var _batchSize = 20;
3890
- var _flushIntervalMs = 1e4;
3891
- var _flushTimer = null;
3892
- var _flushing = false;
3893
- var _nextVersion = 1;
3894
- async function initStore(options) {
4881
+ async function disposeStore() {
3895
4882
  if (_flushTimer !== null) {
3896
4883
  clearInterval(_flushTimer);
3897
4884
  _flushTimer = null;
3898
4885
  }
4886
+ if (_pendingRecords.length > 0) {
4887
+ await flushBatch();
4888
+ }
4889
+ await disposeTurso();
3899
4890
  _pendingRecords = [];
3900
- _flushing = false;
3901
- _batchSize = options?.batchSize ?? 20;
3902
- _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
3903
- let dbPath = options?.dbPath;
3904
- if (!dbPath) {
3905
- const config = await loadConfig();
3906
- dbPath = config.dbPath;
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++);
3907
4908
  }
3908
- let masterKey = options?.masterKey ?? null;
3909
- if (!masterKey) {
3910
- masterKey = await getMasterKey();
3911
- if (!masterKey) {
3912
- throw new Error(
3913
- "No encryption key found. Run /exe-setup to generate one."
3914
- );
3915
- }
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();
3916
4953
  }
3917
- const hexKey = masterKey.toString("hex");
3918
- await initTurso({
3919
- dbPath,
3920
- encryptionKey: hexKey
3921
- });
3922
- await retryOnBusy2(() => ensureSchema(), "ensureSchema");
3923
4954
  try {
3924
- const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3925
- 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
+ }
3926
5004
  } catch {
3927
5005
  }
3928
- 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)) {
3929
5047
  try {
3930
- const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3931
- initShardManager2(hexKey);
5048
+ copyFileSync(walPath, backupPath + "-wal");
3932
5049
  } catch {
3933
5050
  }
3934
- const client = getClient();
3935
- const vResult = await retryOnBusy2(
3936
- () => client.execute("SELECT MAX(version) as max_v FROM memories"),
3937
- "version-query"
3938
- );
3939
- _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
5051
+ }
5052
+ const shmPath = dbPath + "-shm";
5053
+ if (existsSync9(shmPath)) {
3940
5054
  try {
3941
- const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
3942
- await loadGlobalProcedures2();
5055
+ copyFileSync(shmPath, backupPath + "-shm");
3943
5056
  } catch {
3944
5057
  }
3945
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, "-")));
3946
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
+ });
3947
5118
 
3948
5119
  // src/bin/exe-doctor.ts
3949
- init_database();
5120
+ import os6 from "os";
3950
5121
 
3951
5122
  // src/lib/is-main.ts
3952
5123
  import { realpathSync } from "fs";
3953
- import { fileURLToPath as fileURLToPath2 } from "url";
5124
+ import { fileURLToPath } from "url";
3954
5125
  function isMainModule(importMetaUrl) {
3955
5126
  if (process.argv[1] == null) return false;
3956
5127
  if (process.argv[1].includes("mcp/server")) return false;
3957
5128
  try {
3958
5129
  const scriptPath = realpathSync(process.argv[1]);
3959
- const modulePath = realpathSync(fileURLToPath2(importMetaUrl));
5130
+ const modulePath = realpathSync(fileURLToPath(importMetaUrl));
3960
5131
  return scriptPath === modulePath;
3961
5132
  } catch {
3962
5133
  return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
@@ -4229,6 +5400,140 @@ async function detectConflicts(client, projectFilter, agentFilter2) {
4229
5400
  };
4230
5401
  }
4231
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
+
4232
5537
  // src/bin/exe-doctor.ts
4233
5538
  function parseFlags(argv) {
4234
5539
  const flags = { fix: false, dryRun: false, verbose: false, conflicts: false };
@@ -4427,6 +5732,121 @@ function auditHookHealth() {
4427
5732
  const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
4428
5733
  return { logExists: true, totalLines, errorsLastHour, topPatterns };
4429
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
+ }
4430
5850
  async function auditShards() {
4431
5851
  try {
4432
5852
  const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4471,7 +5891,8 @@ async function runAudit(client, flags) {
4471
5891
  }
4472
5892
  const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
4473
5893
  const hookHealth = auditHookHealth();
4474
- 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 };
4475
5896
  }
4476
5897
  function indicator(value, warn) {
4477
5898
  if (value === 0) return "\u{1F7E2}";
@@ -4550,6 +5971,15 @@ function formatReport(report, flags) {
4550
5971
  lines.push(` ${p.count}x: ${p.pattern}`);
4551
5972
  }
4552
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
+ }
4553
5983
  const sh = report.shards;
4554
5984
  if (sh.total > 0) {
4555
5985
  if (sh.unreadable === 0) {
@@ -4619,6 +6049,9 @@ function formatReport(report, flags) {
4619
6049
  if (report.conflicts.superseded > 0) {
4620
6050
  recs.push(`${fmtNum(report.conflicts.superseded)} superseded memories can be deactivated`);
4621
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
+ }
4622
6055
  if (recs.length > 0) {
4623
6056
  lines.push("Recommendations:");
4624
6057
  for (const r of recs) {
@@ -4752,8 +6185,8 @@ function splitAtSentences(text, maxChunkSize) {
4752
6185
  }
4753
6186
  async function main(argv = process.argv.slice(2)) {
4754
6187
  const flags = parseFlags(argv);
4755
- await initStore();
4756
- const client = getClient();
6188
+ const { fastDbInit: fastDbInit2 } = await Promise.resolve().then(() => (init_fast_db_init(), fast_db_init_exports));
6189
+ const client = await fastDbInit2();
4757
6190
  const report = await runAudit(client, flags);
4758
6191
  console.log(formatReport(report, flags));
4759
6192
  if (flags.fix || flags.dryRun) {
@@ -4821,6 +6254,7 @@ export {
4821
6254
  auditDuplicates,
4822
6255
  auditFts,
4823
6256
  auditHookHealth,
6257
+ auditHookOwnership,
4824
6258
  auditNullVectors,
4825
6259
  auditOrphanedProjects,
4826
6260
  auditShards,