@askexenow/exe-os 0.9.63 → 0.9.64

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,6 +15,70 @@ 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
+
18
82
  // src/lib/secure-files.ts
19
83
  import { chmodSync, existsSync, mkdirSync } from "fs";
20
84
  import { chmod, mkdir } from "fs/promises";
@@ -238,669 +302,89 @@ var init_config = __esm({
238
302
  }
239
303
  });
240
304
 
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
- });
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";
256
309
  import path2 from "path";
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 });
263
- }
264
- _shardingEnabled = true;
265
- if (_evictionTimer) clearInterval(_evictionTimer);
266
- _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
267
- _evictionTimer.unref();
310
+ import os2 from "os";
311
+ function normalizeRole(role) {
312
+ return (role ?? "").trim().toLowerCase();
268
313
  }
269
- function isShardingEnabled() {
270
- return _shardingEnabled;
314
+ function isCoordinatorRole(role) {
315
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
271
316
  }
272
- function getShardsDir() {
273
- return SHARDS_DIR;
317
+ function getCoordinatorEmployee(employees) {
318
+ return employees.find((e) => isCoordinatorRole(e.role));
274
319
  }
275
- function getShardClient(projectName) {
276
- if (!_encryptionKey) {
277
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
278
- }
279
- const safeName = safeShardName(projectName);
280
- if (!safeName || safeName === "unknown") {
281
- throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
282
- }
283
- const cached = _shards.get(safeName);
284
- if (cached) {
285
- _shardLastAccess.set(safeName, Date.now());
286
- return cached;
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 [];
287
329
  }
288
- while (_shards.size >= MAX_OPEN_SHARDS) {
289
- evictLRU();
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");
290
340
  }
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;
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, '""')}"`;
299
350
  }
300
- function shardExists(projectName) {
301
- const safeName = safeShardName(projectName);
302
- return existsSync3(path2.join(SHARDS_DIR, `${safeName}.db`));
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();
303
355
  }
304
- function safeShardName(projectName) {
305
- return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
356
+ function stripTrailingSemicolon(sql) {
357
+ return sql.trim().replace(/;+\s*$/u, "");
306
358
  }
307
- function listShards() {
308
- if (!existsSync3(SHARDS_DIR)) return [];
309
- return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
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}`;
364
+ }
365
+ const idx = returningMatch.index;
366
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
310
367
  }
311
- async function auditShardHealth(options = {}) {
312
- if (!_encryptionKey) {
313
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
368
+ function normalizeStatement(stmt) {
369
+ if (typeof stmt === "string") {
370
+ return { kind: "positional", sql: stmt, args: [] };
314
371
  }
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);
372
+ const sql = stmt.sql;
373
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
374
+ return { kind: "positional", sql, args: stmt.args ?? [] };
366
375
  }
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
- };
376
+ return { kind: "named", sql, args: stmt.args };
374
377
  }
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");
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");
904
388
  }
905
389
  return out;
906
390
  }
@@ -1169,8 +653,8 @@ async function loadPrismaClient() {
1169
653
  }
1170
654
  return new PrismaClient2();
1171
655
  }
1172
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os3.homedir(), "exe-db");
1173
- const requireFromExeDb = createRequire(path5.join(exeDbRoot, "package.json"));
656
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
657
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1174
658
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1175
659
  const module = await import(pathToFileURL(prismaEntry).href);
1176
660
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1440,19 +924,10 @@ var init_database_adapter = __esm({
1440
924
  }
1441
925
  });
1442
926
 
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
-
1452
927
  // src/lib/daemon-auth.ts
1453
928
  import crypto from "crypto";
1454
- import path6 from "path";
1455
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
929
+ import path4 from "path";
930
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1456
931
  function normalizeToken(token) {
1457
932
  if (!token) return null;
1458
933
  const trimmed = token.trim();
@@ -1460,7 +935,7 @@ function normalizeToken(token) {
1460
935
  }
1461
936
  function readDaemonToken() {
1462
937
  try {
1463
- if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
938
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1464
939
  return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1465
940
  } catch {
1466
941
  return null;
@@ -1471,7 +946,7 @@ function ensureDaemonToken(seed) {
1471
946
  if (existing) return existing;
1472
947
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1473
948
  ensurePrivateDirSync(EXE_AI_DIR);
1474
- writeFileSync3(DAEMON_TOKEN_PATH, `${token}
949
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1475
950
  `, "utf8");
1476
951
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1477
952
  return token;
@@ -1482,29 +957,18 @@ var init_daemon_auth = __esm({
1482
957
  "use strict";
1483
958
  init_config();
1484
959
  init_secure_files();
1485
- DAEMON_TOKEN_PATH = path6.join(EXE_AI_DIR, "exed.token");
960
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1486
961
  }
1487
962
  });
1488
963
 
1489
964
  // 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
- });
1501
965
  import net from "net";
1502
966
  import os4 from "os";
1503
967
  import { spawn } from "child_process";
1504
968
  import { randomUUID } from "crypto";
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";
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";
1508
972
  function handleData(chunk) {
1509
973
  _buffer += chunk.toString();
1510
974
  if (_buffer.length > MAX_BUFFER) {
@@ -1531,7 +995,7 @@ function handleData(chunk) {
1531
995
  }
1532
996
  }
1533
997
  function cleanupStaleFiles() {
1534
- if (existsSync7(PID_PATH)) {
998
+ if (existsSync5(PID_PATH)) {
1535
999
  try {
1536
1000
  const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1537
1001
  if (pid > 0) {
@@ -1544,21 +1008,21 @@ function cleanupStaleFiles() {
1544
1008
  } catch {
1545
1009
  }
1546
1010
  try {
1547
- unlinkSync3(PID_PATH);
1011
+ unlinkSync2(PID_PATH);
1548
1012
  } catch {
1549
1013
  }
1550
1014
  try {
1551
- unlinkSync3(SOCKET_PATH);
1015
+ unlinkSync2(SOCKET_PATH);
1552
1016
  } catch {
1553
1017
  }
1554
1018
  }
1555
1019
  }
1556
1020
  function findPackageRoot() {
1557
- let dir = path7.dirname(fileURLToPath2(import.meta.url));
1558
- const { root } = path7.parse(dir);
1021
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1022
+ const { root } = path5.parse(dir);
1559
1023
  while (dir !== root) {
1560
- if (existsSync7(path7.join(dir, "package.json"))) return dir;
1561
- dir = path7.dirname(dir);
1024
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1025
+ dir = path5.dirname(dir);
1562
1026
  }
1563
1027
  return null;
1564
1028
  }
@@ -1605,8 +1069,8 @@ function spawnDaemon() {
1605
1069
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1606
1070
  return;
1607
1071
  }
1608
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1609
- if (!existsSync7(daemonPath)) {
1072
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1073
+ if (!existsSync5(daemonPath)) {
1610
1074
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1611
1075
  `);
