@askexenow/exe-os 0.9.11 → 0.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/bin/backfill-conversations.js +22 -1
  2. package/dist/bin/backfill-responses.js +22 -1
  3. package/dist/bin/backfill-vectors.js +22 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +22 -1
  5. package/dist/bin/cli.js +22 -1
  6. package/dist/bin/exe-assign.js +22 -1
  7. package/dist/bin/exe-boot.js +22 -1
  8. package/dist/bin/exe-dispatch.js +22 -1
  9. package/dist/bin/exe-doctor.js +22 -1
  10. package/dist/bin/exe-export-behaviors.js +22 -1
  11. package/dist/bin/exe-forget.js +22 -1
  12. package/dist/bin/exe-gateway.js +22 -1
  13. package/dist/bin/exe-heartbeat.js +22 -1
  14. package/dist/bin/exe-kill.js +22 -1
  15. package/dist/bin/exe-launch-agent.js +22 -1
  16. package/dist/bin/exe-link.js +22 -1
  17. package/dist/bin/exe-pending-messages.js +22 -1
  18. package/dist/bin/exe-pending-notifications.js +22 -1
  19. package/dist/bin/exe-pending-reviews.js +22 -1
  20. package/dist/bin/exe-rename.js +22 -1
  21. package/dist/bin/exe-review.js +22 -1
  22. package/dist/bin/exe-search.js +22 -1
  23. package/dist/bin/exe-session-cleanup.js +22 -1
  24. package/dist/bin/exe-start-codex.js +22 -1
  25. package/dist/bin/exe-start-opencode.js +22 -1
  26. package/dist/bin/exe-status.js +22 -1
  27. package/dist/bin/exe-team.js +22 -1
  28. package/dist/bin/git-sweep.js +22 -1
  29. package/dist/bin/graph-backfill.js +22 -1
  30. package/dist/bin/graph-export.js +22 -1
  31. package/dist/bin/scan-tasks.js +22 -1
  32. package/dist/bin/setup.js +22 -1
  33. package/dist/bin/shard-migrate.js +22 -1
  34. package/dist/bin/wiki-sync.js +22 -1
  35. package/dist/gateway/index.js +22 -1
  36. package/dist/hooks/bug-report-worker.js +22 -1
  37. package/dist/hooks/codex-stop-task-finalizer.js +22 -1
  38. package/dist/hooks/commit-complete.js +22 -1
  39. package/dist/hooks/error-recall.js +22 -1
  40. package/dist/hooks/ingest-worker.js +22 -1
  41. package/dist/hooks/ingest.js +3345 -232
  42. package/dist/hooks/instructions-loaded.js +22 -1
  43. package/dist/hooks/notification.js +22 -1
  44. package/dist/hooks/post-compact.js +22 -1
  45. package/dist/hooks/pre-compact.js +22 -1
  46. package/dist/hooks/pre-tool-use.js +22 -1
  47. package/dist/hooks/prompt-ingest-worker.js +22 -1
  48. package/dist/hooks/prompt-submit.js +1700 -1396
  49. package/dist/hooks/response-ingest-worker.js +22 -1
  50. package/dist/hooks/session-end.js +345 -187
  51. package/dist/hooks/session-start.js +304 -15
  52. package/dist/hooks/stop.js +22 -1
  53. package/dist/hooks/subagent-stop.js +22 -1
  54. package/dist/hooks/summary-worker.js +22 -1
  55. package/dist/index.js +22 -1
  56. package/dist/lib/cloud-sync.js +22 -1
  57. package/dist/lib/database.js +22 -1
  58. package/dist/lib/db.js +22 -1
  59. package/dist/lib/device-registry.js +22 -1
  60. package/dist/lib/exe-daemon.js +39 -1
  61. package/dist/lib/hybrid-search.js +22 -1
  62. package/dist/lib/schedules.js +22 -1
  63. package/dist/lib/store.js +22 -1
  64. package/dist/mcp/server.js +126 -1
  65. package/dist/runtime/index.js +22 -1
  66. package/dist/tui/App.js +22 -1
  67. package/package.json +1 -1
@@ -1321,450 +1321,1126 @@ var init_database_adapter = __esm({
1321
1321
  }
1322
1322
  });
1323
1323
 
1324
- // src/lib/database.ts
1325
- import { createClient } from "@libsql/client";
1326
- async function initDatabase(config) {
1327
- if (_walCheckpointTimer) {
1328
- clearInterval(_walCheckpointTimer);
1329
- _walCheckpointTimer = null;
1324
+ // src/lib/daemon-auth.ts
1325
+ import crypto from "crypto";
1326
+ import path5 from "path";
1327
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1328
+ function normalizeToken(token) {
1329
+ if (!token) return null;
1330
+ const trimmed = token.trim();
1331
+ return trimmed.length > 0 ? trimmed : null;
1332
+ }
1333
+ function readDaemonToken() {
1334
+ try {
1335
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
1336
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
1337
+ } catch {
1338
+ return null;
1330
1339
  }
1331
- if (_daemonClient) {
1332
- _daemonClient.close();
1333
- _daemonClient = null;
1340
+ }
1341
+ function ensureDaemonToken(seed) {
1342
+ const existing = readDaemonToken();
1343
+ if (existing) return existing;
1344
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1345
+ ensurePrivateDirSync(EXE_AI_DIR);
1346
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
1347
+ `, "utf8");
1348
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1349
+ return token;
1350
+ }
1351
+ var DAEMON_TOKEN_PATH;
1352
+ var init_daemon_auth = __esm({
1353
+ "src/lib/daemon-auth.ts"() {
1354
+ "use strict";
1355
+ init_config();
1356
+ init_secure_files();
1357
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
1334
1358
  }
1335
- if (_adapterClient && _adapterClient !== _resilientClient) {
1336
- _adapterClient.close();
1359
+ });
1360
+
1361
+ // src/lib/exe-daemon-client.ts
1362
+ import net from "net";
1363
+ import os4 from "os";
1364
+ import { spawn } from "child_process";
1365
+ import { randomUUID } from "crypto";
1366
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1367
+ import path6 from "path";
1368
+ import { fileURLToPath } from "url";
1369
+ function handleData(chunk) {
1370
+ _buffer += chunk.toString();
1371
+ if (_buffer.length > MAX_BUFFER) {
1372
+ _buffer = "";
1373
+ return;
1337
1374
  }
1338
- _adapterClient = null;
1339
- if (_client) {
1340
- _client.close();
1341
- _client = null;
1342
- _resilientClient = null;
1375
+ let newlineIdx;
1376
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1377
+ const line = _buffer.slice(0, newlineIdx).trim();
1378
+ _buffer = _buffer.slice(newlineIdx + 1);
1379
+ if (!line) continue;
1380
+ try {
1381
+ const response = JSON.parse(line);
1382
+ const id = response.id;
1383
+ if (!id) continue;
1384
+ const entry = _pending.get(id);
1385
+ if (entry) {
1386
+ clearTimeout(entry.timer);
1387
+ _pending.delete(id);
1388
+ entry.resolve(response);
1389
+ }
1390
+ } catch {
1391
+ }
1343
1392
  }
1344
- const opts = {
1345
- url: `file:${config.dbPath}`
1346
- };
1347
- if (config.encryptionKey) {
1348
- opts.encryptionKey = config.encryptionKey;
1393
+ }
1394
+ function cleanupStaleFiles() {
1395
+ if (existsSync6(PID_PATH)) {
1396
+ try {
1397
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1398
+ if (pid > 0) {
1399
+ try {
1400
+ process.kill(pid, 0);
1401
+ return;
1402
+ } catch {
1403
+ }
1404
+ }
1405
+ } catch {
1406
+ }
1407
+ try {
1408
+ unlinkSync2(PID_PATH);
1409
+ } catch {
1410
+ }
1411
+ try {
1412
+ unlinkSync2(SOCKET_PATH);
1413
+ } catch {
1414
+ }
1349
1415
  }
1350
- _client = createClient(opts);
1351
- _resilientClient = wrapWithRetry(_client);
1352
- _adapterClient = _resilientClient;
1353
- _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1354
- });
1355
- _client.execute("PRAGMA journal_mode = WAL").catch(() => {
1356
- });
1357
- if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
1358
- _walCheckpointTimer = setInterval(() => {
1359
- _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
1360
- });
1361
- }, 3e4);
1362
- _walCheckpointTimer.unref();
1363
- if (process.env.DATABASE_URL) {
1364
- _adapterClient = await createPrismaDbAdapter(_resilientClient);
1416
+ }
1417
+ function findPackageRoot() {
1418
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1419
+ const { root } = path6.parse(dir);
1420
+ while (dir !== root) {
1421
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1422
+ dir = path6.dirname(dir);
1365
1423
  }
1424
+ return null;
1366
1425
  }
1367
- function getClient() {
1368
- if (!_adapterClient) {
1369
- throw new Error("Database client not initialized. Call initDatabase() first.");
1426
+ function spawnDaemon() {
1427
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
1428
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1429
+ if (totalGB <= 8) {
1430
+ process.stderr.write(
1431
+ `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1432
+ `
1433
+ );
1434
+ return;
1370
1435
  }
1371
- if (process.env.DATABASE_URL) {
1372
- return _adapterClient;
1436
+ if (totalGB <= 16 && freeGB < 4) {
1437
+ process.stderr.write(
1438
+ `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB free / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1439
+ `
1440
+ );
1441
+ return;
1373
1442
  }
1374
- if (process.env.EXE_IS_DAEMON === "1") {
1375
- return _resilientClient;
1443
+ const pkgRoot = findPackageRoot();
1444
+ if (!pkgRoot) {
1445
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
1446
+ return;
1376
1447
  }
1377
- if (_daemonClient && _daemonClient._isDaemonActive()) {
1378
- return _daemonClient;
1448
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1449
+ if (!existsSync6(daemonPath)) {
1450
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1451
+ `);
1452
+ return;
1453
+ }
1454
+ const resolvedPath = daemonPath;
1455
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1456
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1457
+ `);
1458
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1459
+ let stderrFd = "ignore";
1460
+ try {
1461
+ stderrFd = openSync(logPath, "a");
1462
+ } catch {
1463
+ }
1464
+ const heapCapMB = totalGB <= 8 ? 256 : 512;
1465
+ const nodeArgs = [`--max-old-space-size=${heapCapMB}`, resolvedPath];
1466
+ const child = spawn(process.execPath, nodeArgs, {
1467
+ detached: true,
1468
+ stdio: ["ignore", "ignore", stderrFd],
1469
+ env: {
1470
+ ...process.env,
1471
+ TMUX: void 0,
1472
+ // Daemon is global — must not inherit session scope
1473
+ TMUX_PANE: void 0,
1474
+ // Prevents resolveExeSession() from scoping to one session
1475
+ EXE_DAEMON_SOCK: SOCKET_PATH,
1476
+ EXE_DAEMON_PID: PID_PATH,
1477
+ [DAEMON_TOKEN_ENV]: daemonToken
1478
+ }
1479
+ });
1480
+ child.unref();
1481
+ if (typeof stderrFd === "number") {
1482
+ try {
1483
+ closeSync(stderrFd);
1484
+ } catch {
1485
+ }
1379
1486
  }
1380
- return _resilientClient;
1381
1487
  }
1382
- function getRawClient() {
1383
- if (!_client) {
1384
- throw new Error("Database client not initialized. Call initDatabase() first.");
1488
+ function acquireSpawnLock() {
1489
+ try {
1490
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1491
+ closeSync(fd);
1492
+ return true;
1493
+ } catch {
1494
+ try {
1495
+ const stat = statSync(SPAWN_LOCK_PATH);
1496
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1497
+ try {
1498
+ unlinkSync2(SPAWN_LOCK_PATH);
1499
+ } catch {
1500
+ }
1501
+ try {
1502
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1503
+ closeSync(fd);
1504
+ return true;
1505
+ } catch {
1506
+ }
1507
+ }
1508
+ } catch {
1509
+ }
1510
+ return false;
1385
1511
  }
1386
- return _client;
1387
1512
  }
1388
- async function ensureSchema() {
1389
- const client = getRawClient();
1390
- await client.execute("PRAGMA journal_mode = WAL");
1391
- await client.execute("PRAGMA busy_timeout = 30000");
1392
- await client.execute("PRAGMA wal_autocheckpoint = 1000");
1513
+ function releaseSpawnLock() {
1393
1514
  try {
1394
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
1515
+ unlinkSync2(SPAWN_LOCK_PATH);
1395
1516
  } catch {
1396
1517
  }
1397
- await client.executeMultiple(`
1398
- CREATE TABLE IF NOT EXISTS memories (
1399
- id TEXT PRIMARY KEY,
1400
- agent_id TEXT NOT NULL,
1401
- agent_role TEXT NOT NULL,
1402
- session_id TEXT NOT NULL,
1403
- timestamp TEXT NOT NULL,
1404
- tool_name TEXT NOT NULL,
1405
- project_name TEXT NOT NULL,
1406
- has_error INTEGER NOT NULL DEFAULT 0,
1407
- raw_text TEXT NOT NULL,
1408
- vector F32_BLOB(1024),
1409
- version INTEGER NOT NULL DEFAULT 0
1410
- );
1411
-
1412
- CREATE INDEX IF NOT EXISTS idx_memories_agent
1413
- ON memories(agent_id);
1414
-
1415
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp
1416
- ON memories(timestamp);
1417
-
1418
- CREATE INDEX IF NOT EXISTS idx_memories_session
1419
- ON memories(session_id);
1420
-
1421
- CREATE INDEX IF NOT EXISTS idx_memories_project
1422
- ON memories(project_name);
1423
-
1424
- CREATE INDEX IF NOT EXISTS idx_memories_tool
1425
- ON memories(tool_name);
1426
-
1427
- CREATE INDEX IF NOT EXISTS idx_memories_version
1428
- ON memories(version);
1429
-
1430
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project
1431
- ON memories(agent_id, project_name);
1432
- `);
1433
- await client.executeMultiple(`
1434
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1435
- raw_text,
1436
- content='memories',
1437
- content_rowid='rowid'
1438
- );
1439
-
1440
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1441
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1442
- END;
1443
-
1444
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
1445
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1446
- END;
1447
-
1448
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1449
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1450
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1451
- END;
1452
- `);
1453
- await client.executeMultiple(`
1454
- CREATE TABLE IF NOT EXISTS sync_meta (
1455
- key TEXT PRIMARY KEY,
1456
- value TEXT NOT NULL
1457
- );
1458
- `);
1459
- await client.executeMultiple(`
1460
- CREATE TABLE IF NOT EXISTS tasks (
1461
- id TEXT PRIMARY KEY,
1462
- title TEXT NOT NULL,
1463
- assigned_to TEXT NOT NULL,
1464
- assigned_by TEXT NOT NULL,
1465
- project_name TEXT NOT NULL,
1466
- priority TEXT NOT NULL DEFAULT 'p1',
1467
- status TEXT NOT NULL DEFAULT 'open',
1468
- task_file TEXT,
1469
- created_at TEXT NOT NULL,
1470
- updated_at TEXT NOT NULL
1471
- );
1472
-
1473
- CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
1474
- ON tasks(assigned_to, status);
1475
- `);
1476
- await client.executeMultiple(`
1477
- CREATE TABLE IF NOT EXISTS behaviors (
1478
- id TEXT PRIMARY KEY,
1479
- agent_id TEXT NOT NULL,
1480
- project_name TEXT,
1481
- domain TEXT,
1482
- content TEXT NOT NULL,
1483
- active INTEGER NOT NULL DEFAULT 1,
1484
- created_at TEXT NOT NULL,
1485
- updated_at TEXT NOT NULL
1486
- );
1487
-
1488
- CREATE INDEX IF NOT EXISTS idx_behaviors_agent
1489
- ON behaviors(agent_id, active);
1490
- `);
1491
- try {
1492
- const coordinatorName = getCoordinatorName();
1493
- const existing = await client.execute({
1494
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1495
- args: [coordinatorName]
1496
- });
1497
- if (Number(existing.rows[0]?.cnt) === 0) {
1498
- const seededAt = "2026-03-25T00:00:00Z";
1499
- for (const [domain, content] of [
1500
- ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1501
- ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1502
- ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1503
- ]) {
1504
- await client.execute({
1505
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1506
- VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1507
- args: [coordinatorName, domain, content, seededAt, seededAt]
1508
- });
1509
- }
1518
+ }
1519
+ function connectToSocket() {
1520
+ return new Promise((resolve) => {
1521
+ if (_socket && _connected) {
1522
+ resolve(true);
1523
+ return;
1510
1524
  }
1511
- } catch {
1512
- }
1513
- try {
1514
- await client.execute({
1515
- sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1516
- args: []
1517
- });
1518
- } catch {
1519
- }
1520
- try {
1521
- await client.execute({
1522
- sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1523
- args: []
1525
+ const socket = net.createConnection({ path: SOCKET_PATH });
1526
+ const connectTimeout = setTimeout(() => {
1527
+ socket.destroy();
1528
+ resolve(false);
1529
+ }, 2e3);
1530
+ socket.on("connect", () => {
1531
+ clearTimeout(connectTimeout);
1532
+ _socket = socket;
1533
+ _connected = true;
1534
+ _buffer = "";
1535
+ socket.on("data", handleData);
1536
+ socket.on("close", () => {
1537
+ _connected = false;
1538
+ _socket = null;
1539
+ for (const [id, entry] of _pending) {
1540
+ clearTimeout(entry.timer);
1541
+ _pending.delete(id);
1542
+ entry.resolve({ error: "Connection closed" });
1543
+ }
1544
+ });
1545
+ socket.on("error", () => {
1546
+ _connected = false;
1547
+ _socket = null;
1548
+ });
1549
+ resolve(true);
1524
1550
  });
1525
- } catch {
1526
- }
1527
- try {
1528
- await client.execute({
1529
- sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1530
- args: []
1551
+ socket.on("error", () => {
1552
+ clearTimeout(connectTimeout);
1553
+ resolve(false);
1531
1554
  });
1532
- } catch {
1555
+ });
1556
+ }
1557
+ async function connectEmbedDaemon() {
1558
+ if (_socket && _connected) return true;
1559
+ if (await connectToSocket()) return true;
1560
+ if (acquireSpawnLock()) {
1561
+ try {
1562
+ cleanupStaleFiles();
1563
+ spawnDaemon();
1564
+ } finally {
1565
+ releaseSpawnLock();
1566
+ }
1533
1567
  }
1534
- try {
1535
- await client.execute({
1536
- sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1537
- ON tasks(parent_task_id)
1538
- WHERE parent_task_id IS NOT NULL`,
1539
- args: []
1540
- });
1541
- } catch {
1568
+ const start = Date.now();
1569
+ let delay2 = 100;
1570
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1571
+ await new Promise((r) => setTimeout(r, delay2));
1572
+ if (await connectToSocket()) return true;
1573
+ delay2 = Math.min(delay2 * 2, 3e3);
1542
1574
  }
