@askexenow/exe-os 0.8.83 → 0.8.85

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 (95) hide show
  1. package/dist/bin/backfill-conversations.js +746 -595
  2. package/dist/bin/backfill-responses.js +745 -594
  3. package/dist/bin/backfill-vectors.js +312 -226
  4. package/dist/bin/cleanup-stale-review-tasks.js +97 -2
  5. package/dist/bin/cli.js +14350 -12518
  6. package/dist/bin/exe-agent.js +97 -88
  7. package/dist/bin/exe-assign.js +1003 -854
  8. package/dist/bin/exe-boot.js +1257 -320
  9. package/dist/bin/exe-call.js +10 -0
  10. package/dist/bin/exe-cloud.js +29 -6
  11. package/dist/bin/exe-dispatch.js +210 -34
  12. package/dist/bin/exe-doctor.js +403 -6
  13. package/dist/bin/exe-export-behaviors.js +175 -72
  14. package/dist/bin/exe-forget.js +97 -2
  15. package/dist/bin/exe-gateway.js +550 -171
  16. package/dist/bin/exe-healthcheck.js +1 -0
  17. package/dist/bin/exe-heartbeat.js +100 -5
  18. package/dist/bin/exe-kill.js +175 -72
  19. package/dist/bin/exe-launch-agent.js +189 -76
  20. package/dist/bin/exe-link.js +902 -80
  21. package/dist/bin/exe-new-employee.js +38 -8
  22. package/dist/bin/exe-pending-messages.js +96 -2
  23. package/dist/bin/exe-pending-notifications.js +97 -2
  24. package/dist/bin/exe-pending-reviews.js +98 -3
  25. package/dist/bin/exe-rename.js +564 -23
  26. package/dist/bin/exe-review.js +231 -73
  27. package/dist/bin/exe-search.js +989 -226
  28. package/dist/bin/exe-session-cleanup.js +4806 -1665
  29. package/dist/bin/exe-settings.js +20 -5
  30. package/dist/bin/exe-status.js +97 -2
  31. package/dist/bin/exe-team.js +97 -2
  32. package/dist/bin/git-sweep.js +899 -207
  33. package/dist/bin/graph-backfill.js +175 -72
  34. package/dist/bin/graph-export.js +175 -72
  35. package/dist/bin/install.js +38 -7
  36. package/dist/bin/list-providers.js +1 -0
  37. package/dist/bin/scan-tasks.js +904 -211
  38. package/dist/bin/setup.js +867 -268
  39. package/dist/bin/shard-migrate.js +175 -72
  40. package/dist/bin/update.js +1 -0
  41. package/dist/bin/wiki-sync.js +175 -72
  42. package/dist/gateway/index.js +548 -166
  43. package/dist/hooks/bug-report-worker.js +208 -23
  44. package/dist/hooks/commit-complete.js +897 -205
  45. package/dist/hooks/error-recall.js +988 -226
  46. package/dist/hooks/ingest-worker.js +1638 -1194
  47. package/dist/hooks/ingest.js +3 -0
  48. package/dist/hooks/instructions-loaded.js +707 -97
  49. package/dist/hooks/notification.js +699 -89
  50. package/dist/hooks/post-compact.js +714 -104
  51. package/dist/hooks/pre-compact.js +897 -205
  52. package/dist/hooks/pre-tool-use.js +742 -123
  53. package/dist/hooks/prompt-ingest-worker.js +242 -101
  54. package/dist/hooks/prompt-submit.js +995 -233
  55. package/dist/hooks/response-ingest-worker.js +242 -101
  56. package/dist/hooks/session-end.js +3941 -400
  57. package/dist/hooks/session-start.js +1001 -226
  58. package/dist/hooks/stop.js +725 -115
  59. package/dist/hooks/subagent-stop.js +714 -104
  60. package/dist/hooks/summary-worker.js +1964 -1330
  61. package/dist/index.js +1651 -1053
  62. package/dist/lib/cloud-sync.js +907 -86
  63. package/dist/lib/consolidation.js +2 -1
  64. package/dist/lib/database.js +642 -87
  65. package/dist/lib/db-daemon-client.js +503 -0
  66. package/dist/lib/device-registry.js +547 -7
  67. package/dist/lib/embedder.js +14 -28
  68. package/dist/lib/employee-templates.js +84 -74
  69. package/dist/lib/employees.js +9 -0
  70. package/dist/lib/exe-daemon-client.js +16 -29
  71. package/dist/lib/exe-daemon.js +1955 -922
  72. package/dist/lib/hybrid-search.js +988 -226
  73. package/dist/lib/identity.js +87 -67
  74. package/dist/lib/keychain.js +9 -1
  75. package/dist/lib/messaging.js +8 -1
  76. package/dist/lib/reminders.js +91 -74
  77. package/dist/lib/schedules.js +96 -2
  78. package/dist/lib/skill-learning.js +103 -85
  79. package/dist/lib/store.js +234 -73
  80. package/dist/lib/tasks.js +111 -22
  81. package/dist/lib/tmux-routing.js +120 -31
  82. package/dist/lib/token-spend.js +273 -0
  83. package/dist/lib/ws-client.js +11 -0
  84. package/dist/mcp/server.js +5222 -475
  85. package/dist/mcp/tools/complete-reminder.js +94 -77
  86. package/dist/mcp/tools/create-reminder.js +94 -77
  87. package/dist/mcp/tools/create-task.js +120 -22
  88. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  89. package/dist/mcp/tools/list-reminders.js +94 -77
  90. package/dist/mcp/tools/list-tasks.js +31 -1
  91. package/dist/mcp/tools/send-message.js +8 -1
  92. package/dist/mcp/tools/update-task.js +39 -10
  93. package/dist/runtime/index.js +911 -219
  94. package/dist/tui/App.js +997 -295
  95. package/package.json +6 -1
@@ -309,6 +309,12 @@ function getClient() {
309
309
  if (!_resilientClient) {
310
310
  throw new Error("Database client not initialized. Call initDatabase() first.");
311
311
  }
312
+ if (process.env.EXE_IS_DAEMON === "1") {
313
+ return _resilientClient;
314
+ }
315
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
316
+ return _daemonClient;
317
+ }
312
318
  return _resilientClient;
313
319
  }
314
320
  function getRawClient() {
@@ -797,6 +803,12 @@ async function ensureSchema() {
797
803
  } catch {
798
804
  }
799
805
  }
806
+ try {
807
+ await client.execute(
808
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
809
+ );
810
+ } catch {
811
+ }
800
812
  await client.executeMultiple(`
801
813
  CREATE TABLE IF NOT EXISTS entities (
802
814
  id TEXT PRIMARY KEY,
@@ -849,7 +861,30 @@ async function ensureSchema() {
849
861
  entity_id TEXT NOT NULL,
850
862
  PRIMARY KEY (hyperedge_id, entity_id)
851
863
  );
864
+
865
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
866
+ name,
867
+ content=entities,
868
+ content_rowid=rowid
869
+ );
870
+
871
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
872
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
873
+ END;
874
+
875
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
876
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
877
+ END;
878
+
879
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
880
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
881
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
882
+ END;
852
883
  `);