1612
1076
  return;
@@ -1615,7 +1079,7 @@ function spawnDaemon() {
1615
1079
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1616
1080
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1617
1081
  `);
1618
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1082
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1619
1083
  let stderrFd = "ignore";
1620
1084
  try {
1621
1085
  stderrFd = openSync(logPath, "a");
@@ -1652,10 +1116,10 @@ function acquireSpawnLock() {
1652
1116
  return true;
1653
1117
  } catch {
1654
1118
  try {
1655
- const stat = statSync2(SPAWN_LOCK_PATH);
1119
+ const stat = statSync(SPAWN_LOCK_PATH);
1656
1120
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1657
1121
  try {
1658
- unlinkSync3(SPAWN_LOCK_PATH);
1122
+ unlinkSync2(SPAWN_LOCK_PATH);
1659
1123
  } catch {
1660
1124
  }
1661
1125
  try {
@@ -1672,7 +1136,7 @@ function acquireSpawnLock() {
1672
1136
  }
1673
1137
  function releaseSpawnLock() {
1674
1138
  try {
1675
- unlinkSync3(SPAWN_LOCK_PATH);
1139
+ unlinkSync2(SPAWN_LOCK_PATH);
1676
1140
  } catch {
1677
1141
  }
1678
1142
  }
@@ -1714,223 +1178,60 @@ function connectToSocket() {
1714
1178
  });
1715
1179
  });
1716
1180
  }
1717
- async function connectEmbedDaemon() {
1718
- if (_socket && _connected) return true;
1719
- if (await connectToSocket()) return true;
1720
- if (acquireSpawnLock()) {
1721
- try {
1722
- cleanupStaleFiles();
1723
- spawnDaemon();
1724
- } finally {
1725
- releaseSpawnLock();
1726
- }
1727
- }
1728
- const start = Date.now();
1729
- let delay2 = 100;
1730
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1731
- await new Promise((r) => setTimeout(r, delay2));
1732
- if (await connectToSocket()) return true;
1733
- delay2 = Math.min(delay2 * 2, 3e3);
1734
- }
1735
- return false;
1736
- }
1737
- function sendRequest(texts, priority) {
1738
- return sendDaemonRequest({ texts, priority });
1739
- }
1740
- function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1741
- return new Promise((resolve) => {
1742
- if (!_socket || !_connected) {
1743
- resolve({ error: "Not connected" });
1744
- return;
1745
- }
1746
- const id = randomUUID();
1747
- const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1748
- const timer = setTimeout(() => {
1749
- _pending.delete(id);
1750
- resolve({ error: "Request timeout" });
1751
- }, timeoutMs);
1752
- _pending.set(id, { resolve, timer });
1753
- try {
1754
- _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1755
- } catch {
1756
- clearTimeout(timer);
1757
- _pending.delete(id);
1758
- resolve({ error: "Write failed" });
1759
- }
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;
1181
+ async function connectEmbedDaemon() {
1182
+ if (_socket && _connected) return true;
1183
+ if (await connectToSocket()) return true;
1184
+ if (acquireSpawnLock()) {
1185
+ try {
1186
+ cleanupStaleFiles();
1187
+ spawnDaemon();
1188
+ } finally {
1189
+ releaseSpawnLock();
1841
1190
  }
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
1191
  }
1849
- process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
1850
- `);
1851
- killAndRespawnDaemon();
1852
1192
  const start = Date.now();