1543
- try {
1544
- await client.execute({
1545
- sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1546
- args: []
1547
- });
1548
- } catch {
1575
+ return false;
1576
+ }
1577
+ function sendRequest(texts, priority) {
1578
+ return sendDaemonRequest({ texts, priority });
1579
+ }
1580
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1581
+ return new Promise((resolve) => {
1582
+ if (!_socket || !_connected) {
1583
+ resolve({ error: "Not connected" });
1584
+ return;
1585
+ }
1586
+ const id = randomUUID();
1587
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1588
+ const timer = setTimeout(() => {
1589
+ _pending.delete(id);
1590
+ resolve({ error: "Request timeout" });
1591
+ }, timeoutMs);
1592
+ _pending.set(id, { resolve, timer });
1593
+ try {
1594
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1595
+ } catch {
1596
+ clearTimeout(timer);
1597
+ _pending.delete(id);
1598
+ resolve({ error: "Write failed" });
1599
+ }
1600
+ });
1601
+ }
1602
+ async function pingDaemon() {
1603
+ if (!_socket || !_connected) return null;
1604
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
1605
+ if (response.health) {
1606
+ return response.health;
1549
1607
  }
1550
- try {
1551
- await client.execute({
1552
- sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1553
- args: []
1554
- });
1555
- } catch {
1608
+ return null;
1609
+ }
1610
+ function killAndRespawnDaemon() {
1611
+ if (!acquireSpawnLock()) {
1612
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
1613
+ if (_socket) {
1614
+ _socket.destroy();
1615
+ _socket = null;
1616
+ }
1617
+ _connected = false;
1618
+ _buffer = "";
1619
+ return;
1556
1620
  }
1557
1621
  try {
1558
- await client.execute({
1559
- sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1560
- args: []
1561
- });
1562
- } catch {
1622
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
1623
+ if (existsSync6(PID_PATH)) {
1624
+ try {
1625
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1626
+ if (pid > 0) {
1627
+ try {
1628
+ process.kill(pid, "SIGKILL");
1629
+ } catch {
1630
+ }
1631
+ }
1632
+ } catch {
1633
+ }
1634
+ }
1635
+ if (_socket) {
1636
+ _socket.destroy();
1637
+ _socket = null;
1638
+ }
1639
+ _connected = false;
1640
+ _buffer = "";
1641
+ try {
1642
+ unlinkSync2(PID_PATH);
1643
+ } catch {
1644
+ }
1645
+ try {
1646
+ unlinkSync2(SOCKET_PATH);
1647
+ } catch {
1648
+ }
1649
+ spawnDaemon();
1650
+ } finally {
1651
+ releaseSpawnLock();
1563
1652
  }
1653
+ }
1654
+ function isDaemonTooYoung() {
1564
1655
  try {
1565
- await client.execute({
1566
- sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1567
- args: []
1568
- });
1656
+ const stat = statSync(PID_PATH);
1657
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
1569
1658
  } catch {
1659
+ return false;
1570
1660
  }
1571
- try {
1572
- await client.execute({
1573
- sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1574
- args: []
1575
- });
1576
- } catch {
1577
- }
1578
- try {
1579
- await client.execute({
1580
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1581
- args: []
1582
- });
1583
- } catch {
1661
+ }
1662
+ async function retryThenRestart(doRequest, label) {
1663
+ const result = await doRequest();
1664
+ if (!result.error) {
1665
+ _consecutiveFailures = 0;
1666
+ return result;
1584
1667
  }
1585
- try {
1586
- await client.execute({
1587
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1588
- args: []
1589
- });
1590
- } catch {
1668
+ _consecutiveFailures++;
1669
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
1670
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
1671
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
1672
+ `);
1673
+ await new Promise((r) => setTimeout(r, delayMs));
1674
+ if (!_connected) {
1675
+ if (!await connectToSocket()) continue;
1676
+ }
1677
+ const retry = await doRequest();
1678
+ if (!retry.error) {
1679
+ _consecutiveFailures = 0;
1680
+ return retry;
1681
+ }
1682
+ _consecutiveFailures++;
1591
1683
  }
1592
- try {
1593
- await client.execute({
1594
- sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1595
- args: []
1596
- });
1597
- } catch {
1684
+ if (isDaemonTooYoung()) {
1685
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
1686
+ `);
1687
+ return { error: result.error };
1598
1688
  }
1599
- try {
1600
- await client.execute({
1601
- sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1602
- args: []
1603
- });
1604
- } catch {
1689
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
1690
+ `);
1691
+ killAndRespawnDaemon();
1692
+ const start = Date.now();
1693
+ let delay2 = 200;
1694
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1695
+ await new Promise((r) => setTimeout(r, delay2));
1696
+ if (await connectToSocket()) break;
1697
+ delay2 = Math.min(delay2 * 2, 3e3);
1605
1698
  }
1606
- try {
1607
- await client.execute({
1608
- sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1609
- args: []
1610
- });
1611
- } catch {
1699
+ if (!_connected) return { error: "Daemon restart failed" };
1700
+ const final = await doRequest();
1701
+ if (!final.error) _consecutiveFailures = 0;
1702
+ return final;
1703
+ }
1704
+ async function embedViaClient(text, priority = "high") {
1705
+ if (!_connected && !await connectEmbedDaemon()) return null;
1706
+ _requestCount++;
1707
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1708
+ const health = await pingDaemon();
1709
+ if (!health && !isDaemonTooYoung()) {
1710
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1711
+ `);
1712
+ killAndRespawnDaemon();
1713
+ const start = Date.now();
1714
+ let d = 200;
1715
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1716
+ await new Promise((r) => setTimeout(r, d));
1717
+ if (await connectToSocket()) break;
1718
+ d = Math.min(d * 2, 3e3);
1719
+ }
1720
+ if (!_connected) return null;
1721
+ }
1612
1722
  }
