@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
package/dist/index.js CHANGED
@@ -272,6 +272,7 @@ __export(employees_exports, {
272
272
  DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
273
273
  EMPLOYEES_PATH: () => EMPLOYEES_PATH,
274
274
  addEmployee: () => addEmployee,
275
+ baseAgentName: () => baseAgentName,
275
276
  canCoordinate: () => canCoordinate,
276
277
  getCoordinatorEmployee: () => getCoordinatorEmployee,
277
278
  getCoordinatorName: () => getCoordinatorName,
@@ -368,6 +369,14 @@ function hasRole(agentName, role) {
368
369
  const emp = getEmployee(employees, agentName);
369
370
  return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
370
371
  }
372
+ function baseAgentName(name, employees) {
373
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
374
+ if (!match) return name;
375
+ const base = match[1];
376
+ const roster = employees ?? loadEmployeesSync();
377
+ if (getEmployee(roster, base)) return base;
378
+ return name;
379
+ }
371
380
  function isMultiInstance(agentName, employees) {
372
381
  const roster = employees ?? loadEmployeesSync();
373
382
  const emp = getEmployee(roster, agentName);
@@ -852,388 +861,941 @@ var init_db_retry = __esm({
852
861
  }
853
862
  });
854
863
 
855
- // src/lib/database.ts
856
- var database_exports = {};
857
- __export(database_exports, {
858
- disposeDatabase: () => disposeDatabase,
859
- disposeTurso: () => disposeTurso,
860
- ensureSchema: () => ensureSchema,
861
- getClient: () => getClient,
862
- getRawClient: () => getRawClient,
863
- initDatabase: () => initDatabase,
864
- initTurso: () => initTurso,
865
- isInitialized: () => isInitialized
866
- });
867
- import { createClient } from "@libsql/client";
868
- async function initDatabase(config2) {
869
- if (_client) {
870
- _client.close();
871
- _client = null;
872
- _resilientClient = null;
864
+ // src/lib/exe-daemon-client.ts
865
+ import net from "net";
866
+ import { spawn } from "child_process";
867
+ import { randomUUID as randomUUID2 } from "crypto";
868
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
869
+ import path6 from "path";
870
+ import { fileURLToPath } from "url";
871
+ function handleData(chunk) {
872
+ _buffer += chunk.toString();
873
+ if (_buffer.length > MAX_BUFFER) {
874
+ _buffer = "";
875
+ return;
873
876
  }
874
- const opts = {
875
- url: `file:${config2.dbPath}`
876
- };
877
- if (config2.encryptionKey) {
878
- opts.encryptionKey = config2.encryptionKey;
877
+ let newlineIdx;
878
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
879
+ const line = _buffer.slice(0, newlineIdx).trim();
880
+ _buffer = _buffer.slice(newlineIdx + 1);
881
+ if (!line) continue;
882
+ try {
883
+ const response = JSON.parse(line);
884
+ const id = response.id;
885
+ if (!id) continue;
886
+ const entry = _pending.get(id);
887
+ if (entry) {
888
+ clearTimeout(entry.timer);
889
+ _pending.delete(id);
890
+ entry.resolve(response);
891
+ }
892
+ } catch {
893
+ }
879
894
  }
880
- _client = createClient(opts);
881
- _resilientClient = wrapWithRetry(_client);
882
895
  }
883
- function isInitialized() {
884
- return _client !== null;
885
- }
886
- function getClient() {
887
- if (!_resilientClient) {
888
- throw new Error("Database client not initialized. Call initDatabase() first.");
896
+ function cleanupStaleFiles() {
897
+ if (existsSync5(PID_PATH)) {
898
+ try {
899
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
900
+ if (pid > 0) {
901
+ try {
902
+ process.kill(pid, 0);
903
+ return;
904
+ } catch {
905
+ }
906
+ }
907
+ } catch {
908
+ }
909
+ try {
910
+ unlinkSync2(PID_PATH);
911
+ } catch {
912
+ }
913
+ try {
914
+ unlinkSync2(SOCKET_PATH);
915
+ } catch {
916
+ }
889
917
  }
890
- return _resilientClient;
891
918
  }
892
- function getRawClient() {
893
- if (!_client) {
894
- throw new Error("Database client not initialized. Call initDatabase() first.");
919
+ function findPackageRoot() {
920
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
921
+ const { root } = path6.parse(dir);
922
+ while (dir !== root) {
923
+ if (existsSync5(path6.join(dir, "package.json"))) return dir;
924
+ dir = path6.dirname(dir);
895
925
  }
896
- return _client;
926
+ return null;
897
927
  }
898
- async function ensureSchema() {
899
- const client = getRawClient();
900
- await client.execute("PRAGMA journal_mode = WAL");
901
- await client.execute("PRAGMA busy_timeout = 30000");
902
- await client.execute("PRAGMA wal_autocheckpoint = 1000");
903
- try {
904
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
905
- } catch {
928
+ function spawnDaemon() {
929
+ const pkgRoot = findPackageRoot();
930
+ if (!pkgRoot) {
931
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
932
+ return;
906
933
  }
907
- await client.executeMultiple(`
908
- CREATE TABLE IF NOT EXISTS memories (
909
- id TEXT PRIMARY KEY,
910
- agent_id TEXT NOT NULL,
911
- agent_role TEXT NOT NULL,
912
- session_id TEXT NOT NULL,
913
- timestamp TEXT NOT NULL,
914
- tool_name TEXT NOT NULL,
915
- project_name TEXT NOT NULL,
916
- has_error INTEGER NOT NULL DEFAULT 0,
917
- raw_text TEXT NOT NULL,
918
- vector F32_BLOB(1024),
919
- version INTEGER NOT NULL DEFAULT 0
920
- );
921
-
922
- CREATE INDEX IF NOT EXISTS idx_memories_agent
923
- ON memories(agent_id);
924
-
925
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp
926
- ON memories(timestamp);
927
-
928
- CREATE INDEX IF NOT EXISTS idx_memories_session
929
- ON memories(session_id);
930
-
931
- CREATE INDEX IF NOT EXISTS idx_memories_project
932
- ON memories(project_name);
933
-
934
- CREATE INDEX IF NOT EXISTS idx_memories_tool
935
- ON memories(tool_name);
936
-
937
- CREATE INDEX IF NOT EXISTS idx_memories_version
938
- ON memories(version);
939
-
940
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project
941
- ON memories(agent_id, project_name);
942
- `);
943
- await client.executeMultiple(`
944
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
945
- raw_text,
946
- content='memories',
947
- content_rowid='rowid'
948
- );
949
-
950
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
951
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
952
- END;
953
-
954
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
955
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
956
- END;
957
-
958
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
959
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
960
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
961
- END;
962
- `);
963
- await client.executeMultiple(`
964
- CREATE TABLE IF NOT EXISTS sync_meta (
965
- key TEXT PRIMARY KEY,
966
- value TEXT NOT NULL
967
- );
968
- `);
969
- await client.executeMultiple(`
970
- CREATE TABLE IF NOT EXISTS tasks (
971
- id TEXT PRIMARY KEY,
972
- title TEXT NOT NULL,
973
- assigned_to TEXT NOT NULL,
974
- assigned_by TEXT NOT NULL,
975
- project_name TEXT NOT NULL,
976
- priority TEXT NOT NULL DEFAULT 'p1',
977
- status TEXT NOT NULL DEFAULT 'open',
978
- task_file TEXT,
979
- created_at TEXT NOT NULL,
980
- updated_at TEXT NOT NULL
981
- );
982
-
983
- CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
984
- ON tasks(assigned_to, status);
985
- `);
986
- await client.executeMultiple(`
987
- CREATE TABLE IF NOT EXISTS behaviors (
988
- id TEXT PRIMARY KEY,
989
- agent_id TEXT NOT NULL,
990
- project_name TEXT,
991
- domain TEXT,
992
- content TEXT NOT NULL,
993
- active INTEGER NOT NULL DEFAULT 1,
994
- created_at TEXT NOT NULL,
995
- updated_at TEXT NOT NULL
996
- );
997
-
998
- CREATE INDEX IF NOT EXISTS idx_behaviors_agent
999
- ON behaviors(agent_id, active);
1000
- `);
1001
- try {
1002
- const coordinatorName = getCoordinatorName();
1003
- const existing = await client.execute({
1004
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1005
- args: [coordinatorName]
1006
- });
1007
- if (Number(existing.rows[0]?.cnt) === 0) {
1008
- const seededAt = "2026-03-25T00:00:00Z";
1009
- for (const [domain, content] of [
1010
- ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1011
- ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1012
- ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1013
- ]) {
1014
- await client.execute({
1015
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1016
- VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1017
- args: [coordinatorName, domain, content, seededAt, seededAt]
1018
- });
1019
- }
1020
- }
1021
- } catch {
934
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
935
+ if (!existsSync5(daemonPath)) {
936
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
937
+ `);
938
+ return;
1022
939
  }
940
+ const resolvedPath = daemonPath;
941
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
942
+ `);
943
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
944
+ let stderrFd = "ignore";
1023
945
  try {
1024
- await client.execute({
1025
- sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1026
- args: []
1027
- });
946
+ stderrFd = openSync(logPath, "a");
1028
947
  } catch {
1029
948
  }
1030
- try {
1031
- await client.execute({
1032
- sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1033
- args: []
1034
- });
1035
- } catch {
949
+ const child = spawn(process.execPath, [resolvedPath], {
950
+ detached: true,
951
+ stdio: ["ignore", "ignore", stderrFd],
952
+ env: {
953
+ ...process.env,
954
+ TMUX: void 0,
955
+ // Daemon is global — must not inherit session scope
956
+ TMUX_PANE: void 0,
957
+ // Prevents resolveExeSession() from scoping to one session
958
+ EXE_DAEMON_SOCK: SOCKET_PATH,
959
+ EXE_DAEMON_PID: PID_PATH
960
+ }
961
+ });
962
+ child.unref();
963
+ if (typeof stderrFd === "number") {
964
+ try {
965
+ closeSync(stderrFd);
966
+ } catch {
967
+ }
1036
968
  }
969
+ }
970
+ function acquireSpawnLock() {
1037
971
  try {
1038
- await client.execute({
1039
- sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1040
- args: []
1041
- });
972
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
973
+ closeSync(fd);
974
+ return true;
1042
975
  } catch {
976
+ try {
977
+ const stat = statSync(SPAWN_LOCK_PATH);
978
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
979
+ try {
980
+ unlinkSync2(SPAWN_LOCK_PATH);
981
+ } catch {
982
+ }
983
+ try {
984
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
985
+ closeSync(fd);
986
+ return true;
987
+ } catch {
988
+ }
989
+ }
990
+ } catch {
991
+ }
992
+ return false;
1043
993
  }
994
+ }
995
+ function releaseSpawnLock() {
1044
996
  try {
1045
- await client.execute({
1046
- sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1047
- ON tasks(parent_task_id)
1048
- WHERE parent_task_id IS NOT NULL`,
1049
- args: []
1050
- });
997
+ unlinkSync2(SPAWN_LOCK_PATH);
1051
998
  } catch {
1052
999
  }
1053
- try {
1054
- await client.execute({
1055
- sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1056
- args: []
1000
+ }
1001
+ function connectToSocket() {
1002
+ return new Promise((resolve) => {
1003
+ if (_socket && _connected) {
1004
+ resolve(true);
1005
+ return;
1006
+ }
1007
+ const socket = net.createConnection({ path: SOCKET_PATH });
1008
+ const connectTimeout = setTimeout(() => {
1009
+ socket.destroy();
1010
+ resolve(false);
1011
+ }, 2e3);
1012
+ socket.on("connect", () => {
1013
+ clearTimeout(connectTimeout);
1014
+ _socket = socket;
1015
+ _connected = true;
1016
+ _buffer = "";
1017
+ socket.on("data", handleData);
1018
+ socket.on("close", () => {
1019
+ _connected = false;
1020
+ _socket = null;
1021
+ for (const [id, entry] of _pending) {
1022
+ clearTimeout(entry.timer);
1023
+ _pending.delete(id);
1024
+ entry.resolve({ error: "Connection closed" });
1025
+ }
1026
+ });
1027
+ socket.on("error", () => {
1028
+ _connected = false;
1029
+ _socket = null;
1030
+ });
1031
+ resolve(true);
1057
1032
  });
1058
- } catch {
1059
- }
1060
- try {
1061
- await client.execute({
1062
- sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1063
- args: []
1033
+ socket.on("error", () => {
1034
+ clearTimeout(connectTimeout);
1035
+ resolve(false);
1064
1036
  });
1065
- } catch {
1037
+ });
1038
+ }
1039
+ async function connectEmbedDaemon() {
1040
+ if (_socket && _connected) return true;
1041
+ if (await connectToSocket()) return true;
1042
+ if (acquireSpawnLock()) {
1043
+ try {
1044
+ cleanupStaleFiles();
1045
+ spawnDaemon();
1046
+ } finally {
1047
+ releaseSpawnLock();
1048
+ }
1066
1049
  }
1067
- try {
1068
- await client.execute({
1069
- sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1070
- args: []
1071
- });
1072
- } catch {
1050
+ const start = Date.now();
1051
+ let delay2 = 100;
1052
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1053
+ await new Promise((r) => setTimeout(r, delay2));
1054
+ if (await connectToSocket()) return true;
1055
+ delay2 = Math.min(delay2 * 2, 3e3);
1073
1056
  }
1074
- try {
1075
- await client.execute({
1076
- sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1077
- args: []
1078
- });
1079
- } catch {
1057
+ return false;
1058
+ }
1059
+ function sendRequest(texts, priority) {
1060
+ return sendDaemonRequest({ texts, priority });
1061
+ }
1062
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1063
+ return new Promise((resolve) => {
1064
+ if (!_socket || !_connected) {
1065
+ resolve({ error: "Not connected" });
1066
+ return;
1067
+ }
1068
+ const id = randomUUID2();
1069
+ const timer = setTimeout(() => {
1070
+ _pending.delete(id);
1071
+ resolve({ error: "Request timeout" });
1072
+ }, timeoutMs);
1073
+ _pending.set(id, { resolve, timer });
1074
+ try {
1075
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1076
+ } catch {
1077
+ clearTimeout(timer);
1078
+ _pending.delete(id);
1079
+ resolve({ error: "Write failed" });
1080
+ }
1081
+ });
1082
+ }
1083
+ async function pingDaemon() {
1084
+ if (!_socket || !_connected) return null;
1085
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
1086
+ if (response.health) {
1087
+ return response.health;
1080
1088
  }
1081
- try {
1082
- await client.execute({
1083
- sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1084
- args: []
1085
- });
1086
- } catch {
1089
+ return null;
1090
+ }
1091
+ function killAndRespawnDaemon() {
1092
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
1093
+ if (existsSync5(PID_PATH)) {
1094
+ try {
1095
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1096
+ if (pid > 0) {
1097
+ try {
1098
+ process.kill(pid, "SIGKILL");
1099
+ } catch {
1100
+ }
1101
+ }
1102
+ } catch {
1103
+ }
1087
1104
  }
1088
- try {
1089
- await client.execute({
1090
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1091
- args: []
1092
- });
1093
- } catch {
1105
+ if (_socket) {
1106
+ _socket.destroy();
1107
+ _socket = null;
1094
1108
  }
1109
+ _connected = false;
1110
+ _buffer = "";
1095
1111
  try {
1096
- await client.execute({
1097
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1098
- args: []
1099
- });
1112
+ unlinkSync2(PID_PATH);
1100
1113
  } catch {
1101
1114
  }
1102
1115
  try {
1103
- await client.execute({
1104
- sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1105
- args: []
1106
- });
1116
+ unlinkSync2(SOCKET_PATH);
1107
1117
  } catch {
1108
1118
  }
1109
- try {
1110
- await client.execute({
1111
- sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1112
- args: []
1113
- });
1114
- } catch {
1119
+ spawnDaemon();
1120
+ }
1121
+ async function embedViaClient(text, priority = "high") {
1122
+ if (!_connected && !await connectEmbedDaemon()) return null;
1123
+ _requestCount++;
1124
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1125
+ const health = await pingDaemon();
1126
+ if (!health) {
1127
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1128
+ `);
1129
+ killAndRespawnDaemon();
1130
+ const start = Date.now();
1131
+ let delay2 = 200;
1132
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1133
+ await new Promise((r) => setTimeout(r, delay2));
1134
+ if (await connectToSocket()) break;
1135
+ delay2 = Math.min(delay2 * 2, 3e3);
1136
+ }
1137
+ if (!_connected) return null;
1138
+ }
1115
1139
  }
1116
- try {
1117
- await client.execute({
1118
- sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1119
- args: []
1120
- });
1121
- } catch {
1140
+ const result = await sendRequest([text], priority);
1141
+ if (!result.error && result.vectors?.[0]) return result.vectors[0];
1142
+ if (result.error) {
1143
+ process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
1144
+ `);
1145
+ killAndRespawnDaemon();
1146
+ const start = Date.now();
1147
+ let delay2 = 200;
1148
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1149
+ await new Promise((r) => setTimeout(r, delay2));
1150
+ if (await connectToSocket()) break;
1151
+ delay2 = Math.min(delay2 * 2, 3e3);
1152
+ }
1153
+ if (!_connected) return null;
1154
+ const retry = await sendRequest([text], priority);
1155
+ if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
1156
+ process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
1157
+ `);
1122
1158
  }
1123
- try {
1124
- await client.execute({
1125
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1126
- args: []
1127
- });
1128
- } catch {
1159
+ return null;
1160
+ }
1161
+ function disconnectClient() {
1162
+ if (_socket) {
1163
+ _socket.destroy();
1164
+ _socket = null;
1129
1165
  }
1130
- try {
1131
- await client.execute({
1132
- sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1133
- args: []
1134
- });
1135
- } catch {
1166
+ _connected = false;
1167
+ _buffer = "";
1168
+ for (const [id, entry] of _pending) {
1169
+ clearTimeout(entry.timer);
1170
+ _pending.delete(id);
1171
+ entry.resolve({ error: "Client disconnected" });
1136
1172
  }
1137
- try {
1138
- await client.execute({
1139
- sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
1140
- args: []
1141
- });
1142
- } catch {
1173
+ }
1174
+ function isClientConnected() {
1175
+ return _connected;
1176
+ }
1177
+ 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;
1178
+ var init_exe_daemon_client = __esm({
1179
+ "src/lib/exe-daemon-client.ts"() {
1180
+ "use strict";
1181
+ init_config();
1182
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1183
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1184
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1185
+ SPAWN_LOCK_STALE_MS = 3e4;
1186
+ CONNECT_TIMEOUT_MS = 15e3;
1187
+ REQUEST_TIMEOUT_MS = 3e4;
1188
+ _socket = null;
1189
+ _connected = false;
1190
+ _buffer = "";
1191
+ _requestCount = 0;
1192
+ HEALTH_CHECK_INTERVAL = 100;
1193
+ _pending = /* @__PURE__ */ new Map();
1194
+ MAX_BUFFER = 1e7;
1143
1195
  }
1144
- await client.executeMultiple(`
1145
- CREATE TABLE IF NOT EXISTS consolidations (
1146
- id TEXT PRIMARY KEY,
1147
- consolidated_memory_id TEXT NOT NULL,
1148
- source_memory_id TEXT NOT NULL,
1149
- created_at TEXT NOT NULL
1196
+ });
1197
+
1198
+ // src/lib/daemon-protocol.ts
1199
+ function serializeValue(v) {
1200
+ if (v === null || v === void 0) return null;
1201
+ if (typeof v === "bigint") return Number(v);
1202
+ if (typeof v === "boolean") return v ? 1 : 0;
1203
+ if (v instanceof Uint8Array) {
1204
+ return { __blob: Buffer.from(v).toString("base64") };
1205
+ }
1206
+ if (ArrayBuffer.isView(v)) {
1207
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
1208
+ }
1209
+ if (v instanceof ArrayBuffer) {
1210
+ return { __blob: Buffer.from(v).toString("base64") };
1211
+ }
1212
+ if (typeof v === "string" || typeof v === "number") return v;
1213
+ return String(v);
1214
+ }
1215
+ function deserializeValue(v) {
1216
+ if (v === null) return null;
1217
+ if (typeof v === "object" && v !== null && "__blob" in v) {
1218
+ const buf = Buffer.from(v.__blob, "base64");
1219
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1220
+ }
1221
+ return v;
1222
+ }
1223
+ function deserializeResultSet(srs) {
1224
+ const rows = srs.rows.map((obj) => {
1225
+ const values = srs.columns.map(
1226
+ (col) => deserializeValue(obj[col] ?? null)
1150
1227
  );
1228
+ const row = values;
1229
+ for (let i = 0; i < srs.columns.length; i++) {
1230
+ const col = srs.columns[i];
1231
+ if (col !== void 0) {
1232
+ row[col] = values[i] ?? null;
1233
+ }
1234
+ }
1235
+ Object.defineProperty(row, "length", {
1236
+ value: values.length,
1237
+ enumerable: false
1238
+ });
1239
+ return row;
1240
+ });
1241
+ return {
1242
+ columns: srs.columns,
1243
+ columnTypes: srs.columnTypes ?? [],
1244
+ rows,
1245
+ rowsAffected: srs.rowsAffected,
1246
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
1247
+ toJSON: () => ({
1248
+ columns: srs.columns,
1249
+ columnTypes: srs.columnTypes ?? [],
1250
+ rows: srs.rows,
1251
+ rowsAffected: srs.rowsAffected,
1252
+ lastInsertRowid: srs.lastInsertRowid
1253
+ })
1254
+ };
1255
+ }
1256
+ var init_daemon_protocol = __esm({
1257
+ "src/lib/daemon-protocol.ts"() {
1258
+ "use strict";
1259
+ }
1260
+ });
1151
1261
 
1152
- CREATE INDEX IF NOT EXISTS idx_consolidations_source
1153
- ON consolidations(source_memory_id);
1262
+ // src/lib/db-daemon-client.ts
1263
+ var db_daemon_client_exports = {};
1264
+ __export(db_daemon_client_exports, {
1265
+ createDaemonDbClient: () => createDaemonDbClient,
1266
+ initDaemonDbClient: () => initDaemonDbClient
1267
+ });
1268
+ function normalizeStatement(stmt) {
1269
+ if (typeof stmt === "string") {
1270
+ return { sql: stmt, args: [] };
1271
+ }
1272
+ const sql = stmt.sql;
1273
+ let args = [];
1274
+ if (Array.isArray(stmt.args)) {
1275
+ args = stmt.args.map((v) => serializeValue(v));
1276
+ } else if (stmt.args && typeof stmt.args === "object") {
1277
+ const named = {};
1278
+ for (const [key, val] of Object.entries(stmt.args)) {
1279
+ named[key] = serializeValue(val);
1280
+ }
1281
+ return { sql, args: named };
1282
+ }
1283
+ return { sql, args };
1284
+ }
1285
+ function createDaemonDbClient(fallbackClient) {
1286
+ let _useDaemon = false;
1287
+ const client = {
1288
+ async execute(stmt) {
1289
+ if (!_useDaemon || !isClientConnected()) {
1290
+ return fallbackClient.execute(stmt);
1291
+ }
1292
+ const { sql, args } = normalizeStatement(stmt);
1293
+ const response = await sendDaemonRequest({
1294
+ type: "db-execute",
1295
+ sql,
1296
+ args
1297
+ });
1298
+ if (response.error) {
1299
+ const errMsg = String(response.error);
1300
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1301
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1302
+ `);
1303
+ return fallbackClient.execute(stmt);
1304
+ }
1305
+ throw new Error(errMsg);
1306
+ }
1307
+ if (response.db) {
1308
+ return deserializeResultSet(response.db);
1309
+ }
1310
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1311
+ return fallbackClient.execute(stmt);
1312
+ },
1313
+ async batch(stmts, mode) {
1314
+ if (!_useDaemon || !isClientConnected()) {
1315
+ return fallbackClient.batch(stmts, mode);
1316
+ }
1317
+ const statements = stmts.map(normalizeStatement);
1318
+ const response = await sendDaemonRequest({
1319
+ type: "db-batch",
1320
+ statements,
1321
+ mode: mode ?? "deferred"
1322
+ });
1323
+ if (response.error) {
1324
+ const errMsg = String(response.error);
1325
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1326
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1327
+ `);
1328
+ return fallbackClient.batch(stmts, mode);
1329
+ }
1330
+ throw new Error(errMsg);
1331
+ }
1332
+ const batchResults = response["db-batch"];
1333
+ if (batchResults) {
1334
+ return batchResults.map(deserializeResultSet);
1335
+ }
1336
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1337
+ return fallbackClient.batch(stmts, mode);
1338
+ },
1339
+ // Transaction support — delegate to fallback (transactions need direct connection)
1340
+ async transaction(mode) {
1341
+ return fallbackClient.transaction(mode);
1342
+ },
1343
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1344
+ async executeMultiple(sql) {
1345
+ return fallbackClient.executeMultiple(sql);
1346
+ },
1347
+ // migrate — delegate to fallback
1348
+ async migrate(stmts) {
1349
+ return fallbackClient.migrate(stmts);
1350
+ },
1351
+ // Sync mode — delegate to fallback
1352
+ sync() {
1353
+ return fallbackClient.sync();
1354
+ },
1355
+ close() {
1356
+ _useDaemon = false;
1357
+ },
1358
+ get closed() {
1359
+ return fallbackClient.closed;
1360
+ },
1361
+ get protocol() {
1362
+ return fallbackClient.protocol;
1363
+ }
1364
+ };
1365
+ return {
1366
+ ...client,
1367
+ /** Enable daemon routing (call after confirming daemon is connected) */
1368
+ _enableDaemon() {
1369
+ _useDaemon = true;
1370
+ },
1371
+ /** Check if daemon routing is active */
1372
+ _isDaemonActive() {
1373
+ return _useDaemon && isClientConnected();
1374
+ }
1375
+ };
1376
+ }
1377
+ async function initDaemonDbClient(fallbackClient) {
1378
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1379
+ const connected = await connectEmbedDaemon();
1380
+ if (!connected) {
1381
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1382
+ return null;
1383
+ }
1384
+ const client = createDaemonDbClient(fallbackClient);
1385
+ client._enableDaemon();
1386
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1387
+ return client;
1388
+ }
1389
+ var init_db_daemon_client = __esm({
1390
+ "src/lib/db-daemon-client.ts"() {
1391
+ "use strict";
1392
+ init_exe_daemon_client();
1393
+ init_daemon_protocol();
1394
+ }
1395
+ });
1154
1396
 
1155
- CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
1156
- ON consolidations(consolidated_memory_id);
1157
- `);
1397
+ // src/lib/database.ts
1398
+ var database_exports = {};
1399
+ __export(database_exports, {
1400
+ disposeDatabase: () => disposeDatabase,
1401
+ disposeTurso: () => disposeTurso,
1402
+ ensureSchema: () => ensureSchema,
1403
+ getClient: () => getClient,
1404
+ getRawClient: () => getRawClient,
1405
+ initDaemonClient: () => initDaemonClient,
1406
+ initDatabase: () => initDatabase,
1407
+ initTurso: () => initTurso,
1408
+ isInitialized: () => isInitialized
1409
+ });
1410
+ import { createClient } from "@libsql/client";
1411
+ async function initDatabase(config2) {
1412
+ if (_client) {
1413
+ _client.close();
1414
+ _client = null;
1415
+ _resilientClient = null;
1416
+ }
1417
+ const opts = {
1418
+ url: `file:${config2.dbPath}`
1419
+ };
1420
+ if (config2.encryptionKey) {
1421
+ opts.encryptionKey = config2.encryptionKey;
1422
+ }
1423
+ _client = createClient(opts);
1424
+ _resilientClient = wrapWithRetry(_client);
1425
+ }
1426
+ function isInitialized() {
1427
+ return _client !== null;
1428
+ }
1429
+ function getClient() {
1430
+ if (!_resilientClient) {
1431
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1432
+ }
1433
+ if (process.env.EXE_IS_DAEMON === "1") {
1434
+ return _resilientClient;
1435
+ }
1436
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1437
+ return _daemonClient;
1438
+ }
1439
+ return _resilientClient;
1440
+ }
1441
+ async function initDaemonClient() {
1442
+ if (process.env.EXE_IS_DAEMON === "1") return;
1443
+ if (!_resilientClient) return;
1444
+ try {
1445
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1446
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1447
+ } catch (err) {
1448
+ process.stderr.write(
1449
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1450
+ `
1451
+ );
1452
+ }
1453
+ }
1454
+ function getRawClient() {
1455
+ if (!_client) {
1456
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1457
+ }
1458
+ return _client;
1459
+ }
1460
+ async function ensureSchema() {
1461
+ const client = getRawClient();
1462
+ await client.execute("PRAGMA journal_mode = WAL");
1463
+ await client.execute("PRAGMA busy_timeout = 30000");
1464
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
1465
+ try {
1466
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
1467
+ } catch {
1468
+ }
1158
1469
  await client.executeMultiple(`
1159
- CREATE TABLE IF NOT EXISTS reminders (
1470
+ CREATE TABLE IF NOT EXISTS memories (
1160
1471
  id TEXT PRIMARY KEY,
1161
- text TEXT NOT NULL,
1162
- created_at TEXT NOT NULL,
1163
- due_date TEXT,
1164
- completed_at TEXT
1472
+ agent_id TEXT NOT NULL,
1473
+ agent_role TEXT NOT NULL,
1474
+ session_id TEXT NOT NULL,
1475
+ timestamp TEXT NOT NULL,
1476
+ tool_name TEXT NOT NULL,
1477
+ project_name TEXT NOT NULL,
1478
+ has_error INTEGER NOT NULL DEFAULT 0,
1479
+ raw_text TEXT NOT NULL,
1480
+ vector F32_BLOB(1024),
1481
+ version INTEGER NOT NULL DEFAULT 0
1165
1482
  );
1483
+
1484
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
1485
+ ON memories(agent_id);
1486
+
1487
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
1488
+ ON memories(timestamp);
1489
+
1490
+ CREATE INDEX IF NOT EXISTS idx_memories_session
1491
+ ON memories(session_id);
1492
+
1493
+ CREATE INDEX IF NOT EXISTS idx_memories_project
1494
+ ON memories(project_name);
1495
+
1496
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
1497
+ ON memories(tool_name);
1498
+
1499
+ CREATE INDEX IF NOT EXISTS idx_memories_version
1500
+ ON memories(version);
1501
+
1502
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
1503
+ ON memories(agent_id, project_name);
1166
1504
  `);
1167
1505
  await client.executeMultiple(`
1168
- CREATE TABLE IF NOT EXISTS notifications (
1169
- id TEXT PRIMARY KEY,
1170
- agent_id TEXT NOT NULL,
1171
- agent_role TEXT NOT NULL,
1172
- event TEXT NOT NULL,
1173
- project TEXT NOT NULL,
1174
- summary TEXT NOT NULL,
1175
- task_file TEXT,
1176
- read INTEGER NOT NULL DEFAULT 0,
1177
- created_at TEXT NOT NULL
1506
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1507
+ raw_text,
1508
+ content='memories',
1509
+ content_rowid='rowid'
1178
1510
  );
1179
1511
 
1180
- CREATE INDEX IF NOT EXISTS idx_notifications_read
1181
- ON notifications(read);
1512
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1513
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1514
+ END;
1182
1515
 
1183
- CREATE INDEX IF NOT EXISTS idx_notifications_agent
1184
- ON notifications(agent_id);
1516
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
1517
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1518
+ END;
1185
1519
 
1186
- CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1187
- ON notifications(task_file);
1520
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1521
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1522
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1523
+ END;
1188
1524
  `);
1189
1525
  await client.executeMultiple(`
1190
- CREATE TABLE IF NOT EXISTS schedules (
1191
- id TEXT PRIMARY KEY,
1192
- cron TEXT NOT NULL,
1193
- description TEXT NOT NULL,
1194
- job_type TEXT NOT NULL DEFAULT 'report',
1195
- prompt TEXT,
1196
- assigned_to TEXT,
1197
- project_name TEXT,
1198
- active INTEGER NOT NULL DEFAULT 1,
1199
- use_crontab INTEGER NOT NULL DEFAULT 0,
1200
- created_at TEXT NOT NULL
1526
+ CREATE TABLE IF NOT EXISTS sync_meta (
1527
+ key TEXT PRIMARY KEY,
1528
+ value TEXT NOT NULL
1201
1529
  );
1202
1530
  `);
1203
1531
  await client.executeMultiple(`
1204
- CREATE TABLE IF NOT EXISTS device_registry (
1205
- device_id TEXT PRIMARY KEY,
1206
- friendly_name TEXT NOT NULL,
1207
- hostname TEXT NOT NULL,
1208
- projects TEXT NOT NULL DEFAULT '[]',
1209
- agents TEXT NOT NULL DEFAULT '[]',
1210
- connected INTEGER DEFAULT 0,
1211
- last_seen TEXT NOT NULL
1532
+ CREATE TABLE IF NOT EXISTS tasks (
1533
+ id TEXT PRIMARY KEY,
1534
+ title TEXT NOT NULL,
1535
+ assigned_to TEXT NOT NULL,
1536
+ assigned_by TEXT NOT NULL,
1537
+ project_name TEXT NOT NULL,
1538
+ priority TEXT NOT NULL DEFAULT 'p1',
1539
+ status TEXT NOT NULL DEFAULT 'open',
1540
+ task_file TEXT,
1541
+ created_at TEXT NOT NULL,
1542
+ updated_at TEXT NOT NULL
1212
1543
  );
1544
+
1545
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
1546
+ ON tasks(assigned_to, status);
1213
1547
  `);
1214
1548
  await client.executeMultiple(`
1215
- CREATE TABLE IF NOT EXISTS messages (
1216
- id TEXT PRIMARY KEY,
1217
- from_agent TEXT NOT NULL,
1218
- from_device TEXT NOT NULL DEFAULT 'local',
1219
- target_agent TEXT NOT NULL,
1220
- target_project TEXT,
1221
- target_device TEXT NOT NULL DEFAULT 'local',
1222
- content TEXT NOT NULL,
1223
- priority TEXT DEFAULT 'normal',
1224
- status TEXT DEFAULT 'pending',
1225
- server_seq INTEGER,
1226
- retry_count INTEGER DEFAULT 0,
1227
- created_at TEXT NOT NULL,
1228
- delivered_at TEXT,
1229
- processed_at TEXT,
1230
- failed_at TEXT,
1231
- failure_reason TEXT
1549
+ CREATE TABLE IF NOT EXISTS behaviors (
1550
+ id TEXT PRIMARY KEY,
1551
+ agent_id TEXT NOT NULL,
1552
+ project_name TEXT,
1553
+ domain TEXT,
1554
+ content TEXT NOT NULL,
1555
+ active INTEGER NOT NULL DEFAULT 1,
1556
+ created_at TEXT NOT NULL,
1557
+ updated_at TEXT NOT NULL
1232
1558
  );
1233
1559
 
1234
- CREATE INDEX IF NOT EXISTS idx_messages_target
1235
- ON messages(target_agent, status);
1236
-
1560
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
1561
+ ON behaviors(agent_id, active);
1562
+ `);
1563
+ try {
1564
+ const coordinatorName = getCoordinatorName();
1565
+ const existing = await client.execute({
1566
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1567
+ args: [coordinatorName]
1568
+ });
1569
+ if (Number(existing.rows[0]?.cnt) === 0) {
1570
+ const seededAt = "2026-03-25T00:00:00Z";
1571
+ for (const [domain, content] of [
1572
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1573
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1574
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1575
+ ]) {
1576
+ await client.execute({
1577
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1578
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1579
+ args: [coordinatorName, domain, content, seededAt, seededAt]
1580
+ });
1581
+ }
1582
+ }
1583
+ } catch {
1584
+ }
1585
+ try {
1586
+ await client.execute({
1587
+ sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1588
+ args: []
1589
+ });
1590
+ } catch {
1591
+ }
1592
+ try {
1593
+ await client.execute({
1594
+ sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1595
+ args: []
1596
+ });
1597
+ } catch {
1598
+ }
1599
+ try {
1600
+ await client.execute({
1601
+ sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1602
+ args: []
1603
+ });
1604
+ } catch {
1605
+ }
1606
+ try {
1607
+ await client.execute({
1608
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1609
+ ON tasks(parent_task_id)
1610
+ WHERE parent_task_id IS NOT NULL`,
1611
+ args: []
1612
+ });
1613
+ } catch {
1614
+ }
1615
+ try {
1616
+ await client.execute({
1617
+ sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1618
+ args: []
1619
+ });
1620
+ } catch {
1621
+ }
1622
+ try {
1623
+ await client.execute({
1624
+ sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1625
+ args: []
1626
+ });
1627
+ } catch {
1628
+ }
1629
+ try {
1630
+ await client.execute({
1631
+ sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1632
+ args: []
1633
+ });
1634
+ } catch {
1635
+ }
1636
+ try {
1637
+ await client.execute({
1638
+ sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1639
+ args: []
1640
+ });
1641
+ } catch {
1642
+ }
1643
+ try {
1644
+ await client.execute({
1645
+ sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1646
+ args: []
1647
+ });
1648
+ } catch {
1649
+ }
1650
+ try {
1651
+ await client.execute({
1652
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1653
+ args: []
1654
+ });
1655
+ } catch {
1656
+ }
1657
+ try {
1658
+ await client.execute({
1659
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1660
+ args: []
1661
+ });
1662
+ } catch {
1663
+ }
1664
+ try {
1665
+ await client.execute({
1666
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1667
+ args: []
1668
+ });
1669
+ } catch {
1670
+ }
1671
+ try {
1672
+ await client.execute({
1673
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1674
+ args: []
1675
+ });
1676
+ } catch {
1677
+ }
1678
+ try {
1679
+ await client.execute({
1680
+ sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1681
+ args: []
1682
+ });
1683
+ } catch {
1684
+ }
1685
+ try {
1686
+ await client.execute({
1687
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1688
+ args: []
1689
+ });
1690
+ } catch {
1691
+ }
1692
+ try {
1693
+ await client.execute({
1694
+ sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1695
+ args: []
1696
+ });
1697
+ } catch {
1698
+ }
1699
+ try {
1700
+ await client.execute({
1701
+ sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
1702
+ args: []
1703
+ });
1704
+ } catch {
1705
+ }
1706
+ await client.executeMultiple(`
1707
+ CREATE TABLE IF NOT EXISTS consolidations (
1708
+ id TEXT PRIMARY KEY,
1709
+ consolidated_memory_id TEXT NOT NULL,
1710
+ source_memory_id TEXT NOT NULL,
1711
+ created_at TEXT NOT NULL
1712
+ );
1713
+
1714
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
1715
+ ON consolidations(source_memory_id);
1716
+
1717
+ CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
1718
+ ON consolidations(consolidated_memory_id);
1719
+ `);
1720
+ await client.executeMultiple(`
1721
+ CREATE TABLE IF NOT EXISTS reminders (
1722
+ id TEXT PRIMARY KEY,
1723
+ text TEXT NOT NULL,
1724
+ created_at TEXT NOT NULL,
1725
+ due_date TEXT,
1726
+ completed_at TEXT
1727
+ );
1728
+ `);
1729
+ await client.executeMultiple(`
1730
+ CREATE TABLE IF NOT EXISTS notifications (
1731
+ id TEXT PRIMARY KEY,
1732
+ agent_id TEXT NOT NULL,
1733
+ agent_role TEXT NOT NULL,
1734
+ event TEXT NOT NULL,
1735
+ project TEXT NOT NULL,
1736
+ summary TEXT NOT NULL,
1737
+ task_file TEXT,
1738
+ read INTEGER NOT NULL DEFAULT 0,
1739
+ created_at TEXT NOT NULL
1740
+ );
1741
+
1742
+ CREATE INDEX IF NOT EXISTS idx_notifications_read
1743
+ ON notifications(read);
1744
+
1745
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent
1746
+ ON notifications(agent_id);
1747
+
1748
+ CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1749
+ ON notifications(task_file);
1750
+ `);
1751
+ await client.executeMultiple(`
1752
+ CREATE TABLE IF NOT EXISTS schedules (
1753
+ id TEXT PRIMARY KEY,
1754
+ cron TEXT NOT NULL,
1755
+ description TEXT NOT NULL,
1756
+ job_type TEXT NOT NULL DEFAULT 'report',
1757
+ prompt TEXT,
1758
+ assigned_to TEXT,
1759
+ project_name TEXT,
1760
+ active INTEGER NOT NULL DEFAULT 1,
1761
+ use_crontab INTEGER NOT NULL DEFAULT 0,
1762
+ created_at TEXT NOT NULL
1763
+ );
1764
+ `);
1765
+ await client.executeMultiple(`
1766
+ CREATE TABLE IF NOT EXISTS device_registry (
1767
+ device_id TEXT PRIMARY KEY,
1768
+ friendly_name TEXT NOT NULL,
1769
+ hostname TEXT NOT NULL,
1770
+ projects TEXT NOT NULL DEFAULT '[]',
1771
+ agents TEXT NOT NULL DEFAULT '[]',
1772
+ connected INTEGER DEFAULT 0,
1773
+ last_seen TEXT NOT NULL
1774
+ );
1775
+ `);
1776
+ await client.executeMultiple(`
1777
+ CREATE TABLE IF NOT EXISTS messages (
1778
+ id TEXT PRIMARY KEY,
1779
+ from_agent TEXT NOT NULL,
1780
+ from_device TEXT NOT NULL DEFAULT 'local',
1781
+ target_agent TEXT NOT NULL,
1782
+ target_project TEXT,
1783
+ target_device TEXT NOT NULL DEFAULT 'local',
1784
+ content TEXT NOT NULL,
1785
+ priority TEXT DEFAULT 'normal',
1786
+ status TEXT DEFAULT 'pending',
1787
+ server_seq INTEGER,
1788
+ retry_count INTEGER DEFAULT 0,
1789
+ created_at TEXT NOT NULL,
1790
+ delivered_at TEXT,
1791
+ processed_at TEXT,
1792
+ failed_at TEXT,
1793
+ failure_reason TEXT
1794
+ );
1795
+
1796
+ CREATE INDEX IF NOT EXISTS idx_messages_target
1797
+ ON messages(target_agent, status);
1798
+
1237
1799
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1238
1800
  ON messages(target_agent, from_agent, server_seq);
1239
1801
  `);
@@ -1375,6 +1937,12 @@ async function ensureSchema() {
1375
1937
  } catch {
1376
1938
  }
1377
1939
  }
1940
+ try {
1941
+ await client.execute(
1942
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1943
+ );
1944
+ } catch {
1945
+ }
1378
1946
  await client.executeMultiple(`
1379
1947
  CREATE TABLE IF NOT EXISTS entities (
1380
1948
  id TEXT PRIMARY KEY,
@@ -1427,7 +1995,30 @@ async function ensureSchema() {
1427
1995
  entity_id TEXT NOT NULL,
1428
1996
  PRIMARY KEY (hyperedge_id, entity_id)
1429
1997
  );
1998
+
1999
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
2000
+ name,
2001
+ content=entities,
2002
+ content_rowid=rowid
2003
+ );
2004
+
2005
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
2006
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2007
+ END;
2008
+
2009
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
2010
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2011
+ END;
2012
+
2013
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
2014
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2015
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2016
+ END;
1430
2017
  `);
2018
+ try {
2019
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
2020
+ } catch {
2021
+ }
1431
2022
  await client.executeMultiple(`
1432
2023
  CREATE TABLE IF NOT EXISTS entity_aliases (
1433
2024
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1608,7 +2199,34 @@ async function ensureSchema() {
1608
2199
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1609
2200
  ON conversations(channel_id);
1610
2201
  `);
1611
- try {
2202
+ await client.executeMultiple(`
2203
+ CREATE TABLE IF NOT EXISTS session_agent_map (
2204
+ session_uuid TEXT PRIMARY KEY,
2205
+ agent_id TEXT NOT NULL,
2206
+ session_name TEXT,
2207
+ task_id TEXT,
2208
+ project_name TEXT,
2209
+ started_at TEXT NOT NULL
2210
+ );
2211
+
2212
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2213
+ ON session_agent_map(agent_id);
2214
+ `);
2215
+ try {
2216
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2217
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
2218
+ await client.execute({
2219
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
2220
+ SELECT session_id, agent_id, '', MIN(timestamp)
2221
+ FROM memories
2222
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
2223
+ GROUP BY session_id, agent_id`,
2224
+ args: []
2225
+ });
2226
+ }
2227
+ } catch {
2228
+ }
2229
+ try {
1612
2230
  await client.execute({
1613
2231
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
1614
2232
  args: []
@@ -1741,15 +2359,41 @@ async function ensureSchema() {
1741
2359
  });
1742
2360
  } catch {
1743
2361
  }
2362
+ for (const col of [
2363
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2364
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2365
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2366
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2367
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2368
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2369
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2370
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2371
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2372
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2373
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2374
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2375
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2376
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2377
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2378
+ ]) {
2379
+ try {
2380
+ await client.execute(col);
2381
+ } catch {
2382
+ }
2383
+ }
1744
2384
  }
1745
2385
  async function disposeDatabase() {
2386
+ if (_daemonClient) {
2387
+ _daemonClient.close();
2388
+ _daemonClient = null;
2389
+ }
1746
2390
  if (_client) {
1747
2391
  _client.close();
1748
2392
  _client = null;
1749
2393
  _resilientClient = null;
1750
2394
  }
1751
2395
  }
1752
- var _client, _resilientClient, initTurso, disposeTurso;
2396
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1753
2397
  var init_database = __esm({
1754
2398
  "src/lib/database.ts"() {
1755
2399
  "use strict";
@@ -1757,24 +2401,25 @@ var init_database = __esm({
1757
2401
  init_employees();
1758
2402
  _client = null;
1759
2403
  _resilientClient = null;
2404
+ _daemonClient = null;
1760
2405
  initTurso = initDatabase;
1761
2406
  disposeTurso = disposeDatabase;
1762
2407
  }
1763
2408
  });
1764
2409
 
1765
2410
  // src/lib/license.ts
1766
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1767
- import { randomUUID as randomUUID2 } from "crypto";
1768
- import path6 from "path";
2411
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2412
+ import { randomUUID as randomUUID3 } from "crypto";
2413
+ import path7 from "path";
1769
2414
  import { jwtVerify, importSPKI } from "jose";
1770
2415
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1771
2416
  var init_license = __esm({
1772
2417
  "src/lib/license.ts"() {
1773
2418
  "use strict";
1774
2419
  init_config();
1775
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
1776
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
1777
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2420
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2421
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2422
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
1778
2423
  PLAN_LIMITS = {
1779
2424
  free: { devices: 1, employees: 1, memories: 5e3 },
1780
2425
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1786,12 +2431,12 @@ var init_license = __esm({
1786
2431
  });
1787
2432
 
1788
2433
  // src/lib/plan-limits.ts
1789
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1790
- import path7 from "path";
2434
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2435
+ import path8 from "path";
1791
2436
  function getLicenseSync() {
1792
2437
  try {
1793
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
1794
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
2438
+ if (!existsSync7(CACHE_PATH2)) return freeLicense();
2439
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
1795
2440
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1796
2441
  const parts = raw.token.split(".");
1797
2442
  if (parts.length !== 3) return freeLicense();
@@ -1829,8 +2474,8 @@ function assertEmployeeLimitSync(rosterPath) {
1829
2474
  const filePath = rosterPath ?? EMPLOYEES_PATH;
1830
2475
  let count = 0;
1831
2476
  try {
1832
- if (existsSync6(filePath)) {
1833
- const raw = readFileSync6(filePath, "utf8");
2477
+ if (existsSync7(filePath)) {
2478
+ const raw = readFileSync7(filePath, "utf8");
1834
2479
  const employees = JSON.parse(raw);
1835
2480
  count = Array.isArray(employees) ? employees.length : 0;
1836
2481
  }
@@ -1859,19 +2504,19 @@ var init_plan_limits = __esm({
1859
2504
  this.name = "PlanLimitError";
1860
2505
  }
1861
2506
  };
1862
- CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
2507
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
1863
2508
  }
1864
2509
  });
1865
2510
 
1866
2511
  // src/lib/notifications.ts
1867
2512
  import crypto from "crypto";
1868
- import path8 from "path";
2513
+ import path9 from "path";
1869
2514
  import os6 from "os";
1870
2515
  import {
1871
- readFileSync as readFileSync7,
2516
+ readFileSync as readFileSync8,
1872
2517
  readdirSync,
1873
- unlinkSync as unlinkSync2,
1874
- existsSync as existsSync7,
2518
+ unlinkSync as unlinkSync3,
2519
+ existsSync as existsSync8,
1875
2520
  rmdirSync
1876
2521
  } from "fs";
1877
2522
  async function writeNotification(notification) {
@@ -2035,10 +2680,11 @@ var init_state_bus = __esm({
2035
2680
 
2036
2681
  // src/lib/tasks-crud.ts
2037
2682
  import crypto3 from "crypto";
2038
- import path9 from "path";
2683
+ import path10 from "path";
2684
+ import os7 from "os";
2039
2685
  import { execSync as execSync5 } from "child_process";
2040
2686
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2041
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
2687
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2042
2688
  async function writeCheckpoint(input) {
2043
2689
  const client = getClient();
2044
2690
  const row = await resolveTask(client, input.taskId);
@@ -2079,6 +2725,35 @@ function extractParentFromContext(contextBody) {
2079
2725
  function slugify(title) {
2080
2726
  return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2081
2727
  }
2728
+ function buildKeywordIndex() {
2729
+ const idx = /* @__PURE__ */ new Map();
2730
+ for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
2731
+ for (const kw of keywords) {
2732
+ const existing = idx.get(kw) ?? [];
2733
+ existing.push(role);
2734
+ idx.set(kw, existing);
2735
+ }
2736
+ }
2737
+ return idx;
2738
+ }
2739
+ function checkLaneAffinity(title, context, assigneeName) {
2740
+ const employees = loadEmployeesSync();
2741
+ const employee = employees.find((e) => e.name === assigneeName);
2742
+ if (!employee) return void 0;
2743
+ const assigneeRole = employee.role;
2744
+ const text = `${title} ${context}`.toLowerCase();
2745
+ const matchedRoles = /* @__PURE__ */ new Set();
2746
+ for (const [keyword, roles] of KEYWORD_INDEX) {
2747
+ if (text.includes(keyword)) {
2748
+ for (const role of roles) matchedRoles.add(role);
2749
+ }
2750
+ }
2751
+ if (matchedRoles.size === 0) return void 0;
2752
+ if (matchedRoles.has(assigneeRole)) return void 0;
2753
+ if (assigneeRole === "COO") return void 0;
2754
+ const expectedRoles = Array.from(matchedRoles).join(" or ");
2755
+ return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
2756
+ }
2082
2757
  async function resolveTask(client, identifier, scopeSession) {
2083
2758
  const scope = sessionScopeFilter(scopeSession);
2084
2759
  let result = await client.execute({
@@ -2128,7 +2803,14 @@ async function createTaskCore(input) {
2128
2803
  const id = crypto3.randomUUID();
2129
2804
  const now = (/* @__PURE__ */ new Date()).toISOString();
2130
2805
  const slug = slugify(input.title);
2131
- const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
2806
+ let earlySessionScope = null;
2807
+ try {
2808
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2809
+ earlySessionScope = resolveExeSession2();
2810
+ } catch {
2811
+ }
2812
+ const scope = earlySessionScope ?? "default";
2813
+ const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
2132
2814
  let blockedById = null;
2133
2815
  const initialStatus = input.blockedBy ? "blocked" : "open";
2134
2816
  if (input.blockedBy) {
@@ -2168,22 +2850,24 @@ async function createTaskCore(input) {
2168
2850
  if (dupCheck.rows.length > 0) {
2169
2851
  warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
2170
2852
  }
2853
+ if (!process.env.DISABLE_LANE_AFFINITY) {
2854
+ const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
2855
+ if (laneWarning) {
2856
+ warning = warning ? `${warning}
2857
+ ${laneWarning}` : laneWarning;
2858
+ }
2859
+ }
2171
2860
  if (input.baseDir) {
2172
2861
  try {
2173
- await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
2174
- await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2862
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2863
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2175
2864
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2176
2865
  await ensureGitignoreExe(input.baseDir);
2177
2866
  } catch {
2178
2867
  }
2179
2868
  }
2180
2869
  const complexity = input.complexity ?? "standard";
2181
- let sessionScope = null;
2182
- try {
2183
- const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2184
- sessionScope = resolveExeSession2();
2185
- } catch {
2186
- }
2870
+ const sessionScope = earlySessionScope;
2187
2871
  await client.execute({
2188
2872
  sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
2189
2873
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -2210,6 +2894,39 @@ async function createTaskCore(input) {
2210
2894
  now
2211
2895
  ]
2212
2896
  });
2897
+ if (input.baseDir) {
2898
+ try {
2899
+ const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2900
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
2901
+ const mdDir = path10.dirname(mdPath);
2902
+ if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2903
+ const reviewer = input.reviewer ?? input.assignedBy;
2904
+ const mdContent = `# ${input.title}
2905
+
2906
+ **ID:** ${id}
2907
+ **Status:** ${initialStatus}
2908
+ **Priority:** ${input.priority}
2909
+ **Assigned by:** ${input.assignedBy}
2910
+ **Assigned to:** ${input.assignedTo}
2911
+ **Project:** ${input.projectName}
2912
+ **Created:** ${now.split("T")[0]}${parentTaskId ? `
2913
+ **Parent task:** ${parentTaskId}` : ""}
2914
+ **Reviewer:** ${reviewer}
2915
+
2916
+ ## Context
2917
+
2918
+ ${input.context}
2919
+
2920
+ ## MANDATORY: When done
2921
+
2922
+ You MUST call update_task with status "done" and a result summary when finished.
2923
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2924
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
2925
+ `;
2926
+ await writeFile3(mdPath, mdContent, "utf-8");
2927
+ } catch {
2928
+ }
2929
+ }
2213
2930
  return {
2214
2931
  id,
2215
2932
  title: input.title,
@@ -2402,7 +3119,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2402
3119
  return { row, taskFile, now, taskId };
2403
3120
  }
2404
3121
  }
2405
- if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
3122
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
2406
3123
  process.stderr.write(
2407
3124
  `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2408
3125
  `
@@ -2467,9 +3184,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2467
3184
  return { taskFile, assignedTo, assignedBy, taskSlug };
2468
3185
  }
2469
3186
  async function ensureArchitectureDoc(baseDir, projectName) {
2470
- const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
3187
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2471
3188
  try {
2472
- if (existsSync8(archPath)) return;
3189
+ if (existsSync9(archPath)) return;
2473
3190
  const template = [
2474
3191
  `# ${projectName} \u2014 System Architecture`,
2475
3192
  "",
@@ -2502,10 +3219,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2502
3219
  }
2503
3220
  }
2504
3221
  async function ensureGitignoreExe(baseDir) {
2505
- const gitignorePath = path9.join(baseDir, ".gitignore");
3222
+ const gitignorePath = path10.join(baseDir, ".gitignore");
2506
3223
  try {
2507
- if (existsSync8(gitignorePath)) {
2508
- const content = readFileSync8(gitignorePath, "utf-8");
3224
+ if (existsSync9(gitignorePath)) {
3225
+ const content = readFileSync9(gitignorePath, "utf-8");
2509
3226
  if (/^\/?exe\/?$/m.test(content)) return;
2510
3227
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2511
3228
  } else {
@@ -2514,20 +3231,30 @@ async function ensureGitignoreExe(baseDir) {
2514
3231
  } catch {
2515
3232
  }
2516
3233
  }
2517
- var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
3234
+ var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2518
3235
  var init_tasks_crud = __esm({
2519
3236
  "src/lib/tasks-crud.ts"() {
2520
3237
  "use strict";
2521
3238
  init_database();
2522
3239
  init_task_scope();
3240
+ init_employees();
3241
+ LANE_KEYWORDS = {
3242
+ CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
3243
+ CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
3244
+ "Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
3245
+ "Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
3246
+ "Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
3247
+ "AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
3248
+ };
3249
+ KEYWORD_INDEX = buildKeywordIndex();
2523
3250
  DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2524
3251
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2525
3252
  }
2526
3253
  });
2527
3254
 
2528
3255
  // src/lib/tasks-review.ts
2529
- import path10 from "path";
2530
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3256
+ import path11 from "path";
3257
+ import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2531
3258
  async function countPendingReviews(sessionScope) {
2532
3259
  const client = getClient();
2533
3260
  if (sessionScope) {
@@ -2549,7 +3276,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
2549
3276
  const result2 = await client.execute({
2550
3277
  sql: `SELECT COUNT(*) as cnt FROM tasks
2551
3278
  WHERE status = 'needs_review' AND updated_at > ?
2552
- AND (session_scope = ? OR session_scope IS NULL)`,
3279
+ AND session_scope = ?`,
2553
3280
  args: [sinceIso, sessionScope]
2554
3281
  });
2555
3282
  return Number(result2.rows[0]?.cnt) || 0;
@@ -2567,7 +3294,7 @@ async function listPendingReviews(limit, sessionScope) {
2567
3294
  const result2 = await client.execute({
2568
3295
  sql: `SELECT title, assigned_to, project_name FROM tasks
2569
3296
  WHERE status = 'needs_review'
2570
- AND (session_scope = ? OR session_scope IS NULL)
3297
+ AND session_scope = ?
2571
3298
  ORDER BY priority ASC, created_at DESC LIMIT ?`,
2572
3299
  args: [sessionScope, limit]
2573
3300
  });
@@ -2688,14 +3415,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2688
3415
  if (parts.length >= 3 && parts[0] === "review") {
2689
3416
  const agent = parts[1];
2690
3417
  const slug = parts.slice(2).join("-");
2691
- const originalTaskFile = `exe/${agent}/${slug}.md`;
3418
+ const legacyTaskFile = `exe/${agent}/${slug}.md`;
2692
3419
  const result = await client.execute({
2693
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
2694
- args: [now, originalTaskFile]
3420
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
3421
+ args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
2695
3422
  });
2696
3423
  if (result.rowsAffected > 0) {
2697
3424
  process.stderr.write(
2698
- `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
3425
+ `[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
2699
3426
  `
2700
3427
  );
2701
3428
  }
@@ -2708,11 +3435,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2708
3435
  );
2709
3436
  }
2710
3437
  try {
2711
- const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
2712
- if (existsSync9(cacheDir)) {
3438
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3439
+ if (existsSync10(cacheDir)) {
2713
3440
  for (const f of readdirSync2(cacheDir)) {
2714
3441
  if (f.startsWith("review-notified-")) {
2715
- unlinkSync3(path10.join(cacheDir, f));
3442
+ unlinkSync4(path11.join(cacheDir, f));
2716
3443
  }
2717
3444
  }
2718
3445
  }
@@ -2733,7 +3460,7 @@ var init_tasks_review = __esm({
2733
3460
  });
2734
3461
 
2735
3462
  // src/lib/tasks-chain.ts
2736
- import path11 from "path";
3463
+ import path12 from "path";
2737
3464
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2738
3465
  async function cascadeUnblock(taskId, baseDir, now) {
2739
3466
  const client = getClient();
@@ -2750,7 +3477,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
2750
3477
  });
2751
3478
  for (const ur of unblockedRows.rows) {
2752
3479
  try {
2753
- const ubFile = path11.join(baseDir, String(ur.task_file));
3480
+ const ubFile = path12.join(baseDir, String(ur.task_file));
2754
3481
  let ubContent = await readFile3(ubFile, "utf-8");
2755
3482
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
2756
3483
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -2819,7 +3546,7 @@ var init_tasks_chain = __esm({
2819
3546
 
2820
3547
  // src/lib/project-name.ts
2821
3548
  import { execSync as execSync6 } from "child_process";
2822
- import path12 from "path";
3549
+ import path13 from "path";
2823
3550
  function getProjectName(cwd) {
2824
3551
  const dir = cwd ?? process.cwd();
2825
3552
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -2832,7 +3559,7 @@ function getProjectName(cwd) {
2832
3559
  timeout: 2e3,
2833
3560
  stdio: ["pipe", "pipe", "pipe"]
2834
3561
  }).trim();
2835
- repoRoot = path12.dirname(gitCommonDir);
3562
+ repoRoot = path13.dirname(gitCommonDir);
2836
3563
  } catch {
2837
3564
  repoRoot = execSync6("git rev-parse --show-toplevel", {
2838
3565
  cwd: dir,
@@ -2841,11 +3568,11 @@ function getProjectName(cwd) {
2841
3568
  stdio: ["pipe", "pipe", "pipe"]
2842
3569
  }).trim();
2843
3570
  }
2844
- _cached2 = path12.basename(repoRoot);
3571
+ _cached2 = path13.basename(repoRoot);
2845
3572
  _cachedCwd = dir;
2846
3573
  return _cached2;
2847
3574
  } catch {
2848
- _cached2 = path12.basename(dir);
3575
+ _cached2 = path13.basename(dir);
2849
3576
  _cachedCwd = dir;
2850
3577
  return _cached2;
2851
3578
  }
@@ -2877,7 +3604,7 @@ function findSessionForProject(projectName) {
2877
3604
  const sessions = listSessions();
2878
3605
  for (const s of sessions) {
2879
3606
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2880
- if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3607
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
2881
3608
  }
2882
3609
  return null;
2883
3610
  }
@@ -2923,7 +3650,7 @@ var init_session_scope = __esm({
2923
3650
 
2924
3651
  // src/lib/tasks-notify.ts
2925
3652
  async function dispatchTaskToEmployee(input) {
2926
- if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3653
+ if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
2927
3654
  let crossProject = false;
2928
3655
  if (input.projectName) {
2929
3656
  try {
@@ -3380,8 +4107,8 @@ __export(tasks_exports, {
3380
4107
  updateTaskStatus: () => updateTaskStatus,
3381
4108
  writeCheckpoint: () => writeCheckpoint
3382
4109
  });
3383
- import path13 from "path";
3384
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
4110
+ import path14 from "path";
4111
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
3385
4112
  async function createTask(input) {
3386
4113
  const result = await createTaskCore(input);
3387
4114
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3400,14 +4127,14 @@ async function updateTask(input) {
3400
4127
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3401
4128
  try {
3402
4129
  const agent = String(row.assigned_to);
3403
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3404
- const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
4130
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4131
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3405
4132
  if (input.status === "in_progress") {
3406
4133
  mkdirSync4(cacheDir, { recursive: true });
3407
4134
  writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3408
4135
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3409
4136
  try {
3410
- unlinkSync4(cachePath);
4137
+ unlinkSync5(cachePath);
3411
4138
  } catch {
3412
4139
  }
3413
4140
  }
@@ -3464,7 +4191,7 @@ async function updateTask(input) {
3464
4191
  }
3465
4192
  const isTerminal = input.status === "done" || input.status === "needs_review";
3466
4193
  if (isTerminal) {
3467
- const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
4194
+ const isCoordinator = isCoordinatorName(String(row.assigned_to));
3468
4195
  if (!isCoordinator) {
3469
4196
  notifyTaskDone();
3470
4197
  }
@@ -3489,7 +4216,7 @@ async function updateTask(input) {
3489
4216
  }
3490
4217
  }
3491
4218
  }
3492
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4219
+ if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3493
4220
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3494
4221
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3495
4222
  taskId,
@@ -3505,7 +4232,7 @@ async function updateTask(input) {
3505
4232
  });
3506
4233
  }
3507
4234
  let nextTask;
3508
- if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
4235
+ if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
3509
4236
  try {
3510
4237
  nextTask = await findNextTask(String(row.assigned_to));
3511
4238
  } catch {
@@ -3849,7 +4576,7 @@ var init_capacity_monitor = __esm({
3849
4576
  // src/lib/tmux-routing.ts
3850
4577
  var tmux_routing_exports = {};
3851
4578
  __export(tmux_routing_exports, {
3852
- acquireSpawnLock: () => acquireSpawnLock,
4579
+ acquireSpawnLock: () => acquireSpawnLock2,
3853
4580
  employeeSessionName: () => employeeSessionName,
3854
4581
  ensureEmployee: () => ensureEmployee,
3855
4582
  extractRootExe: () => extractRootExe,
@@ -3864,20 +4591,20 @@ __export(tmux_routing_exports, {
3864
4591
  notifyParentExe: () => notifyParentExe,
3865
4592
  parseParentExe: () => parseParentExe,
3866
4593
  registerParentExe: () => registerParentExe,
3867
- releaseSpawnLock: () => releaseSpawnLock,
4594
+ releaseSpawnLock: () => releaseSpawnLock2,
3868
4595
  resolveExeSession: () => resolveExeSession,
3869
4596
  sendIntercom: () => sendIntercom,
3870
4597
  spawnEmployee: () => spawnEmployee,
3871
4598
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3872
4599
  });
3873
4600
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
3874
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3875
- import path14 from "path";
3876
- import os7 from "os";
3877
- import { fileURLToPath } from "url";
3878
- import { unlinkSync as unlinkSync5 } from "fs";
4601
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4602
+ import path15 from "path";
4603
+ import os8 from "os";
4604
+ import { fileURLToPath as fileURLToPath2 } from "url";
4605
+ import { unlinkSync as unlinkSync6 } from "fs";
3879
4606
  function spawnLockPath(sessionName) {
3880
- return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4607
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3881
4608
  }
3882
4609
  function isProcessAlive(pid) {
3883
4610
  try {
@@ -3887,14 +4614,14 @@ function isProcessAlive(pid) {
3887
4614
  return false;
3888
4615
  }
3889
4616
  }
3890
- function acquireSpawnLock(sessionName) {
3891
- if (!existsSync10(SPAWN_LOCK_DIR)) {
4617
+ function acquireSpawnLock2(sessionName) {
4618
+ if (!existsSync11(SPAWN_LOCK_DIR)) {
3892
4619
  mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
3893
4620
  }
3894
4621
  const lockFile = spawnLockPath(sessionName);
3895
- if (existsSync10(lockFile)) {
4622
+ if (existsSync11(lockFile)) {
3896
4623
  try {
3897
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
4624
+ const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
3898
4625
  const age = Date.now() - lock.timestamp;
3899
4626
  if (isProcessAlive(lock.pid) && age < 6e4) {
3900
4627
  return false;
@@ -3905,22 +4632,22 @@ function acquireSpawnLock(sessionName) {
3905
4632
  writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3906
4633
  return true;
3907
4634
  }
3908
- function releaseSpawnLock(sessionName) {
4635
+ function releaseSpawnLock2(sessionName) {
3909
4636
  try {
3910
- unlinkSync5(spawnLockPath(sessionName));
4637
+ unlinkSync6(spawnLockPath(sessionName));
3911
4638
  } catch {
3912
4639
  }
3913
4640
  }
3914
4641
  function resolveBehaviorsExporterScript() {
3915
4642
  try {
3916
- const thisFile = fileURLToPath(import.meta.url);
3917
- const scriptPath = path14.join(
3918
- path14.dirname(thisFile),
4643
+ const thisFile = fileURLToPath2(import.meta.url);
4644
+ const scriptPath = path15.join(
4645
+ path15.dirname(thisFile),
3919
4646
  "..",
3920
4647
  "bin",
3921
4648
  "exe-export-behaviors.js"
3922
4649
  );
3923
- return existsSync10(scriptPath) ? scriptPath : null;
4650
+ return existsSync11(scriptPath) ? scriptPath : null;
3924
4651
  } catch {
3925
4652
  return null;
3926
4653
  }
@@ -3986,11 +4713,11 @@ function extractRootExe(name) {
3986
4713
  return parts.length > 0 ? parts[parts.length - 1] : null;
3987
4714
  }
3988
4715
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3989
- if (!existsSync10(SESSION_CACHE)) {
4716
+ if (!existsSync11(SESSION_CACHE)) {
3990
4717
  mkdirSync5(SESSION_CACHE, { recursive: true });
3991
4718
  }
3992
4719
  const rootExe = extractRootExe(parentExe) ?? parentExe;
3993
- const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4720
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3994
4721
  writeFileSync6(filePath, JSON.stringify({
3995
4722
  parentExe: rootExe,
3996
4723
  dispatchedBy: dispatchedBy || rootExe,
@@ -3999,7 +4726,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3999
4726
  }
4000
4727
  function getParentExe(sessionKey) {
4001
4728
  try {
4002
- const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4729
+ const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4003
4730
  return data.parentExe || null;
4004
4731
  } catch {
4005
4732
  return null;
@@ -4007,8 +4734,8 @@ function getParentExe(sessionKey) {
4007
4734
  }
4008
4735
  function getDispatchedBy(sessionKey) {
4009
4736
  try {
4010
- const data = JSON.parse(readFileSync9(
4011
- path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4737
+ const data = JSON.parse(readFileSync10(
4738
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4012
4739
  "utf8"
4013
4740
  ));
4014
4741
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4034,10 +4761,10 @@ function isEmployeeAlive(sessionName) {
4034
4761
  }
4035
4762
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
4036
4763
  const base = employeeSessionName(employeeName, exeSession);
4037
- if (!isAlive(base) && acquireSpawnLock(base)) return 0;
4764
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
4038
4765
  for (let i = 2; i <= maxInstances; i++) {
4039
4766
  const candidate = employeeSessionName(employeeName, exeSession, i);
4040
- if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
4767
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
4041
4768
  }
4042
4769
  return null;
4043
4770
  }
@@ -4069,15 +4796,15 @@ async function verifyPaneAtCapacity(sessionName) {
4069
4796
  }
4070
4797
  function readDebounceState() {
4071
4798
  try {
4072
- if (!existsSync10(DEBOUNCE_FILE)) return {};
4073
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
4799
+ if (!existsSync11(DEBOUNCE_FILE)) return {};
4800
+ return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4074
4801
  } catch {
4075
4802
  return {};
4076
4803
  }
4077
4804
  }
4078
4805
  function writeDebounceState(state) {
4079
4806
  try {
4080
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4807
+ if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4081
4808
  writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4082
4809
  } catch {
4083
4810
  }
@@ -4197,7 +4924,7 @@ function notifyParentExe(sessionKey) {
4197
4924
  return true;
4198
4925
  }
4199
4926
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4200
- if (employeeName === "exe" || isCoordinatorName(employeeName)) {
4927
+ if (isCoordinatorName(employeeName)) {
4201
4928
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
4202
4929
  }
4203
4930
  try {
@@ -4269,26 +4996,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4269
4996
  const transport = getTransport();
4270
4997
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4271
4998
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4272
- const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4273
- const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4274
- if (!existsSync10(logDir)) {
4999
+ const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
5000
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5001
+ if (!existsSync11(logDir)) {
4275
5002
  mkdirSync5(logDir, { recursive: true });
4276
5003
  }
4277
5004
  transport.kill(sessionName);
4278
5005
  let cleanupSuffix = "";
4279
5006
  try {
4280
- const thisFile = fileURLToPath(import.meta.url);
4281
- const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4282
- if (existsSync10(cleanupScript)) {
5007
+ const thisFile = fileURLToPath2(import.meta.url);
5008
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5009
+ if (existsSync11(cleanupScript)) {
4283
5010
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4284
5011
  }
4285
5012
  } catch {
4286
5013
  }
4287
5014
  try {
4288
- const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
5015
+ const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
4289
5016
  let claudeJson = {};
4290
5017
  try {
4291
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
5018
+ claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
4292
5019
  } catch {
4293
5020
  }
4294
5021
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4300,13 +5027,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4300
5027
  } catch {
4301
5028
  }
4302
5029
  try {
4303
- const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
5030
+ const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
4304
5031
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4305
- const projSettingsDir = path14.join(settingsDir, normalizedKey);
4306
- const settingsPath = path14.join(projSettingsDir, "settings.json");
5032
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
5033
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
4307
5034
  let settings = {};
4308
5035
  try {
4309
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
5036
+ settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
4310
5037
  } catch {
4311
5038
  }
4312
5039
  const perms = settings.permissions ?? {};
@@ -4347,8 +5074,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4347
5074
  let behaviorsFlag = "";
4348
5075
  let legacyFallbackWarned = false;
4349
5076
  if (!useExeAgent && !useBinSymlink) {
4350
- const identityPath = path14.join(
4351
- os7.homedir(),
5077
+ const identityPath = path15.join(
5078
+ os8.homedir(),
4352
5079
  ".exe-os",
4353
5080
  "identity",
4354
5081
  `${employeeName}.md`
@@ -4357,13 +5084,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4357
5084
  const hasAgentFlag = claudeSupportsAgentFlag();
4358
5085
  if (hasAgentFlag) {
4359
5086
  identityFlag = ` --agent ${employeeName}`;
4360
- } else if (existsSync10(identityPath)) {
5087
+ } else if (existsSync11(identityPath)) {
4361
5088
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4362
5089
  legacyFallbackWarned = true;
4363
5090
  }
4364
5091
  const behaviorsFile = exportBehaviorsSync(
4365
5092
  employeeName,
4366
- path14.basename(spawnCwd),
5093
+ path15.basename(spawnCwd),
4367
5094
  sessionName
4368
5095
  );
4369
5096
  if (behaviorsFile) {
@@ -4378,9 +5105,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4378
5105
  }
4379
5106
  let sessionContextFlag = "";
4380
5107
  try {
4381
- const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
5108
+ const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
4382
5109
  mkdirSync5(ctxDir, { recursive: true });
4383
- const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
5110
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4384
5111
  const ctxContent = [
4385
5112
  `## Session Context`,
4386
5113
  `You are running in tmux session: ${sessionName}.`,
@@ -4419,13 +5146,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4419
5146
  command: spawnCommand
4420
5147
  });
4421
5148
  if (spawnResult.error) {
4422
- releaseSpawnLock(sessionName);
5149
+ releaseSpawnLock2(sessionName);
4423
5150
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4424
5151
  }
4425
5152
  transport.pipeLog(sessionName, logFile);
4426
5153
  try {
4427
5154
  const mySession = getMySession();
4428
- const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5155
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4429
5156
  writeFileSync6(dispatchInfo, JSON.stringify({
4430
5157
  dispatchedBy: mySession,
4431
5158
  rootExe: exeSession,
@@ -4457,7 +5184,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4457
5184
  }
4458
5185
  }
4459
5186
  if (!booted) {
4460
- releaseSpawnLock(sessionName);
5187
+ releaseSpawnLock2(sessionName);
4461
5188
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4462
5189
  }
4463
5190
  if (!useExeAgent) {
@@ -4474,7 +5201,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4474
5201
  pid: 0,
4475
5202
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4476
5203
  });
4477
- releaseSpawnLock(sessionName);
5204
+ releaseSpawnLock2(sessionName);
4478
5205
  return { sessionName };
4479
5206
  }
4480
5207
  var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
@@ -4490,14 +5217,14 @@ var init_tmux_routing = __esm({
4490
5217
  init_intercom_queue();
4491
5218
  init_plan_limits();
4492
5219
  init_employees();
4493
- SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4494
- SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
5220
+ SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5221
+ SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
4495
5222
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4496
5223
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4497
5224
  VERIFY_PANE_LINES = 200;
4498
5225
  INTERCOM_DEBOUNCE_MS = 3e4;
4499
- INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4500
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
5226
+ INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5227
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4501
5228
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4502
5229
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4503
5230
  }
@@ -4514,14 +5241,14 @@ var init_memory = __esm({
4514
5241
 
4515
5242
  // src/lib/keychain.ts
4516
5243
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4517
- import { existsSync as existsSync11 } from "fs";
4518
- import path15 from "path";
4519
- import os8 from "os";
5244
+ import { existsSync as existsSync12 } from "fs";
5245
+ import path16 from "path";
5246
+ import os9 from "os";
4520
5247
  function getKeyDir() {
4521
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
5248
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
4522
5249
  }
4523
5250
  function getKeyPath() {
4524
- return path15.join(getKeyDir(), "master.key");
5251
+ return path16.join(getKeyDir(), "master.key");
4525
5252
  }
4526
5253
  async function tryKeytar() {
4527
5254
  try {
@@ -4542,13 +5269,21 @@ async function getMasterKey() {
4542
5269
  }
4543
5270
  }
4544
5271
  const keyPath = getKeyPath();
4545
- if (!existsSync11(keyPath)) {
5272
+ if (!existsSync12(keyPath)) {
5273
+ process.stderr.write(
5274
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5275
+ `
5276
+ );
4546
5277
  return null;
4547
5278
  }
4548
5279
  try {
4549
5280
  const content = await readFile4(keyPath, "utf-8");
4550
5281
  return Buffer.from(content.trim(), "base64");
4551
- } catch {
5282
+ } catch (err) {
5283
+ process.stderr.write(
5284
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
5285
+ `
5286
+ );
4552
5287
  return null;
4553
5288
  }
4554
5289
  }
@@ -4574,12 +5309,12 @@ __export(shard_manager_exports, {
4574
5309
  listShards: () => listShards,
4575
5310
  shardExists: () => shardExists
4576
5311
  });
4577
- import path16 from "path";
4578
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5312
+ import path17 from "path";
5313
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
4579
5314
  import { createClient as createClient2 } from "@libsql/client";
4580
5315
  function initShardManager(encryptionKey) {
4581
5316
  _encryptionKey = encryptionKey;
4582
- if (!existsSync12(SHARDS_DIR)) {
5317
+ if (!existsSync13(SHARDS_DIR)) {
4583
5318
  mkdirSync6(SHARDS_DIR, { recursive: true });
4584
5319
  }
4585
5320
  _shardingEnabled = true;
@@ -4600,7 +5335,7 @@ function getShardClient(projectName) {
4600
5335
  }
4601
5336
  const cached = _shards.get(safeName);
4602
5337
  if (cached) return cached;
4603
- const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
5338
+ const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
4604
5339
  const client = createClient2({
4605
5340
  url: `file:${dbPath}`,
4606
5341
  encryptionKey: _encryptionKey
@@ -4610,10 +5345,10 @@ function getShardClient(projectName) {
4610
5345
  }
4611
5346
  function shardExists(projectName) {
4612
5347
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4613
- return existsSync12(path16.join(SHARDS_DIR, `${safeName}.db`));
5348
+ return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
4614
5349
  }
4615
5350
  function listShards() {
4616
- if (!existsSync12(SHARDS_DIR)) return [];
5351
+ if (!existsSync13(SHARDS_DIR)) return [];
4617
5352
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4618
5353
  }
4619
5354
  async function ensureShardSchema(client) {
@@ -4799,7 +5534,7 @@ var init_shard_manager = __esm({
4799
5534
  "src/lib/shard-manager.ts"() {
4800
5535
  "use strict";
4801
5536
  init_config();
4802
- SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
5537
+ SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
4803
5538
  _shards = /* @__PURE__ */ new Map();
4804
5539
  _encryptionKey = null;
4805
5540
  _shardingEnabled = false;
@@ -4924,7 +5659,7 @@ __export(global_procedures_exports, {
4924
5659
  loadGlobalProcedures: () => loadGlobalProcedures,
4925
5660
  storeGlobalProcedure: () => storeGlobalProcedure
4926
5661
  });
4927
- import { randomUUID as randomUUID3 } from "crypto";
5662
+ import { randomUUID as randomUUID4 } from "crypto";
4928
5663
  async function loadGlobalProcedures() {
4929
5664
  const client = getClient();
4930
5665
  const result = await client.execute({
@@ -4953,7 +5688,7 @@ ${sections.join("\n\n")}
4953
5688
  `;
4954
5689
  }
4955
5690
  async function storeGlobalProcedure(input) {
4956
- const id = randomUUID3();
5691
+ const id = randomUUID4();
4957
5692
  const now = (/* @__PURE__ */ new Date()).toISOString();
4958
5693
  const client = getClient();
4959
5694
  await client.execute({
@@ -5004,6 +5739,7 @@ __export(store_exports, {
5004
5739
  vectorToBlob: () => vectorToBlob,
5005
5740
  writeMemory: () => writeMemory
5006
5741
  });
5742
+ import { createHash } from "crypto";
5007
5743
  function isBusyError2(err) {
5008
5744
  if (err instanceof Error) {
5009
5745
  const msg = err.message.toLowerCase();
@@ -5077,12 +5813,52 @@ function classifyTier(record) {
5077
5813
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
5078
5814
  return 3;
5079
5815
  }
5816
+ function inferFilePaths(record) {
5817
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
5818
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
5819
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
5820
+ return match ? JSON.stringify([match[1]]) : null;
5821
+ }
5822
+ function inferCommitHash(record) {
5823
+ if (record.tool_name !== "Bash") return null;
5824
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
5825
+ return match ? match[1] : null;
5826
+ }
5827
+ function inferLanguageType(record) {
5828
+ const text = record.raw_text;
5829
+ if (!text || text.length < 10) return null;
5830
+ const trimmed = text.trimStart();
5831
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
5832
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
5833
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
5834
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
5835
+ return "mixed";
5836
+ }
5837
+ function inferDomain(record) {
5838
+ const proj = (record.project_name ?? "").toLowerCase();
5839
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
5840
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
5841
+ return null;
5842
+ }
5080
5843
  async function writeMemory(record) {
5081
5844
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
5082
5845
  throw new Error(
5083
5846
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
5084
5847
  );
5085
5848
  }
5849
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
5850
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
5851
+ return;
5852
+ }
5853
+ try {
5854
+ const client = getClient();
5855
+ const existing = await client.execute({
5856
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
5857
+ args: [contentHash, record.agent_id]
5858
+ });
5859
+ if (existing.rows.length > 0) return;
5860
+ } catch {
5861
+ }
5086
5862
  const dbRow = {
5087
5863
  id: record.id,
5088
5864
  agent_id: record.agent_id,
@@ -5112,7 +5888,23 @@ async function writeMemory(record) {
5112
5888
  supersedes_id: record.supersedes_id ?? null,
5113
5889
  draft: record.draft ? 1 : 0,
5114
5890
  memory_type: record.memory_type ?? "raw",
5115
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
5891
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
5892
+ content_hash: contentHash,
5893
+ intent: record.intent ?? null,
5894
+ outcome: record.outcome ?? null,
5895
+ domain: record.domain ?? inferDomain(record),
5896
+ referenced_entities: record.referenced_entities ?? null,
5897
+ retrieval_count: record.retrieval_count ?? 0,
5898
+ chain_position: record.chain_position ?? null,
5899
+ review_status: record.review_status ?? null,
5900
+ context_window_pct: record.context_window_pct ?? null,
5901
+ file_paths: record.file_paths ?? inferFilePaths(record),
5902
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
5903
+ duration_ms: record.duration_ms ?? null,
5904
+ token_cost: record.token_cost ?? null,
5905
+ audience: record.audience ?? null,
5906
+ language_type: record.language_type ?? inferLanguageType(record),
5907
+ parent_memory_id: record.parent_memory_id ?? null
5116
5908
  };
5117
5909
  _pendingRecords.push(dbRow);
5118
5910
  orgBus.emit({
@@ -5170,80 +5962,85 @@ async function flushBatch() {
5170
5962
  const draft = row.draft ? 1 : 0;
5171
5963
  const memoryType = row.memory_type ?? "raw";
5172
5964
  const trajectory = row.trajectory ?? null;
5173
- return {
5174
- sql: hasVector ? `INSERT OR IGNORE INTO memories
5175
- (id, agent_id, agent_role, session_id, timestamp,
5965
+ const contentHash = row.content_hash ?? null;
5966
+ const intent = row.intent ?? null;
5967
+ const outcome = row.outcome ?? null;
5968
+ const domain = row.domain ?? null;
5969
+ const referencedEntities = row.referenced_entities ?? null;
5970
+ const retrievalCount = row.retrieval_count ?? 0;
5971
+ const chainPosition = row.chain_position ?? null;
5972
+ const reviewStatus = row.review_status ?? null;
5973
+ const contextWindowPct = row.context_window_pct ?? null;
5974
+ const filePaths = row.file_paths ?? null;
5975
+ const commitHash = row.commit_hash ?? null;
5976
+ const durationMs = row.duration_ms ?? null;
5977
+ const tokenCost = row.token_cost ?? null;
5978
+ const audience = row.audience ?? null;
5979
+ const languageType = row.language_type ?? null;
5980
+ const parentMemoryId = row.parent_memory_id ?? null;
5981
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
5176
5982
  tool_name, project_name,
5177
5983
  has_error, raw_text, vector, version, task_id, importance, status,
5178
5984
  confidence, last_accessed,
5179
5985
  workspace_id, document_id, user_id, char_offset, page_number,
5180
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
5181
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
5182
- (id, agent_id, agent_role, session_id, timestamp,
5183
- tool_name, project_name,
5184
- has_error, raw_text, vector, version, task_id, importance, status,
5185
- confidence, last_accessed,
5186
- workspace_id, document_id, user_id, char_offset, page_number,
5187
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
5188
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5189
- args: hasVector ? [
5190
- row.id,
5191
- row.agent_id,
5192
- row.agent_role,
5193
- row.session_id,
5194
- row.timestamp,
5195
- row.tool_name,
5196
- row.project_name,
5197
- row.has_error,
5198
- row.raw_text,
5199
- vectorToBlob(row.vector),
5200
- row.version,
5201
- taskId,
5202
- importance,
5203
- status,
5204
- confidence,
5205
- lastAccessed,
5206
- workspaceId,
5207
- documentId,
5208
- userId,
5209
- charOffset,
5210
- pageNumber,
5211
- sourcePath,
5212
- sourceType,
5213
- tier,
5214
- supersedesId,
5215
- draft,
5216
- memoryType,
5217
- trajectory
5218
- ] : [
5219
- row.id,
5220
- row.agent_id,
5221
- row.agent_role,
5222
- row.session_id,
5223
- row.timestamp,
5224
- row.tool_name,
5225
- row.project_name,
5226
- row.has_error,
5227
- row.raw_text,
5228
- row.version,
5229
- taskId,
5230
- importance,
5231
- status,
5232
- confidence,
5233
- lastAccessed,
5234
- workspaceId,
5235
- documentId,
5236
- userId,
5237
- charOffset,
5238
- pageNumber,
5239
- sourcePath,
5240
- sourceType,
5241
- tier,
5242
- supersedesId,
5243
- draft,
5244
- memoryType,
5245
- trajectory
5246
- ]
5986
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5987
+ intent, outcome, domain, referenced_entities, retrieval_count,
5988
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
5989
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
5990
+ const metaArgs = [
5991
+ intent,
5992
+ outcome,
5993
+ domain,
5994
+ referencedEntities,
5995
+ retrievalCount,
5996
+ chainPosition,
5997
+ reviewStatus,
5998
+ contextWindowPct,
5999
+ filePaths,
6000
+ commitHash,
6001
+ durationMs,
6002
+ tokenCost,
6003
+ audience,
6004
+ languageType,
6005
+ parentMemoryId
6006
+ ];
6007
+ const baseArgs = [
6008
+ row.id,
6009
+ row.agent_id,
6010
+ row.agent_role,
6011
+ row.session_id,
6012
+ row.timestamp,
6013
+ row.tool_name,
6014
+ row.project_name,
6015
+ row.has_error,
6016
+ row.raw_text
6017
+ ];
6018
+ const sharedArgs = [
6019
+ row.version,
6020
+ taskId,
6021
+ importance,
6022
+ status,
6023
+ confidence,
6024
+ lastAccessed,
6025
+ workspaceId,
6026
+ documentId,
6027
+ userId,
6028
+ charOffset,
6029
+ pageNumber,
6030
+ sourcePath,
6031
+ sourceType,
6032
+ tier,
6033
+ supersedesId,
6034
+ draft,
6035
+ memoryType,
6036
+ trajectory,
6037
+ contentHash
6038
+ ];
6039
+ return {
6040
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
6041
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
6042
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6043
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5247
6044
  };
5248
6045
  };
5249
6046
  const globalClient = getClient();
@@ -5950,494 +6747,149 @@ async function createTimelineActivity(personId, params) {
5950
6747
  agentResponse: params.agentResponse || null,
5951
6748
  agentName: params.agentName || null,
5952
6749
  threadId: `${params.platform}:${params.senderId}`,
5953
- direction: params.direction,
5954
- accountId: params.accountId || null
5955
- },
5956
- targetPersonId: personId
5957
- };
5958
- const query = `
5959
- mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
5960
- createTimelineActivity(data: $input) {
5961
- id
5962
- }
5963
- }
5964
- `;
5965
- const res = await gqlRequest(
5966
- query,
5967
- { input }
5968
- );
5969
- if (res.errors && res.errors.length > 0) {
5970
- console.error(
5971
- "[crm-bridge] createTimelineActivity error:",
5972
- res.errors[0].message
5973
- );
5974
- return false;
5975
- }
5976
- return true;
5977
- }
5978
- async function pushConversationToCRM(params) {
5979
- if (!config) return;
5980
- try {
5981
- let personId = await findPersonByContact(params.platform, params.senderId);
5982
- if (!personId) {
5983
- personId = await createPerson(
5984
- params.platform,
5985
- params.senderId,
5986
- params.senderName
5987
- );
5988
- }
5989
- if (!personId) {
5990
- console.error("[crm-bridge] Failed to find or create person for", params.senderId);
5991
- return;
5992
- }
5993
- const ok = await createTimelineActivity(personId, {
5994
- ...params,
5995
- direction: "conversation"
5996
- });
5997
- if (ok) {
5998
- console.log(
5999
- `[crm-bridge] Pushed ${params.platform}/${params.senderId} \u2192 person ${personId}`
6000
- );
6001
- }
6002
- } catch (err) {
6003
- console.error("[crm-bridge] Push failed:", err instanceof Error ? err.message : err);
6004
- }
6005
- }
6006
- async function pushInboundMessageToCRM(params) {
6007
- if (!config) return;
6008
- try {
6009
- let personId = await findPersonByContact(params.platform, params.senderId);
6010
- if (!personId) {
6011
- personId = await createPerson(
6012
- params.platform,
6013
- params.senderId,
6014
- params.senderName
6015
- );
6016
- }
6017
- if (!personId) {
6018
- console.error("[crm-bridge] Failed to find or create person for", params.senderId);
6019
- return;
6020
- }
6021
- const ok = await createTimelineActivity(personId, {
6022
- ...params,
6023
- direction: "inbound"
6024
- });
6025
- if (ok) {
6026
- console.log(
6027
- `[crm-bridge] Inbound ${params.platform}/${params.senderId} \u2192 person ${personId}`
6028
- );
6029
- }
6030
- } catch (err) {
6031
- console.error("[crm-bridge] Inbound push failed:", err instanceof Error ? err.message : err);
6032
- }
6033
- }
6034
- async function pushGatewayEventToCRM(params) {
6035
- if (!config) return;
6036
- try {
6037
- let personId = await findPersonByContact(params.platform, params.senderId);
6038
- if (!personId) {
6039
- personId = await createPerson(
6040
- params.platform,
6041
- params.senderId,
6042
- params.senderName
6043
- );
6044
- }
6045
- if (!personId) {
6046
- console.error("[crm-bridge] Failed to find or create person for", params.senderId);
6047
- return;
6048
- }
6049
- const channelLabel = params.platform.charAt(0).toUpperCase() + params.platform.slice(1);
6050
- const categoryLabel = params.dataCategory.replace(/_/g, " ");
6051
- const input = {
6052
- name: `${channelLabel} ${categoryLabel} \u2014 ${params.senderName || params.senderId}`,
6053
- happensAt: params.timestamp,
6054
- properties: {
6055
- channel: params.platform,
6056
- dataCategory: params.dataCategory,
6057
- senderId: params.senderId,
6058
- senderName: params.senderName || null,
6059
- accountId: params.accountId || null,
6060
- direction: "inbound",
6061
- ...params.eventData
6062
- },
6063
- targetPersonId: personId
6064
- };
6065
- const query = `
6066
- mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
6067
- createTimelineActivity(data: $input) {
6068
- id
6069
- }
6070
- }
6071
- `;
6072
- const res = await gqlRequest(
6073
- query,
6074
- { input }
6075
- );
6076
- if (res.errors && res.errors.length > 0) {
6077
- console.error(
6078
- "[crm-bridge] pushGatewayEvent error:",
6079
- res.errors[0].message
6080
- );
6081
- return;
6082
- }
6083
- console.log(
6084
- `[crm-bridge] Event ${params.dataCategory} ${params.platform}/${params.senderId} \u2192 person ${personId}`
6085
- );
6086
- } catch (err) {
6087
- console.error("[crm-bridge] Event push failed:", err instanceof Error ? err.message : err);
6088
- }
6089
- }
6090
- var config, disabledLogged;
6091
- var init_crm_bridge = __esm({
6092
- "src/gateway/crm-bridge.ts"() {
6093
- "use strict";
6094
- config = null;
6095
- disabledLogged = false;
6096
- }
6097
- });
6098
-
6099
- // src/lib/exe-daemon-client.ts
6100
- import net from "net";
6101
- import { spawn } from "child_process";
6102
- import { randomUUID as randomUUID6 } from "crypto";
6103
- import { existsSync as existsSync13, unlinkSync as unlinkSync6, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
6104
- import path17 from "path";
6105
- import { fileURLToPath as fileURLToPath2 } from "url";
6106
- function handleData(chunk) {
6107
- _buffer += chunk.toString();
6108
- if (_buffer.length > MAX_BUFFER) {
6109
- _buffer = "";
6110
- return;
6111
- }
6112
- let newlineIdx;
6113
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
6114
- const line = _buffer.slice(0, newlineIdx).trim();
6115
- _buffer = _buffer.slice(newlineIdx + 1);
6116
- if (!line) continue;
6117
- try {
6118
- const response = JSON.parse(line);
6119
- const entry = _pending.get(response.id);
6120
- if (entry) {
6121
- clearTimeout(entry.timer);
6122
- _pending.delete(response.id);
6123
- entry.resolve(response);
6124
- }
6125
- } catch {
6126
- }
6127
- }
6128
- }
6129
- function cleanupStaleFiles() {
6130
- if (existsSync13(PID_PATH)) {
6131
- try {
6132
- const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
6133
- if (pid > 0) {
6134
- try {
6135
- process.kill(pid, 0);
6136
- return;
6137
- } catch {
6138
- }
6139
- }
6140
- } catch {
6141
- }
6142
- try {
6143
- unlinkSync6(PID_PATH);
6144
- } catch {
6145
- }
6146
- try {
6147
- unlinkSync6(SOCKET_PATH);
6148
- } catch {
6149
- }
6150
- }
6151
- }
6152
- function findPackageRoot() {
6153
- let dir = path17.dirname(fileURLToPath2(import.meta.url));
6154
- const { root } = path17.parse(dir);
6155
- while (dir !== root) {
6156
- if (existsSync13(path17.join(dir, "package.json"))) return dir;
6157
- dir = path17.dirname(dir);
6158
- }
6159
- return null;
6160
- }
6161
- function spawnDaemon() {
6162
- const pkgRoot = findPackageRoot();
6163
- if (!pkgRoot) {
6164
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
6165
- return;
6166
- }
6167
- const daemonPath = path17.join(pkgRoot, "dist", "lib", "exe-daemon.js");
6168
- if (!existsSync13(daemonPath)) {
6169
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
6170
- `);
6171
- return;
6172
- }
6173
- const resolvedPath = daemonPath;
6174
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
6175
- `);
6176
- const logPath = path17.join(path17.dirname(SOCKET_PATH), "exed.log");
6177
- let stderrFd = "ignore";
6178
- try {
6179
- stderrFd = openSync(logPath, "a");
6180
- } catch {
6181
- }
6182
- const child = spawn(process.execPath, [resolvedPath], {
6183
- detached: true,
6184
- stdio: ["ignore", "ignore", stderrFd],
6185
- env: {
6186
- ...process.env,
6187
- TMUX: void 0,
6188
- // Daemon is global — must not inherit session scope
6189
- TMUX_PANE: void 0,
6190
- // Prevents resolveExeSession() from scoping to one session
6191
- EXE_DAEMON_SOCK: SOCKET_PATH,
6192
- EXE_DAEMON_PID: PID_PATH
6193
- }
6194
- });
6195
- child.unref();
6196
- if (typeof stderrFd === "number") {
6197
- try {
6198
- closeSync(stderrFd);
6199
- } catch {
6200
- }
6201
- }
6202
- }
6203
- function acquireSpawnLock2() {
6204
- try {
6205
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
6206
- closeSync(fd);
6207
- return true;
6208
- } catch {
6209
- try {
6210
- const stat = statSync(SPAWN_LOCK_PATH);
6211
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
6212
- try {
6213
- unlinkSync6(SPAWN_LOCK_PATH);
6214
- } catch {
6215
- }
6216
- try {
6217
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
6218
- closeSync(fd);
6219
- return true;
6220
- } catch {
6221
- }
6222
- }
6223
- } catch {
6224
- }
6225
- return false;
6226
- }
6227
- }
6228
- function releaseSpawnLock2() {
6229
- try {
6230
- unlinkSync6(SPAWN_LOCK_PATH);
6231
- } catch {
6232
- }
6233
- }
6234
- function connectToSocket() {
6235
- return new Promise((resolve) => {
6236
- if (_socket && _connected) {
6237
- resolve(true);
6238
- return;
6239
- }
6240
- const socket = net.createConnection({ path: SOCKET_PATH });
6241
- const connectTimeout = setTimeout(() => {
6242
- socket.destroy();
6243
- resolve(false);
6244
- }, 2e3);
6245
- socket.on("connect", () => {
6246
- clearTimeout(connectTimeout);
6247
- _socket = socket;
6248
- _connected = true;
6249
- _buffer = "";
6250
- socket.on("data", handleData);
6251
- socket.on("close", () => {
6252
- _connected = false;
6253
- _socket = null;
6254
- for (const [id, entry] of _pending) {
6255
- clearTimeout(entry.timer);
6256
- _pending.delete(id);
6257
- entry.resolve({ error: "Connection closed" });
6258
- }
6259
- });
6260
- socket.on("error", () => {
6261
- _connected = false;
6262
- _socket = null;
6263
- });
6264
- resolve(true);
6265
- });
6266
- socket.on("error", () => {
6267
- clearTimeout(connectTimeout);
6268
- resolve(false);
6269
- });
6270
- });
6271
- }
6272
- async function connectEmbedDaemon() {
6273
- if (_socket && _connected) return true;
6274
- if (await connectToSocket()) return true;
6275
- if (acquireSpawnLock2()) {
6276
- try {
6277
- cleanupStaleFiles();
6278
- spawnDaemon();
6279
- } finally {
6280
- releaseSpawnLock2();
6281
- }
6282
- }
6283
- const start = Date.now();
6284
- let delay2 = 100;
6285
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
6286
- await new Promise((r) => setTimeout(r, delay2));
6287
- if (await connectToSocket()) return true;
6288
- delay2 = Math.min(delay2 * 2, 3e3);
6289
- }
6290
- return false;
6291
- }
6292
- function sendRequest(texts, priority) {
6293
- return new Promise((resolve) => {
6294
- if (!_socket || !_connected) {
6295
- resolve({ error: "Not connected" });
6296
- return;
6297
- }
6298
- const id = randomUUID6();
6299
- const timer = setTimeout(() => {
6300
- _pending.delete(id);
6301
- resolve({ error: "Request timeout" });
6302
- }, REQUEST_TIMEOUT_MS);
6303
- _pending.set(id, { resolve, timer });
6304
- try {
6305
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
6306
- } catch {
6307
- clearTimeout(timer);
6308
- _pending.delete(id);
6309
- resolve({ error: "Write failed" });
6310
- }
6311
- });
6312
- }
6313
- async function pingDaemon() {
6314
- if (!_socket || !_connected) return null;
6315
- return new Promise((resolve) => {
6316
- const id = randomUUID6();
6317
- const timer = setTimeout(() => {
6318
- _pending.delete(id);
6319
- resolve(null);
6320
- }, 5e3);
6321
- _pending.set(id, {
6322
- resolve: (data) => {
6323
- if (data.health) {
6324
- resolve(data.health);
6325
- } else {
6326
- resolve(null);
6327
- }
6328
- },
6329
- timer
6330
- });
6331
- try {
6332
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
6333
- } catch {
6334
- clearTimeout(timer);
6335
- _pending.delete(id);
6336
- resolve(null);
6337
- }
6338
- });
6339
- }
6340
- function killAndRespawnDaemon() {
6341
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
6342
- if (existsSync13(PID_PATH)) {
6343
- try {
6344
- const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
6345
- if (pid > 0) {
6346
- try {
6347
- process.kill(pid, "SIGKILL");
6348
- } catch {
6349
- }
6750
+ direction: params.direction,
6751
+ accountId: params.accountId || null
6752
+ },
6753
+ targetPersonId: personId
6754
+ };
6755
+ const query = `
6756
+ mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
6757
+ createTimelineActivity(data: $input) {
6758
+ id
6350
6759
  }
6351
- } catch {
6352
6760
  }
6761
+ `;
6762
+ const res = await gqlRequest(
6763
+ query,
6764
+ { input }
6765
+ );
6766
+ if (res.errors && res.errors.length > 0) {
6767
+ console.error(
6768
+ "[crm-bridge] createTimelineActivity error:",
6769
+ res.errors[0].message
6770
+ );
6771
+ return false;
6353
6772
  }
6354
- if (_socket) {
6355
- _socket.destroy();
6356
- _socket = null;
6357
- }
6358
- _connected = false;
6359
- _buffer = "";
6360
- try {
6361
- unlinkSync6(PID_PATH);
6362
- } catch {
6363
- }
6773
+ return true;
6774
+ }
6775
+ async function pushConversationToCRM(params) {
6776
+ if (!config) return;
6364
6777
  try {
6365
- unlinkSync6(SOCKET_PATH);
6366
- } catch {
6778
+ let personId = await findPersonByContact(params.platform, params.senderId);
6779
+ if (!personId) {
6780
+ personId = await createPerson(
6781
+ params.platform,
6782
+ params.senderId,
6783
+ params.senderName
6784
+ );
6785
+ }
6786
+ if (!personId) {
6787
+ console.error("[crm-bridge] Failed to find or create person for", params.senderId);
6788
+ return;
6789
+ }
6790
+ const ok = await createTimelineActivity(personId, {
6791
+ ...params,
6792
+ direction: "conversation"
6793
+ });
6794
+ if (ok) {
6795
+ console.log(
6796
+ `[crm-bridge] Pushed ${params.platform}/${params.senderId} \u2192 person ${personId}`
6797
+ );
6798
+ }
6799
+ } catch (err) {
6800
+ console.error("[crm-bridge] Push failed:", err instanceof Error ? err.message : err);
6367
6801
  }
6368
- spawnDaemon();
6369
6802
  }
6370
- async function embedViaClient(text, priority = "high") {
6371
- if (!_connected && !await connectEmbedDaemon()) return null;
6372
- _requestCount++;
6373
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
6374
- const health = await pingDaemon();
6375
- if (!health) {
6376
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
6377
- `);
6378
- killAndRespawnDaemon();
6379
- const start = Date.now();
6380
- let delay2 = 200;
6381
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
6382
- await new Promise((r) => setTimeout(r, delay2));
6383
- if (await connectToSocket()) break;
6384
- delay2 = Math.min(delay2 * 2, 3e3);
6385
- }
6386
- if (!_connected) return null;
6803
+ async function pushInboundMessageToCRM(params) {
6804
+ if (!config) return;
6805
+ try {
6806
+ let personId = await findPersonByContact(params.platform, params.senderId);
6807
+ if (!personId) {
6808
+ personId = await createPerson(
6809
+ params.platform,
6810
+ params.senderId,
6811
+ params.senderName
6812
+ );
6387
6813
  }
6388
- }
6389
- const result = await sendRequest([text], priority);
6390
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
6391
- if (result.error) {
6392
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
6393
- `);
6394
- killAndRespawnDaemon();
6395
- const start = Date.now();
6396
- let delay2 = 200;
6397
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
6398
- await new Promise((r) => setTimeout(r, delay2));
6399
- if (await connectToSocket()) break;
6400
- delay2 = Math.min(delay2 * 2, 3e3);
6814
+ if (!personId) {
6815
+ console.error("[crm-bridge] Failed to find or create person for", params.senderId);
6816
+ return;
6401
6817
  }
6402
- if (!_connected) return null;
6403
- const retry = await sendRequest([text], priority);
6404
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
6405
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
6406
- `);
6818
+ const ok = await createTimelineActivity(personId, {
6819
+ ...params,
6820
+ direction: "inbound"
6821
+ });
6822
+ if (ok) {
6823
+ console.log(
6824
+ `[crm-bridge] Inbound ${params.platform}/${params.senderId} \u2192 person ${personId}`
6825
+ );
6826
+ }
6827
+ } catch (err) {
6828
+ console.error("[crm-bridge] Inbound push failed:", err instanceof Error ? err.message : err);
6407
6829
  }
6408
- return null;
6409
6830
  }
6410
- function disconnectClient() {
6411
- if (_socket) {
6412
- _socket.destroy();
6413
- _socket = null;
6414
- }
6415
- _connected = false;
6416
- _buffer = "";
6417
- for (const [id, entry] of _pending) {
6418
- clearTimeout(entry.timer);
6419
- _pending.delete(id);
6420
- entry.resolve({ error: "Client disconnected" });
6831
+ async function pushGatewayEventToCRM(params) {
6832
+ if (!config) return;
6833
+ try {
6834
+ let personId = await findPersonByContact(params.platform, params.senderId);
6835
+ if (!personId) {
6836
+ personId = await createPerson(
6837
+ params.platform,
6838
+ params.senderId,
6839
+ params.senderName
6840
+ );
6841
+ }
6842
+ if (!personId) {
6843
+ console.error("[crm-bridge] Failed to find or create person for", params.senderId);
6844
+ return;
6845
+ }
6846
+ const channelLabel = params.platform.charAt(0).toUpperCase() + params.platform.slice(1);
6847
+ const categoryLabel = params.dataCategory.replace(/_/g, " ");
6848
+ const input = {
6849
+ name: `${channelLabel} ${categoryLabel} \u2014 ${params.senderName || params.senderId}`,
6850
+ happensAt: params.timestamp,
6851
+ properties: {
6852
+ channel: params.platform,
6853
+ dataCategory: params.dataCategory,
6854
+ senderId: params.senderId,
6855
+ senderName: params.senderName || null,
6856
+ accountId: params.accountId || null,
6857
+ direction: "inbound",
6858
+ ...params.eventData
6859
+ },
6860
+ targetPersonId: personId
6861
+ };
6862
+ const query = `
6863
+ mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
6864
+ createTimelineActivity(data: $input) {
6865
+ id
6866
+ }
6867
+ }
6868
+ `;
6869
+ const res = await gqlRequest(
6870
+ query,
6871
+ { input }
6872
+ );
6873
+ if (res.errors && res.errors.length > 0) {
6874
+ console.error(
6875
+ "[crm-bridge] pushGatewayEvent error:",
6876
+ res.errors[0].message
6877
+ );
6878
+ return;
6879
+ }
6880
+ console.log(
6881
+ `[crm-bridge] Event ${params.dataCategory} ${params.platform}/${params.senderId} \u2192 person ${personId}`
6882
+ );
6883
+ } catch (err) {
6884
+ console.error("[crm-bridge] Event push failed:", err instanceof Error ? err.message : err);
6421
6885
  }
6422
6886
  }
6423
- 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;
6424
- var init_exe_daemon_client = __esm({
6425
- "src/lib/exe-daemon-client.ts"() {
6887
+ var config, disabledLogged;
6888
+ var init_crm_bridge = __esm({
6889
+ "src/gateway/crm-bridge.ts"() {
6426
6890
  "use strict";
6427
- init_config();
6428
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path17.join(EXE_AI_DIR, "exed.sock");
6429
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path17.join(EXE_AI_DIR, "exed.pid");
6430
- SPAWN_LOCK_PATH = path17.join(EXE_AI_DIR, "exed-spawn.lock");
6431
- SPAWN_LOCK_STALE_MS = 3e4;
6432
- CONNECT_TIMEOUT_MS = 15e3;
6433
- REQUEST_TIMEOUT_MS = 3e4;
6434
- _socket = null;
6435
- _connected = false;
6436
- _buffer = "";
6437
- _requestCount = 0;
6438
- HEALTH_CHECK_INTERVAL = 100;
6439
- _pending = /* @__PURE__ */ new Map();
6440
- MAX_BUFFER = 1e7;
6891
+ config = null;
6892
+ disabledLogged = false;
6441
6893
  }
6442
6894
  });
6443
6895
 
@@ -6646,7 +7098,7 @@ var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
6646
7098
  var init_wiki_client = __esm({
6647
7099
  "src/lib/wiki-client.ts"() {
6648
7100
  "use strict";
6649
- LOCAL_WIKI_URL = "http://localhost:3001";
7101
+ LOCAL_WIKI_URL = process.env.EXE_WIKI_URL || "http://localhost:3001";
6650
7102
  REQUEST_TIMEOUT_MS2 = 8e3;
6651
7103
  }
6652
7104
  });
@@ -9554,11 +10006,11 @@ function createQuietRenderer() {
9554
10006
  init_state_bus();
9555
10007
 
9556
10008
  // src/runtime/session-manager.ts
9557
- import { randomUUID as randomUUID5 } from "crypto";
10009
+ import { randomUUID as randomUUID6 } from "crypto";
9558
10010
  init_state_bus();
9559
10011
 
9560
10012
  // src/runtime/exe-hooks.ts
9561
- import { randomUUID as randomUUID4 } from "crypto";
10013
+ import { randomUUID as randomUUID5 } from "crypto";
9562
10014
  function createExeOSHooks(config2) {
9563
10015
  let sessionRegistered = false;
9564
10016
  return {
@@ -9617,7 +10069,7 @@ function createExeOSHooks(config2) {
9617
10069
  const toolResponse = result.isError ? { error: result.content } : { output: result.content };
9618
10070
  const rawText = extractSemanticText2(toolName, toolInput, toolResponse);
9619
10071
  await writeMemory2({
9620
- id: randomUUID4(),
10072
+ id: randomUUID5(),
9621
10073
  agent_id: config2.agentId,
9622
10074
  agent_role: "employee",
9623
10075
  session_id: `api-${config2.agentId}`,
@@ -9640,7 +10092,7 @@ function createExeOSHooks(config2) {
9640
10092
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
9641
10093
  if (summary) {
9642
10094
  await writeMemory2({
9643
- id: randomUUID4(),
10095
+ id: randomUUID5(),
9644
10096
  agent_id: config2.agentId,
9645
10097
  agent_role: "employee",
9646
10098
  session_id: `api-${config2.agentId}`,
@@ -9668,8 +10120,8 @@ function createExeOSHooks(config2) {
9668
10120
  },
9669
10121
  async onError(error, _context) {
9670
10122
  try {
9671
- const { classifyError: classifyError2 } = await Promise.resolve().then(() => (init_error_detector(), error_detector_exports));
9672
- const classification = classifyError2(error.message);
10123
+ const { classifyError: classifyError3 } = await Promise.resolve().then(() => (init_error_detector(), error_detector_exports));
10124
+ const classification = classifyError3(error.message);
9673
10125
  process.stderr.write(
9674
10126
  `[exe-hooks] Error (${classification}): ${error.message.slice(0, 100)}
9675
10127
  `
@@ -9713,7 +10165,7 @@ function createExeOSHooks(config2) {
9713
10165
  await client.execute({
9714
10166
  sql: `INSERT OR IGNORE INTO notifications (id, type, source_agent, message, task_file, created_at, read)
9715
10167
  VALUES (?, 'system', ?, ?, NULL, ?, 0)`,
9716
- args: [randomUUID4(), config2.agentId, message.slice(0, 500), (/* @__PURE__ */ new Date()).toISOString()]
10168
+ args: [randomUUID5(), config2.agentId, message.slice(0, 500), (/* @__PURE__ */ new Date()).toISOString()]
9717
10169
  });
9718
10170
  } catch {
9719
10171
  }
@@ -9729,7 +10181,7 @@ function createExeOSHooks(config2) {
9729
10181
  try {
9730
10182
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
9731
10183
  await writeMemory2({
9732
- id: randomUUID4(),
10184
+ id: randomUUID5(),
9733
10185
  agent_id: config2.agentId,
9734
10186
  agent_role: "employee",
9735
10187
  session_id: `api-${config2.agentId}`,
@@ -9751,7 +10203,7 @@ function createExeOSHooks(config2) {
9751
10203
  try {
9752
10204
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
9753
10205
  await writeMemory2({
9754
- id: randomUUID4(),
10206
+ id: randomUUID5(),
9755
10207
  agent_id: config2.agentId,
9756
10208
  agent_role: "employee",
9757
10209
  session_id: `api-${config2.agentId}`,
@@ -9790,7 +10242,7 @@ function createExeOSHooks(config2) {
9790
10242
  if (tasks.rows.length > 0) {
9791
10243
  const taskList = tasks.rows.map((r) => `- [${String(r.status)}] ${String(r.title)} (${String(r.task_file)})`).join("\n");
9792
10244
  await writeMemory2({
9793
- id: randomUUID4(),
10245
+ id: randomUUID5(),
9794
10246
  agent_id: config2.agentId,
9795
10247
  agent_role: "employee",
9796
10248
  session_id: `api-${config2.agentId}`,
@@ -9816,7 +10268,7 @@ ${taskList}`,
9816
10268
  try {
9817
10269
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
9818
10270
  await writeMemory2({
9819
- id: randomUUID4(),
10271
+ id: randomUUID5(),
9820
10272
  agent_id: config2.agentId,
9821
10273
  agent_role: "employee",
9822
10274
  session_id: `api-${config2.agentId}`,
@@ -9843,7 +10295,7 @@ var SessionManager = class {
9843
10295
  eventHandlers = /* @__PURE__ */ new Set();
9844
10296
  /** Start a new agent session for an employee */
9845
10297
  startSession(employeeId, config2) {
9846
- const sessionId = randomUUID5();
10298
+ const sessionId = randomUUID6();
9847
10299
  const abortController = new AbortController();
9848
10300
  const session = {
9849
10301
  info: {
@@ -10096,13 +10548,16 @@ __export(gateway_exports, {
10096
10548
  FULL_ACCESS: () => FULL_ACCESS,
10097
10549
  FailoverCascade: () => FailoverCascade,
10098
10550
  FailoverExhaustedError: () => FailoverExhaustedError,
10551
+ FatalBotError: () => FatalBotError,
10099
10552
  Gateway: () => Gateway,
10100
10553
  IMessageAdapter: () => IMessageAdapter,
10554
+ MaxStepsError: () => MaxStepsError,
10101
10555
  OllamaProvider: () => OllamaProvider,
10102
10556
  OpenAICompatProvider: () => OpenAICompatProvider,
10103
10557
  READ_ONLY: () => READ_ONLY,
10104
10558
  READ_TOOLS: () => READ_TOOLS,
10105
10559
  RateLimiter: () => RateLimiter,
10560
+ RecoverableBotError: () => RecoverableBotError,
10106
10561
  SessionStore: () => SessionStore,
10107
10562
  SignalAdapter: () => SignalAdapter,
10108
10563
  SlackAdapter: () => SlackAdapter,
@@ -10114,6 +10569,7 @@ __export(gateway_exports, {
10114
10569
  buildExecAssistantTools: () => buildExecAssistantTools,
10115
10570
  buildPermissionContext: () => buildPermissionContext,
10116
10571
  checkToolPermission: () => checkToolPermission,
10572
+ classifyError: () => classifyError2,
10117
10573
  createCRMWebhookHandler: () => createCRMWebhookHandler,
10118
10574
  createPerson: () => createPerson,
10119
10575
  createReceptionist: () => createReceptionist,
@@ -10789,10 +11245,49 @@ function buildPermissionContext(platform, permissions) {
10789
11245
  return `[${platform.toUpperCase()} \u2014 allowed: ${parts.join(", ")}]`;
10790
11246
  }
10791
11247
 
11248
+ // src/gateway/bot-errors.ts
11249
+ var FatalBotError = class extends Error {
11250
+ constructor(message, cause) {
11251
+ super(message);
11252
+ this.cause = cause;
11253
+ this.name = "FatalBotError";
11254
+ }
11255
+ fatal = true;
11256
+ };
11257
+ var RecoverableBotError = class extends Error {
11258
+ constructor(message, toolName, cause) {
11259
+ super(message);
11260
+ this.toolName = toolName;
11261
+ this.cause = cause;
11262
+ this.name = "RecoverableBotError";
11263
+ }
11264
+ recoverable = true;
11265
+ };
11266
+ var MaxStepsError = class extends Error {
11267
+ constructor(stepsTaken, maxSteps) {
11268
+ super(
11269
+ `Reached maximum steps (${stepsTaken}/${maxSteps}). Returning partial result.`
11270
+ );
11271
+ this.stepsTaken = stepsTaken;
11272
+ this.maxSteps = maxSteps;
11273
+ this.name = "MaxStepsError";
11274
+ }
11275
+ };
11276
+ function classifyError2(err, toolName) {
11277
+ if (err instanceof FatalBotError) return err;
11278
+ if (err instanceof RecoverableBotError) return err;
11279
+ const message = err instanceof Error ? err.message : String(err);
11280
+ if (message.includes("401") || message.includes("403") || message.includes("authentication") || message.includes("rate_limit")) {
11281
+ return new FatalBotError(message, err);
11282
+ }
11283
+ return new RecoverableBotError(message, toolName, err);
11284
+ }
11285
+
10792
11286
  // src/gateway/bot-runtime.ts
10793
11287
  var DEFAULT_MODEL = "claude-sonnet-4-20250514";
10794
11288
  var MAX_TURNS = 10;
10795
11289
  var MAX_HISTORY = 50;
11290
+ var DEFAULT_PLANNING_INTERVAL = 3;
10796
11291
  function buildExecAssistantSystemPrompt(platform, permissions) {
10797
11292
  const permContext = buildPermissionContext(platform, permissions);
10798
11293
  return `You are the founder's executive assistant (agent_id: "ea").
@@ -10844,7 +11339,7 @@ var BotRuntime = class {
10844
11339
  async processMessage(msg, permissions) {
10845
11340
  const sessionKey = msg.chatType === "group" ? msg.channelId : msg.senderId;
10846
11341
  const history = this.getHistory(sessionKey);
10847
- history.push({ role: "user", content: msg.text });
11342
+ history.push({ role: "user", content: msg.text, frameType: "task" });
10848
11343
  const systemPrompt = this.config.systemPrompt + "\n\n" + buildPermissionContext(msg.platform, permissions);
10849
11344
  const allowedTools = filterToolsForPermissions(
10850
11345
  this.config.tools,
@@ -10852,29 +11347,54 @@ var BotRuntime = class {
10852
11347
  );
10853
11348
  const model = this.config.model ?? DEFAULT_MODEL;
10854
11349
  const maxTurns = this.config.maxTurns ?? MAX_TURNS;
11350
+ const planningInterval = this.config.planningInterval ?? DEFAULT_PLANNING_INTERVAL;
10855
11351
  let turns = 0;
10856
11352
  while (turns < maxTurns) {
10857
11353
  turns++;
10858
- const response = await this.client.messages.create({
10859
- model,
10860
- max_tokens: 4096,
10861
- system: systemPrompt,
10862
- messages: history.map((m) => ({
10863
- role: m.role,
10864
- content: m.content
10865
- })),
10866
- tools: allowedTools.map((t) => ({
10867
- name: t.name,
10868
- description: t.description,
10869
- input_schema: t.input_schema
10870
- }))
10871
- });
11354
+ if (planningInterval > 0 && turns > 1 && turns % planningInterval === 1 && maxTurns - turns >= 2) {
11355
+ history.push({
11356
+ role: "user",
11357
+ content: "[Planning checkpoint] Review what you know so far. What facts have you gathered? What is your plan for the remaining steps? Be concise.",
11358
+ frameType: "planning"
11359
+ });
11360
+ }
11361
+ let response;
11362
+ try {
11363
+ response = await this.client.messages.create({
11364
+ model,
11365
+ max_tokens: 4096,
11366
+ system: systemPrompt,
11367
+ messages: history.map((m) => ({
11368
+ role: m.role,
11369
+ content: m.content
11370
+ })),
11371
+ tools: allowedTools.map((t) => ({
11372
+ name: t.name,
11373
+ description: t.description,
11374
+ input_schema: t.input_schema
11375
+ }))
11376
+ });
11377
+ } catch (err) {
11378
+ const classified = classifyError2(err);
11379
+ if (classified instanceof FatalBotError) {
11380
+ const errorMsg = `Bot error: ${classified.message}`;
11381
+ history.push({ role: "assistant", content: errorMsg, frameType: "error" });
11382
+ this.trimHistory(sessionKey);
11383
+ return errorMsg;
11384
+ }
11385
+ history.push({
11386
+ role: "assistant",
11387
+ content: `[API error \u2014 retrying] ${classified.message}`,
11388
+ frameType: "error"
11389
+ });
11390
+ continue;
11391
+ }
10872
11392
  const toolUseBlocks = response.content.filter(
10873
11393
  (b) => b.type === "tool_use"
10874
11394
  );
10875
11395
  if (toolUseBlocks.length === 0) {
10876
11396
  const textContent = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
10877
- history.push({ role: "assistant", content: textContent });
11397
+ history.push({ role: "assistant", content: textContent, frameType: "assistant" });
10878
11398
  this.trimHistory(sessionKey);
10879
11399
  return textContent;
10880
11400
  }
@@ -10884,9 +11404,11 @@ var BotRuntime = class {
10884
11404
  );
10885
11405
  history.push({
10886
11406
  role: "assistant",
10887
- content: response.content
11407
+ content: response.content,
11408
+ frameType: "assistant"
10888
11409
  });
10889
11410
  const toolResults = [];
11411
+ let hadFatalError = false;
10890
11412
  for (const block of allowed) {
10891
11413
  try {
10892
11414
  const result = await this.config.toolExecutor(
@@ -10899,12 +11421,23 @@ var BotRuntime = class {
10899
11421
  content: result
10900
11422
  });
10901
11423
  } catch (err) {
10902
- toolResults.push({
10903
- type: "tool_result",
10904
- tool_use_id: block.id,
10905
- content: `Error: ${err instanceof Error ? err.message : String(err)}`,
10906
- is_error: true
10907
- });
11424
+ const classified = classifyError2(err, block.name);
11425
+ if (classified instanceof FatalBotError) {
11426
+ toolResults.push({
11427
+ type: "tool_result",
11428
+ tool_use_id: block.id,
11429
+ content: `Fatal error: ${classified.message}`,
11430
+ is_error: true
11431
+ });
11432
+ hadFatalError = true;
11433
+ } else {
11434
+ toolResults.push({
11435
+ type: "tool_result",
11436
+ tool_use_id: block.id,
11437
+ content: `Error (recoverable): ${classified.message}`,
11438
+ is_error: true
11439
+ });
11440
+ }
10908
11441
  }
10909
11442
  }
10910
11443
  for (const { block, check } of blocked) {
@@ -10917,10 +11450,22 @@ var BotRuntime = class {
10917
11450
  }
10918
11451
  history.push({
10919
11452
  role: "user",
10920
- content: toolResults
11453
+ content: toolResults,
11454
+ frameType: hadFatalError ? "error" : "tool_result"
10921
11455
  });
11456
+ if (hadFatalError) {
11457
+ this.trimHistory(sessionKey);
11458
+ return `A fatal error occurred during tool execution. The bot loop has been stopped.`;
11459
+ }
10922
11460
  }
10923
- return "I reached the maximum number of tool calls for this request. Please try again with a more specific question.";
11461
+ const maxErr = new MaxStepsError(turns, maxTurns);
11462
+ history.push({
11463
+ role: "assistant",
11464
+ content: maxErr.message,
11465
+ frameType: "error"
11466
+ });
11467
+ this.trimHistory(sessionKey);
11468
+ return maxErr.message;
10924
11469
  }
10925
11470
  getHistory(sessionKey) {
10926
11471
  if (!this.conversations.has(sessionKey)) {
@@ -10930,9 +11475,19 @@ var BotRuntime = class {
10930
11475
  }
10931
11476
  trimHistory(sessionKey) {
10932
11477
  const history = this.conversations.get(sessionKey);
10933
- if (history && history.length > MAX_HISTORY) {
10934
- this.conversations.set(sessionKey, history.slice(-MAX_HISTORY));
11478
+ if (!history || history.length <= MAX_HISTORY) return;
11479
+ const firstTaskIdx = history.findIndex((m) => m.frameType === "task");
11480
+ let trimmed = history.filter(
11481
+ (m, i) => m.frameType !== "planning" || i >= history.length - MAX_HISTORY
11482
+ );
11483
+ if (trimmed.length > MAX_HISTORY) {
11484
+ const tail = trimmed.slice(-MAX_HISTORY);
11485
+ if (firstTaskIdx >= 0 && !tail.includes(history[firstTaskIdx])) {
11486
+ tail[0] = history[firstTaskIdx];
11487
+ }
11488
+ trimmed = tail;
10935
11489
  }
11490
+ this.conversations.set(sessionKey, trimmed);
10936
11491
  }
10937
11492
  /** Clear conversation history for a session */
10938
11493
  clearHistory(sessionKey) {
@@ -11638,8 +12193,19 @@ import { randomUUID as randomUUID9 } from "crypto";
11638
12193
  import { homedir } from "os";
11639
12194
  import { join } from "path";
11640
12195
  import { mkdirSync as mkdirSync7 } from "fs";
11641
- var RECONNECT_DELAY_MS = 5e3;
12196
+ var INITIAL_BACKOFF_MS = 1e3;
12197
+ var MAX_BACKOFF_MS = 3e5;
12198
+ var BACKOFF_MULTIPLIER = 2;
12199
+ var JITTER_FACTOR = 0.25;
11642
12200
  var AUTH_DIR = join(homedir(), ".exe-os", "whatsapp-auth");
12201
+ function calculateBackoff(retryCount) {
12202
+ const base = Math.min(
12203
+ INITIAL_BACKOFF_MS * BACKOFF_MULTIPLIER ** retryCount,
12204
+ MAX_BACKOFF_MS
12205
+ );
12206
+ const jitter = base * JITTER_FACTOR * (2 * Math.random() - 1);
12207
+ return Math.max(INITIAL_BACKOFF_MS, Math.round(base + jitter));
12208
+ }
11643
12209
  var WhatsAppAdapter = class {
11644
12210
  platform = "whatsapp";
11645
12211
  sock = null;
@@ -11647,6 +12213,9 @@ var WhatsAppAdapter = class {
11647
12213
  connected = false;
11648
12214
  abortController = null;
11649
12215
  authDir = AUTH_DIR;
12216
+ // Resilience state
12217
+ retryCount = 0;
12218
+ disconnectedAt = 0;
11650
12219
  async connect(config2) {
11651
12220
  this.authDir = config2.credentials.authDir ?? AUTH_DIR;
11652
12221
  mkdirSync7(this.authDir, { recursive: true });
@@ -11655,6 +12224,20 @@ var WhatsAppAdapter = class {
11655
12224
  const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
11656
12225
  const { version } = await fetchLatestBaileysVersion();
11657
12226
  this.abortController = new AbortController();
12227
+ let agent;
12228
+ const socksProxy = config2.credentials.socksProxy;
12229
+ if (socksProxy) {
12230
+ try {
12231
+ const modName = "socks-proxy-agent";
12232
+ const mod = await import(modName);
12233
+ const SocksProxyAgent = mod.SocksProxyAgent ?? mod.default;
12234
+ agent = new SocksProxyAgent(socksProxy);
12235
+ console.log(`[whatsapp] Using SOCKS proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")}`);
12236
+ } catch {
12237
+ console.error("[whatsapp] socks-proxy-agent not installed \u2014 run: npm i socks-proxy-agent");
12238
+ throw new Error("SOCKS proxy configured but socks-proxy-agent package not installed");
12239
+ }
12240
+ }
11658
12241
  const sock = makeWASocket({
11659
12242
  auth: {
11660
12243
  creds: state.creds,
@@ -11664,7 +12247,8 @@ var WhatsAppAdapter = class {
11664
12247
  printQRInTerminal: true,
11665
12248
  browser: ["exe-os", "cli", "1.0"],
11666
12249
  syncFullHistory: false,
11667
- markOnlineOnConnect: false
12250
+ markOnlineOnConnect: false,
12251
+ ...agent ? { agent } : {}
11668
12252
  });
11669
12253
  this.sock = sock;
11670
12254
  sock.ev.on("creds.update", saveCreds);
@@ -11672,18 +12256,32 @@ var WhatsAppAdapter = class {
11672
12256
  const { connection, lastDisconnect } = update;
11673
12257
  if (connection === "close") {
11674
12258
  this.connected = false;
12259
+ if (this.disconnectedAt === 0) this.disconnectedAt = Date.now();
11675
12260
  const statusCode = lastDisconnect?.error?.output?.statusCode;
11676
12261
  const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
11677
12262
  if (shouldReconnect && !this.abortController?.signal.aborted) {
11678
- console.log(`[whatsapp] Connection closed (${statusCode}), reconnecting...`);
11679
- setTimeout(() => void this.connect(config2), RECONNECT_DELAY_MS);
12263
+ const delay2 = calculateBackoff(this.retryCount);
12264
+ this.retryCount++;
12265
+ console.log(
12266
+ `[whatsapp] Connection closed (code=${statusCode}), retry #${this.retryCount} in ${(delay2 / 1e3).toFixed(1)}s` + (socksProxy ? ` (proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")})` : "")
12267
+ );
12268
+ setTimeout(() => void this.connect(config2), delay2);
11680
12269
  } else {
11681
12270
  console.log("[whatsapp] Logged out \u2014 clear auth and re-scan QR");
11682
12271
  }
11683
12272
  }
11684
12273
  if (connection === "open") {
12274
+ if (this.retryCount > 0) {
12275
+ const downtimeSec = this.disconnectedAt > 0 ? ((Date.now() - this.disconnectedAt) / 1e3).toFixed(1) : "?";
12276
+ console.log(
12277
+ `[whatsapp] Reconnected after ${this.retryCount} retries (${downtimeSec}s downtime)`
12278
+ );
12279
+ } else {
12280
+ console.log("[whatsapp] Connected via Baileys (linked device)");
12281
+ }
11685
12282
  this.connected = true;
11686
- console.log("[whatsapp] Connected via Baileys (linked device)");
12283
+ this.retryCount = 0;
12284
+ this.disconnectedAt = 0;
11687
12285
  }
11688
12286
  });
11689
12287
  sock.ev.on("messages.upsert", (upsert) => {
@@ -12922,12 +13520,12 @@ var SlackAdapter = class {
12922
13520
  // src/gateway/adapters/imessage.ts
12923
13521
  import { execFile } from "child_process";
12924
13522
  import { promisify } from "util";
12925
- import os9 from "os";
13523
+ import os10 from "os";
12926
13524
  import path18 from "path";
12927
13525
  var execFileAsync = promisify(execFile);
12928
13526
  var POLL_INTERVAL_MS = 5e3;
12929
13527
  var MESSAGES_DB_PATH = path18.join(
12930
- process.env.HOME ?? os9.homedir(),
13528
+ process.env.HOME ?? os10.homedir(),
12931
13529
  "Library/Messages/chat.db"
12932
13530
  );
12933
13531
  var IMessageAdapter = class {
@@ -13773,8 +14371,8 @@ async function ensureCRMContact(info) {
13773
14371
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
13774
14372
  import { randomUUID as randomUUID15 } from "crypto";
13775
14373
  import path19 from "path";
13776
- import os10 from "os";
13777
- var TRIGGERS_PATH = path19.join(os10.homedir(), ".exe-os", "triggers.json");
14374
+ import os11 from "os";
14375
+ var TRIGGERS_PATH = path19.join(os11.homedir(), ".exe-os", "triggers.json");
13778
14376
  var GRAPH_API_VERSION = "v21.0";
13779
14377
  function substituteTemplate(template, record) {
13780
14378
  return template.replace(