1853
- let delay2 = 200;
1193
+ let delay2 = 100;
1854
1194
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1855
1195
  await new Promise((r) => setTimeout(r, delay2));
1856
- if (await connectToSocket()) break;
1196
+ if (await connectToSocket()) return true;
1857
1197
  delay2 = Math.min(delay2 * 2, 3e3);
1858
1198
  }
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;
1199
+ return false;
1897
1200
  }
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
- }
1201
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1202
+ return new Promise((resolve) => {
1203
+ if (!_socket || !_connected) {
1204
+ resolve({ error: "Not connected" });
1205
+ return;
1206
+ }
1207
+ const id = randomUUID();
1208
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1209
+ const timer = setTimeout(() => {
1210
+ _pending.delete(id);
1211
+ resolve({ error: "Request timeout" });
1212
+ }, timeoutMs);
1213
+ _pending.set(id, { resolve, timer });
1214
+ try {
1215
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1216
+ } catch {
1217
+ clearTimeout(timer);
1218
+ _pending.delete(id);
1219
+ resolve({ error: "Write failed" });
1220
+ }
1221
+ });
1910
1222
  }
1911
1223
  function isClientConnected() {
1912
1224
  return _connected;
1913
1225
  }
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;
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;
1926
1227
  var init_exe_daemon_client = __esm({
1927
1228
  "src/lib/exe-daemon-client.ts"() {
1928
1229
  "use strict";
1929
1230
  init_config();
1930
1231
  init_daemon_auth();
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");
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");
1934
1235
  SPAWN_LOCK_STALE_MS = 3e4;
1935
1236
  CONNECT_TIMEOUT_MS = 15e3;
1936
1237
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1938,27 +1239,12 @@ var init_exe_daemon_client = __esm({
1938
1239
  _socket = null;
1939
1240
  _connected = false;
1940
1241
  _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;
1947
1242
  _pending = /* @__PURE__ */ new Map();
1948
1243
  MAX_BUFFER = 1e7;
1949
1244
  }
1950
1245
  });
1951
1246
 
1952
1247
  // 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
- });
1962
1248
  function serializeValue(v) {
1963
1249
  if (v === null || v === void 0) return null;
1964
1250
  if (typeof v === "bigint") return Number(v);
@@ -1983,32 +1269,6 @@ function deserializeValue(v) {
1983
1269
  }
1984
1270
  return v;
1985
1271
  }
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
- }
2012
1272
  function deserializeResultSet(srs) {
2013
1273
  const rows = srs.rows.map((obj) => {
2014
1274
  const values = srs.columns.map(
@@ -2198,7 +1458,7 @@ __export(database_exports, {
2198
1458
  isInitialized: () => isInitialized,
2199
1459
  setExternalClient: () => setExternalClient
2200
1460
  });
2201
- import { createClient as createClient2 } from "@libsql/client";
1461
+ import { createClient } from "@libsql/client";
2202
1462
  async function initDatabase(config) {
2203
1463
  if (_walCheckpointTimer) {
2204
1464
  clearInterval(_walCheckpointTimer);
@@ -2223,7 +1483,7 @@ async function initDatabase(config) {
2223
1483
  if (config.encryptionKey) {
2224
1484
  opts.encryptionKey = config.encryptionKey;
2225
1485
  }
2226
- _client = createClient2(opts);
1486
+ _client = createClient(opts);
2227
1487
  _resilientClient = wrapWithRetry(_client);
2228
1488
  _adapterClient = _resilientClient;
2229
1489
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
@@ -3364,13 +2624,289 @@ async function ensureSchema() {
3364
2624
  } catch {
3365
2625
  }
3366
2626
  try {
3367
- await client.execute({
3368
- sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
3369
- args: []
3370
- });
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");
3371
2840
  } catch {
3372
2841
  }
2842
+ await client.executeMultiple(`
2843
+ CREATE TABLE IF NOT EXISTS memories (
2844
+ id TEXT PRIMARY KEY,
2845
+ agent_id TEXT NOT NULL,
2846
+ agent_role TEXT NOT NULL,
2847
+ session_id TEXT NOT NULL,
2848
+ timestamp TEXT NOT NULL,
2849
+ tool_name TEXT NOT NULL,
2850
+ project_name TEXT NOT NULL,
2851
+ has_error INTEGER NOT NULL DEFAULT 0,
2852
+ raw_text TEXT NOT NULL,
2853
+ vector F32_BLOB(1024),
2854
+ version INTEGER NOT NULL DEFAULT 0
2855
+ );
2856
+
2857
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
2858
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
2859
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
2860
+ `);
2861
+ await client.executeMultiple(`
2862
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
2863
+ raw_text,
2864
+ content='memories',
2865
+ content_rowid='rowid'
2866
+ );
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
+ `);
3373
2881
  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)
3374
2910
  "ALTER TABLE memories ADD COLUMN intent TEXT",
3375
2911
  "ALTER TABLE memories ADD COLUMN outcome TEXT",
3376
2912
  "ALTER TABLE memories ADD COLUMN domain TEXT",
@@ -3385,528 +2921,195 @@ async function ensureSchema() {
3385
2921
  "ALTER TABLE memories ADD COLUMN token_cost REAL",
3386
2922
  "ALTER TABLE memories ADD COLUMN audience TEXT",
3387
2923
  "ALTER TABLE memories ADD COLUMN language_type TEXT",
3388
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
3389
- ]) {
3390
- try {
3391
- await client.execute(col);
3392
- } catch {
3393
- }
3394
- }
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;
2924
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
2925
+ "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
2926
+ ]) {
2927
+ try {
2928
+ await client.execute(col);
2929
+ } catch {
2930
+ }
3562
2931
  }
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";
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
+ }
3574
2941
  }
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");
2942
+ try {
2943
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
2944
+ } catch {
3583
2945
  }
3584
- const keytar = await tryKeytar();
3585
- if (keytar) {
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
+ ]) {
3586
2951
  try {
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
- }
2952
+ await client.execute(idx);
3595
2953
  } catch {
3596
2954
  }