1613
- try {
1614
- await client.execute({
1615
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1616
- args: []
1617
- });
1618
- } catch {
1723
+ const result = await retryThenRestart(
1724
+ () => sendRequest([text], priority),
1725
+ "Embed"
1726
+ );
1727
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
1728
+ }
1729
+ function disconnectClient() {
1730
+ if (_socket) {
1731
+ _socket.destroy();
1732
+ _socket = null;
1619
1733
  }
1620
- try {
1621
- await client.execute({
1622
- sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1623
- args: []
1624
- });
1625
- } catch {
1734
+ _connected = false;
1735
+ _buffer = "";
1736
+ for (const [id, entry] of _pending) {
1737
+ clearTimeout(entry.timer);
1738
+ _pending.delete(id);
1739
+ entry.resolve({ error: "Client disconnected" });
1626
1740
  }
1627
- try {
1628
- await client.execute({
1629
- sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
1630
- args: []
1631
- });
1632
- } catch {
1741
+ }
1742
+ function isClientConnected() {
1743
+ return _connected;
1744
+ }
1745
+ 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;
1746
+ var init_exe_daemon_client = __esm({
1747
+ "src/lib/exe-daemon-client.ts"() {
1748
+ "use strict";
1749
+ init_config();
1750
+ init_daemon_auth();
1751
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1752
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1753
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1754
+ SPAWN_LOCK_STALE_MS = 3e4;
1755
+ CONNECT_TIMEOUT_MS = 15e3;
1756
+ REQUEST_TIMEOUT_MS = 3e4;
1757
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1758
+ _socket = null;
1759
+ _connected = false;
1760
+ _buffer = "";
1761
+ _requestCount = 0;
1762
+ _consecutiveFailures = 0;
1763
+ HEALTH_CHECK_INTERVAL = 100;
1764
+ MAX_RETRIES_BEFORE_RESTART = 3;
1765
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
1766
+ MIN_DAEMON_AGE_MS = 3e4;
1767
+ _pending = /* @__PURE__ */ new Map();
1768
+ MAX_BUFFER = 1e7;
1633
1769
  }
1634
- await client.executeMultiple(`
1635
- CREATE TABLE IF NOT EXISTS consolidations (
1636
- id TEXT PRIMARY KEY,
1637
- consolidated_memory_id TEXT NOT NULL,
1638
- source_memory_id TEXT NOT NULL,
1639
- created_at TEXT NOT NULL
1640
- );
1641
-
1642
- CREATE INDEX IF NOT EXISTS idx_consolidations_source
1643
- ON consolidations(source_memory_id);
1770
+ });
1644
1771
 
1645
- CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
1646
- ON consolidations(consolidated_memory_id);
1647
- `);
1648
- await client.executeMultiple(`
1649
- CREATE TABLE IF NOT EXISTS reminders (
1650
- id TEXT PRIMARY KEY,
1651
- text TEXT NOT NULL,
1652
- created_at TEXT NOT NULL,
1653
- due_date TEXT,
1654
- completed_at TEXT
1655
- );
1656
- `);
1657
- await client.executeMultiple(`
1658
- CREATE TABLE IF NOT EXISTS notifications (
1659
- id TEXT PRIMARY KEY,
1660
- agent_id TEXT NOT NULL,
1661
- agent_role TEXT NOT NULL,
1662
- event TEXT NOT NULL,
1663
- project TEXT NOT NULL,
1664
- summary TEXT NOT NULL,
1665
- task_file TEXT,
1666
- session_scope TEXT,
1667
- read INTEGER NOT NULL DEFAULT 0,
1668
- created_at TEXT NOT NULL
1772
+ // src/lib/daemon-protocol.ts
1773
+ function serializeValue(v) {
1774
+ if (v === null || v === void 0) return null;
1775
+ if (typeof v === "bigint") return Number(v);
1776
+ if (typeof v === "boolean") return v ? 1 : 0;
1777
+ if (v instanceof Uint8Array) {
1778
+ return { __blob: Buffer.from(v).toString("base64") };
1779
+ }
1780
+ if (ArrayBuffer.isView(v)) {
1781
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
1782
+ }
1783
+ if (v instanceof ArrayBuffer) {
1784
+ return { __blob: Buffer.from(v).toString("base64") };
1785
+ }
1786
+ if (typeof v === "string" || typeof v === "number") return v;
1787
+ return String(v);
1788
+ }
1789
+ function deserializeValue(v) {
1790
+ if (v === null) return null;
1791
+ if (typeof v === "object" && v !== null && "__blob" in v) {
1792
+ const buf = Buffer.from(v.__blob, "base64");
1793
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1794
+ }
1795
+ return v;
1796
+ }
1797
+ function deserializeResultSet(srs) {
1798
+ const rows = srs.rows.map((obj) => {
1799
+ const values = srs.columns.map(
1800
+ (col) => deserializeValue(obj[col] ?? null)
1669
1801
  );
1802
+ const row = values;
1803
+ for (let i = 0; i < srs.columns.length; i++) {
1804
+ const col = srs.columns[i];
1805
+ if (col !== void 0) {
1806
+ row[col] = values[i] ?? null;
1807
+ }
1808
+ }
1809
+ Object.defineProperty(row, "length", {
1810
+ value: values.length,
1811
+ enumerable: false
1812
+ });
1813
+ return row;
1814
+ });
1815
+ return {
1816
+ columns: srs.columns,
1817
+ columnTypes: srs.columnTypes ?? [],
1818
+ rows,
1819
+ rowsAffected: srs.rowsAffected,
1820
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
1821
+ toJSON: () => ({
1822
+ columns: srs.columns,
1823
+ columnTypes: srs.columnTypes ?? [],
1824
+ rows: srs.rows,
1825
+ rowsAffected: srs.rowsAffected,
1826
+ lastInsertRowid: srs.lastInsertRowid
1827
+ })
1828
+ };
1829
+ }
1830
+ var init_daemon_protocol = __esm({
1831
+ "src/lib/daemon-protocol.ts"() {
1832
+ "use strict";
1833
+ }
1834
+ });
1670
1835
 
1671
- CREATE INDEX IF NOT EXISTS idx_notifications_read
1672
- ON notifications(read);
1836
+ // src/lib/db-daemon-client.ts
1837
+ var db_daemon_client_exports = {};
1838
+ __export(db_daemon_client_exports, {
1839
+ createDaemonDbClient: () => createDaemonDbClient,
1840
+ initDaemonDbClient: () => initDaemonDbClient
1841
+ });
1842
+ function normalizeStatement2(stmt) {
1843
+ if (typeof stmt === "string") {
1844
+ return { sql: stmt, args: [] };
1845
+ }
1846
+ const sql = stmt.sql;
1847
+ let args = [];
1848
+ if (Array.isArray(stmt.args)) {
1849
+ args = stmt.args.map((v) => serializeValue(v));
1850
+ } else if (stmt.args && typeof stmt.args === "object") {
1851
+ const named = {};
1852
+ for (const [key, val] of Object.entries(stmt.args)) {
1853
+ named[key] = serializeValue(val);
1854
+ }
1855
+ return { sql, args: named };
1856
+ }
1857
+ return { sql, args };
1858
+ }
1859
+ function createDaemonDbClient(fallbackClient) {
1860
+ let _useDaemon = false;
1861
+ const client = {
1862
+ async execute(stmt) {
1863
+ if (!_useDaemon || !isClientConnected()) {
1864
+ return fallbackClient.execute(stmt);
1865
+ }
1866
+ const { sql, args } = normalizeStatement2(stmt);
1867
+ const response = await sendDaemonRequest({
1868
+ type: "db-execute",
1869
+ sql,
1870
+ args
1871
+ });
1872
+ if (response.error) {
1873
+ const errMsg = String(response.error);
1874
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1875
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1876
+ `);
1877
+ return fallbackClient.execute(stmt);
1878
+ }
1879
+ throw new Error(errMsg);
1880
+ }
1881
+ if (response.db) {
1882
+ return deserializeResultSet(response.db);
1883
+ }
1884
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1885
+ return fallbackClient.execute(stmt);
1886
+ },
1887
+ async batch(stmts, mode) {
1888
+ if (!_useDaemon || !isClientConnected()) {
1889
+ return fallbackClient.batch(stmts, mode);
1890
+ }
1891
+ const statements = stmts.map(normalizeStatement2);
1892
+ const response = await sendDaemonRequest({
1893
+ type: "db-batch",
1894
+ statements,
1895
+ mode: mode ?? "deferred"
1896
+ });
1897
+ if (response.error) {
1898
+ const errMsg = String(response.error);
1899
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1900
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1901
+ `);
1902
+ return fallbackClient.batch(stmts, mode);
1903
+ }
1904
+ throw new Error(errMsg);
1905
+ }
1906
+ const batchResults = response["db-batch"];
1907
+ if (batchResults) {
1908
+ return batchResults.map(deserializeResultSet);
1909
+ }
1910
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1911
+ return fallbackClient.batch(stmts, mode);
1912
+ },
1913
+ // Transaction support — delegate to fallback (transactions need direct connection)
1914
+ async transaction(mode) {
1915
+ return fallbackClient.transaction(mode);
1916
+ },
1917
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1918
+ async executeMultiple(sql) {
1919
+ return fallbackClient.executeMultiple(sql);
1920
+ },
1921
+ // migrate — delegate to fallback
1922
+ async migrate(stmts) {
1923
+ return fallbackClient.migrate(stmts);
1924
+ },
1925
+ // Sync mode — delegate to fallback
1926
+ sync() {
1927
+ return fallbackClient.sync();
1928
+ },
1929
+ close() {
1930
+ _useDaemon = false;
1931
+ },
1932
+ get closed() {
1933
+ return fallbackClient.closed;
1934
+ },
1935
+ get protocol() {
1936
+ return fallbackClient.protocol;
1937
+ }
1938
+ };
1939
+ return {
1940
+ ...client,
1941
+ /** Enable daemon routing (call after confirming daemon is connected) */
1942
+ _enableDaemon() {
1943
+ _useDaemon = true;
1944
+ },
1945
+ /** Check if daemon routing is active */
1946
+ _isDaemonActive() {
1947
+ return _useDaemon && isClientConnected();
1948
+ }
1949
+ };
1950
+ }
1951
+ async function initDaemonDbClient(fallbackClient) {
1952
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1953
+ const connected = await connectEmbedDaemon();
1954
+ if (!connected) {
1955
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1956
+ return null;
1957
+ }
1958
+ const client = createDaemonDbClient(fallbackClient);
1959
+ client._enableDaemon();
1960
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1961
+ return client;
1962
+ }
1963
+ var init_db_daemon_client = __esm({
1964
+ "src/lib/db-daemon-client.ts"() {
1965
+ "use strict";
1966
+ init_exe_daemon_client();
1967
+ init_daemon_protocol();
1968
+ }
1969
+ });
1673
1970
 
1674
- CREATE INDEX IF NOT EXISTS idx_notifications_agent
1675
- ON notifications(agent_id, session_scope);
1971
+ // src/lib/database.ts
1972
+ var database_exports = {};
1973
+ __export(database_exports, {
1974
+ disposeDatabase: () => disposeDatabase,
1975
+ disposeTurso: () => disposeTurso,
1976
+ ensureSchema: () => ensureSchema,
1977
+ getClient: () => getClient,
1978
+ getRawClient: () => getRawClient,
1979
+ initDaemonClient: () => initDaemonClient,
1980
+ initDatabase: () => initDatabase,
1981
+ initTurso: () => initTurso,
1982
+ isInitialized: () => isInitialized
1983
+ });
1984
+ import { createClient } from "@libsql/client";
1985
+ async function initDatabase(config) {
1986
+ if (_walCheckpointTimer) {
1987
+ clearInterval(_walCheckpointTimer);
1988
+ _walCheckpointTimer = null;
1989
+ }
1990
+ if (_daemonClient) {
1991
+ _daemonClient.close();
1992
+ _daemonClient = null;
1993
+ }
1994
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1995
+ _adapterClient.close();
1996
+ }
1997
+ _adapterClient = null;
1998
+ if (_client) {
1999
+ _client.close();
2000
+ _client = null;
2001
+ _resilientClient = null;
2002
+ }
2003
+ const opts = {
2004
+ url: `file:${config.dbPath}`
2005
+ };
2006
+ if (config.encryptionKey) {
2007
+ opts.encryptionKey = config.encryptionKey;
2008
+ }
2009
+ _client = createClient(opts);
2010
+ _resilientClient = wrapWithRetry(_client);
2011
+ _adapterClient = _resilientClient;
2012
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
2013
+ });
2014
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
2015
+ });
2016
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
2017
+ _walCheckpointTimer = setInterval(() => {
2018
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
2019
+ });
2020
+ }, 3e4);
2021
+ _walCheckpointTimer.unref();
2022
+ if (process.env.DATABASE_URL) {
2023
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
2024
+ }
2025
+ }
2026
+ function isInitialized() {
2027
+ return _adapterClient !== null || _client !== null;
2028
+ }
2029
+ function getClient() {
2030
+ if (!_adapterClient) {
2031
+ throw new Error("Database client not initialized. Call initDatabase() first.");
2032
+ }
2033
+ if (process.env.DATABASE_URL) {
2034
+ return _adapterClient;
2035
+ }
2036
+ if (process.env.EXE_IS_DAEMON === "1") {
2037
+ return _resilientClient;
2038
+ }
2039
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
2040
+ return _daemonClient;
2041
+ }
2042
+ return _resilientClient;
2043
+ }
2044
+ async function initDaemonClient() {
2045
+ if (process.env.DATABASE_URL) return;
2046
+ if (process.env.EXE_IS_DAEMON === "1") return;
2047
+ if (!_resilientClient) return;
2048
+ try {
2049
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
2050
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
2051
+ } catch (err) {
2052
+ process.stderr.write(
2053
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
2054
+ `
2055
+ );
2056
+ }
2057
+ }
2058
+ function getRawClient() {
2059
+ if (!_client) {
2060
+ throw new Error("Database client not initialized. Call initDatabase() first.");
2061
+ }
2062
+ return _client;
2063
+ }
2064
+ async function ensureSchema() {
2065
+ const client = getRawClient();
2066
+ await client.execute("PRAGMA journal_mode = WAL");
2067
+ await client.execute("PRAGMA busy_timeout = 30000");
2068
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
2069
+ try {
2070
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
2071
+ } catch {
2072
+ }
2073
+ await client.executeMultiple(`
2074
+ CREATE TABLE IF NOT EXISTS memories (
2075
+ id TEXT PRIMARY KEY,
2076
+ agent_id TEXT NOT NULL,
2077
+ agent_role TEXT NOT NULL,
2078
+ session_id TEXT NOT NULL,
2079
+ timestamp TEXT NOT NULL,
2080
+ tool_name TEXT NOT NULL,
2081
+ project_name TEXT NOT NULL,
2082
+ has_error INTEGER NOT NULL DEFAULT 0,
2083
+ raw_text TEXT NOT NULL,
2084
+ vector F32_BLOB(1024),
2085
+ version INTEGER NOT NULL DEFAULT 0
2086
+ );
1676
2087
 
1677
- CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1678
- ON notifications(task_file);
2088
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
2089
+ ON memories(agent_id);
2090
+
2091
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
2092
+ ON memories(timestamp);
2093
+
2094
+ CREATE INDEX IF NOT EXISTS idx_memories_session
2095
+ ON memories(session_id);
2096
+
2097
+ CREATE INDEX IF NOT EXISTS idx_memories_project
2098
+ ON memories(project_name);
2099
+
2100
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
2101
+ ON memories(tool_name);
2102
+
2103
+ CREATE INDEX IF NOT EXISTS idx_memories_version
2104
+ ON memories(version);
2105
+
2106
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
2107
+ ON memories(agent_id, project_name);
1679
2108
  `);
1680
2109
  await client.executeMultiple(`
1681
- CREATE TABLE IF NOT EXISTS schedules (
1682
- id TEXT PRIMARY KEY,
1683
- cron TEXT NOT NULL,
1684
- description TEXT NOT NULL,
1685
- job_type TEXT NOT NULL DEFAULT 'report',
1686
- prompt TEXT,
1687
- assigned_to TEXT,
1688
- project_name TEXT,
1689
- active INTEGER NOT NULL DEFAULT 1,
1690
- use_crontab INTEGER NOT NULL DEFAULT 0,
1691
- created_at TEXT NOT NULL
2110
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
2111
+ raw_text,
2112
+ content='memories',
2113
+ content_rowid='rowid'
1692
2114
  );
2115
+
2116
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
2117
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2118
+ END;
2119
+
2120
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
2121
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2122
+ END;
2123
+
2124
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
2125
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
2126
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
2127
+ END;
1693
2128
  `);
1694
2129
  await client.executeMultiple(`
1695
- CREATE TABLE IF NOT EXISTS device_registry (
1696
- device_id TEXT PRIMARY KEY,
1697
- friendly_name TEXT NOT NULL,
1698
- hostname TEXT NOT NULL,
1699
- projects TEXT NOT NULL DEFAULT '[]',
1700
- agents TEXT NOT NULL DEFAULT '[]',
1701
- connected INTEGER DEFAULT 0,
1702
- last_seen TEXT NOT NULL
2130
+ CREATE TABLE IF NOT EXISTS sync_meta (
2131
+ key TEXT PRIMARY KEY,
2132
+ value TEXT NOT NULL
1703
2133
  );
1704
2134
  `);
1705
2135
  await client.executeMultiple(`
1706
- CREATE TABLE IF NOT EXISTS messages (
1707
- id TEXT PRIMARY KEY,
1708
- from_agent TEXT NOT NULL,
1709
- from_device TEXT NOT NULL DEFAULT 'local',
1710
- target_agent TEXT NOT NULL,
1711
- target_project TEXT,
1712
- target_device TEXT NOT NULL DEFAULT 'local',
1713
- session_scope TEXT,
1714
- content TEXT NOT NULL,
1715
- priority TEXT DEFAULT 'normal',
1716
- status TEXT DEFAULT 'pending',
1717
- server_seq INTEGER,
1718
- retry_count INTEGER DEFAULT 0,
1719
- created_at TEXT NOT NULL,
1720
- delivered_at TEXT,
1721
- processed_at TEXT,
1722
- failed_at TEXT,
1723
- failure_reason TEXT
2136
+ CREATE TABLE IF NOT EXISTS tasks (
2137
+ id TEXT PRIMARY KEY,
2138
+ title TEXT NOT NULL,
2139
+ assigned_to TEXT NOT NULL,
2140
+ assigned_by TEXT NOT NULL,
2141
+ project_name TEXT NOT NULL,
2142
+ priority TEXT NOT NULL DEFAULT 'p1',
2143
+ status TEXT NOT NULL DEFAULT 'open',
2144
+ task_file TEXT,
2145
+ created_at TEXT NOT NULL,
2146
+ updated_at TEXT NOT NULL
1724
2147
  );
1725
2148
 
1726
- CREATE INDEX IF NOT EXISTS idx_messages_target
1727
- ON messages(target_agent, session_scope, status);
2149
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
2150
+ ON tasks(assigned_to, status);
2151
+ `);
2152
+ await client.executeMultiple(`
2153
+ CREATE TABLE IF NOT EXISTS behaviors (
2154
+ id TEXT PRIMARY KEY,
2155
+ agent_id TEXT NOT NULL,
2156
+ project_name TEXT,
2157
+ domain TEXT,
2158
+ content TEXT NOT NULL,
2159
+ active INTEGER NOT NULL DEFAULT 1,
2160
+ created_at TEXT NOT NULL,
2161
+ updated_at TEXT NOT NULL
2162
+ );
1728
2163
 
1729
- CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1730
- ON messages(target_agent, session_scope, from_agent, server_seq);
2164
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
2165
+ ON behaviors(agent_id, active);
1731
2166
  `);
1732
2167
  try {
1733
- await client.execute({
1734
- sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1735
- args: []
2168
+ const coordinatorName = getCoordinatorName();
2169
+ const existing = await client.execute({
2170
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
2171
+ args: [coordinatorName]
1736
2172
  });
2173
+ if (Number(existing.rows[0]?.cnt) === 0) {
2174
+ const seededAt = "2026-03-25T00:00:00Z";
2175
+ for (const [domain, content] of [
2176
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
2177
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
2178
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
2179
+ ]) {
2180
+ await client.execute({
2181
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
2182
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
2183
+ args: [coordinatorName, domain, content, seededAt, seededAt]
2184
+ });
2185
+ }
2186
+ }
1737
2187
  } catch {
1738
2188
  }
1739
2189
  try {
1740
2190
  await client.execute({
1741
- sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2191
+ sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1742
2192
  args: []
1743
2193
  });
1744
2194
  } catch {
1745
2195
  }
1746
- await client.executeMultiple(`
1747
- CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1748
- ON notifications(agent_id, session_scope, read, created_at);
1749
-
1750
- CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1751
- ON messages(target_agent, session_scope, status, created_at);
1752
- `);
1753
2196
  try {
1754
2197
  await client.execute({
1755
- sql: `UPDATE memories SET project_name = 'exe-create' WHERE project_name = 'web'`,
2198
+ sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1756
2199
  args: []
1757
2200
  });
2201
+ } catch {
2202
+ }
2203
+ try {
1758
2204
  await client.execute({
1759
- sql: `UPDATE memories SET project_name = 'exe-os' WHERE project_name = 'worker'`,
2205
+ sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1760
2206
  args: []
1761
2207
  });
2208
+ } catch {
2209
+ }
2210
+ try {
1762
2211
  await client.execute({
1763
- sql: `UPDATE tasks SET project_name = 'exe-create' WHERE project_name = 'web'`,
2212
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
2213
+ ON tasks(parent_task_id)
2214
+ WHERE parent_task_id IS NOT NULL`,
1764
2215
  args: []
1765
2216
  });
2217
+ } catch {
2218
+ }
2219
+ try {
1766
2220
  await client.execute({
1767
- sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
2221
+ sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
2222
+ args: []
2223
+ });
2224
+ } catch {
2225
+ }
2226
+ try {
2227
+ await client.execute({
2228
+ sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
2229
+ args: []
2230
+ });
2231
+ } catch {
2232
+ }
2233
+ try {
2234
+ await client.execute({
2235
+ sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
2236
+ args: []
2237
+ });
2238
+ } catch {
2239
+ }
2240
+ try {
2241
+ await client.execute({
2242
+ sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
2243
+ args: []
2244
+ });
2245
+ } catch {
2246
+ }
2247
+ try {
2248
+ await client.execute({
2249
+ sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
2250
+ args: []
2251
+ });
2252
+ } catch {
2253
+ }
2254
+ try {
2255
+ await client.execute({
2256
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
2257
+ args: []
2258
+ });
2259
+ } catch {
2260
+ }
2261
+ try {
2262
+ await client.execute({
2263
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
2264
+ args: []
2265
+ });
2266
+ } catch {
2267
+ }
2268
+ try {
2269
+ await client.execute({
2270
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
2271
+ args: []
2272
+ });
2273
+ } catch {
2274
+ }
2275
+ try {
2276
+ await client.execute({
2277
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
2278
+ args: []
2279
+ });
2280
+ } catch {
2281
+ }
2282
+ try {
2283
+ await client.execute({
2284
+ sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
2285
+ args: []
2286
+ });
2287
+ } catch {
2288
+ }
2289
+ try {
2290
+ await client.execute({
2291
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
2292
+ args: []
2293
+ });
2294
+ } catch {
2295
+ }
2296
+ try {
2297
+ await client.execute({
2298
+ sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
2299
+ args: []
2300
+ });
2301
+ } catch {
2302
+ }
2303
+ try {
2304
+ await client.execute({
2305
+ sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
2306
+ args: []
2307
+ });
2308
+ } catch {
2309
+ }
2310
+ await client.executeMultiple(`
2311
+ CREATE TABLE IF NOT EXISTS consolidations (
2312
+ id TEXT PRIMARY KEY,
2313
+ consolidated_memory_id TEXT NOT NULL,
2314
+ source_memory_id TEXT NOT NULL,
2315
+ created_at TEXT NOT NULL
2316
+ );
2317
+
2318
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
2319
+ ON consolidations(source_memory_id);
2320
+
2321
+ CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
2322
+ ON consolidations(consolidated_memory_id);
2323
+ `);
2324
+ await client.executeMultiple(`
2325
+ CREATE TABLE IF NOT EXISTS reminders (
2326
+ id TEXT PRIMARY KEY,
2327
+ text TEXT NOT NULL,
2328
+ created_at TEXT NOT NULL,
2329
+ due_date TEXT,
2330
+ completed_at TEXT
2331
+ );
2332
+ `);
2333
+ await client.executeMultiple(`
2334
+ CREATE TABLE IF NOT EXISTS notifications (
2335
+ id TEXT PRIMARY KEY,
2336
+ agent_id TEXT NOT NULL,
2337
+ agent_role TEXT NOT NULL,
2338
+ event TEXT NOT NULL,
2339
+ project TEXT NOT NULL,
2340
+ summary TEXT NOT NULL,
2341
+ task_file TEXT,
2342
+ session_scope TEXT,
2343
+ read INTEGER NOT NULL DEFAULT 0,
2344
+ created_at TEXT NOT NULL
2345
+ );
2346
+
2347
+ CREATE INDEX IF NOT EXISTS idx_notifications_read
2348
+ ON notifications(read);
2349
+
2350
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent
2351
+ ON notifications(agent_id, session_scope);
2352
+
2353
+ CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2354
+ ON notifications(task_file);
2355
+ `);
2356
+ await client.executeMultiple(`
2357
+ CREATE TABLE IF NOT EXISTS schedules (
2358
+ id TEXT PRIMARY KEY,
2359
+ cron TEXT NOT NULL,
2360
+ description TEXT NOT NULL,
2361
+ job_type TEXT NOT NULL DEFAULT 'report',
2362
+ prompt TEXT,
2363
+ assigned_to TEXT,
2364
+ project_name TEXT,
2365
+ active INTEGER NOT NULL DEFAULT 1,
2366
+ use_crontab INTEGER NOT NULL DEFAULT 0,
2367
+ created_at TEXT NOT NULL
2368
+ );
2369
+ `);
2370
+ await client.executeMultiple(`
2371
+ CREATE TABLE IF NOT EXISTS device_registry (
2372
+ device_id TEXT PRIMARY KEY,
2373
+ friendly_name TEXT NOT NULL,
2374
+ hostname TEXT NOT NULL,
2375
+ projects TEXT NOT NULL DEFAULT '[]',
2376
+ agents TEXT NOT NULL DEFAULT '[]',
2377
+ connected INTEGER DEFAULT 0,
2378
+ last_seen TEXT NOT NULL
2379
+ );
2380
+ `);
2381
+ await client.executeMultiple(`
2382
+ CREATE TABLE IF NOT EXISTS messages (
2383
+ id TEXT PRIMARY KEY,
2384
+ from_agent TEXT NOT NULL,
2385
+ from_device TEXT NOT NULL DEFAULT 'local',
2386
+ target_agent TEXT NOT NULL,
2387
+ target_project TEXT,
2388
+ target_device TEXT NOT NULL DEFAULT 'local',
2389
+ session_scope TEXT,
2390
+ content TEXT NOT NULL,
2391
+ priority TEXT DEFAULT 'normal',
2392
+ status TEXT DEFAULT 'pending',
2393
+ server_seq INTEGER,
2394
+ retry_count INTEGER DEFAULT 0,
2395
+ created_at TEXT NOT NULL,
2396
+ delivered_at TEXT,
2397
+ processed_at TEXT,
2398
+ failed_at TEXT,
2399
+ failure_reason TEXT
2400
+ );
2401
+
2402
+ CREATE INDEX IF NOT EXISTS idx_messages_target
2403
+ ON messages(target_agent, session_scope, status);
2404
+
2405
+ CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2406
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2407
+ `);
2408
+ try {
2409
+ await client.execute({
2410
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2411
+ args: []
2412
+ });
2413
+ } catch {
2414
+ }
2415
+ try {
2416
+ await client.execute({
2417
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2418
+ args: []
2419
+ });
2420
+ } catch {
2421
+ }
2422
+ await client.executeMultiple(`
2423
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2424
+ ON notifications(agent_id, session_scope, read, created_at);
2425
+
2426
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2427
+ ON messages(target_agent, session_scope, status, created_at);
2428
+ `);
2429
+ try {
2430
+ await client.execute({
2431
+ sql: `UPDATE memories SET project_name = 'exe-create' WHERE project_name = 'web'`,
2432
+ args: []
2433
+ });
2434
+ await client.execute({
2435
+ sql: `UPDATE memories SET project_name = 'exe-os' WHERE project_name = 'worker'`,
2436
+ args: []
2437
+ });
2438
+ await client.execute({
2439
+ sql: `UPDATE tasks SET project_name = 'exe-create' WHERE project_name = 'web'`,
2440
+ args: []
2441
+ });
2442
+ await client.execute({
2443
+ sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
1768
2444
  args: []
1769
2445
  });
1770
2446
  } catch {
@@ -2157,12 +2833,26 @@ async function ensureSchema() {
2157
2833
  session_name TEXT,
2158
2834
  task_id TEXT,
2159
2835
  project_name TEXT,
2160
- started_at TEXT NOT NULL
2836
+ started_at TEXT NOT NULL,
2837
+ cache_cold_count INTEGER NOT NULL DEFAULT 0
2161
2838
  );
2162
2839
 
2163
2840
  CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2164
2841
  ON session_agent_map(agent_id);
2165
2842
  `);
2843
+ await client.executeMultiple(`
2844
+ CREATE TABLE IF NOT EXISTS agent_file_reads (
2845
+ session_uuid TEXT NOT NULL,
2846
+ agent_id TEXT NOT NULL,
2847
+ file_path TEXT NOT NULL,
2848
+ read_at TEXT NOT NULL,
2849
+ commit_hash TEXT,
2850
+ PRIMARY KEY (session_uuid, file_path)
2851
+ );
2852
+
2853
+ CREATE INDEX IF NOT EXISTS idx_agent_file_reads_agent_read_at
2854
+ ON agent_file_reads(agent_id, read_at);
2855
+ `);
2166
2856
  try {
2167
2857
  const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2168
2858
  if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
@@ -2177,6 +2867,13 @@ async function ensureSchema() {
2177
2867
  }
2178
2868
  } catch {
2179
2869
  }
2870
+ try {
2871
+ await client.execute({
2872
+ sql: `ALTER TABLE session_agent_map ADD COLUMN cache_cold_count INTEGER NOT NULL DEFAULT 0`,
2873
+ args: []
2874
+ });
2875
+ } catch {
2876
+ }
2180
2877
  try {
2181
2878
  await client.execute({
2182
2879
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -2378,14 +3075,14 @@ var init_database = __esm({
2378
3075
 
2379
3076
  // src/lib/keychain.ts
2380
3077
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2381
- import { existsSync as existsSync5 } from "fs";
2382
- import path5 from "path";
2383
- import os4 from "os";
3078
+ import { existsSync as existsSync7 } from "fs";
3079
+ import path7 from "path";
3080
+ import os5 from "os";
2384
3081
  function getKeyDir() {
2385
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
3082
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
2386
3083
  }
2387
3084
  function getKeyPath() {
2388
- return path5.join(getKeyDir(), "master.key");
3085
+ return path7.join(getKeyDir(), "master.key");
2389
3086
  }
2390
3087
  async function tryKeytar() {
2391
3088
  try {
@@ -2406,9 +3103,9 @@ async function getMasterKey() {
2406
3103
  }
2407
3104
  }
2408
3105
  const keyPath = getKeyPath();
2409
- if (!existsSync5(keyPath)) {
3106
+ if (!existsSync7(keyPath)) {
2410
3107
  process.stderr.write(
2411
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3108
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2412
3109
  `
2413
3110
  );
2414
3111
  return null;
@@ -2502,12 +3199,12 @@ __export(shard_manager_exports, {
2502
3199
  listShards: () => listShards,
2503
3200
  shardExists: () => shardExists
2504
3201
  });
2505
- import path6 from "path";
2506
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
3202
+ import path8 from "path";
3203
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
2507
3204
  import { createClient as createClient2 } from "@libsql/client";
2508
3205
  function initShardManager(encryptionKey) {
2509
3206
  _encryptionKey = encryptionKey;
2510
- if (!existsSync6(SHARDS_DIR)) {
3207
+ if (!existsSync8(SHARDS_DIR)) {
2511
3208
  mkdirSync2(SHARDS_DIR, { recursive: true });
2512
3209
  }
2513
3210
  _shardingEnabled = true;
@@ -2537,7 +3234,7 @@ function getShardClient(projectName) {
2537
3234
  while (_shards.size >= MAX_OPEN_SHARDS) {
2538
3235
  evictLRU();
2539
3236
  }
2540
- const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
3237
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
2541
3238
  const client = createClient2({
2542
3239
  url: `file:${dbPath}`,
2543
3240
  encryptionKey: _encryptionKey
@@ -2548,10 +3245,10 @@ function getShardClient(projectName) {
2548
3245
  }
2549
3246
  function shardExists(projectName) {
2550
3247
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2551
- return existsSync6(path6.join(SHARDS_DIR, `${safeName}.db`));
3248
+ return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
2552
3249
  }
2553
3250
  function listShards() {
2554
- if (!existsSync6(SHARDS_DIR)) return [];
3251
+ if (!existsSync8(SHARDS_DIR)) return [];
2555
3252
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2556
3253
  }
2557
3254
  async function ensureShardSchema(client) {
@@ -2798,7 +3495,7 @@ var init_shard_manager = __esm({
2798
3495
  "src/lib/shard-manager.ts"() {
2799
3496
  "use strict";
2800
3497
  init_config();
2801
- SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
3498
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
2802
3499
  SHARD_IDLE_MS = 5 * 60 * 1e3;
2803
3500
  MAX_OPEN_SHARDS = 10;
2804
3501
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -2934,7 +3631,7 @@ __export(global_procedures_exports, {
2934
3631
  loadGlobalProcedures: () => loadGlobalProcedures,
2935
3632
  storeGlobalProcedure: () => storeGlobalProcedure
2936
3633
  });
2937
- import { randomUUID } from "crypto";
3634
+ import { randomUUID as randomUUID2 } from "crypto";
2938
3635
  async function loadGlobalProcedures() {
2939
3636
  const client = getClient();
2940
3637
  const result = await client.execute({
@@ -2963,7 +3660,7 @@ ${sections.join("\n\n")}
2963
3660
  `;
2964
3661
  }
2965
3662
  async function storeGlobalProcedure(input2) {
2966
- const id = randomUUID();
3663
+ const id = randomUUID2();
2967
3664
  const now = (/* @__PURE__ */ new Date()).toISOString();
2968
3665
  const client = getClient();
2969
3666
  await client.execute({
@@ -3313,920 +4010,475 @@ async function flushBatch() {
3313
4010
  trajectory,
3314
4011
  contentHash
3315
4012
  ];
3316
- return {
3317
- sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
3318
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
3319
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3320
- args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
3321
- };
3322
- };
3323
- const globalClient = getClient();
3324
- const globalStmts = batch.map(buildStmt);
3325
- await globalClient.batch(globalStmts, "write");
3326
- _pendingRecords.splice(0, batch.length);
3327
- try {
3328
- const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3329
- if (isShardingEnabled2()) {
3330
- const byProject = /* @__PURE__ */ new Map();
3331
- for (const row of batch) {
3332
- const proj = row.project_name || "unknown";
3333
- if (!byProject.has(proj)) byProject.set(proj, []);
3334
- byProject.get(proj).push(row);
3335
- }
3336
- for (const [project, rows] of byProject) {
3337
- try {
3338
- const shardClient = await getReadyShardClient2(project);
3339
- const shardStmts = rows.map(buildStmt);
3340
- await shardClient.batch(shardStmts, "write");
3341
- } catch (err) {
3342
- process.stderr.write(
3343
- `[store] Shard write failed for ${project}: ${err instanceof Error ? err.message : String(err)}
3344
- `
3345
- );
3346
- }
3347
- }
3348
- }
3349
- } catch {
3350
- }
3351
- return batch.length;
3352
- } finally {
3353
- _flushing = false;
3354
- }
3355
- }
3356
- function buildWikiScopeFilter(options, columnPrefix) {
3357
- const args = [];
3358
- let clause = "";
3359
- if (options?.workspaceId !== void 0) {
3360
- clause += ` AND ${columnPrefix}workspace_id = ?`;
3361
- args.push(options.workspaceId);
3362
- }
3363
- if (options?.userId === void 0) {
3364
- clause += ` AND ${columnPrefix}user_id IS NULL`;
3365
- } else if (options.userId === null) {
3366
- clause += ` AND ${columnPrefix}user_id IS NULL`;
3367
- } else {
3368
- clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
3369
- args.push(options.userId);
3370
- }
3371
- return { clause, args };
3372
- }
3373
- async function searchMemories(queryVector, agentId, options) {
3374
- let client;
3375
- try {
3376
- const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3377
- if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
3378
- client = await getReadyShardClient2(options.projectName);
3379
- } else {
3380
- client = getClient();
3381
- }
3382
- } catch {
3383
- client = getClient();
3384
- }
3385
- const limit = options?.limit ?? 10;
3386
- const statusFilter = options?.includeArchived ? "" : `
3387
- AND COALESCE(status, 'active') = 'active'`;
3388
- const draftFilter = options?.includeDrafts ? "" : `
3389
- AND (draft = 0 OR draft IS NULL)`;
3390
- let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
3391
- tool_name, project_name,
3392
- has_error, raw_text, vector, importance, status,
3393
- confidence, last_accessed,
3394
- workspace_id, document_id, user_id,
3395
- char_offset, page_number,
3396
- source_path, source_type
3397
- FROM memories
3398
- WHERE agent_id = ?
3399
- AND vector IS NOT NULL${statusFilter}${draftFilter}
3400
- AND COALESCE(confidence, 0.7) >= 0.3`;
3401
- const args = [agentId];
3402
- const scope = buildWikiScopeFilter(options, "");
3403
- sql += scope.clause;
3404
- args.push(...scope.args);
3405
- if (options?.projectName) {
3406
- sql += ` AND project_name = ?`;
3407
- args.push(options.projectName);
3408
- }
3409
- if (options?.toolName) {
3410
- sql += ` AND tool_name = ?`;
3411
- args.push(options.toolName);
3412
- }
3413
- if (options?.hasError !== void 0) {
3414
- sql += ` AND has_error = ?`;
3415
- args.push(options.hasError ? 1 : 0);
3416
- }
3417
- if (options?.since) {
3418
- sql += ` AND timestamp >= ?`;
3419
- args.push(options.since);
3420
- }
3421
- if (options?.memoryType) {
3422
- sql += ` AND memory_type = ?`;
3423
- args.push(options.memoryType);
3424
- }
3425
- sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
3426
- args.push(vectorToBlob(queryVector));
3427
- sql += ` LIMIT ?`;
3428
- args.push(limit);
3429
- const result = await client.execute({ sql, args });
3430
- return result.rows.map((row) => ({
3431
- id: row.id,
3432
- agent_id: row.agent_id,
3433
- agent_role: row.agent_role,
3434
- session_id: row.session_id,
3435
- timestamp: row.timestamp,
3436
- tool_name: row.tool_name,
3437
- project_name: row.project_name,
3438
- has_error: row.has_error === 1,
3439
- raw_text: row.raw_text,
3440
- vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
3441
- importance: row.importance ?? 5,
3442
- status: row.status ?? "active",
3443
- confidence: row.confidence ?? 0.7,
3444
- last_accessed: row.last_accessed ?? row.timestamp,
3445
- workspace_id: row.workspace_id ?? null,
3446
- document_id: row.document_id ?? null,
3447
- user_id: row.user_id ?? null,
3448
- char_offset: row.char_offset ?? null,
3449
- page_number: row.page_number ?? null,
3450
- source_path: row.source_path ?? null,
3451
- source_type: row.source_type ?? null
3452
- }));
3453
- }
3454
- async function attachDocumentMetadata(records) {
3455
- const docIds = [
3456
- ...new Set(
3457
- records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
3458
- )
3459
- ];
3460
- if (docIds.length === 0) return records;
3461
- try {
3462
- const client = getClient();
3463
- const placeholders = docIds.map(() => "?").join(",");
3464
- const result = await client.execute({
3465
- sql: `SELECT id, filename, mime, source_type, uploaded_at
3466
- FROM documents
3467
- WHERE id IN (${placeholders})`,
3468
- args: docIds
3469
- });
3470
- const byId = /* @__PURE__ */ new Map();
3471
- for (const row of result.rows) {
3472
- const id = row.id;
3473
- byId.set(id, {
3474
- document_id: id,
3475
- filename: row.filename,
3476
- mime: row.mime ?? null,
3477
- source_type: row.source_type ?? null,
3478
- uploaded_at: row.uploaded_at
3479
- });
3480
- }
3481
- for (const record of records) {
3482
- if (!record.document_id) continue;
3483
- record.document_metadata = byId.get(record.document_id) ?? null;
3484
- }
3485
- } catch {
3486
- }
3487
- return records;
3488
- }
3489
- async function flushTier3(agentId, options) {
3490
- const client = getClient();
3491
- const maxAge = options?.maxAgeHours ?? 72;
3492
- const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
3493
- if (options?.dryRun) {
3494
- const result2 = await client.execute({
3495
- sql: `SELECT COUNT(*) as cnt FROM memories
3496
- WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
3497
- args: [agentId, cutoff]
3498
- });
3499
- return { archived: Number(result2.rows[0]?.cnt ?? 0) };
3500
- }
3501
- const result = await client.execute({
3502
- sql: `UPDATE memories SET status = 'archived'
3503
- WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
3504
- args: [agentId, cutoff]
3505
- });
3506
- return { archived: result.rowsAffected };
3507
- }
3508
- async function disposeStore() {
3509
- if (_flushTimer !== null) {
3510
- clearInterval(_flushTimer);
3511
- _flushTimer = null;
3512
- }
3513
- if (_pendingRecords.length > 0) {
3514
- await flushBatch();
3515
- }
3516
- await disposeTurso();
3517
- _pendingRecords = [];
3518
- _nextVersion = 1;
3519
- }
3520
- function vectorToBlob(vector) {
3521
- const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
3522
- return JSON.stringify(Array.from(f32));
3523
- }
3524
- async function updateMemoryStatus(id, status) {
3525
- const client = getClient();
3526
- await client.execute({
3527
- sql: `UPDATE memories SET status = ? WHERE id = ?`,
3528
- args: [status, id]
3529
- });
3530
- }
3531
- function reserveVersions(count) {
3532
- const reserved = [];
3533
- for (let i = 0; i < count; i++) {
3534
- reserved.push(_nextVersion++);
3535
- }
3536
- return reserved;
3537
- }
3538
- async function getMemoryCardinality(agentId) {
3539
- try {
3540
- const client = getClient();
3541
- const result = await client.execute({
3542
- sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
3543
- args: [agentId]
3544
- });
3545
- return Number(result.rows[0]?.cnt) || 0;
3546
- } catch {
3547
- return 0;
3548
- }
3549
- }
3550
- var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
3551
- var init_store = __esm({
3552
- "src/lib/store.ts"() {
3553
- "use strict";
3554
- init_memory();
3555
- init_database();
3556
- init_keychain();
3557
- init_config();
3558
- init_state_bus();
3559
- INIT_MAX_RETRIES = 3;
3560
- INIT_RETRY_DELAY_MS = 1e3;
3561
- _pendingRecords = [];
3562
- _batchSize = 20;
3563
- _flushIntervalMs = 1e4;
3564
- _flushTimer = null;
3565
- _flushing = false;
3566
- _nextVersion = 1;
3567
- }
3568
- });
3569
-
3570
- // src/lib/self-query-router.ts
3571
- var self_query_router_exports = {};
3572
- __export(self_query_router_exports, {
3573
- routeQuery: () => routeQuery
3574
- });
3575
- async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
3576
- if (query.length < 10) {
3577
- return {
3578
- semanticQuery: query,
3579
- projectFilter: null,
3580
- roleFilter: null,
3581
- timeFilter: null,
3582
- isBroadQuery: false
3583
- };
3584
- }
3585
- try {
3586
- const Anthropic = (await import("@anthropic-ai/sdk")).default;
3587
- const client = new Anthropic();
3588
- const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3589
- const response = await client.messages.create({
3590
- model,
3591
- max_tokens: 256,
3592
- system: `You are a search query router. Extract metadata filters from the user's memory search query. Today is ${now}. Convert relative time references (yesterday, last week) to ISO timestamps.`,
3593
- messages: [{ role: "user", content: query }],
3594
- tools: [EXTRACT_TOOL],
3595
- tool_choice: { type: "tool", name: "extract_search_filters" }
3596
- });
3597
- const toolBlock = response.content.find((b) => b.type === "tool_use");
3598
- if (toolBlock && toolBlock.type === "tool_use") {
3599
- const input2 = toolBlock.input;
3600
- return {
3601
- semanticQuery: input2.semantic_query || query,
3602
- projectFilter: input2.project_filter || null,
3603
- roleFilter: input2.role_filter || null,
3604
- timeFilter: input2.time_filter || null,
3605
- isBroadQuery: Boolean(input2.is_broad_query)
3606
- };
3607
- }
3608
- } catch (err) {
3609
- process.stderr.write(
3610
- `[self-query-router] LLM extraction failed, using passthrough: ${err instanceof Error ? err.message : String(err)}
3611
- `
3612
- );
3613
- }
3614
- return {
3615
- semanticQuery: query,
3616
- projectFilter: null,
3617
- roleFilter: null,
3618
- timeFilter: null,
3619
- isBroadQuery: false
3620
- };
3621
- }
3622
- var EXTRACT_TOOL;
3623
- var init_self_query_router = __esm({
3624
- "src/lib/self-query-router.ts"() {
3625
- "use strict";
3626
- EXTRACT_TOOL = {
3627
- name: "extract_search_filters",
3628
- description: "Extract metadata filters from a memory search query to improve retrieval precision.",
3629
- input_schema: {
3630
- type: "object",
3631
- properties: {
3632
- semantic_query: {
3633
- type: "string",
3634
- description: "The core semantic meaning of the query, stripped of metadata references. This is used for embedding search."
3635
- },
3636
- project_filter: {
3637
- type: ["string", "null"],
3638
- description: "Project name if the query references a specific project (e.g., 'exe-os', 'exe-create'). Null if no project specified."
3639
- },
3640
- role_filter: {
3641
- type: ["string", "null"],
3642
- description: "Agent role if the query targets a specific role (e.g., 'CTO', 'CMO'). Null if no role specified."
3643
- },
3644
- time_filter: {
3645
- type: ["string", "null"],
3646
- description: "ISO 8601 timestamp lower bound if the query references a time period (e.g., 'last week', 'yesterday'). Null if no time reference."
3647
- },
3648
- is_broad_query: {
3649
- type: "boolean",
3650
- description: "True if the query is exploratory/broad (e.g., 'what has the CTO been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
3651
- }
3652
- },
3653
- required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
3654
- }
3655
- };
3656
- }
3657
- });
3658
-
3659
- // src/lib/reranker.ts
3660
- var reranker_exports = {};
3661
- __export(reranker_exports, {
3662
- disposeReranker: () => disposeReranker,
3663
- getRerankerModelPath: () => getRerankerModelPath,
3664
- isRerankerAvailable: () => isRerankerAvailable,
3665
- rerank: () => rerank,
3666
- rerankWithContext: () => rerankWithContext,
3667
- rerankWithScores: () => rerankWithScores
3668
- });
3669
- import path7 from "path";
3670
- import { existsSync as existsSync7 } from "fs";
3671
- function resetIdleTimer() {
3672
- if (_idleTimer) clearTimeout(_idleTimer);
3673
- _idleTimer = setTimeout(() => {
3674
- void disposeReranker();
3675
- }, IDLE_TIMEOUT_MS);
3676
- if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
3677
- _idleTimer.unref();
3678
- }
3679
- }
3680
- function isRerankerAvailable() {
3681
- return existsSync7(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
3682
- }
3683
- function getRerankerModelPath() {
3684
- return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3685
- }
3686
- async function ensureLoaded() {
3687
- if (_rerankerContext) {
3688
- resetIdleTimer();
3689
- return;
3690
- }
3691
- const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3692
- if (!existsSync7(modelPath)) {
3693
- throw new Error(
3694
- `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3695
- );
3696
- }
3697
- process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
3698
- const { getLlama } = await import("node-llama-cpp");
3699
- const llama = await getLlama();
3700
- _rerankerModel = await llama.loadModel({ modelPath });
3701
- _rerankerContext = await _rerankerModel.createEmbeddingContext();
3702
- process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
3703
- resetIdleTimer();
3704
- }
3705
- async function disposeReranker() {
3706
- if (_idleTimer) {
3707
- clearTimeout(_idleTimer);
3708
- _idleTimer = null;
3709
- }
3710
- if (_rerankerContext) {
3711
- try {
3712
- await _rerankerContext.dispose();
3713
- } catch {
3714
- }
3715
- _rerankerContext = null;
3716
- }
3717
- if (_rerankerModel) {
3718
- try {
3719
- await _rerankerModel.dispose();
3720
- } catch {
3721
- }
3722
- _rerankerModel = null;
3723
- }
3724
- process.stderr.write("[reranker] Unloaded (idle timeout).\n");
3725
- }
3726
- async function rerankWithScores(query, texts, topK) {
3727
- if (texts.length === 0) return [];
3728
- await ensureLoaded();
3729
- const ctx = _rerankerContext;
3730
- const scored = [];
3731
- for (let i = 0; i < texts.length; i++) {
3732
- const text = texts[i] ?? "";
3733
- try {
3734
- const input2 = `query: ${query} document: ${text.slice(0, 512)}`;
3735
- const embedding = await ctx.getEmbeddingFor(input2);
3736
- const score = embedding.vector[0] ?? 0;
3737
- scored.push({ text, score, index: i });
3738
- } catch {
3739
- scored.push({ text, score: -1, index: i });
3740
- }
3741
- }
3742
- scored.sort((a, b) => b.score - a.score);
3743
- return typeof topK === "number" ? scored.slice(0, topK) : scored;
3744
- }
3745
- async function rerank(query, candidates, topK = 5) {
3746
- if (candidates.length === 0) return [];
3747
- if (candidates.length <= topK) return candidates;
3748
- const scored = await rerankWithScores(
3749
- query,
3750
- candidates.map((c) => c.raw_text),
3751
- topK
3752
- );
3753
- return scored.map((s) => candidates[s.index]);
3754
- }
3755
- async function rerankWithContext(query, candidates, topK) {
3756
- if (candidates.length === 0) return [];
3757
- await ensureLoaded();
3758
- const ctx = _rerankerContext;
3759
- const scored = [];
3760
- for (let i = 0; i < candidates.length; i++) {
3761
- const candidate = candidates[i];
3762
- try {
3763
- const docText = candidate.context ? `[${candidate.context}] ${candidate.text.slice(0, 460)}` : candidate.text.slice(0, 512);
3764
- const input2 = `query: ${query} document: ${docText}`;
3765
- const embedding = await ctx.getEmbeddingFor(input2);
3766
- const score = embedding.vector[0] ?? 0;
3767
- scored.push({ text: candidate.text, score, index: i });
3768
- } catch {
3769
- scored.push({ text: candidate.text, score: -1, index: i });
3770
- }
3771
- }
3772
- scored.sort((a, b) => b.score - a.score);
3773
- return typeof topK === "number" ? scored.slice(0, topK) : scored;
3774
- }
3775
- var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
3776
- var init_reranker = __esm({
3777
- "src/lib/reranker.ts"() {
3778
- "use strict";
3779
- init_config();
3780
- RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
3781
- IDLE_TIMEOUT_MS = 6e4;
3782
- _rerankerContext = null;
3783
- _rerankerModel = null;
3784
- _idleTimer = null;
3785
- }
3786
- });
3787
-
3788
- // src/lib/daemon-auth.ts
3789
- import crypto from "crypto";
3790
- import path8 from "path";
3791
- import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
3792
- function normalizeToken(token) {
3793
- if (!token) return null;
3794
- const trimmed = token.trim();
3795
- return trimmed.length > 0 ? trimmed : null;
3796
- }
3797
- function readDaemonToken() {
3798
- try {
3799
- if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
3800
- return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
3801
- } catch {
3802
- return null;
3803
- }
3804
- }
3805
- function ensureDaemonToken(seed) {
3806
- const existing = readDaemonToken();
3807
- if (existing) return existing;
3808
- const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
3809
- ensurePrivateDirSync(EXE_AI_DIR);
3810
- writeFileSync3(DAEMON_TOKEN_PATH, `${token}
3811
- `, "utf8");
3812
- enforcePrivateFileSync(DAEMON_TOKEN_PATH);
3813
- return token;
3814
- }
3815
- var DAEMON_TOKEN_PATH;
3816
- var init_daemon_auth = __esm({
3817
- "src/lib/daemon-auth.ts"() {
3818
- "use strict";
3819
- init_config();
3820
- init_secure_files();
3821
- DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
3822
- }
3823
- });
3824
-
3825
- // src/lib/exe-daemon-client.ts
3826
- import net from "net";
3827
- import os5 from "os";
3828
- import { spawn } from "child_process";
3829
- import { randomUUID as randomUUID2 } from "crypto";
3830
- import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
3831
- import path9 from "path";
3832
- import { fileURLToPath } from "url";
3833
- function handleData(chunk) {
3834
- _buffer += chunk.toString();
3835
- if (_buffer.length > MAX_BUFFER) {
3836
- _buffer = "";
3837
- return;
3838
- }
3839
- let newlineIdx;
3840
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
3841
- const line = _buffer.slice(0, newlineIdx).trim();
3842
- _buffer = _buffer.slice(newlineIdx + 1);
3843
- if (!line) continue;
3844
- try {
3845
- const response = JSON.parse(line);
3846
- const id = response.id;
3847
- if (!id) continue;
3848
- const entry = _pending.get(id);
3849
- if (entry) {
3850
- clearTimeout(entry.timer);
3851
- _pending.delete(id);
3852
- entry.resolve(response);
3853
- }
3854
- } catch {
3855
- }
3856
- }
3857
- }
3858
- function cleanupStaleFiles() {
3859
- if (existsSync9(PID_PATH)) {
4013
+ return {
4014
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
4015
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
4016
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4017
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
4018
+ };
4019
+ };
4020
+ const globalClient = getClient();
4021
+ const globalStmts = batch.map(buildStmt);
4022
+ await globalClient.batch(globalStmts, "write");
4023
+ _pendingRecords.splice(0, batch.length);
3860
4024
  try {
3861
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
3862
- if (pid > 0) {
3863
- try {
3864
- process.kill(pid, 0);
3865
- return;
3866
- } catch {
4025
+ const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4026
+ if (isShardingEnabled2()) {
4027
+ const byProject = /* @__PURE__ */ new Map();
4028
+ for (const row of batch) {
4029
+ const proj = row.project_name || "unknown";
4030
+ if (!byProject.has(proj)) byProject.set(proj, []);
4031
+ byProject.get(proj).push(row);
4032
+ }
4033
+ for (const [project, rows] of byProject) {
4034
+ try {
4035
+ const shardClient = await getReadyShardClient2(project);
4036
+ const shardStmts = rows.map(buildStmt);
4037
+ await shardClient.batch(shardStmts, "write");
4038
+ } catch (err) {
4039
+ process.stderr.write(
4040
+ `[store] Shard write failed for ${project}: ${err instanceof Error ? err.message : String(err)}
4041
+ `
4042
+ );
4043
+ }
3867
4044
  }
3868
4045
  }
3869
4046
  } catch {
3870
4047
  }
3871
- try {
3872
- unlinkSync2(PID_PATH);
3873
- } catch {
3874
- }
3875
- try {
3876
- unlinkSync2(SOCKET_PATH);
3877
- } catch {
3878
- }
4048
+ return batch.length;
4049
+ } finally {
4050
+ _flushing = false;
3879
4051
  }
3880
4052
  }
3881
- function findPackageRoot() {
3882
- let dir = path9.dirname(fileURLToPath(import.meta.url));
3883
- const { root } = path9.parse(dir);
3884
- while (dir !== root) {
3885
- if (existsSync9(path9.join(dir, "package.json"))) return dir;
3886
- dir = path9.dirname(dir);
4053
+ function buildWikiScopeFilter(options, columnPrefix) {
4054
+ const args = [];
4055
+ let clause = "";
4056
+ if (options?.workspaceId !== void 0) {
4057
+ clause += ` AND ${columnPrefix}workspace_id = ?`;
4058
+ args.push(options.workspaceId);
3887
4059
  }
3888
- return null;
4060
+ if (options?.userId === void 0) {
4061
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
4062
+ } else if (options.userId === null) {
4063
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
4064
+ } else {
4065
+ clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
4066
+ args.push(options.userId);
4067
+ }
4068
+ return { clause, args };
3889
4069
  }
3890
- function spawnDaemon() {
3891
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
3892
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
3893
- if (totalGB <= 8) {
3894
- process.stderr.write(
3895
- `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
3896
- `
3897
- );
3898
- return;
4070
+ async function searchMemories(queryVector, agentId, options) {
4071
+ let client;
4072
+ try {
4073
+ const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4074
+ if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
4075
+ client = await getReadyShardClient2(options.projectName);
4076
+ } else {
4077
+ client = getClient();
4078
+ }
4079
+ } catch {
4080
+ client = getClient();
3899
4081
  }
3900
- if (totalGB <= 16 && freeGB < 4) {
3901
- process.stderr.write(
3902
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB free / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
3903
- `
3904
- );
3905
- return;
4082
+ const limit = options?.limit ?? 10;
4083
+ const statusFilter = options?.includeArchived ? "" : `
4084
+ AND COALESCE(status, 'active') = 'active'`;
4085
+ const draftFilter = options?.includeDrafts ? "" : `
4086
+ AND (draft = 0 OR draft IS NULL)`;
4087
+ let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4088
+ tool_name, project_name,
4089
+ has_error, raw_text, vector, importance, status,
4090
+ confidence, last_accessed,
4091
+ workspace_id, document_id, user_id,
4092
+ char_offset, page_number,
4093
+ source_path, source_type
4094
+ FROM memories
4095
+ WHERE agent_id = ?
4096
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
4097
+ AND COALESCE(confidence, 0.7) >= 0.3`;
4098
+ const args = [agentId];
4099
+ const scope = buildWikiScopeFilter(options, "");
4100
+ sql += scope.clause;
4101
+ args.push(...scope.args);
4102
+ if (options?.projectName) {
4103
+ sql += ` AND project_name = ?`;
4104
+ args.push(options.projectName);
3906
4105
  }
3907
- const pkgRoot = findPackageRoot();
3908
- if (!pkgRoot) {
3909
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
3910
- return;
4106
+ if (options?.toolName) {
4107
+ sql += ` AND tool_name = ?`;
4108
+ args.push(options.toolName);
3911
4109
  }
3912
- const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3913
- if (!existsSync9(daemonPath)) {
3914
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
3915
- `);
3916
- return;
4110
+ if (options?.hasError !== void 0) {
4111
+ sql += ` AND has_error = ?`;
4112
+ args.push(options.hasError ? 1 : 0);
3917
4113
  }
3918
- const resolvedPath = daemonPath;
3919
- const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
3920
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
3921
- `);
3922
- const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
3923
- let stderrFd = "ignore";
4114
+ if (options?.since) {
4115
+ sql += ` AND timestamp >= ?`;
4116
+ args.push(options.since);
4117
+ }
4118
+ if (options?.memoryType) {
4119
+ sql += ` AND memory_type = ?`;
4120
+ args.push(options.memoryType);
4121
+ }
4122
+ sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
4123
+ args.push(vectorToBlob(queryVector));
4124
+ sql += ` LIMIT ?`;
4125
+ args.push(limit);
4126
+ const result = await client.execute({ sql, args });
4127
+ return result.rows.map((row) => ({
4128
+ id: row.id,
4129
+ agent_id: row.agent_id,
4130
+ agent_role: row.agent_role,
4131
+ session_id: row.session_id,
4132
+ timestamp: row.timestamp,
4133
+ tool_name: row.tool_name,
4134
+ project_name: row.project_name,
4135
+ has_error: row.has_error === 1,
4136
+ raw_text: row.raw_text,
4137
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
4138
+ importance: row.importance ?? 5,
4139
+ status: row.status ?? "active",
4140
+ confidence: row.confidence ?? 0.7,
4141
+ last_accessed: row.last_accessed ?? row.timestamp,
4142
+ workspace_id: row.workspace_id ?? null,
4143
+ document_id: row.document_id ?? null,
4144
+ user_id: row.user_id ?? null,
4145
+ char_offset: row.char_offset ?? null,
4146
+ page_number: row.page_number ?? null,
4147
+ source_path: row.source_path ?? null,
4148
+ source_type: row.source_type ?? null
4149
+ }));
4150
+ }
4151
+ async function attachDocumentMetadata(records) {
4152
+ const docIds = [
4153
+ ...new Set(
4154
+ records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
4155
+ )
4156
+ ];
4157
+ if (docIds.length === 0) return records;
3924
4158
  try {
3925
- stderrFd = openSync(logPath, "a");
4159
+ const client = getClient();
4160
+ const placeholders = docIds.map(() => "?").join(",");
4161
+ const result = await client.execute({
4162
+ sql: `SELECT id, filename, mime, source_type, uploaded_at
4163
+ FROM documents
4164
+ WHERE id IN (${placeholders})`,
4165
+ args: docIds
4166
+ });
4167
+ const byId = /* @__PURE__ */ new Map();
4168
+ for (const row of result.rows) {
4169
+ const id = row.id;
4170
+ byId.set(id, {
4171
+ document_id: id,
4172
+ filename: row.filename,
4173
+ mime: row.mime ?? null,
4174
+ source_type: row.source_type ?? null,
4175
+ uploaded_at: row.uploaded_at
4176
+ });
4177
+ }
4178
+ for (const record of records) {
4179
+ if (!record.document_id) continue;
4180
+ record.document_metadata = byId.get(record.document_id) ?? null;
4181
+ }
3926
4182
  } catch {
3927
4183
  }
3928
- const heapCapMB = totalGB <= 8 ? 256 : 512;
3929
- const nodeArgs = [`--max-old-space-size=${heapCapMB}`, resolvedPath];
3930
- const child = spawn(process.execPath, nodeArgs, {
3931
- detached: true,
3932
- stdio: ["ignore", "ignore", stderrFd],
3933
- env: {
3934
- ...process.env,
3935
- TMUX: void 0,
3936
- // Daemon is global must not inherit session scope
3937
- TMUX_PANE: void 0,
3938
- // Prevents resolveExeSession() from scoping to one session
3939
- EXE_DAEMON_SOCK: SOCKET_PATH,
3940
- EXE_DAEMON_PID: PID_PATH,
3941
- [DAEMON_TOKEN_ENV]: daemonToken
3942
- }
4184
+ return records;
4185
+ }
4186
+ async function flushTier3(agentId, options) {
4187
+ const client = getClient();
4188
+ const maxAge = options?.maxAgeHours ?? 72;
4189
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
4190
+ if (options?.dryRun) {
4191
+ const result2 = await client.execute({
4192
+ sql: `SELECT COUNT(*) as cnt FROM memories
4193
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
4194
+ args: [agentId, cutoff]
4195
+ });
4196
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
4197
+ }
4198
+ const result = await client.execute({
4199
+ sql: `UPDATE memories SET status = 'archived'
4200
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
4201
+ args: [agentId, cutoff]
3943
4202
  });
3944
- child.unref();
3945
- if (typeof stderrFd === "number") {
3946
- try {
3947
- closeSync(stderrFd);
3948
- } catch {
3949
- }
4203
+ return { archived: result.rowsAffected };
4204
+ }
4205
+ async function disposeStore() {
4206
+ if (_flushTimer !== null) {
4207
+ clearInterval(_flushTimer);
4208
+ _flushTimer = null;
4209
+ }
4210
+ if (_pendingRecords.length > 0) {
4211
+ await flushBatch();
3950
4212
  }
4213
+ await disposeTurso();
4214
+ _pendingRecords = [];
4215
+ _nextVersion = 1;
4216
+ }
4217
+ function vectorToBlob(vector) {
4218
+ const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
4219
+ return JSON.stringify(Array.from(f32));
4220
+ }
4221
+ async function updateMemoryStatus(id, status) {
4222
+ const client = getClient();
4223
+ await client.execute({
4224
+ sql: `UPDATE memories SET status = ? WHERE id = ?`,
4225
+ args: [status, id]
4226
+ });
3951
4227
  }
3952
- function acquireSpawnLock() {
3953
- try {
3954
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
3955
- closeSync(fd);
3956
- return true;
3957
- } catch {
3958
- try {
3959
- const stat = statSync(SPAWN_LOCK_PATH);
3960
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
3961
- try {
3962
- unlinkSync2(SPAWN_LOCK_PATH);
3963
- } catch {
3964
- }
3965
- try {
3966
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
3967
- closeSync(fd);
3968
- return true;
3969
- } catch {
3970
- }
3971
- }
3972
- } catch {
3973
- }
3974
- return false;
4228
+ function reserveVersions(count) {
4229
+ const reserved = [];
4230
+ for (let i = 0; i < count; i++) {
4231
+ reserved.push(_nextVersion++);
3975
4232
  }
4233
+ return reserved;
3976
4234
  }
3977
- function releaseSpawnLock() {
4235
+ async function getMemoryCardinality(agentId) {
3978
4236
  try {
3979
- unlinkSync2(SPAWN_LOCK_PATH);
4237
+ const client = getClient();
4238
+ const result = await client.execute({
4239
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
4240
+ args: [agentId]
4241
+ });
4242
+ return Number(result.rows[0]?.cnt) || 0;
3980
4243
  } catch {
4244
+ return 0;
3981
4245
  }
3982
4246
  }
3983
- function connectToSocket() {
3984
- return new Promise((resolve) => {
3985
- if (_socket && _connected) {
3986
- resolve(true);
3987
- return;
3988
- }
3989
- const socket = net.createConnection({ path: SOCKET_PATH });
3990
- const connectTimeout = setTimeout(() => {
3991
- socket.destroy();
3992
- resolve(false);
3993
- }, 2e3);
3994
- socket.on("connect", () => {
3995
- clearTimeout(connectTimeout);
3996
- _socket = socket;
3997
- _connected = true;
3998
- _buffer = "";
3999
- socket.on("data", handleData);
4000
- socket.on("close", () => {
4001
- _connected = false;
4002
- _socket = null;
4003
- for (const [id, entry] of _pending) {
4004
- clearTimeout(entry.timer);
4005
- _pending.delete(id);
4006
- entry.resolve({ error: "Connection closed" });
4007
- }
4008
- });
4009
- socket.on("error", () => {
4010
- _connected = false;
4011
- _socket = null;
4012
- });
4013
- resolve(true);
4014
- });
4015
- socket.on("error", () => {
4016
- clearTimeout(connectTimeout);
4017
- resolve(false);
4247
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
4248
+ var init_store = __esm({
4249
+ "src/lib/store.ts"() {
4250
+ "use strict";
4251
+ init_memory();
4252
+ init_database();
4253
+ init_keychain();
4254
+ init_config();
4255
+ init_state_bus();
4256
+ INIT_MAX_RETRIES = 3;
4257
+ INIT_RETRY_DELAY_MS = 1e3;
4258
+ _pendingRecords = [];
4259
+ _batchSize = 20;
4260
+ _flushIntervalMs = 1e4;
4261
+ _flushTimer = null;
4262
+ _flushing = false;
4263
+ _nextVersion = 1;
4264
+ }
4265
+ });
4266
+
4267
+ // src/lib/self-query-router.ts
4268
+ var self_query_router_exports = {};
4269
+ __export(self_query_router_exports, {
4270
+ routeQuery: () => routeQuery
4271
+ });
4272
+ async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
4273
+ if (query.length < 10) {
4274
+ return {
4275
+ semanticQuery: query,
4276
+ projectFilter: null,
4277
+ roleFilter: null,
4278
+ timeFilter: null,
4279
+ isBroadQuery: false
4280
+ };
4281
+ }
4282
+ try {
4283
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
4284
+ const client = new Anthropic();
4285
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4286
+ const response = await client.messages.create({
4287
+ model,
4288
+ max_tokens: 256,
4289
+ system: `You are a search query router. Extract metadata filters from the user's memory search query. Today is ${now}. Convert relative time references (yesterday, last week) to ISO timestamps.`,
4290
+ messages: [{ role: "user", content: query }],
4291
+ tools: [EXTRACT_TOOL],
4292
+ tool_choice: { type: "tool", name: "extract_search_filters" }
4018
4293
  });
4019
- });
4020
- }
4021
- async function connectEmbedDaemon() {
4022
- if (_socket && _connected) return true;
4023
- if (await connectToSocket()) return true;
4024
- if (acquireSpawnLock()) {
4025
- try {
4026
- cleanupStaleFiles();
4027
- spawnDaemon();
4028
- } finally {
4029
- releaseSpawnLock();
4294
+ const toolBlock = response.content.find((b) => b.type === "tool_use");
4295
+ if (toolBlock && toolBlock.type === "tool_use") {
4296
+ const input2 = toolBlock.input;
4297
+ return {
4298
+ semanticQuery: input2.semantic_query || query,
4299
+ projectFilter: input2.project_filter || null,
4300
+ roleFilter: input2.role_filter || null,
4301
+ timeFilter: input2.time_filter || null,
4302
+ isBroadQuery: Boolean(input2.is_broad_query)
4303
+ };
4030
4304
  }
4305
+ } catch (err) {
4306
+ process.stderr.write(
4307
+ `[self-query-router] LLM extraction failed, using passthrough: ${err instanceof Error ? err.message : String(err)}
4308
+ `
4309
+ );
4031
4310
  }
4032
- const start = Date.now();
4033
- let delay2 = 100;
4034
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
4035
- await new Promise((r) => setTimeout(r, delay2));
4036
- if (await connectToSocket()) return true;
4037
- delay2 = Math.min(delay2 * 2, 3e3);
4311
+ return {
4312
+ semanticQuery: query,
4313
+ projectFilter: null,
4314
+ roleFilter: null,
4315
+ timeFilter: null,
4316
+ isBroadQuery: false
4317
+ };
4318
+ }
4319
+ var EXTRACT_TOOL;
4320
+ var init_self_query_router = __esm({
4321
+ "src/lib/self-query-router.ts"() {
4322
+ "use strict";
4323
+ EXTRACT_TOOL = {
4324
+ name: "extract_search_filters",
4325
+ description: "Extract metadata filters from a memory search query to improve retrieval precision.",
4326
+ input_schema: {
4327
+ type: "object",
4328
+ properties: {
4329
+ semantic_query: {
4330
+ type: "string",
4331
+ description: "The core semantic meaning of the query, stripped of metadata references. This is used for embedding search."
4332
+ },
4333
+ project_filter: {
4334
+ type: ["string", "null"],
4335
+ description: "Project name if the query references a specific project (e.g., 'exe-os', 'exe-create'). Null if no project specified."
4336
+ },
4337
+ role_filter: {
4338
+ type: ["string", "null"],
4339
+ description: "Agent role if the query targets a specific role (e.g., 'CTO', 'CMO'). Null if no role specified."
4340
+ },
4341
+ time_filter: {
4342
+ type: ["string", "null"],
4343
+ description: "ISO 8601 timestamp lower bound if the query references a time period (e.g., 'last week', 'yesterday'). Null if no time reference."
4344
+ },
4345
+ is_broad_query: {
4346
+ type: "boolean",
4347
+ description: "True if the query is exploratory/broad (e.g., 'what has the CTO been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
4348
+ }
4349
+ },
4350
+ required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
4351
+ }
4352
+ };
4353
+ }
4354
+ });
4355
+
4356
+ // src/lib/reranker.ts
4357
+ var reranker_exports = {};
4358
+ __export(reranker_exports, {
4359
+ disposeReranker: () => disposeReranker,
4360
+ getRerankerModelPath: () => getRerankerModelPath,
4361
+ isRerankerAvailable: () => isRerankerAvailable,
4362
+ rerank: () => rerank,
4363
+ rerankWithContext: () => rerankWithContext,
4364
+ rerankWithScores: () => rerankWithScores
4365
+ });
4366
+ import path9 from "path";
4367
+ import { existsSync as existsSync9 } from "fs";
4368
+ function resetIdleTimer() {
4369
+ if (_idleTimer) clearTimeout(_idleTimer);
4370
+ _idleTimer = setTimeout(() => {
4371
+ void disposeReranker();
4372
+ }, IDLE_TIMEOUT_MS);
4373
+ if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
4374
+ _idleTimer.unref();
4038
4375
  }
4039
- return false;
4040
4376
  }
4041
- function sendRequest(texts, priority) {
4042
- return sendDaemonRequest({ texts, priority });
4377
+ function isRerankerAvailable() {
4378
+ return existsSync9(path9.join(MODELS_DIR, RERANKER_MODEL_FILE));
4043
4379
  }
4044
- function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
4045
- return new Promise((resolve) => {
4046
- if (!_socket || !_connected) {
4047
- resolve({ error: "Not connected" });
4048
- return;
4049
- }
4050
- const id = randomUUID2();
4051
- const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
4052
- const timer = setTimeout(() => {
4053
- _pending.delete(id);
4054
- resolve({ error: "Request timeout" });
4055
- }, timeoutMs);
4056
- _pending.set(id, { resolve, timer });
4057
- try {
4058
- _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
4059
- } catch {
4060
- clearTimeout(timer);
4061
- _pending.delete(id);
4062
- resolve({ error: "Write failed" });
4063
- }
4064
- });
4380
+ function getRerankerModelPath() {
4381
+ return path9.join(MODELS_DIR, RERANKER_MODEL_FILE);
4065
4382
  }
4066
- async function pingDaemon() {
4067
- if (!_socket || !_connected) return null;
4068
- const response = await sendDaemonRequest({ type: "health" }, 5e3);
4069
- if (response.health) {
4070
- return response.health;
4383
+ async function ensureLoaded() {
4384
+ if (_rerankerContext) {
4385
+ resetIdleTimer();
4386
+ return;
4387
+ }
4388
+ const modelPath = path9.join(MODELS_DIR, RERANKER_MODEL_FILE);
4389
+ if (!existsSync9(modelPath)) {
4390
+ throw new Error(
4391
+ `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
4392
+ );
4071
4393
  }
4072
- return null;
4394
+ process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
4395
+ const { getLlama } = await import("node-llama-cpp");
4396
+ const llama = await getLlama();
4397
+ _rerankerModel = await llama.loadModel({ modelPath });
4398
+ _rerankerContext = await _rerankerModel.createEmbeddingContext();
4399
+ process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
4400
+ resetIdleTimer();
4073
4401
  }
4074
- function killAndRespawnDaemon() {
4075
- if (!acquireSpawnLock()) {
4076
- process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
4077
- if (_socket) {
4078
- _socket.destroy();
4079
- _socket = null;
4080
- }
4081
- _connected = false;
4082
- _buffer = "";
4083
- return;
4402
+ async function disposeReranker() {
4403
+ if (_idleTimer) {
4404
+ clearTimeout(_idleTimer);
4405
+ _idleTimer = null;
4084
4406
  }
4085
- try {
4086
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
4087
- if (existsSync9(PID_PATH)) {
4088
- try {
4089
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
4090
- if (pid > 0) {
4091
- try {
4092
- process.kill(pid, "SIGKILL");
4093
- } catch {
4094
- }
4095
- }
4096
- } catch {
4097
- }
4098
- }
4099
- if (_socket) {
4100
- _socket.destroy();
4101
- _socket = null;
4102
- }
4103
- _connected = false;
4104
- _buffer = "";
4407
+ if (_rerankerContext) {
4105
4408
  try {
4106
- unlinkSync2(PID_PATH);
4409
+ await _rerankerContext.dispose();
4107
4410
  } catch {
4108
4411
  }
4412
+ _rerankerContext = null;
4413
+ }
4414
+ if (_rerankerModel) {
4109
4415
  try {
4110
- unlinkSync2(SOCKET_PATH);
4416
+ await _rerankerModel.dispose();
4111
4417
  } catch {
4112
4418
  }
4113
- spawnDaemon();
4114
- } finally {
4115
- releaseSpawnLock();
4116
- }
4117
- }
4118
- function isDaemonTooYoung() {
4119
- try {
4120
- const stat = statSync(PID_PATH);
4121
- return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
4122
- } catch {
4123
- return false;
4419
+ _rerankerModel = null;
4124
4420
  }
4421
+ process.stderr.write("[reranker] Unloaded (idle timeout).\n");
4125
4422
  }
4126
- async function retryThenRestart(doRequest, label) {
4127
- const result = await doRequest();
4128
- if (!result.error) {
4129
- _consecutiveFailures = 0;
4130
- return result;
4131
- }
4132
- _consecutiveFailures++;
4133
- for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
4134
- const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
4135
- process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
4136
- `);
4137
- await new Promise((r) => setTimeout(r, delayMs));
4138
- if (!_connected) {
4139
- if (!await connectToSocket()) continue;
4140
- }
4141
- const retry = await doRequest();
4142
- if (!retry.error) {
4143
- _consecutiveFailures = 0;
4144
- return retry;
4423
+ async function rerankWithScores(query, texts, topK) {
4424
+ if (texts.length === 0) return [];
4425
+ await ensureLoaded();
4426
+ const ctx = _rerankerContext;
4427
+ const scored = [];
4428
+ for (let i = 0; i < texts.length; i++) {
4429
+ const text = texts[i] ?? "";
4430
+ try {
4431
+ const input2 = `query: ${query} document: ${text.slice(0, 512)}`;
4432
+ const embedding = await ctx.getEmbeddingFor(input2);
4433
+ const score = embedding.vector[0] ?? 0;
4434
+ scored.push({ text, score, index: i });
4435
+ } catch {
4436
+ scored.push({ text, score: -1, index: i });
4145
4437
  }
4146
- _consecutiveFailures++;
4147
4438
  }
4148
- if (isDaemonTooYoung()) {
4149
- process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
4150
- `);
4151
- return { error: result.error };
4152
- }
4153
- process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
4154
- `);
4155
- killAndRespawnDaemon();
4156
- const start = Date.now();
4157
- let delay2 = 200;
4158
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
4159
- await new Promise((r) => setTimeout(r, delay2));
4160
- if (await connectToSocket()) break;
4161
- delay2 = Math.min(delay2 * 2, 3e3);
4162
- }
4163
- if (!_connected) return { error: "Daemon restart failed" };
4164
- const final = await doRequest();
4165
- if (!final.error) _consecutiveFailures = 0;
4166
- return final;
4439
+ scored.sort((a, b) => b.score - a.score);
4440
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
4167
4441
  }
4168
- async function embedViaClient(text, priority = "high") {
4169
- if (!_connected && !await connectEmbedDaemon()) return null;
4170
- _requestCount++;
4171
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
4172
- const health = await pingDaemon();
4173
- if (!health && !isDaemonTooYoung()) {
4174
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
4175
- `);
4176
- killAndRespawnDaemon();
4177
- const start = Date.now();
4178
- let d = 200;
4179
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
4180
- await new Promise((r) => setTimeout(r, d));
4181
- if (await connectToSocket()) break;
4182
- d = Math.min(d * 2, 3e3);
4183
- }
4184
- if (!_connected) return null;
4185
- }
4186
- }
4187
- const result = await retryThenRestart(
4188
- () => sendRequest([text], priority),
4189
- "Embed"
4442
+ async function rerank(query, candidates, topK = 5) {
4443
+ if (candidates.length === 0) return [];
4444
+ if (candidates.length <= topK) return candidates;
4445
+ const scored = await rerankWithScores(
4446
+ query,
4447
+ candidates.map((c) => c.raw_text),
4448
+ topK
4190
4449
  );
4191
- return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
4450
+ return scored.map((s) => candidates[s.index]);
4192
4451
  }
4193
- function disconnectClient() {
4194
- if (_socket) {
4195
- _socket.destroy();
4196
- _socket = null;
4197
- }
4198
- _connected = false;
4199
- _buffer = "";
4200
- for (const [id, entry] of _pending) {
4201
- clearTimeout(entry.timer);
4202
- _pending.delete(id);
4203
- entry.resolve({ error: "Client disconnected" });
4452
+ async function rerankWithContext(query, candidates, topK) {
4453
+ if (candidates.length === 0) return [];
4454
+ await ensureLoaded();
4455
+ const ctx = _rerankerContext;
4456
+ const scored = [];
4457
+ for (let i = 0; i < candidates.length; i++) {
4458
+ const candidate = candidates[i];
4459
+ try {
4460
+ const docText = candidate.context ? `[${candidate.context}] ${candidate.text.slice(0, 460)}` : candidate.text.slice(0, 512);
4461
+ const input2 = `query: ${query} document: ${docText}`;
4462
+ const embedding = await ctx.getEmbeddingFor(input2);
4463
+ const score = embedding.vector[0] ?? 0;
4464
+ scored.push({ text: candidate.text, score, index: i });
4465
+ } catch {
4466
+ scored.push({ text: candidate.text, score: -1, index: i });
4467
+ }
4204
4468
  }
4469
+ scored.sort((a, b) => b.score - a.score);
4470
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
4205
4471
  }
4206
- 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;
4207
- var init_exe_daemon_client = __esm({
4208
- "src/lib/exe-daemon-client.ts"() {
4472
+ var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
4473
+ var init_reranker = __esm({
4474
+ "src/lib/reranker.ts"() {
4209
4475
  "use strict";
4210
4476
  init_config();
4211
- init_daemon_auth();
4212
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
4213
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
4214
- SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
4215
- SPAWN_LOCK_STALE_MS = 3e4;
4216
- CONNECT_TIMEOUT_MS = 15e3;
4217
- REQUEST_TIMEOUT_MS = 3e4;
4218
- DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
4219
- _socket = null;
4220
- _connected = false;
4221
- _buffer = "";
4222
- _requestCount = 0;
4223
- _consecutiveFailures = 0;
4224
- HEALTH_CHECK_INTERVAL = 100;
4225
- MAX_RETRIES_BEFORE_RESTART = 3;
4226
- RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
4227
- MIN_DAEMON_AGE_MS = 3e4;
4228
- _pending = /* @__PURE__ */ new Map();
4229
- MAX_BUFFER = 1e7;
4477
+ RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
4478
+ IDLE_TIMEOUT_MS = 6e4;
4479
+ _rerankerContext = null;
4480
+ _rerankerModel = null;
4481
+ _idleTimer = null;
4230
4482
  }
4231
4483
  });
4232
4484
 
@@ -4267,10 +4519,10 @@ async function disposeEmbedder() {
4267
4519
  async function embedDirect(text) {
4268
4520
  const llamaCpp = await import("node-llama-cpp");
4269
4521
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4270
- const { existsSync: existsSync20 } = await import("fs");
4271
- const path24 = await import("path");
4272
- const modelPath = path24.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4273
- if (!existsSync20(modelPath)) {
4522
+ const { existsSync: existsSync21 } = await import("fs");
4523
+ const path25 = await import("path");
4524
+ const modelPath = path25.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4525
+ if (!existsSync21(modelPath)) {
4274
4526
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
4275
4527
  }
4276
4528
  const llama = await llamaCpp.getLlama();
@@ -5212,13 +5464,13 @@ var init_session_key = __esm({
5212
5464
  });
5213
5465
 
5214
5466
  // src/lib/session-registry.ts
5215
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync11 } from "fs";
5216
- import path13 from "path";
5217
- import os6 from "os";
5467
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync12 } from "fs";
5468
+ import path14 from "path";
5469
+ import os7 from "os";
5218
5470
  function registerSession(entry) {
5219
- const dir = path13.dirname(REGISTRY_PATH);
5220
- if (!existsSync11(dir)) {
5221
- mkdirSync4(dir, { recursive: true });
5471
+ const dir = path14.dirname(REGISTRY_PATH);
5472
+ if (!existsSync12(dir)) {
5473
+ mkdirSync5(dir, { recursive: true });
5222
5474
  }
5223
5475
  const sessions = listSessions();
5224
5476
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -5227,11 +5479,11 @@ function registerSession(entry) {
5227
5479
  } else {
5228
5480
  sessions.push(entry);
5229
5481
  }
5230
- writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5482
+ writeFileSync6(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5231
5483
  }
5232
5484
  function listSessions() {
5233
5485
  try {
5234
- const raw = readFileSync8(REGISTRY_PATH, "utf8");
5486
+ const raw = readFileSync9(REGISTRY_PATH, "utf8");
5235
5487
  return JSON.parse(raw);
5236
5488
  } catch {
5237
5489
  return [];
@@ -5241,7 +5493,7 @@ var REGISTRY_PATH;
5241
5493
  var init_session_registry = __esm({
5242
5494
  "src/lib/session-registry.ts"() {
5243
5495
  "use strict";
5244
- REGISTRY_PATH = path13.join(os6.homedir(), ".exe-os", "session-registry.json");
5496
+ REGISTRY_PATH = path14.join(os7.homedir(), ".exe-os", "session-registry.json");
5245
5497
  }
5246
5498
  });
5247
5499
 
@@ -5442,17 +5694,17 @@ __export(intercom_queue_exports, {
5442
5694
  queueIntercom: () => queueIntercom,
5443
5695
  readQueue: () => readQueue
5444
5696
  });
5445
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
5446
- import path14 from "path";
5447
- import os7 from "os";
5697
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync3, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
5698
+ import path15 from "path";
5699
+ import os8 from "os";
5448
5700
  function ensureDir() {
5449
- const dir = path14.dirname(QUEUE_PATH);
5450
- if (!existsSync12(dir)) mkdirSync5(dir, { recursive: true });
5701
+ const dir = path15.dirname(QUEUE_PATH);
5702
+ if (!existsSync13(dir)) mkdirSync6(dir, { recursive: true });
5451
5703
  }
5452
5704
  function readQueue() {
5453
5705
  try {
5454
- if (!existsSync12(QUEUE_PATH)) return [];
5455
- return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
5706
+ if (!existsSync13(QUEUE_PATH)) return [];
5707
+ return JSON.parse(readFileSync10(QUEUE_PATH, "utf8"));
5456
5708
  } catch {
5457
5709
  return [];
5458
5710
  }
@@ -5460,7 +5712,7 @@ function readQueue() {
5460
5712
  function writeQueue(queue) {
5461
5713
  ensureDir();
5462
5714
  const tmp = `${QUEUE_PATH}.tmp`;
5463
- writeFileSync6(tmp, JSON.stringify(queue, null, 2));
5715
+ writeFileSync7(tmp, JSON.stringify(queue, null, 2));
5464
5716
  renameSync3(tmp, QUEUE_PATH);
5465
5717
  }
5466
5718
  function queueIntercom(targetSession, reason) {
@@ -5552,29 +5804,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
5552
5804
  var init_intercom_queue = __esm({
5553
5805
  "src/lib/intercom-queue.ts"() {
5554
5806
  "use strict";
5555
- QUEUE_PATH = path14.join(os7.homedir(), ".exe-os", "intercom-queue.json");
5807
+ QUEUE_PATH = path15.join(os8.homedir(), ".exe-os", "intercom-queue.json");
5556
5808
  MAX_RETRIES2 = 5;
5557
5809
  TTL_MS = 60 * 60 * 1e3;
5558
- INTERCOM_LOG = path14.join(os7.homedir(), ".exe-os", "intercom.log");
5810
+ INTERCOM_LOG = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5559
5811
  }
5560
5812
  });
5561
5813
 
5562
5814
  // src/lib/license.ts
5563
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
5815
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync7 } from "fs";
5564
5816
  import { randomUUID as randomUUID3 } from "crypto";
5565
5817
  import { createRequire as createRequire2 } from "module";
5566
5818
  import { pathToFileURL as pathToFileURL2 } from "url";
5567
- import os8 from "os";
5568
- import path15 from "path";
5819
+ import os9 from "os";
5820
+ import path16 from "path";
5569
5821
  import { jwtVerify, importSPKI } from "jose";
5570
5822
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
5571
5823
  var init_license = __esm({
5572
5824
  "src/lib/license.ts"() {
5573
5825
  "use strict";
5574
5826
  init_config();
5575
- LICENSE_PATH = path15.join(EXE_AI_DIR, "license.key");
5576
- CACHE_PATH = path15.join(EXE_AI_DIR, "license-cache.json");
5577
- DEVICE_ID_PATH = path15.join(EXE_AI_DIR, "device-id");
5827
+ LICENSE_PATH = path16.join(EXE_AI_DIR, "license.key");
5828
+ CACHE_PATH = path16.join(EXE_AI_DIR, "license-cache.json");
5829
+ DEVICE_ID_PATH = path16.join(EXE_AI_DIR, "device-id");
5578
5830
  PLAN_LIMITS = {
5579
5831
  free: { devices: 1, employees: 1, memories: 5e3 },
5580
5832
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -5586,12 +5838,12 @@ var init_license = __esm({
5586
5838
  });
5587
5839
 
5588
5840
  // src/lib/plan-limits.ts
5589
- import { readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
5590
- import path16 from "path";
5841
+ import { readFileSync as readFileSync12, existsSync as existsSync15 } from "fs";
5842
+ import path17 from "path";
5591
5843
  function getLicenseSync() {
5592
5844
  try {
5593
- if (!existsSync14(CACHE_PATH2)) return freeLicense();
5594
- const raw = JSON.parse(readFileSync11(CACHE_PATH2, "utf8"));
5845
+ if (!existsSync15(CACHE_PATH2)) return freeLicense();
5846
+ const raw = JSON.parse(readFileSync12(CACHE_PATH2, "utf8"));
5595
5847
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
5596
5848
  const parts = raw.token.split(".");
5597
5849
  if (parts.length !== 3) return freeLicense();
@@ -5629,8 +5881,8 @@ function assertEmployeeLimitSync(rosterPath) {
5629
5881
  const filePath = rosterPath ?? EMPLOYEES_PATH;
5630
5882
  let count = 0;
5631
5883
  try {
5632
- if (existsSync14(filePath)) {
5633
- const raw = readFileSync11(filePath, "utf8");
5884
+ if (existsSync15(filePath)) {
5885
+ const raw = readFileSync12(filePath, "utf8");
5634
5886
  const employees = JSON.parse(raw);
5635
5887
  count = Array.isArray(employees) ? employees.length : 0;
5636
5888
  }
@@ -5659,7 +5911,7 @@ var init_plan_limits = __esm({
5659
5911
  this.name = "PlanLimitError";
5660
5912
  }
5661
5913
  };
5662
- CACHE_PATH2 = path16.join(EXE_AI_DIR, "license-cache.json");
5914
+ CACHE_PATH2 = path17.join(EXE_AI_DIR, "license-cache.json");
5663
5915
  }
5664
5916
  });
5665
5917
 
@@ -5698,13 +5950,13 @@ var init_task_scope = __esm({
5698
5950
 
5699
5951
  // src/lib/notifications.ts
5700
5952
  import crypto3 from "crypto";
5701
- import path17 from "path";
5702
- import os9 from "os";
5953
+ import path18 from "path";
5954
+ import os10 from "os";
5703
5955
  import {
5704
- readFileSync as readFileSync12,
5956
+ readFileSync as readFileSync13,
5705
5957
  readdirSync as readdirSync4,
5706
- unlinkSync as unlinkSync4,
5707
- existsSync as existsSync15,
5958
+ unlinkSync as unlinkSync5,
5959
+ existsSync as existsSync16,
5708
5960
  rmdirSync
5709
5961
  } from "fs";
5710
5962
  async function writeNotification(notification) {
@@ -5851,11 +6103,11 @@ var init_session_scope = __esm({
5851
6103
 
5852
6104
  // src/lib/tasks-crud.ts
5853
6105
  import crypto5 from "crypto";
5854
- import path18 from "path";
5855
- import os10 from "os";
6106
+ import path19 from "path";
6107
+ import os11 from "os";
5856
6108
  import { execSync as execSync7 } from "child_process";
5857
6109
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5858
- import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
6110
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
5859
6111
  async function writeCheckpoint(input2) {
5860
6112
  const client = getClient();
5861
6113
  const row = await resolveTask(client, input2.taskId);
@@ -6049,8 +6301,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
6049
6301
  }
6050
6302
  if (input2.baseDir) {
6051
6303
  try {
6052
- await mkdir4(path18.join(input2.baseDir, "exe", "output"), { recursive: true });
6053
- await mkdir4(path18.join(input2.baseDir, "exe", "research"), { recursive: true });
6304
+ await mkdir4(path19.join(input2.baseDir, "exe", "output"), { recursive: true });
6305
+ await mkdir4(path19.join(input2.baseDir, "exe", "research"), { recursive: true });
6054
6306
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
6055
6307
  await ensureGitignoreExe(input2.baseDir);
6056
6308
  } catch {
@@ -6086,10 +6338,10 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
6086
6338
  });
6087
6339
  if (input2.baseDir) {
6088
6340
  try {
6089
- const EXE_OS_DIR = path18.join(os10.homedir(), ".exe-os");
6090
- const mdPath = path18.join(EXE_OS_DIR, taskFile);
6091
- const mdDir = path18.dirname(mdPath);
6092
- if (!existsSync16(mdDir)) await mkdir4(mdDir, { recursive: true });
6341
+ const EXE_OS_DIR = path19.join(os11.homedir(), ".exe-os");
6342
+ const mdPath = path19.join(EXE_OS_DIR, taskFile);
6343
+ const mdDir = path19.dirname(mdPath);
6344
+ if (!existsSync17(mdDir)) await mkdir4(mdDir, { recursive: true });
6093
6345
  const reviewer = input2.reviewer ?? input2.assignedBy;
6094
6346
  const mdContent = `# ${input2.title}
6095
6347
 
@@ -6389,9 +6641,9 @@ async function deleteTaskCore(taskId, _baseDir) {
6389
6641
  return { taskFile, assignedTo, assignedBy, taskSlug };
6390
6642
  }
6391
6643
  async function ensureArchitectureDoc(baseDir, projectName) {
6392
- const archPath = path18.join(baseDir, "exe", "ARCHITECTURE.md");
6644
+ const archPath = path19.join(baseDir, "exe", "ARCHITECTURE.md");
6393
6645
  try {
6394
- if (existsSync16(archPath)) return;
6646
+ if (existsSync17(archPath)) return;
6395
6647
  const template = [
6396
6648
  `# ${projectName} \u2014 System Architecture`,
6397
6649
  "",
@@ -6424,10 +6676,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
6424
6676
  }
6425
6677
  }
6426
6678
  async function ensureGitignoreExe(baseDir) {
6427
- const gitignorePath = path18.join(baseDir, ".gitignore");
6679
+ const gitignorePath = path19.join(baseDir, ".gitignore");
6428
6680
  try {
6429
- if (existsSync16(gitignorePath)) {
6430
- const content = readFileSync13(gitignorePath, "utf-8");
6681
+ if (existsSync17(gitignorePath)) {
6682
+ const content = readFileSync14(gitignorePath, "utf-8");
6431
6683
  if (/^\/?exe\/?$/m.test(content)) return;
6432
6684
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
6433
6685
  } else {
@@ -6470,8 +6722,8 @@ __export(tasks_review_exports, {
6470
6722
  isStale: () => isStale,
6471
6723
  listPendingReviews: () => listPendingReviews
6472
6724
  });
6473
- import path19 from "path";
6474
- import { existsSync as existsSync17, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
6725
+ import path20 from "path";
6726
+ import { existsSync as existsSync18, readdirSync as readdirSync5, unlinkSync as unlinkSync6 } from "fs";
6475
6727
  function formatAge(isoTimestamp) {
6476
6728
  if (!isoTimestamp) return "";
6477
6729
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -6740,11 +6992,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
6740
6992
  );
6741
6993
  }
6742
6994
  try {
6743
- const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
6744
- if (existsSync17(cacheDir)) {
6995
+ const cacheDir = path20.join(EXE_AI_DIR, "session-cache");
6996
+ if (existsSync18(cacheDir)) {
6745
6997
  for (const f of readdirSync5(cacheDir)) {
6746
6998
  if (f.startsWith("review-notified-")) {
6747
- unlinkSync5(path19.join(cacheDir, f));
6999
+ unlinkSync6(path20.join(cacheDir, f));
6748
7000
  }
6749
7001
  }
6750
7002
  }
@@ -6766,7 +7018,7 @@ var init_tasks_review = __esm({
6766
7018
  });
6767
7019
 
6768
7020
  // src/lib/tasks-chain.ts
6769
- import path20 from "path";
7021
+ import path21 from "path";
6770
7022
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
6771
7023
  async function cascadeUnblock(taskId, baseDir, now) {
6772
7024
  const client = getClient();
@@ -6783,7 +7035,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
6783
7035
  });
6784
7036
  for (const ur of unblockedRows.rows) {
6785
7037
  try {
6786
- const ubFile = path20.join(baseDir, String(ur.task_file));
7038
+ const ubFile = path21.join(baseDir, String(ur.task_file));
6787
7039
  let ubContent = await readFile4(ubFile, "utf-8");
6788
7040
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
6789
7041
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -7247,8 +7499,8 @@ __export(tasks_exports, {
7247
7499
  updateTaskStatus: () => updateTaskStatus,
7248
7500
  writeCheckpoint: () => writeCheckpoint
7249
7501
  });
7250
- import path21 from "path";
7251
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
7502
+ import path22 from "path";
7503
+ import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7 } from "fs";
7252
7504
  async function createTask(input2) {
7253
7505
  const result = await createTaskCore(input2);
7254
7506
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -7267,14 +7519,14 @@ async function updateTask(input2) {
7267
7519
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
7268
7520
  try {
7269
7521
  const agent = String(row.assigned_to);
7270
- const cacheDir = path21.join(EXE_AI_DIR, "session-cache");
7271
- const cachePath = path21.join(cacheDir, `current-task-${agent}.json`);
7522
+ const cacheDir = path22.join(EXE_AI_DIR, "session-cache");
7523
+ const cachePath = path22.join(cacheDir, `current-task-${agent}.json`);
7272
7524
  if (input2.status === "in_progress") {
7273
- mkdirSync7(cacheDir, { recursive: true });
7274
- writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
7525
+ mkdirSync8(cacheDir, { recursive: true });
7526
+ writeFileSync9(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
7275
7527
  } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
7276
7528
  try {
7277
- unlinkSync6(cachePath);
7529
+ unlinkSync7(cachePath);
7278
7530
  } catch {
7279
7531
  }
7280
7532
  }
@@ -7739,13 +7991,13 @@ __export(tmux_routing_exports, {
7739
7991
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
7740
7992
  });
7741
7993
  import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
7742
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, existsSync as existsSync18, appendFileSync, readdirSync as readdirSync6 } from "fs";
7743
- import path22 from "path";
7744
- import os11 from "os";
7994
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, existsSync as existsSync19, appendFileSync, readdirSync as readdirSync6 } from "fs";
7995
+ import path23 from "path";
7996
+ import os12 from "os";
7745
7997
  import { fileURLToPath as fileURLToPath2 } from "url";
7746
- import { unlinkSync as unlinkSync7 } from "fs";
7998
+ import { unlinkSync as unlinkSync8 } from "fs";
7747
7999
  function spawnLockPath(sessionName) {
7748
- return path22.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
8000
+ return path23.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7749
8001
  }
7750
8002
  function isProcessAlive(pid) {
7751
8003
  try {
@@ -7756,13 +8008,13 @@ function isProcessAlive(pid) {
7756
8008
  }
7757
8009
  }
7758
8010
  function acquireSpawnLock2(sessionName) {
7759
- if (!existsSync18(SPAWN_LOCK_DIR)) {
7760
- mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
8011
+ if (!existsSync19(SPAWN_LOCK_DIR)) {
8012
+ mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
7761
8013
  }
7762
8014
  const lockFile = spawnLockPath(sessionName);
7763
- if (existsSync18(lockFile)) {
8015
+ if (existsSync19(lockFile)) {
7764
8016
  try {
7765
- const lock = JSON.parse(readFileSync14(lockFile, "utf8"));
8017
+ const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
7766
8018
  const age = Date.now() - lock.timestamp;
7767
8019
  if (isProcessAlive(lock.pid) && age < 6e4) {
7768
8020
  return false;
@@ -7770,25 +8022,25 @@ function acquireSpawnLock2(sessionName) {
7770
8022
  } catch {
7771
8023
  }
7772
8024
  }
7773
- writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
8025
+ writeFileSync10(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7774
8026
  return true;
7775
8027
  }
7776
8028
  function releaseSpawnLock2(sessionName) {
7777
8029
  try {
7778
- unlinkSync7(spawnLockPath(sessionName));
8030
+ unlinkSync8(spawnLockPath(sessionName));
7779
8031
  } catch {
7780
8032
  }
7781
8033
  }
7782
8034
  function resolveBehaviorsExporterScript() {
7783
8035
  try {
7784
8036
  const thisFile = fileURLToPath2(import.meta.url);
7785
- const scriptPath = path22.join(
7786
- path22.dirname(thisFile),
8037
+ const scriptPath = path23.join(
8038
+ path23.dirname(thisFile),
7787
8039
  "..",
7788
8040
  "bin",
7789
8041
  "exe-export-behaviors.js"
7790
8042
  );
7791
- return existsSync18(scriptPath) ? scriptPath : null;
8043
+ return existsSync19(scriptPath) ? scriptPath : null;
7792
8044
  } catch {
7793
8045
  return null;
7794
8046
  }
@@ -7854,12 +8106,12 @@ function extractRootExe(name) {
7854
8106
  return parts.length > 0 ? parts[parts.length - 1] : null;
7855
8107
  }
7856
8108
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7857
- if (!existsSync18(SESSION_CACHE)) {
7858
- mkdirSync8(SESSION_CACHE, { recursive: true });
8109
+ if (!existsSync19(SESSION_CACHE)) {
8110
+ mkdirSync9(SESSION_CACHE, { recursive: true });
7859
8111
  }
7860
8112
  const rootExe = extractRootExe(parentExe) ?? parentExe;
7861
- const filePath = path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
7862
- writeFileSync9(filePath, JSON.stringify({
8113
+ const filePath = path23.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
8114
+ writeFileSync10(filePath, JSON.stringify({
7863
8115
  parentExe: rootExe,
7864
8116
  dispatchedBy: dispatchedBy || rootExe,
7865
8117
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7867,7 +8119,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7867
8119
  }
7868
8120
  function getParentExe(sessionKey) {
7869
8121
  try {
7870
- const data = JSON.parse(readFileSync14(path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
8122
+ const data = JSON.parse(readFileSync15(path23.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
7871
8123
  return data.parentExe || null;
7872
8124
  } catch {
7873
8125
  return null;
@@ -7875,8 +8127,8 @@ function getParentExe(sessionKey) {
7875
8127
  }
7876
8128
  function getDispatchedBy(sessionKey) {
7877
8129
  try {
7878
- const data = JSON.parse(readFileSync14(
7879
- path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
8130
+ const data = JSON.parse(readFileSync15(
8131
+ path23.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
7880
8132
  "utf8"
7881
8133
  ));
7882
8134
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -7946,8 +8198,8 @@ async function verifyPaneAtCapacity(sessionName) {
7946
8198
  }
7947
8199
  function readDebounceState() {
7948
8200
  try {
7949
- if (!existsSync18(DEBOUNCE_FILE)) return {};
7950
- const raw = JSON.parse(readFileSync14(DEBOUNCE_FILE, "utf8"));
8201
+ if (!existsSync19(DEBOUNCE_FILE)) return {};
8202
+ const raw = JSON.parse(readFileSync15(DEBOUNCE_FILE, "utf8"));
7951
8203
  const state = {};
7952
8204
  for (const [key, val] of Object.entries(raw)) {
7953
8205
  if (typeof val === "number") {
@@ -7963,8 +8215,8 @@ function readDebounceState() {
7963
8215
  }
7964
8216
  function writeDebounceState(state) {
7965
8217
  try {
7966
- if (!existsSync18(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
7967
- writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
8218
+ if (!existsSync19(SESSION_CACHE)) mkdirSync9(SESSION_CACHE, { recursive: true });
8219
+ writeFileSync10(DEBOUNCE_FILE, JSON.stringify(state));
7968
8220
  } catch {
7969
8221
  }
7970
8222
  }
@@ -8063,8 +8315,8 @@ function sendIntercom(targetSession) {
8063
8315
  try {
8064
8316
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8065
8317
  const agent = baseAgentName(rawAgent);
8066
- const markerPath = path22.join(SESSION_CACHE, `current-task-${agent}.json`);
8067
- if (existsSync18(markerPath)) {
8318
+ const markerPath = path23.join(SESSION_CACHE, `current-task-${agent}.json`);
8319
+ if (existsSync19(markerPath)) {
8068
8320
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker + not idle \u2014 will auto-chain)`);
8069
8321
  return "debounced";
8070
8322
  }
@@ -8074,8 +8326,8 @@ function sendIntercom(targetSession) {
8074
8326
  try {
8075
8327
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
8076
8328
  const agent = baseAgentName(rawAgent);
8077
- const taskDir = path22.join(process.cwd(), "exe", agent);
8078
- if (existsSync18(taskDir)) {
8329
+ const taskDir = path23.join(process.cwd(), "exe", agent);
8330
+ if (existsSync19(taskDir)) {
8079
8331
  const files = readdirSync6(taskDir).filter(
8080
8332
  (f) => f.endsWith(".md") && f !== "DONE.txt"
8081
8333
  );
@@ -8241,26 +8493,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8241
8493
  const transport = getTransport();
8242
8494
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
8243
8495
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
8244
- const logDir = path22.join(os11.homedir(), ".exe-os", "session-logs");
8245
- const logFile = path22.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8246
- if (!existsSync18(logDir)) {
8247
- mkdirSync8(logDir, { recursive: true });
8496
+ const logDir = path23.join(os12.homedir(), ".exe-os", "session-logs");
8497
+ const logFile = path23.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8498
+ if (!existsSync19(logDir)) {
8499
+ mkdirSync9(logDir, { recursive: true });
8248
8500
  }
8249
8501
  transport.kill(sessionName);
8250
8502
  let cleanupSuffix = "";
8251
8503
  try {
8252
8504
  const thisFile = fileURLToPath2(import.meta.url);
8253
- const cleanupScript = path22.join(path22.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8254
- if (existsSync18(cleanupScript)) {
8505
+ const cleanupScript = path23.join(path23.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8506
+ if (existsSync19(cleanupScript)) {
8255
8507
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
8256
8508
  }
8257
8509
  } catch {
8258
8510
  }
8259
8511
  try {
8260
- const claudeJsonPath = path22.join(os11.homedir(), ".claude.json");
8512
+ const claudeJsonPath = path23.join(os12.homedir(), ".claude.json");
8261
8513
  let claudeJson = {};
8262
8514
  try {
8263
- claudeJson = JSON.parse(readFileSync14(claudeJsonPath, "utf8"));
8515
+ claudeJson = JSON.parse(readFileSync15(claudeJsonPath, "utf8"));
8264
8516
  } catch {
8265
8517
  }
8266
8518
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -8268,17 +8520,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8268
8520
  const trustDir = opts?.cwd ?? projectDir;
8269
8521
  if (!projects[trustDir]) projects[trustDir] = {};
8270
8522
  projects[trustDir].hasTrustDialogAccepted = true;
8271
- writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8523
+ writeFileSync10(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8272
8524
  } catch {
8273
8525
  }
8274
8526
  try {
8275
- const settingsDir = path22.join(os11.homedir(), ".claude", "projects");
8527
+ const settingsDir = path23.join(os12.homedir(), ".claude", "projects");
8276
8528
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
8277
- const projSettingsDir = path22.join(settingsDir, normalizedKey);
8278
- const settingsPath = path22.join(projSettingsDir, "settings.json");
8529
+ const projSettingsDir = path23.join(settingsDir, normalizedKey);
8530
+ const settingsPath = path23.join(projSettingsDir, "settings.json");
8279
8531
  let settings = {};
8280
8532
  try {
8281
- settings = JSON.parse(readFileSync14(settingsPath, "utf8"));
8533
+ settings = JSON.parse(readFileSync15(settingsPath, "utf8"));
8282
8534
  } catch {
8283
8535
  }
8284
8536
  const perms = settings.permissions ?? {};
@@ -8306,8 +8558,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8306
8558
  if (changed) {
8307
8559
  perms.allow = allow;
8308
8560
  settings.permissions = perms;
8309
- mkdirSync8(projSettingsDir, { recursive: true });
8310
- writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8561
+ mkdirSync9(projSettingsDir, { recursive: true });
8562
+ writeFileSync10(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8311
8563
  }
8312
8564
  } catch {
8313
8565
  }
@@ -8322,8 +8574,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8322
8574
  let behaviorsFlag = "";
8323
8575
  let legacyFallbackWarned = false;
8324
8576
  if (!useExeAgent && !useBinSymlink) {
8325
- const identityPath = path22.join(
8326
- os11.homedir(),
8577
+ const identityPath = path23.join(
8578
+ os12.homedir(),
8327
8579
  ".exe-os",
8328
8580
  "identity",
8329
8581
  `${employeeName}.md`
@@ -8332,13 +8584,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8332
8584
  const hasAgentFlag = claudeSupportsAgentFlag();
8333
8585
  if (hasAgentFlag) {
8334
8586
  identityFlag = ` --agent ${employeeName}`;
8335
- } else if (existsSync18(identityPath)) {
8587
+ } else if (existsSync19(identityPath)) {
8336
8588
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
8337
8589
  legacyFallbackWarned = true;
8338
8590
  }
8339
8591
  const behaviorsFile = exportBehaviorsSync(
8340
8592
  employeeName,
8341
- path22.basename(spawnCwd),
8593
+ path23.basename(spawnCwd),
8342
8594
  sessionName
8343
8595
  );
8344
8596
  if (behaviorsFile) {
@@ -8353,16 +8605,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8353
8605
  }
8354
8606
  let sessionContextFlag = "";
8355
8607
  try {
8356
- const ctxDir = path22.join(os11.homedir(), ".exe-os", "session-cache");
8357
- mkdirSync8(ctxDir, { recursive: true });
8358
- const ctxFile = path22.join(ctxDir, `session-context-${sessionName}.md`);
8608
+ const ctxDir = path23.join(os12.homedir(), ".exe-os", "session-cache");
8609
+ mkdirSync9(ctxDir, { recursive: true });
8610
+ const ctxFile = path23.join(ctxDir, `session-context-${sessionName}.md`);
8359
8611
  const ctxContent = [
8360
8612
  `## Session Context`,
8361
8613
  `You are running in tmux session: ${sessionName}.`,
8362
8614
  `Your parent coordinator session is ${exeSession}.`,
8363
8615
  `Your employees (if any) use the -${exeSession} suffix.`
8364
8616
  ].join("\n");
8365
- writeFileSync9(ctxFile, ctxContent);
8617
+ writeFileSync10(ctxFile, ctxContent);
8366
8618
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
8367
8619
  } catch {
8368
8620
  }
@@ -8439,8 +8691,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8439
8691
  transport.pipeLog(sessionName, logFile);
8440
8692
  try {
8441
8693
  const mySession = getMySession();
8442
- const dispatchInfo = path22.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8443
- writeFileSync9(dispatchInfo, JSON.stringify({
8694
+ const dispatchInfo = path23.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8695
+ writeFileSync10(dispatchInfo, JSON.stringify({
8444
8696
  dispatchedBy: mySession,
8445
8697
  rootExe: exeSession,
8446
8698
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -8514,15 +8766,15 @@ var init_tmux_routing = __esm({
8514
8766
  init_intercom_queue();
8515
8767
  init_plan_limits();
8516
8768
  init_employees();
8517
- SPAWN_LOCK_DIR = path22.join(os11.homedir(), ".exe-os", "spawn-locks");
8518
- SESSION_CACHE = path22.join(os11.homedir(), ".exe-os", "session-cache");
8769
+ SPAWN_LOCK_DIR = path23.join(os12.homedir(), ".exe-os", "spawn-locks");
8770
+ SESSION_CACHE = path23.join(os12.homedir(), ".exe-os", "session-cache");
8519
8771
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
8520
8772
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
8521
8773
  VERIFY_PANE_LINES = 200;
8522
8774
  INTERCOM_DEBOUNCE_MS = 3e4;
8523
8775
  CODEX_DEBOUNCE_MS = 12e4;
8524
- INTERCOM_LOG2 = path22.join(os11.homedir(), ".exe-os", "intercom.log");
8525
- DEBOUNCE_FILE = path22.join(SESSION_CACHE, "intercom-debounce.json");
8776
+ INTERCOM_LOG2 = path23.join(os12.homedir(), ".exe-os", "intercom.log");
8777
+ DEBOUNCE_FILE = path23.join(SESSION_CACHE, "intercom-debounce.json");
8526
8778
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
8527
8779
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
8528
8780
  }
@@ -8806,8 +9058,8 @@ init_config();
8806
9058
  init_config();
8807
9059
  init_store();
8808
9060
  import { spawn as spawn2 } from "child_process";
8809
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, existsSync as existsSync19, openSync as openSync2, closeSync as closeSync2 } from "fs";
8810
- import path23 from "path";
9061
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync20, openSync as openSync2, closeSync as closeSync2 } from "fs";
9062
+ import path24 from "path";
8811
9063
  import { fileURLToPath as fileURLToPath3 } from "url";
8812
9064
 
8813
9065
  // src/lib/hybrid-search.ts
@@ -9343,14 +9595,44 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
9343
9595
  }
9344
9596
  }
9345
9597
 
9598
+ // src/lib/cache-warmth.ts
9599
+ import os6 from "os";
9600
+ import path12 from "path";
9601
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
9602
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
9603
+ var CACHE_DIR = path12.join(
9604
+ process.env.EXE_OS_DIR ?? path12.join(os6.homedir(), ".exe-os"),
9605
+ "session-cache"
9606
+ );
9607
+ function getStatePath(sessionKey) {
9608
+ return path12.join(CACHE_DIR, `cache-warmth-${sessionKey}.json`);
9609
+ }
9610
+ function isCacheCold(sessionKey) {
9611
+ const statePath = getStatePath(sessionKey);
9612
+ if (!existsSync11(statePath)) {
9613
+ return { cold: true, idleMs: Number.POSITIVE_INFINITY };
9614
+ }
9615
+ try {
9616
+ const state = JSON.parse(readFileSync7(statePath, "utf-8"));
9617
+ const lastApiCallAt = new Date(state.lastApiCallAt).getTime();
9618
+ if (!Number.isFinite(lastApiCallAt)) {
9619
+ return { cold: true, idleMs: Number.POSITIVE_INFINITY };
9620
+ }
9621
+ const idleMs = Date.now() - lastApiCallAt;
9622
+ return { cold: idleMs > CACHE_TTL_MS, idleMs };
9623
+ } catch {
9624
+ return { cold: true, idleMs: Number.POSITIVE_INFINITY };
9625
+ }
9626
+ }
9627
+
9346
9628
  // src/lib/active-agent.ts
9347
9629
  init_config();
9348
9630
  init_session_key();
9349
9631
  init_employees();
9350
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
9632
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
9351
9633
  import { execSync as execSync5 } from "child_process";
9352
- import path12 from "path";
9353
- var CACHE_DIR = path12.join(EXE_AI_DIR, "session-cache");
9634
+ import path13 from "path";
9635
+ var CACHE_DIR2 = path13.join(EXE_AI_DIR, "session-cache");
9354
9636
  var STALE_MS = 24 * 60 * 60 * 1e3;
9355
9637
  function isNameWithOptionalInstance(candidate, baseName) {
9356
9638
  if (candidate === baseName) return true;
@@ -9395,19 +9677,19 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
9395
9677
  return null;
9396
9678
  }
9397
9679
  function getMarkerPath() {
9398
- return path12.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
9680
+ return path13.join(CACHE_DIR2, `active-agent-${getSessionKey()}.json`);
9399
9681
  }
9400
9682
  function getActiveAgent() {
9401
9683
  try {
9402
9684
  const markerPath = getMarkerPath();
9403
- const raw = readFileSync7(markerPath, "utf8");
9685
+ const raw = readFileSync8(markerPath, "utf8");
9404
9686
  const data = JSON.parse(raw);
9405
9687
  if (data.agentId) {
9406
9688
  if (data.startedAt) {
9407
9689
  const age = Date.now() - new Date(data.startedAt).getTime();
9408
9690
  if (age > STALE_MS) {
9409
9691
  try {
9410
- unlinkSync3(markerPath);
9692
+ unlinkSync4(markerPath);
9411
9693
  } catch {
9412
9694
  }
9413
9695
  } else {
@@ -9453,7 +9735,7 @@ if (!process.env.AGENT_ID) {
9453
9735
  if (!loadConfigSync().autoRetrieval) {
9454
9736
  process.exit(0);
9455
9737
  }
9456
- var WORKER_LOG_PATH = path23.join(EXE_AI_DIR, "workers.log");
9738
+ var WORKER_LOG_PATH = path24.join(EXE_AI_DIR, "workers.log");
9457
9739
  function openWorkerLog() {
9458
9740
  try {
9459
9741
  return openSync2(WORKER_LOG_PATH, "a");
@@ -9461,10 +9743,10 @@ function openWorkerLog() {
9461
9743
  return "ignore";
9462
9744
  }
9463
9745
  }
9464
- var CACHE_DIR2 = path23.join(EXE_AI_DIR, "session-cache");
9746
+ var CACHE_DIR3 = path24.join(EXE_AI_DIR, "session-cache");
9465
9747
  function loadInjectedIds(sessionId) {
9466
9748
  try {
9467
- const raw = readFileSync15(path23.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
9749
+ const raw = readFileSync16(path24.join(CACHE_DIR3, `${sessionId}.json`), "utf8");
9468
9750
  return new Set(JSON.parse(raw));
9469
9751
  } catch {
9470
9752
  return /* @__PURE__ */ new Set();
@@ -9472,9 +9754,9 @@ function loadInjectedIds(sessionId) {
9472
9754
  }
9473
9755
  function saveInjectedIds(sessionId, ids) {
9474
9756
  try {
9475
- mkdirSync9(CACHE_DIR2, { recursive: true });
9476
- writeFileSync10(
9477
- path23.join(CACHE_DIR2, `${sessionId}.json`),
9757
+ mkdirSync10(CACHE_DIR3, { recursive: true });
9758
+ writeFileSync11(
9759
+ path24.join(CACHE_DIR3, `${sessionId}.json`),
9478
9760
  JSON.stringify([...ids])
9479
9761
  );
9480
9762
  } catch {
@@ -9569,6 +9851,28 @@ process.stdin.on("end", async () => {
9569
9851
  }
9570
9852
  const config = await loadConfig();
9571
9853
  const search = config.hookSearchMode === "hybrid" ? hybridSearch : lightweightSearch;
9854
+ let cacheContext = "";
9855
+ try {
9856
+ const sessionKey = getSessionKey();
9857
+ const cacheStatus = isCacheCold(sessionKey);
9858
+ if (cacheStatus.cold) {
9859
+ const idleMinutes = Number.isFinite(cacheStatus.idleMs) ? `${Math.max(1, Math.floor(cacheStatus.idleMs / 6e4))}m` : "5m+";
9860
+ cacheContext = `## Cache Status
9861
+ \u26A0\uFE0F Cache cold (idle ${idleMinutes}). Next response will re-process the full context (higher cost). This is informational \u2014 no action needed.`;
9862
+ try {
9863
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
9864
+ const client = getClient2();
9865
+ await client.execute({
9866
+ sql: `UPDATE session_agent_map
9867
+ SET cache_cold_count = COALESCE(cache_cold_count, 0) + 1
9868
+ WHERE session_uuid = ?`,
9869
+ args: [data.session_id]
9870
+ });
9871
+ } catch {
9872
+ }
9873
+ }
9874
+ } catch {
9875
+ }
9572
9876
  const memories = await search(
9573
9877
  prompt.slice(0, 200),
9574
9878
  agent.agentId,
@@ -9592,7 +9896,7 @@ ${fresh.map(
9592
9896
  try {
9593
9897
  const { countPendingReviews: countPendingReviews2, countNewPendingReviewsSince: countNewPendingReviewsSince2 } = await Promise.resolve().then(() => (init_tasks_review(), tasks_review_exports));
9594
9898
  const sessionKey = getSessionKey();
9595
- const lastCheckPath = path23.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
9899
+ const lastCheckPath = path24.join(CACHE_DIR3, `review-lastcheck-${sessionKey}.json`);
9596
9900
  let sessionScope;
9597
9901
  try {
9598
9902
  const { execSync: execSync9 } = await import("child_process");
@@ -9602,7 +9906,7 @@ ${fresh.map(
9602
9906
  }
9603
9907
  let lastCheckedAt = "";
9604
9908
  try {
9605
- lastCheckedAt = readFileSync15(lastCheckPath, "utf8").trim();
9909
+ lastCheckedAt = readFileSync16(lastCheckPath, "utf8").trim();
9606
9910
  } catch {
9607
9911
  }
9608
9912
  const totalCount = await countPendingReviews2(sessionScope);
@@ -9637,12 +9941,12 @@ IMPORTANT: After completing your current task, you MUST address the pending revi
9637
9941
  }
9638
9942
  }
9639
9943
  const { writeFileSync: wf, mkdirSync: md } = await import("fs");
9640
- md(CACHE_DIR2, { recursive: true });
9944
+ md(CACHE_DIR3, { recursive: true });
9641
9945
  wf(lastCheckPath, (/* @__PURE__ */ new Date()).toISOString());
9642
9946
  } catch {
9643
9947
  }
9644
9948
  }
9645
- const combined = [memoryContext, reviewContext].filter(Boolean).join("\n");
9949
+ const combined = [cacheContext, memoryContext, reviewContext].filter(Boolean).join("\n\n");
9646
9950
  if (combined.length > 0) {
9647
9951
  const output = JSON.stringify({
9648
9952
  hookSpecificOutput: {
@@ -9660,11 +9964,11 @@ IMPORTANT: After completing your current task, you MUST address the pending revi
9660
9964
  function spawnPromptWorker(prompt, sessionId, agent) {
9661
9965
  if (!loadConfigSync().autoIngestion) return;
9662
9966
  try {
9663
- const workerPath = path23.resolve(
9664
- path23.dirname(fileURLToPath3(import.meta.url)),
9967
+ const workerPath = path24.resolve(
9968
+ path24.dirname(fileURLToPath3(import.meta.url)),
9665
9969
  "prompt-ingest-worker.js"
9666
9970
  );
9667
- if (!existsSync19(workerPath)) {
9971
+ if (!existsSync20(workerPath)) {
9668
9972
  process.stderr.write(`[prompt-submit] WARN: prompt-ingest-worker not found at ${workerPath}
9669
9973
  `);
9670
9974
  return;