884
+ try {
885
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
886
+ } catch {
887
+ }
853
888
  await client.executeMultiple(`
854
889
  CREATE TABLE IF NOT EXISTS entity_aliases (
855
890
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1030,6 +1065,33 @@ async function ensureSchema() {
1030
1065
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1031
1066
  ON conversations(channel_id);
1032
1067
  `);
1068
+ await client.executeMultiple(`
1069
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1070
+ session_uuid TEXT PRIMARY KEY,
1071
+ agent_id TEXT NOT NULL,
1072
+ session_name TEXT,
1073
+ task_id TEXT,
1074
+ project_name TEXT,
1075
+ started_at TEXT NOT NULL
1076
+ );
1077
+
1078
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1079
+ ON session_agent_map(agent_id);
1080
+ `);
1081
+ try {
1082
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1083
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1084
+ await client.execute({
1085
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1086
+ SELECT session_id, agent_id, '', MIN(timestamp)
1087
+ FROM memories
1088
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1089
+ GROUP BY session_id, agent_id`,
1090
+ args: []
1091
+ });
1092
+ }
1093
+ } catch {
1094
+ }
1033
1095
  try {
1034
1096
  await client.execute({
1035
1097
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1163,8 +1225,30 @@ async function ensureSchema() {
1163
1225
  });
1164
1226
  } catch {
1165
1227
  }
1228
+ for (const col of [
1229
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
1230
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
1231
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
1232
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
1233
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
1234
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
1235
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
1236
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
1237
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
1238
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
1239
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
1240
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
1241
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
1242
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
1243
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1244
+ ]) {
1245
+ try {
1246
+ await client.execute(col);
1247
+ } catch {
1248
+ }
1249
+ }
1166
1250
  }
1167
- var _client, _resilientClient, initTurso;
1251
+ var _client, _resilientClient, _daemonClient, initTurso;
1168
1252
  var init_database = __esm({
1169
1253
  "src/lib/database.ts"() {
1170
1254
  "use strict";
@@ -1172,6 +1256,7 @@ var init_database = __esm({
1172
1256
  init_employees();
1173
1257
  _client = null;
1174
1258
  _resilientClient = null;
1259
+ _daemonClient = null;
1175
1260
  initTurso = initDatabase;
1176
1261
  }
1177
1262
  });
@@ -1602,546 +1687,135 @@ ${p.content}`).join("\n\n");
1602
1687
  }
1603
1688
  });
1604
1689
 