3597
2955
  }
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
- `
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)
3603
2965
  );
3604
- return null;
2966
+
2967
+ CREATE TABLE IF NOT EXISTS relationships (
2968
+ id TEXT PRIMARY KEY,
2969
+ source_entity_id TEXT NOT NULL,
2970
+ target_entity_id TEXT NOT NULL,
2971
+ type TEXT NOT NULL,
2972
+ weight REAL DEFAULT 1.0,
2973
+ timestamp TEXT NOT NULL,
2974
+ properties TEXT DEFAULT '{}',
2975
+ UNIQUE(source_entity_id, target_entity_id, type)
2976
+ );
2977
+
2978
+ CREATE TABLE IF NOT EXISTS entity_memories (
2979
+ entity_id TEXT NOT NULL,
2980
+ memory_id TEXT NOT NULL,
2981
+ PRIMARY KEY (entity_id, memory_id)
2982
+ );
2983
+
2984
+ CREATE TABLE IF NOT EXISTS relationship_memories (
2985
+ relationship_id TEXT NOT NULL,
2986
+ memory_id TEXT NOT NULL,
2987
+ PRIMARY KEY (relationship_id, memory_id)
2988
+ );
2989
+
2990
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
2991
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
2992
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
2993
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
2994
+ CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
2995
+
2996
+ CREATE TABLE IF NOT EXISTS hyperedges (
2997
+ id TEXT PRIMARY KEY,
2998
+ label TEXT NOT NULL,
2999
+ relation TEXT NOT NULL,
3000
+ confidence REAL DEFAULT 1.0,
3001
+ timestamp TEXT NOT NULL
3002
+ );
3003
+
3004
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
3005
+ hyperedge_id TEXT NOT NULL,
3006
+ entity_id TEXT NOT NULL,
3007
+ PRIMARY KEY (hyperedge_id, entity_id)
3008
+ );
3009
+ `);
3010
+ for (const col of [
3011
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
3012
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
3013
+ ]) {
3014
+ try {
3015
+ await client.execute(col);
3016
+ } catch {
3017
+ }
3605
3018
  }
3019
+ }
3020
+ async function getReadyShardClient(projectName) {
3021
+ const safeName = safeShardName(projectName);
3022
+ let client = getShardClient(projectName);
3606
3023
  try {
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;
3024
+ await ensureShardSchema(client);
3025
+ return client;
3646
3026
  } catch (err) {
3647
- process.stderr.write(
3648
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(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()})
3649
3040
  `
3650
- );
3651
- return null;
3041
+ );
3042
+ }
3043
+ client = getShardClient(projectName);
3044
+ await ensureShardSchema(client);
3045
+ return client;
3652
3046
  }
3653
3047
  }
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:";
3661
- }
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();
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
+ }
3716
3056
  }
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;
3753
- }
3754
- function hashMemoryContent(text) {
3755
- return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3756
- }
3757
- function scopedDedupArgs(input) {
3758
- return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3759
- }
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)
3057
+ if (oldest) {
3058
+ const client = _shards.get(oldest);
3059
+ if (client) {
3060
+ client.close();
3791
3061
  }
3792
- };
3062
+ _shards.delete(oldest);
3063
+ _shardLastAccess.delete(oldest);
3064
+ }
3793
3065
  }
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
- });
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);
3843
3072
  }
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");
3073
+ }
3074
+ for (const name of toEvict) {
3075
+ const client = _shards.get(name);
3076
+ if (client) {
3077
+ client.close();
3868
3078
  }
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
- );
3079
+ _shards.delete(name);
3080
+ _shardLastAccess.delete(name);
3874
3081
  }
3875
3082
  }
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);
3083
+ function getOpenShardCount() {
3084
+ return _shards.size;
3085
+ }
3086
+ function disposeShards() {
3087
+ if (_evictionTimer) {
3088
+ clearInterval(_evictionTimer);
3089
+ _evictionTimer = null;
3090
+ }
3091
+ for (const [, client] of _shards) {
3092
+ client.close();
3093
+ }
3094
+ _shards.clear();
3095
+ _shardLastAccess.clear();
3096
+ _shardingEnabled = false;
3097
+ _encryptionKey = null;
3884
3098
  }
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"() {
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"() {
3888
3102
  "use strict";
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
- ];
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;
3910
3113
  }
3911
3114
  });
3912
3115
 
@@ -4165,969 +3368,595 @@ ${p.content}`).join("\n\n");
4165
3368
  }
4166
3369
  });
4167
3370
 
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
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
4174
3380
  });
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;
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;
4237
3424
  }
4238
- return cards;
4239
3425
  }
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
- }));
3426
+ function registerWorkerPid(pid) {
3427
+ try {
3428
+ mkdirSync3(WORKER_PID_DIR, { recursive: true });
3429
+ writeFileSync3(path8.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3430
+ } catch {
3431
+ }
3432
+ }
3433
+ function cleanupWorkerPid() {
3434
+ try {
3435
+ unlinkSync3(path8.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3436
+ } catch {
3437
+ }
3438
+ }
3439
+ function tryAcquireBackfillLock() {
3440
+ try {
3441
+ mkdirSync3(WORKER_PID_DIR, { recursive: true });
3442
+ if (existsSync8(BACKFILL_LOCK)) {
3443
+ try {
3444
+ const pid = parseInt(
3445
+ __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
3446
+ 10
3447
+ );
3448
+ if (!isNaN(pid) && pid > 0) {
3449
+ try {
3450
+ process.kill(pid, 0);
3451
+ return false;
3452
+ } catch {
3453
+ }
3454
+ }
3455
+ } catch {
3456
+ }
3457
+ }
3458
+ writeFileSync3(BACKFILL_LOCK, String(process.pid));
3459
+ return true;
3460
+ } catch {
3461
+ return true;
3462
+ }
3463
+ }
3464
+ function releaseBackfillLock() {
3465
+ try {
3466
+ unlinkSync3(BACKFILL_LOCK);
3467
+ } catch {
3468
+ }
4315
3469
  }
4316
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
4317
- var init_memory_cards = __esm({
4318
- "src/lib/memory-cards.ts"() {
3470
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
3471
+ var init_worker_gate = __esm({
3472
+ "src/lib/worker-gate.ts"() {
4319
3473
  "use strict";
4320
- init_database();
4321
- MAX_CARDS_PER_MEMORY = 6;
4322
- MAX_SENTENCE_CHARS = 360;
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");
4323
3478
  }
4324
3479
  });
4325
3480
 
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
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
4343
3491
  });
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");
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;
4348
3498
  }
4349
- return false;
3499
+ return null;
4350
3500
  }
4351
- async function retryOnBusy2(fn, label) {
4352
- for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
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)) {
4353
3512
  try {
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)));
3513
+ copyFileSync(walPath, backupPath + "-wal");
3514
+ } catch {
4362
3515
  }