1605
- // src/bin/backfill-conversations.ts
1606
- import crypto from "crypto";
1607
- import { createReadStream } from "fs";
1608
- import { readdir, stat } from "fs/promises";
1609
- import path6 from "path";
1610
- import { createInterface } from "readline";
1611
- import { homedir } from "os";
1612
- import { parseArgs } from "util";
1613
-
1614
- // src/types/memory.ts
1615
- var EMBEDDING_DIM = 1024;
1616
-
1617
- // src/lib/store.ts
1618
- init_database();
1619
-
1620
- // src/lib/keychain.ts
1621
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1622
- import { existsSync as existsSync3 } from "fs";
1623
- import path3 from "path";
1624
- import os3 from "os";
1625
- var SERVICE = "exe-mem";
1626
- var ACCOUNT = "master-key";
1627
- function getKeyDir() {
1628
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
1629
- }
1630
- function getKeyPath() {
1631
- return path3.join(getKeyDir(), "master.key");
1632
- }
1633
- async function tryKeytar() {
1634
- try {
1635
- return await import("keytar");
1636
- } catch {
1637
- return null;
1690
+ // src/lib/exe-daemon-client.ts
1691
+ import net from "net";
1692
+ import { spawn } from "child_process";
1693
+ import { randomUUID as randomUUID2 } from "crypto";
1694
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1695
+ import path5 from "path";
1696
+ import { fileURLToPath } from "url";
1697
+ function handleData(chunk) {
1698
+ _buffer += chunk.toString();
1699
+ if (_buffer.length > MAX_BUFFER) {
1700
+ _buffer = "";
1701
+ return;
1638
1702
  }
1639
- }
1640
- async function getMasterKey() {
1641
- const keytar = await tryKeytar();
1642
- if (keytar) {
1703
+ let newlineIdx;
1704
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1705
+ const line = _buffer.slice(0, newlineIdx).trim();
1706
+ _buffer = _buffer.slice(newlineIdx + 1);
1707
+ if (!line) continue;
1643
1708
  try {
1644
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
1645
- if (stored) {
1646
- return Buffer.from(stored, "base64");
1709
+ const response = JSON.parse(line);
1710
+ const id = response.id;
1711
+ if (!id) continue;
1712
+ const entry = _pending.get(id);
1713
+ if (entry) {
1714
+ clearTimeout(entry.timer);
1715
+ _pending.delete(id);
1716
+ entry.resolve(response);
1647
1717
  }
1648
1718
  } catch {
1649
1719
  }
1650
1720
  }
1651
- const keyPath = getKeyPath();
1652
- if (!existsSync3(keyPath)) {
1653
- return null;
1654
- }
1655
- try {
1656
- const content = await readFile3(keyPath, "utf-8");
1657
- return Buffer.from(content.trim(), "base64");
1658
- } catch {
1659
- return null;
1660
- }
1661
1721
  }
1662
-
1663
- // src/lib/store.ts
1664
- init_config();
1665
-
1666
- // src/lib/state-bus.ts
1667
- var StateBus = class {
1668
- handlers = /* @__PURE__ */ new Map();
1669
- globalHandlers = /* @__PURE__ */ new Set();
1670
- /** Emit an event to all subscribers */
1671
- emit(event) {
1672
- const typeHandlers = this.handlers.get(event.type);
1673
- if (typeHandlers) {
1674
- for (const handler of typeHandlers) {
1722
+ function cleanupStaleFiles() {
1723
+ if (existsSync5(PID_PATH)) {
1724
+ try {
1725
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1726
+ if (pid > 0) {
1675
1727
  try {
1676
- handler(event);
1728
+ process.kill(pid, 0);
1729
+ return;
1677
1730
  } catch {
1678
1731
  }
1679
1732
  }
1733
+ } catch {
1680
1734
  }
1681
- for (const handler of this.globalHandlers) {
1682
- try {
1683
- handler(event);
1684
- } catch {
1685
- }
1735
+ try {
1736
+ unlinkSync2(PID_PATH);
1737
+ } catch {
1686
1738
  }
1687
- }
1688
- /** Subscribe to a specific event type */
1689
- on(type, handler) {
1690
- if (!this.handlers.has(type)) {
1691
- this.handlers.set(type, /* @__PURE__ */ new Set());
1739
+ try {
1740
+ unlinkSync2(SOCKET_PATH);
1741
+ } catch {
1692
1742
  }
1693
- this.handlers.get(type).add(handler);
1694
1743
  }
1695
- /** Subscribe to ALL events */
1696
- onAny(handler) {
1697
- this.globalHandlers.add(handler);
1744
+ }
1745
+ function findPackageRoot() {
1746
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1747
+ const { root } = path5.parse(dir);
1748
+ while (dir !== root) {
1749
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1750
+ dir = path5.dirname(dir);
1698
1751
  }
1699
- /** Unsubscribe from a specific event type */
1700
- off(type, handler) {
1701
- this.handlers.get(type)?.delete(handler);
1752
+ return null;
1753
+ }
1754
+ function spawnDaemon() {
1755
+ const pkgRoot = findPackageRoot();
1756
+ if (!pkgRoot) {
1757
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
1758
+ return;
1702
1759
  }
1703
- /** Unsubscribe from ALL events */
1704
- offAny(handler) {
1705
- this.globalHandlers.delete(handler);
1760
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1761
+ if (!existsSync5(daemonPath)) {
1762
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1763
+ `);
1764
+ return;
1706
1765
  }
1707
- /** Remove all listeners */
1708
- clear() {
1709
- this.handlers.clear();
1710
- this.globalHandlers.clear();
1766
+ const resolvedPath = daemonPath;
1767
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1768
+ `);
1769
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1770
+ let stderrFd = "ignore";
1771
+ try {
1772
+ stderrFd = openSync(logPath, "a");
1773
+ } catch {
1711
1774
  }
1712
- };
1713
- var orgBus = new StateBus();
1714
-
1715
- // src/lib/store.ts
1716
- var INIT_MAX_RETRIES = 3;
1717
- var INIT_RETRY_DELAY_MS = 1e3;
1718
- function isBusyError2(err) {
1719
- if (err instanceof Error) {
1720
- const msg = err.message.toLowerCase();
1721
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
1775
+ const child = spawn(process.execPath, [resolvedPath], {
1776
+ detached: true,
1777
+ stdio: ["ignore", "ignore", stderrFd],
1778
+ env: {
1779
+ ...process.env,
1780
+ TMUX: void 0,
1781
+ // Daemon is global — must not inherit session scope
1782
+ TMUX_PANE: void 0,
1783
+ // Prevents resolveExeSession() from scoping to one session
1784
+ EXE_DAEMON_SOCK: SOCKET_PATH,
1785
+ EXE_DAEMON_PID: PID_PATH
1786
+ }
1787
+ });
1788
+ child.unref();
1789
+ if (typeof stderrFd === "number") {
1790
+ try {
1791
+ closeSync(stderrFd);
1792
+ } catch {
1793
+ }
1722
1794
  }
1723
- return false;
1724
1795
  }
1725
- async function retryOnBusy2(fn, label) {
1726
- for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1796
+ function acquireSpawnLock() {
1797
+ try {
1798
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1799
+ closeSync(fd);
1800
+ return true;
1801
+ } catch {
1727
1802
  try {
1728
- return await fn();
1729
- } catch (err) {
1730
- if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1731
- process.stderr.write(
1732
- `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1733
- `
1734
- );
1735
- await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1803
+ const stat2 = statSync(SPAWN_LOCK_PATH);
1804
+ if (Date.now() - stat2.mtimeMs > SPAWN_LOCK_STALE_MS) {
1805
+ try {
1806
+ unlinkSync2(SPAWN_LOCK_PATH);
1807
+ } catch {
1808
+ }
1809
+ try {
1810
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1811
+ closeSync(fd);
1812
+ return true;
1813
+ } catch {
1814
+ }
1815
+ }
1816
+ } catch {
1736
1817
  }
1737
- }
1738
- throw new Error("unreachable");
1739
- }
1740
- var _pendingRecords = [];
1741
- var _batchSize = 20;
1742
- var _flushIntervalMs = 1e4;
1743
- var _flushTimer = null;
1744
- var _flushing = false;
1745
- var _nextVersion = 1;
1746
- async function initStore(options) {
1747
- if (_flushTimer !== null) {
1748
- clearInterval(_flushTimer);
1749
- _flushTimer = null;
1750
- }
1751
- _pendingRecords = [];
1752
- _flushing = false;
1753
- _batchSize = options?.batchSize ?? 20;
1754
- _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
1755
- let dbPath = options?.dbPath;
1756
- if (!dbPath) {
1757
- const config = await loadConfig();
1758
- dbPath = config.dbPath;
1759
- }
1760
- let masterKey = options?.masterKey ?? null;
1761
- if (!masterKey) {
1762
- masterKey = await getMasterKey();
1763
- if (!masterKey) {
1764
- throw new Error(
1765
- "No encryption key found. Run /exe-setup to generate one."
1766
- );
1767
- }
1768
- }
1769
- const hexKey = masterKey.toString("hex");
1770
- await initTurso({
1771
- dbPath,
1772
- encryptionKey: hexKey
1773
- });
1774
- await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1775
- try {
1776
- const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1777
- initShardManager2(hexKey);
1778
- } catch {
1779
- }
1780
- const client = getClient();
1781
- const vResult = await retryOnBusy2(
1782
- () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1783
- "version-query"
1784
- );
1785
- _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1786
- try {
1787
- const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
1788
- await loadGlobalProcedures2();
1789
- } catch {
1790
- }
1791
- }
1792
- function classifyTier(record) {
1793
- if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
1794
- if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1795
- return 3;
1796
- }
1797
- async function writeMemory(record) {
1798
- if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1799
- throw new Error(
1800
- `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
1801
- );
1802
- }
1803
- const dbRow = {
1804
- id: record.id,
1805
- agent_id: record.agent_id,
1806
- agent_role: record.agent_role,
1807
- session_id: record.session_id,
1808
- timestamp: record.timestamp,
1809
- tool_name: record.tool_name,
1810
- project_name: record.project_name,
1811
- has_error: record.has_error ? 1 : 0,
1812
- raw_text: record.raw_text,
1813
- vector: record.vector,
1814
- version: 0,
1815
- // Placeholder — assigned atomically at flush time
1816
- task_id: record.task_id ?? null,
1817
- importance: record.importance ?? 5,
1818
- status: record.status ?? "active",
1819
- confidence: record.confidence ?? 0.7,
1820
- last_accessed: record.last_accessed ?? record.timestamp,
1821
- workspace_id: record.workspace_id ?? null,
1822
- document_id: record.document_id ?? null,
1823
- user_id: record.user_id ?? null,
1824
- char_offset: record.char_offset ?? null,
1825
- page_number: record.page_number ?? null,
1826
- source_path: record.source_path ?? null,
1827
- source_type: record.source_type ?? null,
1828
- tier: record.tier ?? classifyTier(record),
1829
- supersedes_id: record.supersedes_id ?? null,
1830
- draft: record.draft ? 1 : 0,
1831
- memory_type: record.memory_type ?? "raw",
1832
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
1833
- };
1834
- _pendingRecords.push(dbRow);
1835
- orgBus.emit({
1836
- type: "memory_stored",
1837
- agentId: record.agent_id,
1838
- project: record.project_name,
1839
- timestamp: record.timestamp
1840
- });
1841
- const MAX_PENDING = 1e3;
1842
- if (_pendingRecords.length > MAX_PENDING) {
1843
- const dropped = _pendingRecords.length - MAX_PENDING;
1844
- _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
1845
- console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
1846
- }
1847
- if (_flushTimer === null) {
1848
- _flushTimer = setInterval(() => {
1849
- void flushBatch();
1850
- }, _flushIntervalMs);
1851
- if (_flushTimer && typeof _flushTimer === "object" && "unref" in _flushTimer) {
1852
- _flushTimer.unref();
1853
- }
1854
- }
1855
- if (_pendingRecords.length >= _batchSize) {
1856
- await flushBatch();
1857
- }
1858
- }
1859
- async function flushBatch() {
1860
- if (_flushing || _pendingRecords.length === 0) return 0;
1861
- _flushing = true;
1862
- try {
1863
- const batch = _pendingRecords.slice(0);
1864
- const client = getClient();
1865
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1866
- let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1867
- for (const row of batch) {
1868
- row.version = baseVersion++;
1869
- }
1870
- _nextVersion = baseVersion;
1871
- const buildStmt = (row) => {
1872
- const hasVector = row.vector !== null;
1873
- const taskId = row.task_id ?? null;
1874
- const importance = row.importance ?? 5;
1875
- const status = row.status ?? "active";
1876
- const confidence = row.confidence ?? 0.7;
1877
- const lastAccessed = row.last_accessed ?? row.timestamp;
1878
- const workspaceId = row.workspace_id ?? null;
1879
- const documentId = row.document_id ?? null;
1880
- const userId = row.user_id ?? null;
1881
- const charOffset = row.char_offset ?? null;
1882
- const pageNumber = row.page_number ?? null;
1883
- const sourcePath = row.source_path ?? null;
1884
- const sourceType = row.source_type ?? null;
1885
- const tier = row.tier ?? 3;
1886
- const supersedesId = row.supersedes_id ?? null;
1887
- const draft = row.draft ? 1 : 0;
1888
- const memoryType = row.memory_type ?? "raw";
1889
- const trajectory = row.trajectory ?? null;
1890
- return {
1891
- sql: hasVector ? `INSERT OR IGNORE INTO memories
1892
- (id, agent_id, agent_role, session_id, timestamp,
1893
- tool_name, project_name,
1894
- has_error, raw_text, vector, version, task_id, importance, status,
1895
- confidence, last_accessed,
1896
- workspace_id, document_id, user_id, char_offset, page_number,
1897
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1898
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1899
- (id, agent_id, agent_role, session_id, timestamp,
1900
- tool_name, project_name,
1901
- has_error, raw_text, vector, version, task_id, importance, status,
1902
- confidence, last_accessed,
1903
- workspace_id, document_id, user_id, char_offset, page_number,
1904
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1905
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1906
- args: hasVector ? [
1907
- row.id,
1908
- row.agent_id,
1909
- row.agent_role,
1910
- row.session_id,
1911
- row.timestamp,
1912
- row.tool_name,
1913
- row.project_name,
1914
- row.has_error,
1915
- row.raw_text,
1916
- vectorToBlob(row.vector),
1917
- row.version,
1918
- taskId,
1919
- importance,
1920
- status,
1921
- confidence,
1922
- lastAccessed,
1923
- workspaceId,
1924
- documentId,
1925
- userId,
1926
- charOffset,
1927
- pageNumber,
1928
- sourcePath,
1929
- sourceType,
1930
- tier,
1931
- supersedesId,
1932
- draft,
1933
- memoryType,
1934
- trajectory
1935
- ] : [
1936
- row.id,
1937
- row.agent_id,
1938
- row.agent_role,
1939
- row.session_id,
1940
- row.timestamp,
1941
- row.tool_name,
1942
- row.project_name,
1943
- row.has_error,
1944
- row.raw_text,
1945
- row.version,
1946
- taskId,
1947
- importance,
1948
- status,
1949
- confidence,
1950
- lastAccessed,
1951
- workspaceId,
1952
- documentId,
1953
- userId,
1954
- charOffset,
1955
- pageNumber,
1956
- sourcePath,
1957
- sourceType,
1958
- tier,
1959
- supersedesId,
1960
- draft,
1961
- memoryType,
1962
- trajectory
1963
- ]
1964
- };
1965
- };
1966
- const globalClient = getClient();
1967
- const globalStmts = batch.map(buildStmt);
1968
- await globalClient.batch(globalStmts, "write");
1969
- _pendingRecords.splice(0, batch.length);
1970
- try {
1971
- const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1972
- if (isShardingEnabled2()) {
1973
- const byProject = /* @__PURE__ */ new Map();
1974
- for (const row of batch) {
1975
- const proj = row.project_name || "unknown";
1976
- if (!byProject.has(proj)) byProject.set(proj, []);
1977
- byProject.get(proj).push(row);
1978
- }
1979
- for (const [project, rows] of byProject) {
1980
- try {
1981
- const shardClient = await getReadyShardClient2(project);
1982
- const shardStmts = rows.map(buildStmt);
1983
- await shardClient.batch(shardStmts, "write");
1984
- } catch (err) {
1985
- process.stderr.write(
1986
- `[store] Shard write failed for ${project}: ${err instanceof Error ? err.message : String(err)}
1987
- `
1988
- );
1989
- }
1990
- }
1991
- }
1992
- } catch {
1993
- }
1994
- return batch.length;
1995
- } finally {
1996
- _flushing = false;
1997
- }
1998
- }
1999
- function vectorToBlob(vector) {
2000
- const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
2001
- return JSON.stringify(Array.from(f32));
2002
- }
2003
-
2004
- // src/lib/exe-daemon-client.ts
2005
- init_config();
2006
- import net from "net";
2007
- import { spawn } from "child_process";
2008
- import { randomUUID as randomUUID2 } from "crypto";
2009
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2010
- import path5 from "path";
2011
- import { fileURLToPath } from "url";
2012
- var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
2013
- var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
2014
- var SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
2015
- var SPAWN_LOCK_STALE_MS = 3e4;
2016
- var CONNECT_TIMEOUT_MS = 15e3;
2017
- var REQUEST_TIMEOUT_MS = 3e4;
2018
- var _socket = null;
2019
- var _connected = false;
2020
- var _buffer = "";
2021
- var _requestCount = 0;
2022
- var HEALTH_CHECK_INTERVAL = 100;
2023
- var _pending = /* @__PURE__ */ new Map();
2024
- var MAX_BUFFER = 1e7;
2025
- function handleData(chunk) {
2026
- _buffer += chunk.toString();
2027
- if (_buffer.length > MAX_BUFFER) {
2028
- _buffer = "";
2029
- return;
2030
- }
2031
- let newlineIdx;
2032
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
2033
- const line = _buffer.slice(0, newlineIdx).trim();
2034
- _buffer = _buffer.slice(newlineIdx + 1);
2035
- if (!line) continue;
2036
- try {
2037
- const response = JSON.parse(line);
2038
- const entry = _pending.get(response.id);
2039
- if (entry) {
2040
- clearTimeout(entry.timer);
2041
- _pending.delete(response.id);
2042
- entry.resolve(response);
2043
- }
2044
- } catch {
2045
- }
2046
- }
2047
- }
2048
- function cleanupStaleFiles() {
2049
- if (existsSync5(PID_PATH)) {
2050
- try {
2051
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2052
- if (pid > 0) {
2053
- try {
2054
- process.kill(pid, 0);
2055
- return;
2056
- } catch {
2057
- }
2058
- }
2059
- } catch {
2060
- }
2061
- try {
2062
- unlinkSync2(PID_PATH);
2063
- } catch {
2064
- }
2065
- try {
2066
- unlinkSync2(SOCKET_PATH);
2067
- } catch {
2068
- }
2069
- }
2070
- }
2071
- function findPackageRoot() {
2072
- let dir = path5.dirname(fileURLToPath(import.meta.url));
2073
- const { root } = path5.parse(dir);
2074
- while (dir !== root) {
2075
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
2076
- dir = path5.dirname(dir);
2077
- }
2078
- return null;
2079
- }
2080
- function spawnDaemon() {
2081
- const pkgRoot = findPackageRoot();
2082
- if (!pkgRoot) {
2083
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
2084
- return;
2085
- }
2086
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2087
- if (!existsSync5(daemonPath)) {
2088
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2089
- `);
2090
- return;
2091
- }
2092
- const resolvedPath = daemonPath;
2093
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2094
- `);
2095
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2096
- let stderrFd = "ignore";
2097
- try {
2098
- stderrFd = openSync(logPath, "a");
2099
- } catch {
2100
- }
2101
- const child = spawn(process.execPath, [resolvedPath], {
2102
- detached: true,
2103
- stdio: ["ignore", "ignore", stderrFd],
2104
- env: {
2105
- ...process.env,
2106
- TMUX: void 0,
2107
- // Daemon is global — must not inherit session scope
2108
- TMUX_PANE: void 0,
2109
- // Prevents resolveExeSession() from scoping to one session
2110
- EXE_DAEMON_SOCK: SOCKET_PATH,
2111
- EXE_DAEMON_PID: PID_PATH
2112
- }
2113
- });
2114
- child.unref();
2115
- if (typeof stderrFd === "number") {
2116
- try {
2117
- closeSync(stderrFd);
2118
- } catch {
2119
- }
2120
- }
2121
- }
2122
- function acquireSpawnLock() {
2123
- try {
2124
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
2125
- closeSync(fd);
2126
- return true;
2127
- } catch {
2128
- try {
2129
- const stat2 = statSync(SPAWN_LOCK_PATH);
2130
- if (Date.now() - stat2.mtimeMs > SPAWN_LOCK_STALE_MS) {
2131
- try {
2132
- unlinkSync2(SPAWN_LOCK_PATH);
2133
- } catch {
2134
- }
2135
- try {
2136
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
2137
- closeSync(fd);
2138
- return true;
2139
- } catch {
2140
- }
2141
- }
2142
- } catch {
2143
- }
2144
- return false;
1818
+ return false;
2145
1819
  }
2146
1820
  }
2147
1821
  function releaseSpawnLock() {
@@ -2209,6 +1883,9 @@ async function connectEmbedDaemon() {
2209
1883
  return false;
2210
1884
  }
2211
1885
  function sendRequest(texts, priority) {
1886
+ return sendDaemonRequest({ texts, priority });
1887
+ }
1888
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2212
1889
  return new Promise((resolve) => {
2213
1890
  if (!_socket || !_connected) {
2214
1891
  resolve({ error: "Not connected" });
@@ -2218,10 +1895,10 @@ function sendRequest(texts, priority) {
2218
1895
  const timer = setTimeout(() => {
2219
1896
  _pending.delete(id);
2220
1897
  resolve({ error: "Request timeout" });
2221
- }, REQUEST_TIMEOUT_MS);
1898
+ }, timeoutMs);
2222
1899
  _pending.set(id, { resolve, timer });
2223
1900
  try {
2224
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
1901
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2225
1902
  } catch {
2226
1903
  clearTimeout(timer);
2227
1904
  _pending.delete(id);
@@ -2231,103 +1908,576 @@ function sendRequest(texts, priority) {
2231
1908
  }
2232
1909
  async function pingDaemon() {
2233
1910
  if (!_socket || !_connected) return null;
2234
- return new Promise((resolve) => {
2235
- const id = randomUUID2();
2236
- const timer = setTimeout(() => {
2237
- _pending.delete(id);
2238
- resolve(null);
2239
- }, 5e3);
2240
- _pending.set(id, {
2241
- resolve: (data) => {
2242
- if (data.health) {
2243
- resolve(data.health);
2244
- } else {
2245
- resolve(null);
1911
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
1912
+ if (response.health) {
1913
+ return response.health;
1914
+ }
1915
+ return null;
1916
+ }
1917
+ function killAndRespawnDaemon() {
1918
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
1919
+ if (existsSync5(PID_PATH)) {
1920
+ try {
1921
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1922
+ if (pid > 0) {
1923
+ try {
1924
+ process.kill(pid, "SIGKILL");
1925
+ } catch {
2246
1926
  }
2247
- },
2248
- timer
2249
- });
1927
+ }
1928
+ } catch {
1929
+ }
1930
+ }
1931
+ if (_socket) {
1932
+ _socket.destroy();
1933
+ _socket = null;
1934
+ }
1935
+ _connected = false;
1936
+ _buffer = "";
1937
+ try {
1938
+ unlinkSync2(PID_PATH);
1939
+ } catch {
1940
+ }
1941
+ try {
1942
+ unlinkSync2(SOCKET_PATH);
1943
+ } catch {
1944
+ }
1945
+ spawnDaemon();
1946
+ }
1947
+ async function embedViaClient(text, priority = "high") {
1948
+ if (!_connected && !await connectEmbedDaemon()) return null;
1949
+ _requestCount++;
1950
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1951
+ const health = await pingDaemon();
1952
+ if (!health) {
1953
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1954
+ `);
1955
+ killAndRespawnDaemon();
1956
+ const start = Date.now();
1957
+ let delay2 = 200;
1958
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1959
+ await new Promise((r) => setTimeout(r, delay2));
1960
+ if (await connectToSocket()) break;
1961
+ delay2 = Math.min(delay2 * 2, 3e3);
1962
+ }
1963
+ if (!_connected) return null;
1964
+ }
1965
+ }
1966
+ const result = await sendRequest([text], priority);
1967
+ if (!result.error && result.vectors?.[0]) return result.vectors[0];
1968
+ if (result.error) {
1969
+ process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
1970
+ `);
1971
+ killAndRespawnDaemon();
1972
+ const start = Date.now();
1973
+ let delay2 = 200;
1974
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1975
+ await new Promise((r) => setTimeout(r, delay2));
1976
+ if (await connectToSocket()) break;
1977
+ delay2 = Math.min(delay2 * 2, 3e3);
1978
+ }
1979
+ if (!_connected) return null;
1980
+ const retry = await sendRequest([text], priority);
1981
+ if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
1982
+ process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
1983
+ `);
1984
+ }
1985
+ return null;
1986
+ }
1987
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
1988
+ var init_exe_daemon_client = __esm({
1989
+ "src/lib/exe-daemon-client.ts"() {
1990
+ "use strict";
1991
+ init_config();
1992
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1993
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1994
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1995
+ SPAWN_LOCK_STALE_MS = 3e4;
1996
+ CONNECT_TIMEOUT_MS = 15e3;
1997
+ REQUEST_TIMEOUT_MS = 3e4;
1998
+ _socket = null;
1999
+ _connected = false;
2000
+ _buffer = "";
2001
+ _requestCount = 0;
2002
+ HEALTH_CHECK_INTERVAL = 100;
2003
+ _pending = /* @__PURE__ */ new Map();
2004
+ MAX_BUFFER = 1e7;
2005
+ }
2006
+ });
2007
+
2008
+ // src/bin/backfill-conversations.ts
2009
+ import crypto from "crypto";
2010
+ import { createReadStream } from "fs";
2011
+ import { readdir, stat } from "fs/promises";
2012
+ import path6 from "path";
2013
+ import { createInterface } from "readline";
2014
+ import { homedir } from "os";
2015
+ import { parseArgs } from "util";
2016
+
2017
+ // src/lib/store.ts
2018
+ import { createHash } from "crypto";
2019
+
2020
+ // src/types/memory.ts
2021
+ var EMBEDDING_DIM = 1024;
2022
+
2023
+ // src/lib/store.ts
2024
+ init_database();
2025
+
2026
+ // src/lib/keychain.ts
2027
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2028
+ import { existsSync as existsSync3 } from "fs";
2029
+ import path3 from "path";
2030
+ import os3 from "os";
2031
+ var SERVICE = "exe-mem";
2032
+ var ACCOUNT = "master-key";
2033
+ function getKeyDir() {
2034
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
2035
+ }
2036
+ function getKeyPath() {
2037
+ return path3.join(getKeyDir(), "master.key");
2038
+ }
2039
+ async function tryKeytar() {
2040
+ try {
2041
+ return await import("keytar");
2042
+ } catch {
2043
+ return null;
2044
+ }
2045
+ }
2046
+ async function getMasterKey() {
2047
+ const keytar = await tryKeytar();
2048
+ if (keytar) {
2250
2049
  try {
2251
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
2050
+ const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2051
+ if (stored) {
2052
+ return Buffer.from(stored, "base64");
2053
+ }
2252
2054
  } catch {
2253
- clearTimeout(timer);
2254
- _pending.delete(id);
2255
- resolve(null);
2256
2055
  }
2257
- });
2056
+ }
2057
+ const keyPath = getKeyPath();
2058
+ if (!existsSync3(keyPath)) {
2059
+ process.stderr.write(
2060
+ `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2061
+ `
2062
+ );
2063
+ return null;
2064
+ }
2065
+ try {
2066
+ const content = await readFile3(keyPath, "utf-8");
2067
+ return Buffer.from(content.trim(), "base64");
2068
+ } catch (err) {
2069
+ process.stderr.write(
2070
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
2071
+ `
2072
+ );
2073
+ return null;
2074
+ }
2075
+ }
2076
+
2077
+ // src/lib/store.ts
2078
+ init_config();
2079
+
2080
+ // src/lib/state-bus.ts
2081
+ var StateBus = class {
2082
+ handlers = /* @__PURE__ */ new Map();
2083
+ globalHandlers = /* @__PURE__ */ new Set();
2084
+ /** Emit an event to all subscribers */
2085
+ emit(event) {
2086
+ const typeHandlers = this.handlers.get(event.type);
2087
+ if (typeHandlers) {
2088
+ for (const handler of typeHandlers) {
2089
+ try {
2090
+ handler(event);
2091
+ } catch {
2092
+ }
2093
+ }
2094
+ }
2095
+ for (const handler of this.globalHandlers) {
2096
+ try {
2097
+ handler(event);
2098
+ } catch {
2099
+ }
2100
+ }
2101
+ }
2102
+ /** Subscribe to a specific event type */
2103
+ on(type, handler) {
2104
+ if (!this.handlers.has(type)) {
2105
+ this.handlers.set(type, /* @__PURE__ */ new Set());
2106
+ }
2107
+ this.handlers.get(type).add(handler);
2108
+ }
2109
+ /** Subscribe to ALL events */
2110
+ onAny(handler) {
2111
+ this.globalHandlers.add(handler);
2112
+ }
2113
+ /** Unsubscribe from a specific event type */
2114
+ off(type, handler) {
2115
+ this.handlers.get(type)?.delete(handler);
2116
+ }
2117
+ /** Unsubscribe from ALL events */
2118
+ offAny(handler) {
2119
+ this.globalHandlers.delete(handler);
2120
+ }
2121
+ /** Remove all listeners */
2122
+ clear() {
2123
+ this.handlers.clear();
2124
+ this.globalHandlers.clear();
2125
+ }
2126
+ };
2127
+ var orgBus = new StateBus();
2128
+
2129
+ // src/lib/store.ts
2130
+ var INIT_MAX_RETRIES = 3;
2131
+ var INIT_RETRY_DELAY_MS = 1e3;
2132
+ function isBusyError2(err) {
2133
+ if (err instanceof Error) {
2134
+ const msg = err.message.toLowerCase();
2135
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
2136
+ }
2137
+ return false;
2258
2138
  }
2259
- function killAndRespawnDaemon() {
2260
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
2261
- if (existsSync5(PID_PATH)) {
2139
+ async function retryOnBusy2(fn, label) {
2140
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
2262
2141
  try {
2263
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2264
- if (pid > 0) {
2265
- try {
2266
- process.kill(pid, "SIGKILL");
2267
- } catch {
2268
- }
2269
- }
2270
- } catch {
2142
+ return await fn();
2143
+ } catch (err) {
2144
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
2145
+ process.stderr.write(
2146
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
2147
+ `
2148
+ );
2149
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
2271
2150
  }
2272
2151
  }
2273
- if (_socket) {
2274
- _socket.destroy();
2275
- _socket = null;
2152
+ throw new Error("unreachable");
2153
+ }
2154
+ var _pendingRecords = [];
2155
+ var _batchSize = 20;
2156
+ var _flushIntervalMs = 1e4;
2157
+ var _flushTimer = null;
2158
+ var _flushing = false;
2159
+ var _nextVersion = 1;
2160
+ async function initStore(options) {
2161
+ if (_flushTimer !== null) {
2162
+ clearInterval(_flushTimer);
2163
+ _flushTimer = null;
2276
2164
  }
2277
- _connected = false;
2278
- _buffer = "";
2165
+ _pendingRecords = [];
2166
+ _flushing = false;
2167
+ _batchSize = options?.batchSize ?? 20;
2168
+ _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
2169
+ let dbPath = options?.dbPath;
2170
+ if (!dbPath) {
2171
+ const config = await loadConfig();
2172
+ dbPath = config.dbPath;
2173
+ }
2174
+ let masterKey = options?.masterKey ?? null;
2175
+ if (!masterKey) {
2176
+ masterKey = await getMasterKey();
2177
+ if (!masterKey) {
2178
+ throw new Error(
2179
+ "No encryption key found. Run /exe-setup to generate one."
2180
+ );
2181
+ }
2182
+ }
2183
+ const hexKey = masterKey.toString("hex");
2184
+ await initTurso({
2185
+ dbPath,
2186
+ encryptionKey: hexKey
2187
+ });
2188
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
2279
2189
  try {
2280
- unlinkSync2(PID_PATH);
2190
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
2191
+ initShardManager2(hexKey);
2281
2192
  } catch {
2282
2193
  }
2194
+ const client = getClient();
2195
+ const vResult = await retryOnBusy2(
2196
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
2197
+ "version-query"
2198
+ );
2199
+ _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2283
2200
  try {
2284
- unlinkSync2(SOCKET_PATH);
2201
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
2202
+ await loadGlobalProcedures2();
2285
2203
  } catch {
2286
2204
  }
2287
- spawnDaemon();
2288
2205
  }
2289
- async function embedViaClient(text, priority = "high") {
2290
- if (!_connected && !await connectEmbedDaemon()) return null;
2291
- _requestCount++;
2292
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
2293
- const health = await pingDaemon();
2294
- if (!health) {
2295
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
2296
- `);
2297
- killAndRespawnDaemon();
2298
- const start = Date.now();
2299
- let delay2 = 200;
2300
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2301
- await new Promise((r) => setTimeout(r, delay2));
2302
- if (await connectToSocket()) break;
2303
- delay2 = Math.min(delay2 * 2, 3e3);
2304
- }
2305
- if (!_connected) return null;
2206
+ function classifyTier(record) {
2207
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
2208
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
2209
+ return 3;
2210
+ }
2211
+ function inferFilePaths(record) {
2212
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
2213
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
2214
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
2215
+ return match ? JSON.stringify([match[1]]) : null;
2216
+ }
2217
+ function inferCommitHash(record) {
2218
+ if (record.tool_name !== "Bash") return null;
2219
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
2220
+ return match ? match[1] : null;
2221
+ }
2222
+ function inferLanguageType(record) {
2223
+ const text = record.raw_text;
2224
+ if (!text || text.length < 10) return null;
2225
+ const trimmed = text.trimStart();
2226
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
2227
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
2228
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
2229
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
2230
+ return "mixed";
2231
+ }
2232
+ function inferDomain(record) {
2233
+ const proj = (record.project_name ?? "").toLowerCase();
2234
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
2235
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
2236
+ return null;
2237
+ }
2238
+ async function writeMemory(record) {
2239
+ if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
2240
+ throw new Error(
2241
+ `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
2242
+ );
2243
+ }
2244
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
2245
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
2246
+ return;
2247
+ }
2248
+ try {
2249
+ const client = getClient();
2250
+ const existing = await client.execute({
2251
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
2252
+ args: [contentHash, record.agent_id]
2253
+ });
2254
+ if (existing.rows.length > 0) return;
2255
+ } catch {
2256
+ }
2257
+ const dbRow = {
2258
+ id: record.id,
2259
+ agent_id: record.agent_id,
2260
+ agent_role: record.agent_role,
2261
+ session_id: record.session_id,
2262
+ timestamp: record.timestamp,
2263
+ tool_name: record.tool_name,
2264
+ project_name: record.project_name,
2265
+ has_error: record.has_error ? 1 : 0,
2266
+ raw_text: record.raw_text,
2267
+ vector: record.vector,
2268
+ version: 0,
2269
+ // Placeholder — assigned atomically at flush time
2270
+ task_id: record.task_id ?? null,
2271
+ importance: record.importance ?? 5,
2272
+ status: record.status ?? "active",
2273
+ confidence: record.confidence ?? 0.7,
2274
+ last_accessed: record.last_accessed ?? record.timestamp,
2275
+ workspace_id: record.workspace_id ?? null,
2276
+ document_id: record.document_id ?? null,
2277
+ user_id: record.user_id ?? null,
2278
+ char_offset: record.char_offset ?? null,
2279
+ page_number: record.page_number ?? null,
2280
+ source_path: record.source_path ?? null,
2281
+ source_type: record.source_type ?? null,
2282
+ tier: record.tier ?? classifyTier(record),
2283
+ supersedes_id: record.supersedes_id ?? null,
2284
+ draft: record.draft ? 1 : 0,
2285
+ memory_type: record.memory_type ?? "raw",
2286
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
2287
+ content_hash: contentHash,
2288
+ intent: record.intent ?? null,
2289
+ outcome: record.outcome ?? null,
2290
+ domain: record.domain ?? inferDomain(record),
2291
+ referenced_entities: record.referenced_entities ?? null,
2292
+ retrieval_count: record.retrieval_count ?? 0,
2293
+ chain_position: record.chain_position ?? null,
2294
+ review_status: record.review_status ?? null,
2295
+ context_window_pct: record.context_window_pct ?? null,
2296
+ file_paths: record.file_paths ?? inferFilePaths(record),
2297
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
2298
+ duration_ms: record.duration_ms ?? null,
2299
+ token_cost: record.token_cost ?? null,
2300
+ audience: record.audience ?? null,
2301
+ language_type: record.language_type ?? inferLanguageType(record),
2302
+ parent_memory_id: record.parent_memory_id ?? null
2303
+ };
2304
+ _pendingRecords.push(dbRow);
2305
+ orgBus.emit({
2306
+ type: "memory_stored",
2307
+ agentId: record.agent_id,
2308
+ project: record.project_name,
2309
+ timestamp: record.timestamp
2310
+ });
2311
+ const MAX_PENDING = 1e3;
2312
+ if (_pendingRecords.length > MAX_PENDING) {
2313
+ const dropped = _pendingRecords.length - MAX_PENDING;
2314
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
2315
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
2316
+ }
2317
+ if (_flushTimer === null) {
2318
+ _flushTimer = setInterval(() => {
2319
+ void flushBatch();
2320
+ }, _flushIntervalMs);
2321
+ if (_flushTimer && typeof _flushTimer === "object" && "unref" in _flushTimer) {
2322
+ _flushTimer.unref();
2306
2323
  }
2307
2324
  }
2308
- const result = await sendRequest([text], priority);
2309
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
2310
- if (result.error) {
2311
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
2312
- `);
2313
- killAndRespawnDaemon();
2314
- const start = Date.now();
2315
- let delay2 = 200;
2316
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2317
- await new Promise((r) => setTimeout(r, delay2));
2318
- if (await connectToSocket()) break;
2319
- delay2 = Math.min(delay2 * 2, 3e3);
2325
+ if (_pendingRecords.length >= _batchSize) {
2326
+ await flushBatch();
2327
+ }
2328
+ }
2329
+ async function flushBatch() {
2330
+ if (_flushing || _pendingRecords.length === 0) return 0;
2331
+ _flushing = true;
2332
+ try {
2333
+ const batch = _pendingRecords.slice(0);
2334
+ const client = getClient();
2335
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2336
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2337
+ for (const row of batch) {
2338
+ row.version = baseVersion++;
2320
2339
  }
2321
- if (!_connected) return null;
2322
- const retry = await sendRequest([text], priority);
2323
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
2324
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
2325
- `);
2340
+ _nextVersion = baseVersion;
2341
+ const buildStmt = (row) => {
2342
+ const hasVector = row.vector !== null;
2343
+ const taskId = row.task_id ?? null;
2344
+ const importance = row.importance ?? 5;
2345
+ const status = row.status ?? "active";
2346
+ const confidence = row.confidence ?? 0.7;
2347
+ const lastAccessed = row.last_accessed ?? row.timestamp;
2348
+ const workspaceId = row.workspace_id ?? null;
2349
+ const documentId = row.document_id ?? null;
2350
+ const userId = row.user_id ?? null;
2351
+ const charOffset = row.char_offset ?? null;
2352
+ const pageNumber = row.page_number ?? null;
2353
+ const sourcePath = row.source_path ?? null;
2354
+ const sourceType = row.source_type ?? null;
2355
+ const tier = row.tier ?? 3;
2356
+ const supersedesId = row.supersedes_id ?? null;
2357
+ const draft = row.draft ? 1 : 0;
2358
+ const memoryType = row.memory_type ?? "raw";
2359
+ const trajectory = row.trajectory ?? null;
2360
+ const contentHash = row.content_hash ?? null;
2361
+ const intent = row.intent ?? null;
2362
+ const outcome = row.outcome ?? null;
2363
+ const domain = row.domain ?? null;
2364
+ const referencedEntities = row.referenced_entities ?? null;
2365
+ const retrievalCount = row.retrieval_count ?? 0;
2366
+ const chainPosition = row.chain_position ?? null;
2367
+ const reviewStatus = row.review_status ?? null;
2368
+ const contextWindowPct = row.context_window_pct ?? null;
2369
+ const filePaths = row.file_paths ?? null;
2370
+ const commitHash = row.commit_hash ?? null;
2371
+ const durationMs = row.duration_ms ?? null;
2372
+ const tokenCost = row.token_cost ?? null;
2373
+ const audience = row.audience ?? null;
2374
+ const languageType = row.language_type ?? null;
2375
+ const parentMemoryId = row.parent_memory_id ?? null;
2376
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
2377
+ tool_name, project_name,
2378
+ has_error, raw_text, vector, version, task_id, importance, status,
2379
+ confidence, last_accessed,
2380
+ workspace_id, document_id, user_id, char_offset, page_number,
2381
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
2382
+ intent, outcome, domain, referenced_entities, retrieval_count,
2383
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
2384
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
2385
+ const metaArgs = [
2386
+ intent,
2387
+ outcome,
2388
+ domain,
2389
+ referencedEntities,
2390
+ retrievalCount,
2391
+ chainPosition,
2392
+ reviewStatus,
2393
+ contextWindowPct,
2394
+ filePaths,
2395
+ commitHash,
2396
+ durationMs,
2397
+ tokenCost,
2398
+ audience,
2399
+ languageType,
2400
+ parentMemoryId
2401
+ ];
2402
+ const baseArgs = [
2403
+ row.id,
2404
+ row.agent_id,
2405
+ row.agent_role,
2406
+ row.session_id,
2407
+ row.timestamp,
2408
+ row.tool_name,
2409
+ row.project_name,
2410
+ row.has_error,
2411
+ row.raw_text
2412
+ ];
2413
+ const sharedArgs = [
2414
+ row.version,
2415
+ taskId,
2416
+ importance,
2417
+ status,
2418
+ confidence,
2419
+ lastAccessed,
2420
+ workspaceId,
2421
+ documentId,
2422
+ userId,
2423
+ charOffset,
2424
+ pageNumber,
2425
+ sourcePath,
2426
+ sourceType,
2427
+ tier,
2428
+ supersedesId,
2429
+ draft,
2430
+ memoryType,
2431
+ trajectory,
2432
+ contentHash
2433
+ ];
2434
+ return {
2435
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
2436
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
2437
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2438
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
2439
+ };
2440
+ };
2441
+ const globalClient = getClient();
2442
+ const globalStmts = batch.map(buildStmt);
2443
+ await globalClient.batch(globalStmts, "write");
2444
+ _pendingRecords.splice(0, batch.length);
2445
+ try {
2446
+ const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
2447
+ if (isShardingEnabled2()) {
2448
+ const byProject = /* @__PURE__ */ new Map();
2449
+ for (const row of batch) {
2450
+ const proj = row.project_name || "unknown";
2451
+ if (!byProject.has(proj)) byProject.set(proj, []);
2452
+ byProject.get(proj).push(row);
2453
+ }
2454
+ for (const [project, rows] of byProject) {
2455
+ try {
2456
+ const shardClient = await getReadyShardClient2(project);
2457
+ const shardStmts = rows.map(buildStmt);
2458
+ await shardClient.batch(shardStmts, "write");
2459
+ } catch (err) {
2460
+ process.stderr.write(
2461
+ `[store] Shard write failed for ${project}: ${err instanceof Error ? err.message : String(err)}
2462
+ `
2463
+ );
2464
+ }
2465
+ }
2466
+ }
2467
+ } catch {
2468
+ }
2469
+ return batch.length;
2470
+ } finally {
2471
+ _flushing = false;
2326
2472
  }
2327
- return null;
2473
+ }
2474
+ function vectorToBlob(vector) {
2475
+ const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
2476
+ return JSON.stringify(Array.from(f32));
2328
2477
  }
2329
2478
 
2330
2479
  // src/bin/backfill-conversations.ts
2480
+ init_exe_daemon_client();
2331
2481
  init_database();
2332
2482
 
2333
2483
  // src/lib/is-main.ts
@@ -2335,6 +2485,7 @@ import { realpathSync } from "fs";
2335
2485
  import { fileURLToPath as fileURLToPath2 } from "url";
2336
2486
  function isMainModule(importMetaUrl) {
2337
2487
  if (process.argv[1] == null) return false;
2488
+ if (process.argv[1].includes("mcp/server")) return false;
2338
2489
  try {
2339
2490
  const scriptPath = realpathSync(process.argv[1]);
2340
2491
  const modulePath = realpathSync(fileURLToPath2(importMetaUrl));
@@ -2650,7 +2801,7 @@ async function backfillConversations(options) {
2650
2801
  await writeMemory({
2651
2802
  id: crypto.randomUUID(),
2652
2803
  agent_id: conv.agentId,
2653
- agent_role: isCoordinatorName(conv.agentId) || conv.agentId === "exe" ? "COO" : "specialist",
2804
+ agent_role: isCoordinatorName(conv.agentId) ? "COO" : "specialist",
2654
2805
  session_id: conv.sessionId,
2655
2806
  timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
2656
2807
  tool_name: TOOL_NAME,