4363
3516
  }
4364
- throw new Error("unreachable");
4365
- }
4366
- async function initStore(options) {
4367
- if (_flushTimer !== null) {
4368
- clearInterval(_flushTimer);
4369
- _flushTimer = null;
4370
- }
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;
3517
+ const shmPath = dbPath + "-shm";
3518
+ if (existsSync9(shmPath)) {
3519
+ try {
3520
+ copyFileSync(shmPath, backupPath + "-shm");
3521
+ } catch {
3522
+ }
4379
3523
  }
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
- );
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
+ }
4387
3543
  }
3544
+ } catch {
4388
3545
  }
4389
- const hexKey = masterKey.toString("hex");
4390
- await initTurso({
4391
- dbPath,
4392
- encryptionKey: hexKey
4393
- });
4394
- await retryOnBusy2(() => ensureSchema(), "ensureSchema");
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
+ }
3560
+ }
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"];
3581
+ }
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;
4395
3607
  try {
4396
- const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4397
- await initDaemonClient2();
3608
+ return execSync2(
3609
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3610
+ { encoding: "utf-8", timeout: 5e3 }
3611
+ ).trim();
4398
3612
  } catch {
3613
+ return null;
4399
3614
  }
4400
- if (!options?.lightweight) {
3615
+ }
3616
+ function macKeychainSet(value) {
3617
+ if (process.platform !== "darwin") return false;
3618
+ try {
4401
3619
  try {
4402
- const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4403
- initShardManager2(hexKey);
3620
+ execSync2(
3621
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3622
+ { timeout: 5e3 }
3623
+ );
4404
3624
  } catch {
4405
3625
  }
4406
- const client = getClient();
4407
- const vResult = await retryOnBusy2(
4408
- () => client.execute("SELECT MAX(version) as max_v FROM memories"),
4409
- "version-query"
3626
+ execSync2(
3627
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3628
+ { timeout: 5e3 }
4410
3629
  );
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
- }
3630
+ return true;
3631
+ } catch {
3632
+ return false;
4417
3633
  }
4418
3634
  }
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;
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
+ }
4450
3645
  }
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}`
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 }
4455
3652
  );
3653
+ return true;
3654
+ } catch {
3655
+ return false;
4456
3656
  }
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;
4466
- }
3657
+ }
3658
+ async function tryKeytar() {
4467
3659
  try {
4468
- const existing = await findScopedDuplicate({
4469
- contentHash,
4470
- agentId: record.agent_id,
4471
- projectName: record.project_name,
4472
- memoryType
4473
- });
4474
- if (existing) return;
3660
+ return await import("keytar");
4475
3661
  } catch {
3662
+ return null;
4476
3663
  }
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
- }
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;
4544
3680
  }
4545
- if (_pendingRecords.length >= _batchSize) {
4546
- await flushBatch();
3681
+ }
3682
+ function readMachineId() {
3683
+ try {
3684
+ const { readFileSync: readFileSync6 } = __require("fs");
3685
+ return readFileSync6("/etc/machine-id", "utf-8").trim();
3686
+ } catch {
3687
+ return "";
4547
3688
  }
4548
3689
  }
4549
- async function flushBatch() {
4550
- if (_flushing || _pendingRecords.length === 0) return 0;
4551
- _flushing = true;
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;
4552
3701
  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");
4664
- try {
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);
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;
3715
+ }
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";
3727
+ }
3728
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3729
+ await chmod2(keyPath, 384);
3730
+ return "plaintext";
3731
+ }
3732
+ async function getMasterKey() {
3733
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3734
+ if (nativeValue) {
3735
+ return Buffer.from(nativeValue, "base64");
3736
+ }
3737
+ const keytar = await tryKeytar();
3738
+ if (keytar) {
4671
3739
  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
- }
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");
4704
3745
  }
3746
+ return Buffer.from(keytarValue, "base64");
4705
3747
  }
4706
3748
  } catch {
4707
3749
  }
4708
- return batch.length;
4709
- } finally {
4710
- _flushing = false;
4711
- }
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);
4727
3750
  }
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
- };
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;
4736
3758
  }
4737
- return { clause: "", args: [] };
4738
- }
4739
- async function searchMemories(queryVector, agentId, options) {
4740
- let client;
4741
3759
  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);
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;
4745
3776
  } else {
4746
- client = getClient();
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;
3805
+ }
3806
+ }
3807
+
3808
+ // src/lib/store.ts
3809
+ init_config();
3810
+
3811
+ // src/lib/state-bus.ts
3812
+ var StateBus = class {
3813
+ handlers = /* @__PURE__ */ new Map();
3814
+ globalHandlers = /* @__PURE__ */ new Set();
3815
+ /** Emit an event to all subscribers */
3816
+ emit(event) {
3817
+ const typeHandlers = this.handlers.get(event.type);
3818
+ if (typeHandlers) {
3819
+ for (const handler of typeHandlers) {
3820
+ try {
3821
+ handler(event);
3822
+ } catch {
3823
+ }
3824
+ }
4747
3825
  }
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
- });
3826
+ for (const handler of this.globalHandlers) {
3827
+ try {
3828
+ handler(event);
3829
+ } catch {
3830
+ }
4853
3831
  }
4854
- for (const record of records) {
4855
- if (!record.document_id) continue;
4856
- record.document_metadata = byId.get(record.document_id) ?? null;
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());
4857
3837
  }
4858
- } catch {
3838
+ this.handlers.get(type).add(handler);
4859
3839
  }
4860
- return records;
4861
- }
4862
- async function flushTier3(agentId, options) {
4863
- const client = getClient();
4864
- const maxAge = options?.maxAgeHours ?? 72;
4865
- const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
4866
- if (options?.dryRun) {
4867
- const result2 = await client.execute({
4868
- sql: `SELECT COUNT(*) as cnt FROM memories
4869
- WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
4870
- args: [agentId, cutoff]
4871
- });
4872
- return { archived: Number(result2.rows[0]?.cnt ?? 0) };
3840
+ /** Subscribe to ALL events */
3841
+ onAny(handler) {
3842
+ this.globalHandlers.add(handler);
4873
3843
  }
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 };
4880
- }
4881
- async function disposeStore() {
4882
- if (_flushTimer !== null) {
4883
- clearInterval(_flushTimer);
4884
- _flushTimer = null;
3844
+ /** Unsubscribe from a specific event type */
3845
+ off(type, handler) {
3846
+ this.handlers.get(type)?.delete(handler);
4885
3847
  }
4886
- if (_pendingRecords.length > 0) {
4887
- await flushBatch();
3848
+ /** Unsubscribe from ALL events */
3849
+ offAny(handler) {
3850
+ this.globalHandlers.delete(handler);
4888
3851
  }
4889
- await disposeTurso();
4890
- _pendingRecords = [];
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++);
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");
4908
3870
  }
4909
- return reserved;
3871
+ return false;
4910
3872
  }
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;
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
+ }
4921
3885
  }
3886
+ throw new Error("unreachable");
4922
3887
  }
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;
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) {
3895
+ if (_flushTimer !== null) {
3896
+ clearInterval(_flushTimer);
4938
3897
  _flushTimer = null;
4939
- _flushing = false;
4940
- _nextVersion = 1;
4941
3898
  }
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();
3899
+ _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;
4953
3907
  }
4954
- try {
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;
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
+ );
5003
3915
  }
5004
- } catch {
5005
- }
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
3916
  }
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;
3917
+ const hexKey = masterKey.toString("hex");
3918
+ await initTurso({
3919
+ dbPath,
3920
+ encryptionKey: hexKey
3921
+ });
3922
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
3923
+ try {
3924
+ const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3925
+ await initDaemonClient2();
3926
+ } catch {
5033
3927
  }
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)) {
3928
+ if (!options?.lightweight) {
5047
3929
  try {
5048
- copyFileSync(walPath, backupPath + "-wal");
3930
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3931
+ initShardManager2(hexKey);
5049
3932
  } catch {
5050
3933
  }
5051
- }
5052
- const shmPath = dbPath + "-shm";
5053
- if (existsSync9(shmPath)) {
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;
5054
3940
  try {
5055
- copyFileSync(shmPath, backupPath + "-shm");
3941
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
3942
+ await loadGlobalProcedures2();
5056
3943
  } catch {
5057
3944
  }
5058
3945
  }
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, "-")));
5100
3946
  }
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
- });
5118
3947
 
5119
3948
  // src/bin/exe-doctor.ts
5120
- import os6 from "os";
3949
+ init_database();
5121
3950
 
5122
3951
  // src/lib/is-main.ts
5123
3952
  import { realpathSync } from "fs";
5124
- import { fileURLToPath } from "url";
3953
+ import { fileURLToPath as fileURLToPath2 } from "url";
5125
3954
  function isMainModule(importMetaUrl) {
5126
3955
  if (process.argv[1] == null) return false;
5127
3956
  if (process.argv[1].includes("mcp/server")) return false;
5128
3957
  try {
5129
3958
  const scriptPath = realpathSync(process.argv[1]);
5130
- const modulePath = realpathSync(fileURLToPath(importMetaUrl));
3959
+ const modulePath = realpathSync(fileURLToPath2(importMetaUrl));
5131
3960
  return scriptPath === modulePath;
5132
3961
  } catch {
5133
3962
  return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
@@ -5400,140 +4229,6 @@ async function detectConflicts(client, projectFilter, agentFilter2) {
5400
4229
  };
5401
4230
  }
5402
4231
 
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
-
5537
4232
  // src/bin/exe-doctor.ts
5538
4233
  function parseFlags(argv) {
5539
4234
  const flags = { fix: false, dryRun: false, verbose: false, conflicts: false };
@@ -5732,121 +4427,6 @@ function auditHookHealth() {
5732
4427
  const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
5733
4428
  return { logExists: true, totalLines, errorsLastHour, topPatterns };
5734
4429
  }
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
- }
5850
4430
  async function auditShards() {
5851
4431
  try {
5852
4432
  const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -5891,8 +4471,7 @@ async function runAudit(client, flags) {
5891
4471
  }
5892
4472
  const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
5893
4473
  const hookHealth = auditHookHealth();
5894
- const hookOwnership = auditHookOwnership();
5895
- return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, hookOwnership, shards };
4474
+ return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, shards };
5896
4475
  }
5897
4476
  function indicator(value, warn) {
5898
4477
  if (value === 0) return "\u{1F7E2}";
@@ -5971,15 +4550,6 @@ function formatReport(report, flags) {
5971
4550
  lines.push(` ${p.count}x: ${p.pattern}`);
5972
4551
  }
5973
4552
  }
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
- }
5983
4553
  const sh = report.shards;
5984
4554
  if (sh.total > 0) {
5985
4555
  if (sh.unreadable === 0) {
@@ -6049,9 +4619,6 @@ function formatReport(report, flags) {
6049
4619
  if (report.conflicts.superseded > 0) {
6050
4620
  recs.push(`${fmtNum(report.conflicts.superseded)} superseded memories can be deactivated`);
6051
4621
  }
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
- }
6055
4622
  if (recs.length > 0) {
6056
4623
  lines.push("Recommendations:");
6057
4624
  for (const r of recs) {
@@ -6185,8 +4752,8 @@ function splitAtSentences(text, maxChunkSize) {
6185
4752
  }
6186
4753
  async function main(argv = process.argv.slice(2)) {
6187
4754
  const flags = parseFlags(argv);
6188
- const { fastDbInit: fastDbInit2 } = await Promise.resolve().then(() => (init_fast_db_init(), fast_db_init_exports));
6189
- const client = await fastDbInit2();
4755
+ await initStore();
4756
+ const client = getClient();
6190
4757
  const report = await runAudit(client, flags);
6191
4758
  console.log(formatReport(report, flags));
6192
4759
  if (flags.fix || flags.dryRun) {
@@ -6254,7 +4821,6 @@ export {
6254
4821
  auditDuplicates,
6255
4822
  auditFts,
6256
4823
  auditHookHealth,
6257
- auditHookOwnership,
6258
4824
  auditNullVectors,
6259
4825
  auditOrphanedProjects,
6260
4826
  auditShards,