@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
@@ -274,6 +274,93 @@ var init_memory = __esm({
274
274
  }
275
275
  });
276
276
 
277
+ // src/lib/daemon-protocol.ts
278
+ function serializeValue(v) {
279
+ if (v === null || v === void 0) return null;
280
+ if (typeof v === "bigint") return Number(v);
281
+ if (typeof v === "boolean") return v ? 1 : 0;
282
+ if (v instanceof Uint8Array) {
283
+ return { __blob: Buffer.from(v).toString("base64") };
284
+ }
285
+ if (ArrayBuffer.isView(v)) {
286
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
287
+ }
288
+ if (v instanceof ArrayBuffer) {
289
+ return { __blob: Buffer.from(v).toString("base64") };
290
+ }
291
+ if (typeof v === "string" || typeof v === "number") return v;
292
+ return String(v);
293
+ }
294
+ function deserializeValue(v) {
295
+ if (v === null) return null;
296
+ if (typeof v === "object" && v !== null && "__blob" in v) {
297
+ const buf = Buffer.from(v.__blob, "base64");
298
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
299
+ }
300
+ return v;
301
+ }
302
+ function deserializeArgs(args) {
303
+ return args.map(deserializeValue);
304
+ }
305
+ function serializeResultSet(rs) {
306
+ const rows = [];
307
+ for (const row of rs.rows) {
308
+ const obj = {};
309
+ for (let i = 0; i < rs.columns.length; i++) {
310
+ const col = rs.columns[i];
311
+ if (col !== void 0) {
312
+ obj[col] = serializeValue(row[i]);
313
+ }
314
+ }
315
+ rows.push(obj);
316
+ }
317
+ return {
318
+ columns: [...rs.columns],
319
+ columnTypes: [...rs.columnTypes ?? []],
320
+ rows,
321
+ rowsAffected: typeof rs.rowsAffected === "bigint" ? Number(rs.rowsAffected) : rs.rowsAffected ?? 0,
322
+ lastInsertRowid: rs.lastInsertRowid != null ? typeof rs.lastInsertRowid === "bigint" ? Number(rs.lastInsertRowid) : rs.lastInsertRowid : null
323
+ };
324
+ }
325
+ function deserializeResultSet(srs) {
326
+ const rows = srs.rows.map((obj) => {
327
+ const values = srs.columns.map(
328
+ (col) => deserializeValue(obj[col] ?? null)
329
+ );
330
+ const row = values;
331
+ for (let i = 0; i < srs.columns.length; i++) {
332
+ const col = srs.columns[i];
333
+ if (col !== void 0) {
334
+ row[col] = values[i] ?? null;
335
+ }
336
+ }
337
+ Object.defineProperty(row, "length", {
338
+ value: values.length,
339
+ enumerable: false
340
+ });
341
+ return row;
342
+ });
343
+ return {
344
+ columns: srs.columns,
345
+ columnTypes: srs.columnTypes ?? [],
346
+ rows,
347
+ rowsAffected: srs.rowsAffected,
348
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
349
+ toJSON: () => ({
350
+ columns: srs.columns,
351
+ columnTypes: srs.columnTypes ?? [],
352
+ rows: srs.rows,
353
+ rowsAffected: srs.rowsAffected,
354
+ lastInsertRowid: srs.lastInsertRowid
355
+ })
356
+ };
357
+ }
358
+ var init_daemon_protocol = __esm({
359
+ "src/lib/daemon-protocol.ts"() {
360
+ "use strict";
361
+ }
362
+ });
363
+
277
364
  // src/lib/session-registry.ts
278
365
  var session_registry_exports = {};
279
366
  __export(session_registry_exports, {
@@ -729,6 +816,31 @@ var init_db_retry = __esm({
729
816
  });
730
817
 
731
818
  // src/lib/employees.ts
819
+ var employees_exports = {};
820
+ __export(employees_exports, {
821
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
822
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
823
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
824
+ addEmployee: () => addEmployee,
825
+ baseAgentName: () => baseAgentName,
826
+ canCoordinate: () => canCoordinate,
827
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
828
+ getCoordinatorName: () => getCoordinatorName,
829
+ getEmployee: () => getEmployee,
830
+ getEmployeeByRole: () => getEmployeeByRole,
831
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
832
+ hasRole: () => hasRole,
833
+ isCoordinatorName: () => isCoordinatorName,
834
+ isCoordinatorRole: () => isCoordinatorRole,
835
+ isMultiInstance: () => isMultiInstance,
836
+ loadEmployees: () => loadEmployees,
837
+ loadEmployeesSync: () => loadEmployeesSync,
838
+ normalizeRole: () => normalizeRole,
839
+ normalizeRosterCase: () => normalizeRosterCase,
840
+ registerBinSymlinks: () => registerBinSymlinks,
841
+ saveEmployees: () => saveEmployees,
842
+ validateEmployeeName: () => validateEmployeeName
843
+ });
732
844
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
733
845
  import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
734
846
  import { execSync as execSync4 } from "child_process";
@@ -750,6 +862,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
750
862
  if (!agentName) return false;
751
863
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
752
864
  }
865
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
866
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
867
+ }
868
+ function validateEmployeeName(name) {
869
+ if (!name) {
870
+ return { valid: false, error: "Name is required" };
871
+ }
872
+ if (name.length > 32) {
873
+ return { valid: false, error: "Name must be 32 characters or fewer" };
874
+ }
875
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
876
+ return {
877
+ valid: false,
878
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
879
+ };
880
+ }
881
+ return { valid: true };
882
+ }
753
883
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
754
884
  if (!existsSync4(employeesPath)) {
755
885
  return [];
@@ -761,6 +891,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
761
891
  return [];
762
892
  }
763
893
  }
894
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
895
+ await mkdir2(path4.dirname(employeesPath), { recursive: true });
896
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
897
+ }
764
898
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
765
899
  if (!existsSync4(employeesPath)) return [];
766
900
  try {
@@ -772,12 +906,110 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
772
906
  function getEmployee(employees, name) {
773
907
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
774
908
  }
909
+ function getEmployeeByRole(employees, role) {
910
+ const lower = role.toLowerCase();
911
+ return employees.find((e) => e.role.toLowerCase() === lower);
912
+ }
913
+ function getEmployeeNamesByRole(employees, role) {
914
+ const lower = role.toLowerCase();
915
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
916
+ }
917
+ function hasRole(agentName, role) {
918
+ const employees = loadEmployeesSync();
919
+ const emp = getEmployee(employees, agentName);
920
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
921
+ }
922
+ function baseAgentName(name, employees) {
923
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
924
+ if (!match) return name;
925
+ const base = match[1];
926
+ const roster = employees ?? loadEmployeesSync();
927
+ if (getEmployee(roster, base)) return base;
928
+ return name;
929
+ }
775
930
  function isMultiInstance(agentName, employees) {
776
931
  const roster = employees ?? loadEmployeesSync();
777
932
  const emp = getEmployee(roster, agentName);
778
933
  if (!emp) return false;
779
934
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
780
935
  }
936
+ function addEmployee(employees, employee) {
937
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
938
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
939
+ throw new Error(`Employee '${normalized.name}' already exists`);
940
+ }
941
+ return [...employees, normalized];
942
+ }
943
+ async function normalizeRosterCase(rosterPath) {
944
+ const employees = await loadEmployees(rosterPath);
945
+ let changed = false;
946
+ for (const emp of employees) {
947
+ if (emp.name !== emp.name.toLowerCase()) {
948
+ const oldName = emp.name;
949
+ emp.name = emp.name.toLowerCase();
950
+ changed = true;
951
+ try {
952
+ const identityDir = path4.join(os4.homedir(), ".exe-os", "identity");
953
+ const oldPath = path4.join(identityDir, `${oldName}.md`);
954
+ const newPath = path4.join(identityDir, `${emp.name}.md`);
955
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
956
+ renameSync3(oldPath, newPath);
957
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
958
+ const content = readFileSync4(oldPath, "utf-8");
959
+ writeFileSync3(newPath, content, "utf-8");
960
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
961
+ unlinkSync(oldPath);
962
+ }
963
+ }
964
+ } catch {
965
+ }
966
+ }
967
+ }
968
+ if (changed) {
969
+ await saveEmployees(employees, rosterPath);
970
+ }
971
+ return changed;
972
+ }
973
+ function findExeBin() {
974
+ try {
975
+ return execSync4(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
976
+ } catch {
977
+ return null;
978
+ }
979
+ }
980
+ function registerBinSymlinks(name) {
981
+ const created = [];
982
+ const skipped = [];
983
+ const errors = [];
984
+ const exeBinPath = findExeBin();
985
+ if (!exeBinPath) {
986
+ errors.push("Could not find 'exe-os' in PATH");
987
+ return { created, skipped, errors };
988
+ }
989
+ const binDir = path4.dirname(exeBinPath);
990
+ let target;
991
+ try {
992
+ target = readlinkSync(exeBinPath);
993
+ } catch {
994
+ errors.push("Could not read 'exe' symlink");
995
+ return { created, skipped, errors };
996
+ }
997
+ for (const suffix of ["", "-opencode"]) {
998
+ const linkName = `${name}${suffix}`;
999
+ const linkPath = path4.join(binDir, linkName);
1000
+ if (existsSync4(linkPath)) {
1001
+ skipped.push(linkName);
1002
+ continue;
1003
+ }
1004
+ try {
1005
+ symlinkSync(target, linkPath);
1006
+ created.push(linkName);
1007
+ } catch (err) {
1008
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1009
+ }
1010
+ }
1011
+ return { created, skipped, errors };
1012
+ }
781
1013
  var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
782
1014
  var init_employees = __esm({
783
1015
  "src/lib/employees.ts"() {
@@ -790,284 +1022,773 @@ var init_employees = __esm({
790
1022
  }
791
1023
  });
792
1024
 
793
- // src/lib/database.ts
794
- var database_exports = {};
795
- __export(database_exports, {
796
- disposeDatabase: () => disposeDatabase,
797
- disposeTurso: () => disposeTurso,
798
- ensureSchema: () => ensureSchema,
799
- getClient: () => getClient,
800
- getRawClient: () => getRawClient,
801
- initDatabase: () => initDatabase,
802
- initTurso: () => initTurso,
803
- isInitialized: () => isInitialized
804
- });
805
- import { createClient } from "@libsql/client";
806
- async function initDatabase(config) {
807
- if (_client) {
808
- _client.close();
809
- _client = null;
810
- _resilientClient = null;
1025
+ // src/lib/exe-daemon-client.ts
1026
+ import net from "net";
1027
+ import { spawn } from "child_process";
1028
+ import { randomUUID } from "crypto";
1029
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1030
+ import path5 from "path";
1031
+ import { fileURLToPath } from "url";
1032
+ function handleData(chunk) {
1033
+ _buffer += chunk.toString();
1034
+ if (_buffer.length > MAX_BUFFER) {
1035
+ _buffer = "";
1036
+ return;
811
1037
  }
812
- const opts = {
813
- url: `file:${config.dbPath}`
814
- };
815
- if (config.encryptionKey) {
816
- opts.encryptionKey = config.encryptionKey;
1038
+ let newlineIdx;
1039
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1040
+ const line = _buffer.slice(0, newlineIdx).trim();
1041
+ _buffer = _buffer.slice(newlineIdx + 1);
1042
+ if (!line) continue;
1043
+ try {
1044
+ const response = JSON.parse(line);
1045
+ const id = response.id;
1046
+ if (!id) continue;
1047
+ const entry = _pending.get(id);
1048
+ if (entry) {
1049
+ clearTimeout(entry.timer);
1050
+ _pending.delete(id);
1051
+ entry.resolve(response);
1052
+ }
1053
+ } catch {
1054
+ }
817
1055
  }
818
- _client = createClient(opts);
819
- _resilientClient = wrapWithRetry(_client);
820
1056
  }
821
- function isInitialized() {
822
- return _client !== null;
1057
+ function cleanupStaleFiles() {
1058
+ if (existsSync5(PID_PATH)) {
1059
+ try {
1060
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1061
+ if (pid > 0) {
1062
+ try {
1063
+ process.kill(pid, 0);
1064
+ return;
1065
+ } catch {
1066
+ }
1067
+ }
1068
+ } catch {
1069
+ }
1070
+ try {
1071
+ unlinkSync2(PID_PATH);
1072
+ } catch {
1073
+ }
1074
+ try {
1075
+ unlinkSync2(SOCKET_PATH);
1076
+ } catch {
1077
+ }
1078
+ }
823
1079
  }
824
- function getClient() {
825
- if (!_resilientClient) {
826
- throw new Error("Database client not initialized. Call initDatabase() first.");
1080
+ function findPackageRoot() {
1081
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1082
+ const { root } = path5.parse(dir);
1083
+ while (dir !== root) {
1084
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1085
+ dir = path5.dirname(dir);
827
1086
  }
828
- return _resilientClient;
1087
+ return null;
829
1088
  }
830
- function getRawClient() {
831
- if (!_client) {
832
- throw new Error("Database client not initialized. Call initDatabase() first.");
1089
+ function spawnDaemon() {
1090
+ const pkgRoot = findPackageRoot();
1091
+ if (!pkgRoot) {
1092
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
1093
+ return;
1094
+ }
1095
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1096
+ if (!existsSync5(daemonPath)) {
1097
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1098
+ `);
1099
+ return;
1100
+ }
1101
+ const resolvedPath = daemonPath;
1102
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1103
+ `);
1104
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1105
+ let stderrFd = "ignore";
1106
+ try {
1107
+ stderrFd = openSync(logPath, "a");
1108
+ } catch {
1109
+ }
1110
+ const child = spawn(process.execPath, [resolvedPath], {
1111
+ detached: true,
1112
+ stdio: ["ignore", "ignore", stderrFd],
1113
+ env: {
1114
+ ...process.env,
1115
+ TMUX: void 0,
1116
+ // Daemon is global — must not inherit session scope
1117
+ TMUX_PANE: void 0,
1118
+ // Prevents resolveExeSession() from scoping to one session
1119
+ EXE_DAEMON_SOCK: SOCKET_PATH,
1120
+ EXE_DAEMON_PID: PID_PATH
1121
+ }
1122
+ });
1123
+ child.unref();
1124
+ if (typeof stderrFd === "number") {
1125
+ try {
1126
+ closeSync(stderrFd);
1127
+ } catch {
1128
+ }
833
1129
  }
834
- return _client;
835
1130
  }
836
- async function ensureSchema() {
837
- const client = getRawClient();
838
- await client.execute("PRAGMA journal_mode = WAL");
839
- await client.execute("PRAGMA busy_timeout = 30000");
840
- await client.execute("PRAGMA wal_autocheckpoint = 1000");
1131
+ function acquireSpawnLock() {
841
1132
  try {
842
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
843
- } catch {
844
- }
845
- await client.executeMultiple(`
846
- CREATE TABLE IF NOT EXISTS memories (
847
- id TEXT PRIMARY KEY,
848
- agent_id TEXT NOT NULL,
849
- agent_role TEXT NOT NULL,
850
- session_id TEXT NOT NULL,
851
- timestamp TEXT NOT NULL,
852
- tool_name TEXT NOT NULL,
853
- project_name TEXT NOT NULL,
854
- has_error INTEGER NOT NULL DEFAULT 0,
855
- raw_text TEXT NOT NULL,
856
- vector F32_BLOB(1024),
857
- version INTEGER NOT NULL DEFAULT 0
858
- );
859
-
860
- CREATE INDEX IF NOT EXISTS idx_memories_agent
861
- ON memories(agent_id);
862
-
863
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp
864
- ON memories(timestamp);
865
-
866
- CREATE INDEX IF NOT EXISTS idx_memories_session
867
- ON memories(session_id);
868
-
869
- CREATE INDEX IF NOT EXISTS idx_memories_project
870
- ON memories(project_name);
871
-
872
- CREATE INDEX IF NOT EXISTS idx_memories_tool
873
- ON memories(tool_name);
874
-
875
- CREATE INDEX IF NOT EXISTS idx_memories_version
876
- ON memories(version);
877
-
878
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project
879
- ON memories(agent_id, project_name);
880
- `);
881
- await client.executeMultiple(`
882
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
883
- raw_text,
884
- content='memories',
885
- content_rowid='rowid'
886
- );
887
-
888
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
889
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
890
- END;
891
-
892
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
893
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
894
- END;
895
-
896
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
897
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
898
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
899
- END;
900
- `);
901
- await client.executeMultiple(`
902
- CREATE TABLE IF NOT EXISTS sync_meta (
903
- key TEXT PRIMARY KEY,
904
- value TEXT NOT NULL
905
- );
906
- `);
907
- await client.executeMultiple(`
908
- CREATE TABLE IF NOT EXISTS tasks (
909
- id TEXT PRIMARY KEY,
910
- title TEXT NOT NULL,
911
- assigned_to TEXT NOT NULL,
912
- assigned_by TEXT NOT NULL,
913
- project_name TEXT NOT NULL,
914
- priority TEXT NOT NULL DEFAULT 'p1',
915
- status TEXT NOT NULL DEFAULT 'open',
916
- task_file TEXT,
917
- created_at TEXT NOT NULL,
918
- updated_at TEXT NOT NULL
919
- );
920
-
921
- CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
922
- ON tasks(assigned_to, status);
923
- `);
924
- await client.executeMultiple(`
925
- CREATE TABLE IF NOT EXISTS behaviors (
926
- id TEXT PRIMARY KEY,
927
- agent_id TEXT NOT NULL,
928
- project_name TEXT,
929
- domain TEXT,
930
- content TEXT NOT NULL,
931
- active INTEGER NOT NULL DEFAULT 1,
932
- created_at TEXT NOT NULL,
933
- updated_at TEXT NOT NULL
934
- );
935
-
936
- CREATE INDEX IF NOT EXISTS idx_behaviors_agent
937
- ON behaviors(agent_id, active);
938
- `);
939
- try {
940
- const coordinatorName = getCoordinatorName();
941
- const existing = await client.execute({
942
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
943
- args: [coordinatorName]
944
- });
945
- if (Number(existing.rows[0]?.cnt) === 0) {
946
- const seededAt = "2026-03-25T00:00:00Z";
947
- for (const [domain, content] of [
948
- ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
949
- ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
950
- ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
951
- ]) {
952
- await client.execute({
953
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
954
- VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
955
- args: [coordinatorName, domain, content, seededAt, seededAt]
956
- });
957
- }
958
- }
1133
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1134
+ closeSync(fd);
1135
+ return true;
959
1136
  } catch {
1137
+ try {
1138
+ const stat = statSync(SPAWN_LOCK_PATH);
1139
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1140
+ try {
1141
+ unlinkSync2(SPAWN_LOCK_PATH);
1142
+ } catch {
1143
+ }
1144
+ try {
1145
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1146
+ closeSync(fd);
1147
+ return true;
1148
+ } catch {
1149
+ }
1150
+ }
1151
+ } catch {
1152
+ }
1153
+ return false;
960
1154
  }
1155
+ }
1156
+ function releaseSpawnLock() {
961
1157
  try {
962
- await client.execute({
963
- sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
964
- args: []
965
- });
1158
+ unlinkSync2(SPAWN_LOCK_PATH);
966
1159
  } catch {
967
1160
  }
968
- try {
969
- await client.execute({
970
- sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
971
- args: []
1161
+ }
1162
+ function connectToSocket() {
1163
+ return new Promise((resolve) => {
1164
+ if (_socket && _connected) {
1165
+ resolve(true);
1166
+ return;
1167
+ }
1168
+ const socket = net.createConnection({ path: SOCKET_PATH });
1169
+ const connectTimeout = setTimeout(() => {
1170
+ socket.destroy();
1171
+ resolve(false);
1172
+ }, 2e3);
1173
+ socket.on("connect", () => {
1174
+ clearTimeout(connectTimeout);
1175
+ _socket = socket;
1176
+ _connected = true;
1177
+ _buffer = "";
1178
+ socket.on("data", handleData);
1179
+ socket.on("close", () => {
1180
+ _connected = false;
1181
+ _socket = null;
1182
+ for (const [id, entry] of _pending) {
1183
+ clearTimeout(entry.timer);
1184
+ _pending.delete(id);
1185
+ entry.resolve({ error: "Connection closed" });
1186
+ }
1187
+ });
1188
+ socket.on("error", () => {
1189
+ _connected = false;
1190
+ _socket = null;
1191
+ });
1192
+ resolve(true);
972
1193
  });
973
- } catch {
974
- }
975
- try {
976
- await client.execute({
977
- sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
978
- args: []
1194
+ socket.on("error", () => {
1195
+ clearTimeout(connectTimeout);
1196
+ resolve(false);
979
1197
  });
980
- } catch {
1198
+ });
1199
+ }
1200
+ async function connectEmbedDaemon() {
1201
+ if (_socket && _connected) return true;
1202
+ if (await connectToSocket()) return true;
1203
+ if (acquireSpawnLock()) {
1204
+ try {
1205
+ cleanupStaleFiles();
1206
+ spawnDaemon();
1207
+ } finally {
1208
+ releaseSpawnLock();
1209
+ }
981
1210
  }
982
- try {
983
- await client.execute({
984
- sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
985
- ON tasks(parent_task_id)
986
- WHERE parent_task_id IS NOT NULL`,
987
- args: []
988
- });
989
- } catch {
1211
+ const start = Date.now();
1212
+ let delay2 = 100;
1213
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1214
+ await new Promise((r) => setTimeout(r, delay2));
1215
+ if (await connectToSocket()) return true;
1216
+ delay2 = Math.min(delay2 * 2, 3e3);
990
1217
  }
991
- try {
992
- await client.execute({
993
- sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
994
- args: []
995
- });
996
- } catch {
1218
+ return false;
1219
+ }
1220
+ function sendRequest(texts, priority) {
1221
+ return sendDaemonRequest({ texts, priority });
1222
+ }
1223
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1224
+ return new Promise((resolve) => {
1225
+ if (!_socket || !_connected) {
1226
+ resolve({ error: "Not connected" });
1227
+ return;
1228
+ }
1229
+ const id = randomUUID();
1230
+ const timer = setTimeout(() => {
1231
+ _pending.delete(id);
1232
+ resolve({ error: "Request timeout" });
1233
+ }, timeoutMs);
1234
+ _pending.set(id, { resolve, timer });
1235
+ try {
1236
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1237
+ } catch {
1238
+ clearTimeout(timer);
1239
+ _pending.delete(id);
1240
+ resolve({ error: "Write failed" });
1241
+ }
1242
+ });
1243
+ }
1244
+ async function pingDaemon() {
1245
+ if (!_socket || !_connected) return null;
1246
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
1247
+ if (response.health) {
1248
+ return response.health;
997
1249
  }
998
- try {
999
- await client.execute({
1000
- sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1001
- args: []
1002
- });
1003
- } catch {
1250
+ return null;
1251
+ }
1252
+ function killAndRespawnDaemon() {
1253
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
1254
+ if (existsSync5(PID_PATH)) {
1255
+ try {
1256
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1257
+ if (pid > 0) {
1258
+ try {
1259
+ process.kill(pid, "SIGKILL");
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ } catch {
1264
+ }
1004
1265
  }
1005
- try {
1006
- await client.execute({
1007
- sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1008
- args: []
1009
- });
1010
- } catch {
1266
+ if (_socket) {
1267
+ _socket.destroy();
1268
+ _socket = null;
1011
1269
  }
1270
+ _connected = false;
1271
+ _buffer = "";
1012
1272
  try {
1013
- await client.execute({
1014
- sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1015
- args: []
1016
- });
1273
+ unlinkSync2(PID_PATH);
1017
1274
  } catch {
1018
1275
  }
1019
1276
  try {
1020
- await client.execute({
1021
- sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1022
- args: []
1023
- });
1277
+ unlinkSync2(SOCKET_PATH);
1024
1278
  } catch {
1025
1279
  }
1026
- try {
1027
- await client.execute({
1028
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1029
- args: []
1030
- });
1031
- } catch {
1280
+ spawnDaemon();
1281
+ }
1282
+ async function embedViaClient(text, priority = "high") {
1283
+ if (!_connected && !await connectEmbedDaemon()) return null;
1284
+ _requestCount++;
1285
+ if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1286
+ const health = await pingDaemon();
1287
+ if (!health) {
1288
+ process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1289
+ `);
1290
+ killAndRespawnDaemon();
1291
+ const start = Date.now();
1292
+ let delay2 = 200;
1293
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1294
+ await new Promise((r) => setTimeout(r, delay2));
1295
+ if (await connectToSocket()) break;
1296
+ delay2 = Math.min(delay2 * 2, 3e3);
1297
+ }
1298
+ if (!_connected) return null;
1299
+ }
1032
1300
  }
1033
- try {
1034
- await client.execute({
1035
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1036
- args: []
1037
- });
1038
- } catch {
1301
+ const result = await sendRequest([text], priority);
1302
+ if (!result.error && result.vectors?.[0]) return result.vectors[0];
1303
+ if (result.error) {
1304
+ process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
1305
+ `);
1306
+ killAndRespawnDaemon();
1307
+ const start = Date.now();
1308
+ let delay2 = 200;
1309
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1310
+ await new Promise((r) => setTimeout(r, delay2));
1311
+ if (await connectToSocket()) break;
1312
+ delay2 = Math.min(delay2 * 2, 3e3);
1313
+ }
1314
+ if (!_connected) return null;
1315
+ const retry = await sendRequest([text], priority);
1316
+ if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
1317
+ process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
1318
+ `);
1039
1319
  }
1040
- try {
1041
- await client.execute({
1042
- sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1043
- args: []
1320
+ return null;
1321
+ }
1322
+ function disconnectClient() {
1323
+ if (_socket) {
1324
+ _socket.destroy();
1325
+ _socket = null;
1326
+ }
1327
+ _connected = false;
1328
+ _buffer = "";
1329
+ for (const [id, entry] of _pending) {
1330
+ clearTimeout(entry.timer);
1331
+ _pending.delete(id);
1332
+ entry.resolve({ error: "Client disconnected" });
1333
+ }
1334
+ }
1335
+ function isClientConnected() {
1336
+ return _connected;
1337
+ }
1338
+ 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;
1339
+ var init_exe_daemon_client = __esm({
1340
+ "src/lib/exe-daemon-client.ts"() {
1341
+ "use strict";
1342
+ init_config();
1343
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1344
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1345
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1346
+ SPAWN_LOCK_STALE_MS = 3e4;
1347
+ CONNECT_TIMEOUT_MS = 15e3;
1348
+ REQUEST_TIMEOUT_MS = 3e4;
1349
+ _socket = null;
1350
+ _connected = false;
1351
+ _buffer = "";
1352
+ _requestCount = 0;
1353
+ HEALTH_CHECK_INTERVAL = 100;
1354
+ _pending = /* @__PURE__ */ new Map();
1355
+ MAX_BUFFER = 1e7;
1356
+ }
1357
+ });
1358
+
1359
+ // src/lib/db-daemon-client.ts
1360
+ var db_daemon_client_exports = {};
1361
+ __export(db_daemon_client_exports, {
1362
+ createDaemonDbClient: () => createDaemonDbClient,
1363
+ initDaemonDbClient: () => initDaemonDbClient
1364
+ });
1365
+ function normalizeStatement(stmt) {
1366
+ if (typeof stmt === "string") {
1367
+ return { sql: stmt, args: [] };
1368
+ }
1369
+ const sql = stmt.sql;
1370
+ let args = [];
1371
+ if (Array.isArray(stmt.args)) {
1372
+ args = stmt.args.map((v) => serializeValue(v));
1373
+ } else if (stmt.args && typeof stmt.args === "object") {
1374
+ const named = {};
1375
+ for (const [key, val] of Object.entries(stmt.args)) {
1376
+ named[key] = serializeValue(val);
1377
+ }
1378
+ return { sql, args: named };
1379
+ }
1380
+ return { sql, args };
1381
+ }
1382
+ function createDaemonDbClient(fallbackClient) {
1383
+ let _useDaemon = false;
1384
+ const client = {
1385
+ async execute(stmt) {
1386
+ if (!_useDaemon || !isClientConnected()) {
1387
+ return fallbackClient.execute(stmt);
1388
+ }
1389
+ const { sql, args } = normalizeStatement(stmt);
1390
+ const response = await sendDaemonRequest({
1391
+ type: "db-execute",
1392
+ sql,
1393
+ args
1394
+ });
1395
+ if (response.error) {
1396
+ const errMsg = String(response.error);
1397
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1398
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1399
+ `);
1400
+ return fallbackClient.execute(stmt);
1401
+ }
1402
+ throw new Error(errMsg);
1403
+ }
1404
+ if (response.db) {
1405
+ return deserializeResultSet(response.db);
1406
+ }
1407
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1408
+ return fallbackClient.execute(stmt);
1409
+ },
1410
+ async batch(stmts, mode) {
1411
+ if (!_useDaemon || !isClientConnected()) {
1412
+ return fallbackClient.batch(stmts, mode);
1413
+ }
1414
+ const statements = stmts.map(normalizeStatement);
1415
+ const response = await sendDaemonRequest({
1416
+ type: "db-batch",
1417
+ statements,
1418
+ mode: mode ?? "deferred"
1419
+ });
1420
+ if (response.error) {
1421
+ const errMsg = String(response.error);
1422
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1423
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1424
+ `);
1425
+ return fallbackClient.batch(stmts, mode);
1426
+ }
1427
+ throw new Error(errMsg);
1428
+ }
1429
+ const batchResults = response["db-batch"];
1430
+ if (batchResults) {
1431
+ return batchResults.map(deserializeResultSet);
1432
+ }
1433
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1434
+ return fallbackClient.batch(stmts, mode);
1435
+ },
1436
+ // Transaction support — delegate to fallback (transactions need direct connection)
1437
+ async transaction(mode) {
1438
+ return fallbackClient.transaction(mode);
1439
+ },
1440
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1441
+ async executeMultiple(sql) {
1442
+ return fallbackClient.executeMultiple(sql);
1443
+ },
1444
+ // migrate — delegate to fallback
1445
+ async migrate(stmts) {
1446
+ return fallbackClient.migrate(stmts);
1447
+ },
1448
+ // Sync mode — delegate to fallback
1449
+ sync() {
1450
+ return fallbackClient.sync();
1451
+ },
1452
+ close() {
1453
+ _useDaemon = false;
1454
+ },
1455
+ get closed() {
1456
+ return fallbackClient.closed;
1457
+ },
1458
+ get protocol() {
1459
+ return fallbackClient.protocol;
1460
+ }
1461
+ };
1462
+ return {
1463
+ ...client,
1464
+ /** Enable daemon routing (call after confirming daemon is connected) */
1465
+ _enableDaemon() {
1466
+ _useDaemon = true;
1467
+ },
1468
+ /** Check if daemon routing is active */
1469
+ _isDaemonActive() {
1470
+ return _useDaemon && isClientConnected();
1471
+ }
1472
+ };
1473
+ }
1474
+ async function initDaemonDbClient(fallbackClient) {
1475
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1476
+ const connected = await connectEmbedDaemon();
1477
+ if (!connected) {
1478
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1479
+ return null;
1480
+ }
1481
+ const client = createDaemonDbClient(fallbackClient);
1482
+ client._enableDaemon();
1483
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1484
+ return client;
1485
+ }
1486
+ var init_db_daemon_client = __esm({
1487
+ "src/lib/db-daemon-client.ts"() {
1488
+ "use strict";
1489
+ init_exe_daemon_client();
1490
+ init_daemon_protocol();
1491
+ }
1492
+ });
1493
+
1494
+ // src/lib/database.ts
1495
+ var database_exports = {};
1496
+ __export(database_exports, {
1497
+ disposeDatabase: () => disposeDatabase,
1498
+ disposeTurso: () => disposeTurso,
1499
+ ensureSchema: () => ensureSchema,
1500
+ getClient: () => getClient,
1501
+ getRawClient: () => getRawClient,
1502
+ initDaemonClient: () => initDaemonClient,
1503
+ initDatabase: () => initDatabase,
1504
+ initTurso: () => initTurso,
1505
+ isInitialized: () => isInitialized
1506
+ });
1507
+ import { createClient } from "@libsql/client";
1508
+ async function initDatabase(config) {
1509
+ if (_client) {
1510
+ _client.close();
1511
+ _client = null;
1512
+ _resilientClient = null;
1513
+ }
1514
+ const opts = {
1515
+ url: `file:${config.dbPath}`
1516
+ };
1517
+ if (config.encryptionKey) {
1518
+ opts.encryptionKey = config.encryptionKey;
1519
+ }
1520
+ _client = createClient(opts);
1521
+ _resilientClient = wrapWithRetry(_client);
1522
+ }
1523
+ function isInitialized() {
1524
+ return _client !== null;
1525
+ }
1526
+ function getClient() {
1527
+ if (!_resilientClient) {
1528
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1529
+ }
1530
+ if (process.env.EXE_IS_DAEMON === "1") {
1531
+ return _resilientClient;
1532
+ }
1533
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1534
+ return _daemonClient;
1535
+ }
1536
+ return _resilientClient;
1537
+ }
1538
+ async function initDaemonClient() {
1539
+ if (process.env.EXE_IS_DAEMON === "1") return;
1540
+ if (!_resilientClient) return;
1541
+ try {
1542
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1543
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1544
+ } catch (err) {
1545
+ process.stderr.write(
1546
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1547
+ `
1548
+ );
1549
+ }
1550
+ }
1551
+ function getRawClient() {
1552
+ if (!_client) {
1553
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1554
+ }
1555
+ return _client;
1556
+ }
1557
+ async function ensureSchema() {
1558
+ const client = getRawClient();
1559
+ await client.execute("PRAGMA journal_mode = WAL");
1560
+ await client.execute("PRAGMA busy_timeout = 30000");
1561
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
1562
+ try {
1563
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
1564
+ } catch {
1565
+ }
1566
+ await client.executeMultiple(`
1567
+ CREATE TABLE IF NOT EXISTS memories (
1568
+ id TEXT PRIMARY KEY,
1569
+ agent_id TEXT NOT NULL,
1570
+ agent_role TEXT NOT NULL,
1571
+ session_id TEXT NOT NULL,
1572
+ timestamp TEXT NOT NULL,
1573
+ tool_name TEXT NOT NULL,
1574
+ project_name TEXT NOT NULL,
1575
+ has_error INTEGER NOT NULL DEFAULT 0,
1576
+ raw_text TEXT NOT NULL,
1577
+ vector F32_BLOB(1024),
1578
+ version INTEGER NOT NULL DEFAULT 0
1579
+ );
1580
+
1581
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
1582
+ ON memories(agent_id);
1583
+
1584
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
1585
+ ON memories(timestamp);
1586
+
1587
+ CREATE INDEX IF NOT EXISTS idx_memories_session
1588
+ ON memories(session_id);
1589
+
1590
+ CREATE INDEX IF NOT EXISTS idx_memories_project
1591
+ ON memories(project_name);
1592
+
1593
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
1594
+ ON memories(tool_name);
1595
+
1596
+ CREATE INDEX IF NOT EXISTS idx_memories_version
1597
+ ON memories(version);
1598
+
1599
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
1600
+ ON memories(agent_id, project_name);
1601
+ `);
1602
+ await client.executeMultiple(`
1603
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1604
+ raw_text,
1605
+ content='memories',
1606
+ content_rowid='rowid'
1607
+ );
1608
+
1609
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1610
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1611
+ END;
1612
+
1613
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
1614
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1615
+ END;
1616
+
1617
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1618
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1619
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1620
+ END;
1621
+ `);
1622
+ await client.executeMultiple(`
1623
+ CREATE TABLE IF NOT EXISTS sync_meta (
1624
+ key TEXT PRIMARY KEY,
1625
+ value TEXT NOT NULL
1626
+ );
1627
+ `);
1628
+ await client.executeMultiple(`
1629
+ CREATE TABLE IF NOT EXISTS tasks (
1630
+ id TEXT PRIMARY KEY,
1631
+ title TEXT NOT NULL,
1632
+ assigned_to TEXT NOT NULL,
1633
+ assigned_by TEXT NOT NULL,
1634
+ project_name TEXT NOT NULL,
1635
+ priority TEXT NOT NULL DEFAULT 'p1',
1636
+ status TEXT NOT NULL DEFAULT 'open',
1637
+ task_file TEXT,
1638
+ created_at TEXT NOT NULL,
1639
+ updated_at TEXT NOT NULL
1640
+ );
1641
+
1642
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
1643
+ ON tasks(assigned_to, status);
1644
+ `);
1645
+ await client.executeMultiple(`
1646
+ CREATE TABLE IF NOT EXISTS behaviors (
1647
+ id TEXT PRIMARY KEY,
1648
+ agent_id TEXT NOT NULL,
1649
+ project_name TEXT,
1650
+ domain TEXT,
1651
+ content TEXT NOT NULL,
1652
+ active INTEGER NOT NULL DEFAULT 1,
1653
+ created_at TEXT NOT NULL,
1654
+ updated_at TEXT NOT NULL
1655
+ );
1656
+
1657
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
1658
+ ON behaviors(agent_id, active);
1659
+ `);
1660
+ try {
1661
+ const coordinatorName = getCoordinatorName();
1662
+ const existing = await client.execute({
1663
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1664
+ args: [coordinatorName]
1044
1665
  });
1666
+ if (Number(existing.rows[0]?.cnt) === 0) {
1667
+ const seededAt = "2026-03-25T00:00:00Z";
1668
+ for (const [domain, content] of [
1669
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1670
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1671
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1672
+ ]) {
1673
+ await client.execute({
1674
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1675
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1676
+ args: [coordinatorName, domain, content, seededAt, seededAt]
1677
+ });
1678
+ }
1679
+ }
1045
1680
  } catch {
1046
1681
  }
1047
1682
  try {
1048
1683
  await client.execute({
1049
- sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1684
+ sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1050
1685
  args: []
1051
1686
  });
1052
1687
  } catch {
1053
1688
  }
1054
1689
  try {
1055
1690
  await client.execute({
1056
- sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1691
+ sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1057
1692
  args: []
1058
1693
  });
1059
1694
  } catch {
1060
1695
  }
1061
1696
  try {
1062
1697
  await client.execute({
1063
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1698
+ sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1064
1699
  args: []
1065
1700
  });
1066
1701
  } catch {
1067
1702
  }
1068
1703
  try {
1069
1704
  await client.execute({
1070
- sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1705
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1706
+ ON tasks(parent_task_id)
1707
+ WHERE parent_task_id IS NOT NULL`,
1708
+ args: []
1709
+ });
1710
+ } catch {
1711
+ }
1712
+ try {
1713
+ await client.execute({
1714
+ sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1715
+ args: []
1716
+ });
1717
+ } catch {
1718
+ }
1719
+ try {
1720
+ await client.execute({
1721
+ sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1722
+ args: []
1723
+ });
1724
+ } catch {
1725
+ }
1726
+ try {
1727
+ await client.execute({
1728
+ sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1729
+ args: []
1730
+ });
1731
+ } catch {
1732
+ }
1733
+ try {
1734
+ await client.execute({
1735
+ sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1736
+ args: []
1737
+ });
1738
+ } catch {
1739
+ }
1740
+ try {
1741
+ await client.execute({
1742
+ sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1743
+ args: []
1744
+ });
1745
+ } catch {
1746
+ }
1747
+ try {
1748
+ await client.execute({
1749
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1750
+ args: []
1751
+ });
1752
+ } catch {
1753
+ }
1754
+ try {
1755
+ await client.execute({
1756
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1757
+ args: []
1758
+ });
1759
+ } catch {
1760
+ }
1761
+ try {
1762
+ await client.execute({
1763
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1764
+ args: []
1765
+ });
1766
+ } catch {
1767
+ }
1768
+ try {
1769
+ await client.execute({
1770
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1771
+ args: []
1772
+ });
1773
+ } catch {
1774
+ }
1775
+ try {
1776
+ await client.execute({
1777
+ sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1778
+ args: []
1779
+ });
1780
+ } catch {
1781
+ }
1782
+ try {
1783
+ await client.execute({
1784
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1785
+ args: []
1786
+ });
1787
+ } catch {
1788
+ }
1789
+ try {
1790
+ await client.execute({
1791
+ sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1071
1792
  args: []
1072
1793
  });
1073
1794
  } catch {
@@ -1313,6 +2034,12 @@ async function ensureSchema() {
1313
2034
  } catch {
1314
2035
  }
1315
2036
  }
2037
+ try {
2038
+ await client.execute(
2039
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
2040
+ );
2041
+ } catch {
2042
+ }
1316
2043
  await client.executeMultiple(`
1317
2044
  CREATE TABLE IF NOT EXISTS entities (
1318
2045
  id TEXT PRIMARY KEY,
@@ -1365,7 +2092,30 @@ async function ensureSchema() {
1365
2092
  entity_id TEXT NOT NULL,
1366
2093
  PRIMARY KEY (hyperedge_id, entity_id)
1367
2094
  );
2095
+
2096
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
2097
+ name,
2098
+ content=entities,
2099
+ content_rowid=rowid
2100
+ );
2101
+
2102
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
2103
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2104
+ END;
2105
+
2106
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
2107
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2108
+ END;
2109
+
2110
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
2111
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2112
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2113
+ END;
1368
2114
  `);
2115
+ try {
2116
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
2117
+ } catch {
2118
+ }
1369
2119
  await client.executeMultiple(`
1370
2120
  CREATE TABLE IF NOT EXISTS entity_aliases (
1371
2121
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1546,6 +2296,33 @@ async function ensureSchema() {
1546
2296
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1547
2297
  ON conversations(channel_id);
1548
2298
  `);
2299
+ await client.executeMultiple(`
2300
+ CREATE TABLE IF NOT EXISTS session_agent_map (
2301
+ session_uuid TEXT PRIMARY KEY,
2302
+ agent_id TEXT NOT NULL,
2303
+ session_name TEXT,
2304
+ task_id TEXT,
2305
+ project_name TEXT,
2306
+ started_at TEXT NOT NULL
2307
+ );
2308
+
2309
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2310
+ ON session_agent_map(agent_id);
2311
+ `);
2312
+ try {
2313
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2314
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
2315
+ await client.execute({
2316
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
2317
+ SELECT session_id, agent_id, '', MIN(timestamp)
2318
+ FROM memories
2319
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
2320
+ GROUP BY session_id, agent_id`,
2321
+ args: []
2322
+ });
2323
+ }
2324
+ } catch {
2325
+ }
1549
2326
  try {
1550
2327
  await client.execute({
1551
2328
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1679,15 +2456,41 @@ async function ensureSchema() {
1679
2456
  });
1680
2457
  } catch {
1681
2458
  }
2459
+ for (const col of [
2460
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2461
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2462
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2463
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2464
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2465
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2466
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2467
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2468
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2469
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2470
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2471
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2472
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2473
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2474
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2475
+ ]) {
2476
+ try {
2477
+ await client.execute(col);
2478
+ } catch {
2479
+ }
2480
+ }
1682
2481
  }
1683
2482
  async function disposeDatabase() {
2483
+ if (_daemonClient) {
2484
+ _daemonClient.close();
2485
+ _daemonClient = null;
2486
+ }
1684
2487
  if (_client) {
1685
2488
  _client.close();
1686
2489
  _client = null;
1687
2490
  _resilientClient = null;
1688
2491
  }
1689
2492
  }
1690
- var _client, _resilientClient, initTurso, disposeTurso;
2493
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1691
2494
  var init_database = __esm({
1692
2495
  "src/lib/database.ts"() {
1693
2496
  "use strict";
@@ -1695,24 +2498,25 @@ var init_database = __esm({
1695
2498
  init_employees();
1696
2499
  _client = null;
1697
2500
  _resilientClient = null;
2501
+ _daemonClient = null;
1698
2502
  initTurso = initDatabase;
1699
2503
  disposeTurso = disposeDatabase;
1700
2504
  }
1701
2505
  });
1702
2506
 
1703
2507
  // src/lib/license.ts
1704
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1705
- import { randomUUID } from "crypto";
1706
- import path5 from "path";
2508
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2509
+ import { randomUUID as randomUUID2 } from "crypto";
2510
+ import path6 from "path";
1707
2511
  import { jwtVerify, importSPKI } from "jose";
1708
2512
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1709
2513
  var init_license = __esm({
1710
2514
  "src/lib/license.ts"() {
1711
2515
  "use strict";
1712
2516
  init_config();
1713
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
1714
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
1715
- DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
2517
+ LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
2518
+ CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
2519
+ DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
1716
2520
  PLAN_LIMITS = {
1717
2521
  free: { devices: 1, employees: 1, memories: 5e3 },
1718
2522
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1724,12 +2528,12 @@ var init_license = __esm({
1724
2528
  });
1725
2529
 
1726
2530
  // src/lib/plan-limits.ts
1727
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1728
- import path6 from "path";
2531
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2532
+ import path7 from "path";
1729
2533
  function getLicenseSync() {
1730
2534
  try {
1731
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
1732
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
2535
+ if (!existsSync7(CACHE_PATH2)) return freeLicense();
2536
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
1733
2537
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1734
2538
  const parts = raw.token.split(".");
1735
2539
  if (parts.length !== 3) return freeLicense();
@@ -1767,8 +2571,8 @@ function assertEmployeeLimitSync(rosterPath) {
1767
2571
  const filePath = rosterPath ?? EMPLOYEES_PATH;
1768
2572
  let count = 0;
1769
2573
  try {
1770
- if (existsSync6(filePath)) {
1771
- const raw = readFileSync6(filePath, "utf8");
2574
+ if (existsSync7(filePath)) {
2575
+ const raw = readFileSync7(filePath, "utf8");
1772
2576
  const employees = JSON.parse(raw);
1773
2577
  count = Array.isArray(employees) ? employees.length : 0;
1774
2578
  }
@@ -1797,19 +2601,19 @@ var init_plan_limits = __esm({
1797
2601
  this.name = "PlanLimitError";
1798
2602
  }
1799
2603
  };
1800
- CACHE_PATH2 = path6.join(EXE_AI_DIR, "license-cache.json");
2604
+ CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
1801
2605
  }
1802
2606
  });
1803
2607
 
1804
2608
  // src/lib/notifications.ts
1805
2609
  import crypto from "crypto";
1806
- import path7 from "path";
2610
+ import path8 from "path";
1807
2611
  import os5 from "os";
1808
2612
  import {
1809
- readFileSync as readFileSync7,
2613
+ readFileSync as readFileSync8,
1810
2614
  readdirSync,
1811
- unlinkSync as unlinkSync2,
1812
- existsSync as existsSync7,
2615
+ unlinkSync as unlinkSync3,
2616
+ existsSync as existsSync8,
1813
2617
  rmdirSync
1814
2618
  } from "fs";
1815
2619
  async function writeNotification(notification) {
@@ -1958,6 +2762,11 @@ var init_session_kill_telemetry = __esm({
1958
2762
  });
1959
2763
 
1960
2764
  // src/lib/task-scope.ts
2765
+ var task_scope_exports = {};
2766
+ __export(task_scope_exports, {
2767
+ getCurrentSessionScope: () => getCurrentSessionScope,
2768
+ sessionScopeFilter: () => sessionScopeFilter
2769
+ });
1961
2770
  function getCurrentSessionScope() {
1962
2771
  try {
1963
2772
  return resolveExeSession();
@@ -2038,10 +2847,11 @@ var init_state_bus = __esm({
2038
2847
 
2039
2848
  // src/lib/tasks-crud.ts
2040
2849
  import crypto3 from "crypto";
2041
- import path8 from "path";
2850
+ import path9 from "path";
2851
+ import os6 from "os";
2042
2852
  import { execSync as execSync5 } from "child_process";
2043
2853
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2044
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
2854
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2045
2855
  async function writeCheckpoint(input) {
2046
2856
  const client = getClient();
2047
2857
  const row = await resolveTask(client, input.taskId);
@@ -2082,6 +2892,35 @@ function extractParentFromContext(contextBody) {
2082
2892
  function slugify(title) {
2083
2893
  return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2084
2894
  }
2895
+ function buildKeywordIndex() {
2896
+ const idx = /* @__PURE__ */ new Map();
2897
+ for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
2898
+ for (const kw of keywords) {
2899
+ const existing = idx.get(kw) ?? [];
2900
+ existing.push(role);
2901
+ idx.set(kw, existing);
2902
+ }
2903
+ }
2904
+ return idx;
2905
+ }
2906
+ function checkLaneAffinity(title, context, assigneeName) {
2907
+ const employees = loadEmployeesSync();
2908
+ const employee = employees.find((e) => e.name === assigneeName);
2909
+ if (!employee) return void 0;
2910
+ const assigneeRole = employee.role;
2911
+ const text = `${title} ${context}`.toLowerCase();
2912
+ const matchedRoles = /* @__PURE__ */ new Set();
2913
+ for (const [keyword, roles] of KEYWORD_INDEX) {
2914
+ if (text.includes(keyword)) {
2915
+ for (const role of roles) matchedRoles.add(role);
2916
+ }
2917
+ }
2918
+ if (matchedRoles.size === 0) return void 0;
2919
+ if (matchedRoles.has(assigneeRole)) return void 0;
2920
+ if (assigneeRole === "COO") return void 0;
2921
+ const expectedRoles = Array.from(matchedRoles).join(" or ");
2922
+ return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
2923
+ }
2085
2924
  async function resolveTask(client, identifier, scopeSession) {
2086
2925
  const scope = sessionScopeFilter(scopeSession);
2087
2926
  let result = await client.execute({
@@ -2131,7 +2970,14 @@ async function createTaskCore(input) {
2131
2970
  const id = crypto3.randomUUID();
2132
2971
  const now = (/* @__PURE__ */ new Date()).toISOString();
2133
2972
  const slug = slugify(input.title);
2134
- const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
2973
+ let earlySessionScope = null;
2974
+ try {
2975
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2976
+ earlySessionScope = resolveExeSession2();
2977
+ } catch {
2978
+ }
2979
+ const scope = earlySessionScope ?? "default";
2980
+ const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
2135
2981
  let blockedById = null;
2136
2982
  const initialStatus = input.blockedBy ? "blocked" : "open";
2137
2983
  if (input.blockedBy) {
@@ -2171,22 +3017,24 @@ async function createTaskCore(input) {
2171
3017
  if (dupCheck.rows.length > 0) {
2172
3018
  warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
2173
3019
  }
3020
+ if (!process.env.DISABLE_LANE_AFFINITY) {
3021
+ const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
3022
+ if (laneWarning) {
3023
+ warning = warning ? `${warning}
3024
+ ${laneWarning}` : laneWarning;
3025
+ }
3026
+ }
2174
3027
  if (input.baseDir) {
2175
3028
  try {
2176
- await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
2177
- await mkdir3(path8.join(input.baseDir, "exe", "research"), { recursive: true });
3029
+ await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
3030
+ await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2178
3031
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2179
3032
  await ensureGitignoreExe(input.baseDir);
2180
3033
  } catch {
2181
3034
  }
2182
3035
  }
2183
3036
  const complexity = input.complexity ?? "standard";
2184
- let sessionScope = null;
2185
- try {
2186
- const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2187
- sessionScope = resolveExeSession2();
2188
- } catch {
2189
- }
3037
+ const sessionScope = earlySessionScope;
2190
3038
  await client.execute({
2191
3039
  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)
2192
3040
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -2213,6 +3061,39 @@ async function createTaskCore(input) {
2213
3061
  now
2214
3062
  ]
2215
3063
  });
3064
+ if (input.baseDir) {
3065
+ try {
3066
+ const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
3067
+ const mdPath = path9.join(EXE_OS_DIR, taskFile);
3068
+ const mdDir = path9.dirname(mdPath);
3069
+ if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
3070
+ const reviewer = input.reviewer ?? input.assignedBy;
3071
+ const mdContent = `# ${input.title}
3072
+
3073
+ **ID:** ${id}
3074
+ **Status:** ${initialStatus}
3075
+ **Priority:** ${input.priority}
3076
+ **Assigned by:** ${input.assignedBy}
3077
+ **Assigned to:** ${input.assignedTo}
3078
+ **Project:** ${input.projectName}
3079
+ **Created:** ${now.split("T")[0]}${parentTaskId ? `
3080
+ **Parent task:** ${parentTaskId}` : ""}
3081
+ **Reviewer:** ${reviewer}
3082
+
3083
+ ## Context
3084
+
3085
+ ${input.context}
3086
+
3087
+ ## MANDATORY: When done
3088
+
3089
+ You MUST call update_task with status "done" and a result summary when finished.
3090
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3091
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3092
+ `;
3093
+ await writeFile3(mdPath, mdContent, "utf-8");
3094
+ } catch {
3095
+ }
3096
+ }
2216
3097
  return {
2217
3098
  id,
2218
3099
  title: input.title,
@@ -2405,7 +3286,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2405
3286
  return { row, taskFile, now, taskId };
2406
3287
  }
2407
3288
  }
2408
- if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
3289
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
2409
3290
  process.stderr.write(
2410
3291
  `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2411
3292
  `
@@ -2470,9 +3351,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2470
3351
  return { taskFile, assignedTo, assignedBy, taskSlug };
2471
3352
  }
2472
3353
  async function ensureArchitectureDoc(baseDir, projectName) {
2473
- const archPath = path8.join(baseDir, "exe", "ARCHITECTURE.md");
3354
+ const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
2474
3355
  try {
2475
- if (existsSync8(archPath)) return;
3356
+ if (existsSync9(archPath)) return;
2476
3357
  const template = [
2477
3358
  `# ${projectName} \u2014 System Architecture`,
2478
3359
  "",
@@ -2505,10 +3386,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2505
3386
  }
2506
3387
  }
2507
3388
  async function ensureGitignoreExe(baseDir) {
2508
- const gitignorePath = path8.join(baseDir, ".gitignore");
3389
+ const gitignorePath = path9.join(baseDir, ".gitignore");
2509
3390
  try {
2510
- if (existsSync8(gitignorePath)) {
2511
- const content = readFileSync8(gitignorePath, "utf-8");
3391
+ if (existsSync9(gitignorePath)) {
3392
+ const content = readFileSync9(gitignorePath, "utf-8");
2512
3393
  if (/^\/?exe\/?$/m.test(content)) return;
2513
3394
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2514
3395
  } else {
@@ -2517,12 +3398,22 @@ async function ensureGitignoreExe(baseDir) {
2517
3398
  } catch {
2518
3399
  }
2519
3400
  }
2520
- var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
3401
+ var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2521
3402
  var init_tasks_crud = __esm({
2522
3403
  "src/lib/tasks-crud.ts"() {
2523
3404
  "use strict";
2524
3405
  init_database();
2525
3406
  init_task_scope();
3407
+ init_employees();
3408
+ LANE_KEYWORDS = {
3409
+ CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
3410
+ CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
3411
+ "Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
3412
+ "Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
3413
+ "Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
3414
+ "AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
3415
+ };
3416
+ KEYWORD_INDEX = buildKeywordIndex();
2526
3417
  DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2527
3418
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2528
3419
  }
@@ -2539,8 +3430,8 @@ __export(tasks_review_exports, {
2539
3430
  getReviewChecklist: () => getReviewChecklist,
2540
3431
  listPendingReviews: () => listPendingReviews
2541
3432
  });
2542
- import path9 from "path";
2543
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3433
+ import path10 from "path";
3434
+ import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2544
3435
  async function countPendingReviews(sessionScope) {
2545
3436
  const client = getClient();
2546
3437
  if (sessionScope) {
@@ -2562,7 +3453,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
2562
3453
  const result2 = await client.execute({
2563
3454
  sql: `SELECT COUNT(*) as cnt FROM tasks
2564
3455
  WHERE status = 'needs_review' AND updated_at > ?
2565
- AND (session_scope = ? OR session_scope IS NULL)`,
3456
+ AND session_scope = ?`,
2566
3457
  args: [sinceIso, sessionScope]
2567
3458
  });
2568
3459
  return Number(result2.rows[0]?.cnt) || 0;
@@ -2580,7 +3471,7 @@ async function listPendingReviews(limit, sessionScope) {
2580
3471
  const result2 = await client.execute({
2581
3472
  sql: `SELECT title, assigned_to, project_name FROM tasks
2582
3473
  WHERE status = 'needs_review'
2583
- AND (session_scope = ? OR session_scope IS NULL)
3474
+ AND session_scope = ?
2584
3475
  ORDER BY priority ASC, created_at DESC LIMIT ?`,
2585
3476
  args: [sessionScope, limit]
2586
3477
  });
@@ -2681,7 +3572,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
2681
3572
  const taskFile = String(row.task_file);
2682
3573
  const employees = await loadEmployees();
2683
3574
  const coordinatorName = getCoordinatorName(employees);
2684
- if (String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to), employees)) return;
3575
+ if (isCoordinatorName(String(row.assigned_to), employees)) return;
2685
3576
  if (String(row.title).startsWith("Review:")) return;
2686
3577
  const fileName = taskFile.split("/").pop() ?? "";
2687
3578
  if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
@@ -2790,14 +3681,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2790
3681
  if (parts.length >= 3 && parts[0] === "review") {
2791
3682
  const agent = parts[1];
2792
3683
  const slug = parts.slice(2).join("-");
2793
- const originalTaskFile = `exe/${agent}/${slug}.md`;
3684
+ const legacyTaskFile = `exe/${agent}/${slug}.md`;
2794
3685
  const result = await client.execute({
2795
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
2796
- args: [now, originalTaskFile]
3686
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
3687
+ args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
2797
3688
  });
2798
3689
  if (result.rowsAffected > 0) {
2799
3690
  process.stderr.write(
2800
- `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
3691
+ `[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
2801
3692
  `
2802
3693
  );
2803
3694
  }
@@ -2810,11 +3701,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2810
3701
  );
2811
3702
  }
2812
3703
  try {
2813
- const cacheDir = path9.join(EXE_AI_DIR, "session-cache");
2814
- if (existsSync9(cacheDir)) {
3704
+ const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
3705
+ if (existsSync10(cacheDir)) {
2815
3706
  for (const f of readdirSync2(cacheDir)) {
2816
3707
  if (f.startsWith("review-notified-")) {
2817
- unlinkSync3(path9.join(cacheDir, f));
3708
+ unlinkSync4(path10.join(cacheDir, f));
2818
3709
  }
2819
3710
  }
2820
3711
  }
@@ -2835,7 +3726,7 @@ var init_tasks_review = __esm({
2835
3726
  });
2836
3727
 
2837
3728
  // src/lib/tasks-chain.ts
2838
- import path10 from "path";
3729
+ import path11 from "path";
2839
3730
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2840
3731
  async function cascadeUnblock(taskId, baseDir, now) {
2841
3732
  const client = getClient();
@@ -2852,7 +3743,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
2852
3743
  });
2853
3744
  for (const ur of unblockedRows.rows) {
2854
3745
  try {
2855
- const ubFile = path10.join(baseDir, String(ur.task_file));
3746
+ const ubFile = path11.join(baseDir, String(ur.task_file));
2856
3747
  let ubContent = await readFile3(ubFile, "utf-8");
2857
3748
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
2858
3749
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -2921,7 +3812,7 @@ var init_tasks_chain = __esm({
2921
3812
 
2922
3813
  // src/lib/project-name.ts
2923
3814
  import { execSync as execSync6 } from "child_process";
2924
- import path11 from "path";
3815
+ import path12 from "path";
2925
3816
  function getProjectName(cwd) {
2926
3817
  const dir = cwd ?? process.cwd();
2927
3818
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -2934,7 +3825,7 @@ function getProjectName(cwd) {
2934
3825
  timeout: 2e3,
2935
3826
  stdio: ["pipe", "pipe", "pipe"]
2936
3827
  }).trim();
2937
- repoRoot = path11.dirname(gitCommonDir);
3828
+ repoRoot = path12.dirname(gitCommonDir);
2938
3829
  } catch {
2939
3830
  repoRoot = execSync6("git rev-parse --show-toplevel", {
2940
3831
  cwd: dir,
@@ -2943,11 +3834,11 @@ function getProjectName(cwd) {
2943
3834
  stdio: ["pipe", "pipe", "pipe"]
2944
3835
  }).trim();
2945
3836
  }
2946
- _cached2 = path11.basename(repoRoot);
3837
+ _cached2 = path12.basename(repoRoot);
2947
3838
  _cachedCwd = dir;
2948
3839
  return _cached2;
2949
3840
  } catch {
2950
- _cached2 = path11.basename(dir);
3841
+ _cached2 = path12.basename(dir);
2951
3842
  _cachedCwd = dir;
2952
3843
  return _cached2;
2953
3844
  }
@@ -2979,7 +3870,7 @@ function findSessionForProject(projectName) {
2979
3870
  const sessions = listSessions();
2980
3871
  for (const s of sessions) {
2981
3872
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2982
- if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3873
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
2983
3874
  }
2984
3875
  return null;
2985
3876
  }
@@ -3025,7 +3916,7 @@ var init_session_scope = __esm({
3025
3916
 
3026
3917
  // src/lib/tasks-notify.ts
3027
3918
  async function dispatchTaskToEmployee(input) {
3028
- if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3919
+ if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3029
3920
  let crossProject = false;
3030
3921
  if (input.projectName) {
3031
3922
  try {
@@ -3420,8 +4311,8 @@ __export(tasks_exports, {
3420
4311
  updateTaskStatus: () => updateTaskStatus,
3421
4312
  writeCheckpoint: () => writeCheckpoint
3422
4313
  });
3423
- import path12 from "path";
3424
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
4314
+ import path13 from "path";
4315
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
3425
4316
  async function createTask(input) {
3426
4317
  const result = await createTaskCore(input);
3427
4318
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3440,14 +4331,14 @@ async function updateTask(input) {
3440
4331
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3441
4332
  try {
3442
4333
  const agent = String(row.assigned_to);
3443
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3444
- const cachePath = path12.join(cacheDir, `current-task-${agent}.json`);
4334
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4335
+ const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3445
4336
  if (input.status === "in_progress") {
3446
4337
  mkdirSync4(cacheDir, { recursive: true });
3447
4338
  writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3448
4339
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3449
4340
  try {
3450
- unlinkSync4(cachePath);
4341
+ unlinkSync5(cachePath);
3451
4342
  } catch {
3452
4343
  }
3453
4344
  }
@@ -3504,7 +4395,7 @@ async function updateTask(input) {
3504
4395
  }
3505
4396
  const isTerminal = input.status === "done" || input.status === "needs_review";
3506
4397
  if (isTerminal) {
3507
- const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
4398
+ const isCoordinator = isCoordinatorName(String(row.assigned_to));
3508
4399
  if (!isCoordinator) {
3509
4400
  notifyTaskDone();
3510
4401
  }
@@ -3529,7 +4420,7 @@ async function updateTask(input) {
3529
4420
  }
3530
4421
  }
3531
4422
  }
3532
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4423
+ if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3533
4424
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3534
4425
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3535
4426
  taskId,
@@ -3545,7 +4436,7 @@ async function updateTask(input) {
3545
4436
  });
3546
4437
  }
3547
4438
  let nextTask;
3548
- if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
4439
+ if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
3549
4440
  try {
3550
4441
  nextTask = await findNextTask(String(row.assigned_to));
3551
4442
  } catch {
@@ -3889,7 +4780,7 @@ var init_capacity_monitor = __esm({
3889
4780
  // src/lib/tmux-routing.ts
3890
4781
  var tmux_routing_exports = {};
3891
4782
  __export(tmux_routing_exports, {
3892
- acquireSpawnLock: () => acquireSpawnLock,
4783
+ acquireSpawnLock: () => acquireSpawnLock2,
3893
4784
  employeeSessionName: () => employeeSessionName,
3894
4785
  ensureEmployee: () => ensureEmployee,
3895
4786
  extractRootExe: () => extractRootExe,
@@ -3904,20 +4795,20 @@ __export(tmux_routing_exports, {
3904
4795
  notifyParentExe: () => notifyParentExe,
3905
4796
  parseParentExe: () => parseParentExe,
3906
4797
  registerParentExe: () => registerParentExe,
3907
- releaseSpawnLock: () => releaseSpawnLock,
4798
+ releaseSpawnLock: () => releaseSpawnLock2,
3908
4799
  resolveExeSession: () => resolveExeSession,
3909
4800
  sendIntercom: () => sendIntercom,
3910
4801
  spawnEmployee: () => spawnEmployee,
3911
4802
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3912
4803
  });
3913
4804
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
3914
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3915
- import path13 from "path";
3916
- import os6 from "os";
3917
- import { fileURLToPath } from "url";
3918
- import { unlinkSync as unlinkSync5 } from "fs";
4805
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4806
+ import path14 from "path";
4807
+ import os7 from "os";
4808
+ import { fileURLToPath as fileURLToPath2 } from "url";
4809
+ import { unlinkSync as unlinkSync6 } from "fs";
3919
4810
  function spawnLockPath(sessionName) {
3920
- return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4811
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3921
4812
  }
3922
4813
  function isProcessAlive(pid) {
3923
4814
  try {
@@ -3927,14 +4818,14 @@ function isProcessAlive(pid) {
3927
4818
  return false;
3928
4819
  }
3929
4820
  }
3930
- function acquireSpawnLock(sessionName) {
3931
- if (!existsSync10(SPAWN_LOCK_DIR)) {
4821
+ function acquireSpawnLock2(sessionName) {
4822
+ if (!existsSync11(SPAWN_LOCK_DIR)) {
3932
4823
  mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
3933
4824
  }
3934
4825
  const lockFile = spawnLockPath(sessionName);
3935
- if (existsSync10(lockFile)) {
4826
+ if (existsSync11(lockFile)) {
3936
4827
  try {
3937
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
4828
+ const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
3938
4829
  const age = Date.now() - lock.timestamp;
3939
4830
  if (isProcessAlive(lock.pid) && age < 6e4) {
3940
4831
  return false;
@@ -3945,22 +4836,22 @@ function acquireSpawnLock(sessionName) {
3945
4836
  writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3946
4837
  return true;
3947
4838
  }
3948
- function releaseSpawnLock(sessionName) {
4839
+ function releaseSpawnLock2(sessionName) {
3949
4840
  try {
3950
- unlinkSync5(spawnLockPath(sessionName));
4841
+ unlinkSync6(spawnLockPath(sessionName));
3951
4842
  } catch {
3952
4843
  }
3953
4844
  }
3954
4845
  function resolveBehaviorsExporterScript() {
3955
4846
  try {
3956
- const thisFile = fileURLToPath(import.meta.url);
3957
- const scriptPath = path13.join(
3958
- path13.dirname(thisFile),
4847
+ const thisFile = fileURLToPath2(import.meta.url);
4848
+ const scriptPath = path14.join(
4849
+ path14.dirname(thisFile),
3959
4850
  "..",
3960
4851
  "bin",
3961
4852
  "exe-export-behaviors.js"
3962
4853
  );
3963
- return existsSync10(scriptPath) ? scriptPath : null;
4854
+ return existsSync11(scriptPath) ? scriptPath : null;
3964
4855
  } catch {
3965
4856
  return null;
3966
4857
  }
@@ -4026,11 +4917,11 @@ function extractRootExe(name) {
4026
4917
  return parts.length > 0 ? parts[parts.length - 1] : null;
4027
4918
  }
4028
4919
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4029
- if (!existsSync10(SESSION_CACHE)) {
4920
+ if (!existsSync11(SESSION_CACHE)) {
4030
4921
  mkdirSync5(SESSION_CACHE, { recursive: true });
4031
4922
  }
4032
4923
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4033
- const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4924
+ const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4034
4925
  writeFileSync6(filePath, JSON.stringify({
4035
4926
  parentExe: rootExe,
4036
4927
  dispatchedBy: dispatchedBy || rootExe,
@@ -4039,7 +4930,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4039
4930
  }
4040
4931
  function getParentExe(sessionKey) {
4041
4932
  try {
4042
- const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4933
+ const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4043
4934
  return data.parentExe || null;
4044
4935
  } catch {
4045
4936
  return null;
@@ -4047,8 +4938,8 @@ function getParentExe(sessionKey) {
4047
4938
  }
4048
4939
  function getDispatchedBy(sessionKey) {
4049
4940
  try {
4050
- const data = JSON.parse(readFileSync9(
4051
- path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4941
+ const data = JSON.parse(readFileSync10(
4942
+ path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4052
4943
  "utf8"
4053
4944
  ));
4054
4945
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4074,10 +4965,10 @@ function isEmployeeAlive(sessionName) {
4074
4965
  }
4075
4966
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
4076
4967
  const base = employeeSessionName(employeeName, exeSession);
4077
- if (!isAlive(base) && acquireSpawnLock(base)) return 0;
4968
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
4078
4969
  for (let i = 2; i <= maxInstances; i++) {
4079
4970
  const candidate = employeeSessionName(employeeName, exeSession, i);
4080
- if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
4971
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
4081
4972
  }
4082
4973
  return null;
4083
4974
  }
@@ -4109,15 +5000,15 @@ async function verifyPaneAtCapacity(sessionName) {
4109
5000
  }
4110
5001
  function readDebounceState() {
4111
5002
  try {
4112
- if (!existsSync10(DEBOUNCE_FILE)) return {};
4113
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
5003
+ if (!existsSync11(DEBOUNCE_FILE)) return {};
5004
+ return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4114
5005
  } catch {
4115
5006
  return {};
4116
5007
  }
4117
5008
  }
4118
5009
  function writeDebounceState(state) {
4119
5010
  try {
4120
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
5011
+ if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4121
5012
  writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4122
5013
  } catch {
4123
5014
  }
@@ -4237,7 +5128,7 @@ function notifyParentExe(sessionKey) {
4237
5128
  return true;
4238
5129
  }
4239
5130
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4240
- if (employeeName === "exe" || isCoordinatorName(employeeName)) {
5131
+ if (isCoordinatorName(employeeName)) {
4241
5132
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
4242
5133
  }
4243
5134
  try {
@@ -4309,26 +5200,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4309
5200
  const transport = getTransport();
4310
5201
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4311
5202
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4312
- const logDir = path13.join(os6.homedir(), ".exe-os", "session-logs");
4313
- const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4314
- if (!existsSync10(logDir)) {
5203
+ const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
5204
+ const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5205
+ if (!existsSync11(logDir)) {
4315
5206
  mkdirSync5(logDir, { recursive: true });
4316
5207
  }
4317
5208
  transport.kill(sessionName);
4318
5209
  let cleanupSuffix = "";
4319
5210
  try {
4320
- const thisFile = fileURLToPath(import.meta.url);
4321
- const cleanupScript = path13.join(path13.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4322
- if (existsSync10(cleanupScript)) {
5211
+ const thisFile = fileURLToPath2(import.meta.url);
5212
+ const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5213
+ if (existsSync11(cleanupScript)) {
4323
5214
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4324
5215
  }
4325
5216
  } catch {
4326
5217
  }
4327
5218
  try {
4328
- const claudeJsonPath = path13.join(os6.homedir(), ".claude.json");
5219
+ const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
4329
5220
  let claudeJson = {};
4330
5221
  try {
4331
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
5222
+ claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
4332
5223
  } catch {
4333
5224
  }
4334
5225
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4340,13 +5231,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4340
5231
  } catch {
4341
5232
  }
4342
5233
  try {
4343
- const settingsDir = path13.join(os6.homedir(), ".claude", "projects");
5234
+ const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
4344
5235
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4345
- const projSettingsDir = path13.join(settingsDir, normalizedKey);
4346
- const settingsPath = path13.join(projSettingsDir, "settings.json");
5236
+ const projSettingsDir = path14.join(settingsDir, normalizedKey);
5237
+ const settingsPath = path14.join(projSettingsDir, "settings.json");
4347
5238
  let settings = {};
4348
5239
  try {
4349
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
5240
+ settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
4350
5241
  } catch {
4351
5242
  }
4352
5243
  const perms = settings.permissions ?? {};
@@ -4387,8 +5278,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4387
5278
  let behaviorsFlag = "";
4388
5279
  let legacyFallbackWarned = false;
4389
5280
  if (!useExeAgent && !useBinSymlink) {
4390
- const identityPath = path13.join(
4391
- os6.homedir(),
5281
+ const identityPath = path14.join(
5282
+ os7.homedir(),
4392
5283
  ".exe-os",
4393
5284
  "identity",
4394
5285
  `${employeeName}.md`
@@ -4397,13 +5288,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4397
5288
  const hasAgentFlag = claudeSupportsAgentFlag();
4398
5289
  if (hasAgentFlag) {
4399
5290
  identityFlag = ` --agent ${employeeName}`;
4400
- } else if (existsSync10(identityPath)) {
5291
+ } else if (existsSync11(identityPath)) {
4401
5292
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4402
5293
  legacyFallbackWarned = true;
4403
5294
  }
4404
5295
  const behaviorsFile = exportBehaviorsSync(
4405
5296
  employeeName,
4406
- path13.basename(spawnCwd),
5297
+ path14.basename(spawnCwd),
4407
5298
  sessionName
4408
5299
  );
4409
5300
  if (behaviorsFile) {
@@ -4418,9 +5309,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4418
5309
  }
4419
5310
  let sessionContextFlag = "";
4420
5311
  try {
4421
- const ctxDir = path13.join(os6.homedir(), ".exe-os", "session-cache");
5312
+ const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4422
5313
  mkdirSync5(ctxDir, { recursive: true });
4423
- const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
5314
+ const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4424
5315
  const ctxContent = [
4425
5316
  `## Session Context`,
4426
5317
  `You are running in tmux session: ${sessionName}.`,
@@ -4459,13 +5350,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4459
5350
  command: spawnCommand
4460
5351
  });
4461
5352
  if (spawnResult.error) {
4462
- releaseSpawnLock(sessionName);
5353
+ releaseSpawnLock2(sessionName);
4463
5354
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4464
5355
  }
4465
5356
  transport.pipeLog(sessionName, logFile);
4466
5357
  try {
4467
5358
  const mySession = getMySession();
4468
- const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5359
+ const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4469
5360
  writeFileSync6(dispatchInfo, JSON.stringify({
4470
5361
  dispatchedBy: mySession,
4471
5362
  rootExe: exeSession,
@@ -4497,7 +5388,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4497
5388
  }
4498
5389
  }
4499
5390
  if (!booted) {
4500
- releaseSpawnLock(sessionName);
5391
+ releaseSpawnLock2(sessionName);
4501
5392
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4502
5393
  }
4503
5394
  if (!useExeAgent) {
@@ -4514,7 +5405,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4514
5405
  pid: 0,
4515
5406
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4516
5407
  });
4517
- releaseSpawnLock(sessionName);
5408
+ releaseSpawnLock2(sessionName);
4518
5409
  return { sessionName };
4519
5410
  }
4520
5411
  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;
@@ -4530,14 +5421,14 @@ var init_tmux_routing = __esm({
4530
5421
  init_intercom_queue();
4531
5422
  init_plan_limits();
4532
5423
  init_employees();
4533
- SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
4534
- SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
5424
+ SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
5425
+ SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4535
5426
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4536
5427
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4537
5428
  VERIFY_PANE_LINES = 200;
4538
5429
  INTERCOM_DEBOUNCE_MS = 3e4;
4539
- INTERCOM_LOG2 = path13.join(os6.homedir(), ".exe-os", "intercom.log");
4540
- DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
5430
+ INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
5431
+ DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4541
5432
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4542
5433
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4543
5434
  }
@@ -4821,9 +5712,9 @@ __export(agent_signals_exports, {
4821
5712
  hasOpenTasks: () => hasOpenTasks,
4822
5713
  hasUnreadInbox: () => hasUnreadInbox
4823
5714
  });
4824
- import { readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
4825
- import os7 from "os";
4826
- import path14 from "path";
5715
+ import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
5716
+ import os8 from "os";
5717
+ import path15 from "path";
4827
5718
  async function hasOpenTasks(client, agentId) {
4828
5719
  try {
4829
5720
  const scope = sessionScopeFilter(null);
@@ -4865,10 +5756,10 @@ async function hasUnreadInbox(client, agentId) {
4865
5756
  return CONSERVATIVE_ON_ERROR;
4866
5757
  }
4867
5758
  }
4868
- function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path14.join(os7.homedir(), ".exe-os", "intercom.log")) {
4869
- if (!existsSync11(intercomLog)) return false;
5759
+ function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path15.join(os8.homedir(), ".exe-os", "intercom.log")) {
5760
+ if (!existsSync12(intercomLog)) return false;
4870
5761
  try {
4871
- const raw = readFileSync10(intercomLog, "utf8");
5762
+ const raw = readFileSync11(intercomLog, "utf8");
4872
5763
  const lines = raw.split("\n");
4873
5764
  for (let i = lines.length - 1; i >= 0; i--) {
4874
5765
  const line = lines[i];
@@ -4910,6 +5801,8 @@ var init_agent_signals = __esm({
4910
5801
  // src/lib/daemon-orchestration.ts
4911
5802
  var daemon_orchestration_exports = {};
4912
5803
  __export(daemon_orchestration_exports, {
5804
+ AUTO_WAKE_COOLDOWN_MS: () => AUTO_WAKE_COOLDOWN_MS,
5805
+ AUTO_WAKE_MAX_RETRIES: () => AUTO_WAKE_MAX_RETRIES,
4913
5806
  IDLE_KILL_INTERCOM_ACK_WINDOW_MS: () => IDLE_KILL_INTERCOM_ACK_WINDOW_MS,
4914
5807
  IDLE_NUDGE_DEDUP_MS: () => IDLE_NUDGE_DEDUP_MS,
4915
5808
  ORPHAN_PATTERNS: () => ORPHAN_PATTERNS,
@@ -4917,8 +5810,10 @@ __export(daemon_orchestration_exports, {
4917
5810
  REVIEW_NUDGE_COOLDOWN_MS: () => REVIEW_NUDGE_COOLDOWN_MS,
4918
5811
  SESSION_CONTEXT_THRESHOLD_PCT: () => SESSION_CONTEXT_THRESHOLD_PCT,
4919
5812
  SESSION_TTL_HOURS: () => SESSION_TTL_HOURS,
5813
+ _resetAutoWakeState: () => _resetAutoWakeState,
4920
5814
  checkSessionTTL: () => checkSessionTTL,
4921
5815
  classifyTtlKillReason: () => classifyTtlKillReason,
5816
+ createAutoWakeRealDeps: () => createAutoWakeRealDeps,
4922
5817
  createIdleKillRealDeps: () => createIdleKillRealDeps,
4923
5818
  createIdleNudgeRealDeps: () => createIdleNudgeRealDeps,
4924
5819
  createOrphanReaperRealDeps: () => createOrphanReaperRealDeps,
@@ -4927,15 +5822,17 @@ __export(daemon_orchestration_exports, {
4927
5822
  loadNudgeState: () => loadNudgeState,
4928
5823
  pollIdleEmployees: () => pollIdleEmployees,
4929
5824
  pollIdleKill: () => pollIdleKill,
5825
+ pollOrphanedTasks: () => pollOrphanedTasks,
4930
5826
  pollReviewNudge: () => pollReviewNudge,
4931
5827
  reapOrphanedMcpProcesses: () => reapOrphanedMcpProcesses,
4932
5828
  saveNudgeState: () => saveNudgeState,
5829
+ shouldAutoWake: () => shouldAutoWake,
4933
5830
  shouldKillIdleSession: () => shouldKillIdleSession,
4934
5831
  shouldKillSession: () => shouldKillSession,
4935
5832
  shouldNudgeEmployee: () => shouldNudgeEmployee
4936
5833
  });
4937
5834
  import { execSync as execSync9 } from "child_process";
4938
- import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
5835
+ import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
4939
5836
  import { homedir } from "os";
4940
5837
  import { join } from "path";
4941
5838
  function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
@@ -4965,7 +5862,7 @@ function shouldKillIdleSession(input) {
4965
5862
  }
4966
5863
  async function pollIdleEmployees(deps, lastNudge) {
4967
5864
  const registered = deps.listRegisteredSessions().filter(
4968
- (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
5865
+ (s) => !isCoordinatorName(s.agentId)
4969
5866
  );
4970
5867
  if (registered.length === 0) return [];
4971
5868
  let liveSessions;
@@ -4994,7 +5891,7 @@ async function pollIdleEmployees(deps, lastNudge) {
4994
5891
  }
4995
5892
  function checkSessionTTL(deps) {
4996
5893
  const registered = deps.listRegisteredSessions().filter(
4997
- (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
5894
+ (s) => !isCoordinatorName(s.agentId)
4998
5895
  );
4999
5896
  if (registered.length === 0) return [];
5000
5897
  let liveSessions;
@@ -5030,7 +5927,7 @@ function checkSessionTTL(deps) {
5030
5927
  async function pollIdleKill(deps, idleTickCounts, opts) {
5031
5928
  if (!opts.enabled) return [];
5032
5929
  const registered = deps.listRegisteredSessions().filter(
5033
- (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId) && !isExeSession(s.windowName)
5930
+ (s) => !isCoordinatorName(s.agentId) && !isExeSession(s.windowName)
5034
5931
  );
5035
5932
  if (registered.length === 0) return [];
5036
5933
  let liveSessions;
@@ -5126,8 +6023,8 @@ async function pollReviewNudge(deps, state) {
5126
6023
  function loadNudgeState() {
5127
6024
  const state = { lastNudge: /* @__PURE__ */ new Map() };
5128
6025
  try {
5129
- if (!existsSync12(NUDGE_STATE_PATH)) return state;
5130
- const raw = JSON.parse(readFileSync11(NUDGE_STATE_PATH, "utf8"));
6026
+ if (!existsSync13(NUDGE_STATE_PATH)) return state;
6027
+ const raw = JSON.parse(readFileSync12(NUDGE_STATE_PATH, "utf8"));
5131
6028
  if (Array.isArray(raw)) {
5132
6029
  for (const [key, val] of raw) {
5133
6030
  if (key && typeof val?.at === "number" && typeof val?.count === "number") {
@@ -5268,6 +6165,134 @@ function createIdleKillRealDeps(getClient2, intercomAckWindowMs) {
5268
6165
  }
5269
6166
  };
5270
6167
  }
6168
+ function _resetAutoWakeState() {
6169
+ _autoWakeLastSpawn.clear();
6170
+ _autoWakeTaskRetries.clear();
6171
+ }
6172
+ function shouldAutoWake(input) {
6173
+ if (isCoordinatorName(input.agentId)) return false;
6174
+ if (input.hasRunningSession) return false;
6175
+ if (input.nowMs - input.lastSpawnMs < input.cooldownMs) return false;
6176
+ return true;
6177
+ }
6178
+ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
6179
+ let liveSessions;
6180
+ try {
6181
+ liveSessions = deps.listTmuxSessions();
6182
+ } catch {
6183
+ return [];
6184
+ }
6185
+ const liveAgents = /* @__PURE__ */ new Set();
6186
+ for (const session of liveSessions) {
6187
+ const agent = deps.parseAgentFromSession(session);
6188
+ if (agent) liveAgents.add(agent);
6189
+ }
6190
+ let tasksByAgent;
6191
+ try {
6192
+ tasksByAgent = await deps.queryTasksByAgent();
6193
+ } catch {
6194
+ return [];
6195
+ }
6196
+ const agentTasks = /* @__PURE__ */ new Map();
6197
+ for (const t of tasksByAgent) {
6198
+ const existing = agentTasks.get(t.agentId) ?? [];
6199
+ existing.push({ taskId: t.taskId, priority: t.priority });
6200
+ agentTasks.set(t.agentId, existing);
6201
+ }
6202
+ const woken = [];
6203
+ for (const [agentId, tasks] of agentTasks) {
6204
+ if (!shouldAutoWake({
6205
+ agentId,
6206
+ hasRunningSession: liveAgents.has(agentId),
6207
+ lastSpawnMs: _autoWakeLastSpawn.get(agentId) ?? 0,
6208
+ nowMs,
6209
+ cooldownMs: AUTO_WAKE_COOLDOWN_MS
6210
+ })) {
6211
+ continue;
6212
+ }
6213
+ const topTask = tasks[0];
6214
+ const retries = _autoWakeTaskRetries.get(topTask.taskId) ?? 0;
6215
+ if (retries >= AUTO_WAKE_MAX_RETRIES) {
6216
+ try {
6217
+ await deps.markTaskBlocked(
6218
+ topTask.taskId,
6219
+ `Auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times \u2014 marking blocked for manual intervention`
6220
+ );
6221
+ process.stderr.write(
6222
+ `[auto-wake] ${agentId} task ${topTask.taskId} exceeded ${AUTO_WAKE_MAX_RETRIES} retries \u2014 marked blocked
6223
+ `
6224
+ );
6225
+ } catch {
6226
+ }
6227
+ continue;
6228
+ }
6229
+ process.stderr.write(
6230
+ `[auto-wake] ${agentId} has ${tasks.length} pending task(s) but no session \u2014 spawning (attempt ${retries + 1} for task ${topTask.taskId})
6231
+ `
6232
+ );
6233
+ try {
6234
+ const result = deps.ensureEmployee(agentId);
6235
+ if (result.status === "failed") {
6236
+ process.stderr.write(
6237
+ `[auto-wake] Failed to spawn ${agentId}: ${result.error ?? "unknown"}
6238
+ `
6239
+ );
6240
+ continue;
6241
+ }
6242
+ _autoWakeLastSpawn.set(agentId, nowMs);
6243
+ _autoWakeTaskRetries.set(topTask.taskId, retries + 1);
6244
+ woken.push(agentId);
6245
+ } catch (err) {
6246
+ process.stderr.write(
6247
+ `[auto-wake] Error spawning ${agentId}: ${err instanceof Error ? err.message : String(err)}
6248
+ `
6249
+ );
6250
+ }
6251
+ }
6252
+ return woken;
6253
+ }
6254
+ function createAutoWakeRealDeps(getClient2, sessionScope, projectDir) {
6255
+ return {
6256
+ listTmuxSessions: () => {
6257
+ const { listTmuxSessions: listTmuxSessions2 } = (init_tmux_status(), __toCommonJS(tmux_status_exports));
6258
+ return listTmuxSessions2();
6259
+ },
6260
+ queryTasksByAgent: async () => {
6261
+ const client = getClient2();
6262
+ const scope = sessionScopeFilter(sessionScope);
6263
+ const result = await client.execute({
6264
+ sql: `SELECT assigned_to, id, priority FROM tasks
6265
+ WHERE status IN ('open', 'in_progress')${scope.sql}
6266
+ ORDER BY
6267
+ CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
6268
+ created_at ASC`,
6269
+ args: [...scope.args]
6270
+ });
6271
+ return result.rows.map((r) => ({
6272
+ agentId: String(r.assigned_to),
6273
+ taskId: String(r.id),
6274
+ priority: String(r.priority)
6275
+ }));
6276
+ },
6277
+ ensureEmployee: (agentName) => {
6278
+ const { ensureEmployee: ensureEmployee2 } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
6279
+ return ensureEmployee2(agentName, sessionScope, projectDir);
6280
+ },
6281
+ markTaskBlocked: async (taskId, reason) => {
6282
+ const client = getClient2();
6283
+ await client.execute({
6284
+ sql: `UPDATE tasks SET status = 'blocked', result = ?, updated_at = ? WHERE id = ?`,
6285
+ args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
6286
+ });
6287
+ },
6288
+ parseAgentFromSession: (sessionName) => {
6289
+ const { baseAgentName: baseAgentName2 } = (init_employees(), __toCommonJS(employees_exports));
6290
+ if (!sessionName.includes("-")) return null;
6291
+ const agentPart = sessionName.split("-")[0];
6292
+ return baseAgentName2(agentPart);
6293
+ }
6294
+ };
6295
+ }
5271
6296
  function reapOrphanedMcpProcesses(deps) {
5272
6297
  const lines = deps.listProcesses();
5273
6298
  const reaped = [];
@@ -5318,19 +6343,23 @@ function createOrphanReaperRealDeps() {
5318
6343
  selfPid: process.pid
5319
6344
  };
5320
6345
  }
5321
- var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
6346
+ var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, AUTO_WAKE_COOLDOWN_MS, AUTO_WAKE_MAX_RETRIES, _autoWakeLastSpawn, _autoWakeTaskRetries, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
5322
6347
  var init_daemon_orchestration = __esm({
5323
6348
  "src/lib/daemon-orchestration.ts"() {
5324
6349
  "use strict";
5325
6350
  init_tmux_routing();
5326
6351
  init_task_scope();
5327
6352
  init_employees();
5328
- IDLE_NUDGE_DEDUP_MS = 6e4;
5329
- SESSION_TTL_HOURS = 4;
5330
- SESSION_CONTEXT_THRESHOLD_PCT = 50;
5331
- IDLE_KILL_INTERCOM_ACK_WINDOW_MS = 1e4;
6353
+ IDLE_NUDGE_DEDUP_MS = Number(process.env.EXE_NUDGE_INTERVAL_MS) || 6e4;
6354
+ SESSION_TTL_HOURS = Number(process.env.EXE_SESSION_TTL_HOURS) || 4;
6355
+ SESSION_CONTEXT_THRESHOLD_PCT = Number(process.env.EXE_CONTEXT_KILL_PCT) || 50;
6356
+ IDLE_KILL_INTERCOM_ACK_WINDOW_MS = Number(process.env.EXE_INTERCOM_ACK_WINDOW_MS) || 1e4;
5332
6357
  REVIEW_NUDGE_COOLDOWN_MS = 3e5;
5333
6358
  NUDGE_STATE_PATH = join(homedir(), ".exe-os", "review-nudge-state.json");
6359
+ AUTO_WAKE_COOLDOWN_MS = 5 * 60 * 1e3;
6360
+ AUTO_WAKE_MAX_RETRIES = 3;
6361
+ _autoWakeLastSpawn = /* @__PURE__ */ new Map();
6362
+ _autoWakeTaskRetries = /* @__PURE__ */ new Map();
5334
6363
  ORPHAN_SIGKILL_DELAY_MS = 5e3;
5335
6364
  ORPHAN_PATTERNS = [
5336
6365
  "exe-os/dist/mcp/server.js",
@@ -5351,14 +6380,14 @@ __export(keychain_exports, {
5351
6380
  setMasterKey: () => setMasterKey
5352
6381
  });
5353
6382
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5354
- import { existsSync as existsSync13 } from "fs";
5355
- import path15 from "path";
5356
- import os8 from "os";
6383
+ import { existsSync as existsSync14 } from "fs";
6384
+ import path16 from "path";
6385
+ import os9 from "os";
5357
6386
  function getKeyDir() {
5358
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
6387
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5359
6388
  }
5360
6389
  function getKeyPath() {
5361
- return path15.join(getKeyDir(), "master.key");
6390
+ return path16.join(getKeyDir(), "master.key");
5362
6391
  }
5363
6392
  async function tryKeytar() {
5364
6393
  try {
@@ -5379,13 +6408,21 @@ async function getMasterKey() {
5379
6408
  }
5380
6409
  }
5381
6410
  const keyPath = getKeyPath();
5382
- if (!existsSync13(keyPath)) {
6411
+ if (!existsSync14(keyPath)) {
6412
+ process.stderr.write(
6413
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6414
+ `
6415
+ );
5383
6416
  return null;
5384
6417
  }
5385
6418
  try {
5386
6419
  const content = await readFile4(keyPath, "utf-8");
5387
6420
  return Buffer.from(content.trim(), "base64");
5388
- } catch {
6421
+ } catch (err) {
6422
+ process.stderr.write(
6423
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
6424
+ `
6425
+ );
5389
6426
  return null;
5390
6427
  }
5391
6428
  }
@@ -5414,7 +6451,7 @@ async function deleteMasterKey() {
5414
6451
  }
5415
6452
  }
5416
6453
  const keyPath = getKeyPath();
5417
- if (existsSync13(keyPath)) {
6454
+ if (existsSync14(keyPath)) {
5418
6455
  await unlink(keyPath);
5419
6456
  }
5420
6457
  }
@@ -5469,12 +6506,12 @@ __export(shard_manager_exports, {
5469
6506
  listShards: () => listShards,
5470
6507
  shardExists: () => shardExists
5471
6508
  });
5472
- import path16 from "path";
5473
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
6509
+ import path17 from "path";
6510
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5474
6511
  import { createClient as createClient2 } from "@libsql/client";
5475
6512
  function initShardManager(encryptionKey) {
5476
6513
  _encryptionKey = encryptionKey;
5477
- if (!existsSync14(SHARDS_DIR)) {
6514
+ if (!existsSync15(SHARDS_DIR)) {
5478
6515
  mkdirSync6(SHARDS_DIR, { recursive: true });
5479
6516
  }
5480
6517
  _shardingEnabled = true;
@@ -5495,7 +6532,7 @@ function getShardClient(projectName) {
5495
6532
  }
5496
6533
  const cached = _shards.get(safeName);
5497
6534
  if (cached) return cached;
5498
- const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
6535
+ const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5499
6536
  const client = createClient2({
5500
6537
  url: `file:${dbPath}`,
5501
6538
  encryptionKey: _encryptionKey
@@ -5505,10 +6542,10 @@ function getShardClient(projectName) {
5505
6542
  }
5506
6543
  function shardExists(projectName) {
5507
6544
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5508
- return existsSync14(path16.join(SHARDS_DIR, `${safeName}.db`));
6545
+ return existsSync15(path17.join(SHARDS_DIR, `${safeName}.db`));
5509
6546
  }
5510
6547
  function listShards() {
5511
- if (!existsSync14(SHARDS_DIR)) return [];
6548
+ if (!existsSync15(SHARDS_DIR)) return [];
5512
6549
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5513
6550
  }
5514
6551
  async function ensureShardSchema(client) {
@@ -5694,7 +6731,7 @@ var init_shard_manager = __esm({
5694
6731
  "src/lib/shard-manager.ts"() {
5695
6732
  "use strict";
5696
6733
  init_config();
5697
- SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
6734
+ SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5698
6735
  _shards = /* @__PURE__ */ new Map();
5699
6736
  _encryptionKey = null;
5700
6737
  _shardingEnabled = false;
@@ -5819,7 +6856,7 @@ __export(global_procedures_exports, {
5819
6856
  loadGlobalProcedures: () => loadGlobalProcedures,
5820
6857
  storeGlobalProcedure: () => storeGlobalProcedure
5821
6858
  });
5822
- import { randomUUID as randomUUID2 } from "crypto";
6859
+ import { randomUUID as randomUUID3 } from "crypto";
5823
6860
  async function loadGlobalProcedures() {
5824
6861
  const client = getClient();
5825
6862
  const result = await client.execute({
@@ -5848,7 +6885,7 @@ ${sections.join("\n\n")}
5848
6885
  `;
5849
6886
  }
5850
6887
  async function storeGlobalProcedure(input) {
5851
- const id = randomUUID2();
6888
+ const id = randomUUID3();
5852
6889
  const now = (/* @__PURE__ */ new Date()).toISOString();
5853
6890
  const client = getClient();
5854
6891
  await client.execute({
@@ -5899,6 +6936,7 @@ __export(store_exports, {
5899
6936
  vectorToBlob: () => vectorToBlob,
5900
6937
  writeMemory: () => writeMemory
5901
6938
  });
6939
+ import { createHash } from "crypto";
5902
6940
  function isBusyError2(err) {
5903
6941
  if (err instanceof Error) {
5904
6942
  const msg = err.message.toLowerCase();
@@ -5972,12 +7010,52 @@ function classifyTier(record) {
5972
7010
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
5973
7011
  return 3;
5974
7012
  }
7013
+ function inferFilePaths(record) {
7014
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
7015
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
7016
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
7017
+ return match ? JSON.stringify([match[1]]) : null;
7018
+ }
7019
+ function inferCommitHash(record) {
7020
+ if (record.tool_name !== "Bash") return null;
7021
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
7022
+ return match ? match[1] : null;
7023
+ }
7024
+ function inferLanguageType(record) {
7025
+ const text = record.raw_text;
7026
+ if (!text || text.length < 10) return null;
7027
+ const trimmed = text.trimStart();
7028
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
7029
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
7030
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
7031
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
7032
+ return "mixed";
7033
+ }
7034
+ function inferDomain(record) {
7035
+ const proj = (record.project_name ?? "").toLowerCase();
7036
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
7037
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
7038
+ return null;
7039
+ }
5975
7040
  async function writeMemory(record) {
5976
7041
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
5977
7042
  throw new Error(
5978
7043
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
5979
7044
  );
5980
7045
  }
7046
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
7047
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
7048
+ return;
7049
+ }
7050
+ try {
7051
+ const client = getClient();
7052
+ const existing = await client.execute({
7053
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
7054
+ args: [contentHash, record.agent_id]
7055
+ });
7056
+ if (existing.rows.length > 0) return;
7057
+ } catch {
7058
+ }
5981
7059
  const dbRow = {
5982
7060
  id: record.id,
5983
7061
  agent_id: record.agent_id,
@@ -6007,7 +7085,23 @@ async function writeMemory(record) {
6007
7085
  supersedes_id: record.supersedes_id ?? null,
6008
7086
  draft: record.draft ? 1 : 0,
6009
7087
  memory_type: record.memory_type ?? "raw",
6010
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
7088
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
7089
+ content_hash: contentHash,
7090
+ intent: record.intent ?? null,
7091
+ outcome: record.outcome ?? null,
7092
+ domain: record.domain ?? inferDomain(record),
7093
+ referenced_entities: record.referenced_entities ?? null,
7094
+ retrieval_count: record.retrieval_count ?? 0,
7095
+ chain_position: record.chain_position ?? null,
7096
+ review_status: record.review_status ?? null,
7097
+ context_window_pct: record.context_window_pct ?? null,
7098
+ file_paths: record.file_paths ?? inferFilePaths(record),
7099
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
7100
+ duration_ms: record.duration_ms ?? null,
7101
+ token_cost: record.token_cost ?? null,
7102
+ audience: record.audience ?? null,
7103
+ language_type: record.language_type ?? inferLanguageType(record),
7104
+ parent_memory_id: record.parent_memory_id ?? null
6011
7105
  };
6012
7106
  _pendingRecords.push(dbRow);
6013
7107
  orgBus.emit({
@@ -6065,80 +7159,85 @@ async function flushBatch() {
6065
7159
  const draft = row.draft ? 1 : 0;
6066
7160
  const memoryType = row.memory_type ?? "raw";
6067
7161
  const trajectory = row.trajectory ?? null;
6068
- return {
6069
- sql: hasVector ? `INSERT OR IGNORE INTO memories
6070
- (id, agent_id, agent_role, session_id, timestamp,
6071
- tool_name, project_name,
6072
- has_error, raw_text, vector, version, task_id, importance, status,
6073
- confidence, last_accessed,
6074
- workspace_id, document_id, user_id, char_offset, page_number,
6075
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
6076
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
6077
- (id, agent_id, agent_role, session_id, timestamp,
7162
+ const contentHash = row.content_hash ?? null;
7163
+ const intent = row.intent ?? null;
7164
+ const outcome = row.outcome ?? null;
7165
+ const domain = row.domain ?? null;
7166
+ const referencedEntities = row.referenced_entities ?? null;
7167
+ const retrievalCount = row.retrieval_count ?? 0;
7168
+ const chainPosition = row.chain_position ?? null;
7169
+ const reviewStatus = row.review_status ?? null;
7170
+ const contextWindowPct = row.context_window_pct ?? null;
7171
+ const filePaths = row.file_paths ?? null;
7172
+ const commitHash = row.commit_hash ?? null;
7173
+ const durationMs = row.duration_ms ?? null;
7174
+ const tokenCost = row.token_cost ?? null;
7175
+ const audience = row.audience ?? null;
7176
+ const languageType = row.language_type ?? null;
7177
+ const parentMemoryId = row.parent_memory_id ?? null;
7178
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
6078
7179
  tool_name, project_name,
6079
7180
  has_error, raw_text, vector, version, task_id, importance, status,
6080
7181
  confidence, last_accessed,
6081
7182
  workspace_id, document_id, user_id, char_offset, page_number,
6082
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
6083
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6084
- args: hasVector ? [
6085
- row.id,
6086
- row.agent_id,
6087
- row.agent_role,
6088
- row.session_id,
6089
- row.timestamp,
6090
- row.tool_name,
6091
- row.project_name,
6092
- row.has_error,
6093
- row.raw_text,
6094
- vectorToBlob(row.vector),
6095
- row.version,
6096
- taskId,
6097
- importance,
6098
- status,
6099
- confidence,
6100
- lastAccessed,
6101
- workspaceId,
6102
- documentId,
6103
- userId,
6104
- charOffset,
6105
- pageNumber,
6106
- sourcePath,
6107
- sourceType,
6108
- tier,
6109
- supersedesId,
6110
- draft,
6111
- memoryType,
6112
- trajectory
6113
- ] : [
6114
- row.id,
6115
- row.agent_id,
6116
- row.agent_role,
6117
- row.session_id,
6118
- row.timestamp,
6119
- row.tool_name,
6120
- row.project_name,
6121
- row.has_error,
6122
- row.raw_text,
6123
- row.version,
6124
- taskId,
6125
- importance,
6126
- status,
6127
- confidence,
6128
- lastAccessed,
6129
- workspaceId,
6130
- documentId,
6131
- userId,
6132
- charOffset,
6133
- pageNumber,
6134
- sourcePath,
6135
- sourceType,
6136
- tier,
6137
- supersedesId,
6138
- draft,
6139
- memoryType,
6140
- trajectory
6141
- ]
7183
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
7184
+ intent, outcome, domain, referenced_entities, retrieval_count,
7185
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
7186
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
7187
+ const metaArgs = [
7188
+ intent,
7189
+ outcome,
7190
+ domain,
7191
+ referencedEntities,
7192
+ retrievalCount,
7193
+ chainPosition,
7194
+ reviewStatus,
7195
+ contextWindowPct,
7196
+ filePaths,
7197
+ commitHash,
7198
+ durationMs,
7199
+ tokenCost,
7200
+ audience,
7201
+ languageType,
7202
+ parentMemoryId
7203
+ ];
7204
+ const baseArgs = [
7205
+ row.id,
7206
+ row.agent_id,
7207
+ row.agent_role,
7208
+ row.session_id,
7209
+ row.timestamp,
7210
+ row.tool_name,
7211
+ row.project_name,
7212
+ row.has_error,
7213
+ row.raw_text
7214
+ ];
7215
+ const sharedArgs = [
7216
+ row.version,
7217
+ taskId,
7218
+ importance,
7219
+ status,
7220
+ confidence,
7221
+ lastAccessed,
7222
+ workspaceId,
7223
+ documentId,
7224
+ userId,
7225
+ charOffset,
7226
+ pageNumber,
7227
+ sourcePath,
7228
+ sourceType,
7229
+ tier,
7230
+ supersedesId,
7231
+ draft,
7232
+ memoryType,
7233
+ trajectory,
7234
+ contentHash
7235
+ ];
7236
+ return {
7237
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
7238
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
7239
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
7240
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
6142
7241
  };
6143
7242
  };
6144
7243
  const globalClient = getClient();
@@ -6505,20 +7604,11 @@ function createRealDeps(getClient2) {
6505
7604
  },
6506
7605
  countPendingReviews: async () => {
6507
7606
  const client = getClient2();
6508
- const coordinatorName = getCoordinatorName();
6509
- const rpScope = sessionScopeFilter(void 0, "r");
7607
+ const rpScope = sessionScopeFilter();
6510
7608
  const result = await client.execute({
6511
- sql: `SELECT COUNT(*) as count FROM tasks r
6512
- WHERE (r.assigned_to = ? OR r.assigned_to = 'exe')
6513
- AND r.status IN ('open', 'in_progress')
6514
- AND r.title LIKE 'Review:%'${rpScope.sql}
6515
- AND NOT EXISTS (
6516
- SELECT 1 FROM tasks t
6517
- WHERE t.id = r.parent_task_id
6518
- AND t.status = 'done'
6519
- AND t.updated_at > r.created_at
6520
- )`,
6521
- args: [coordinatorName, ...rpScope.args]
7609
+ sql: `SELECT COUNT(*) as count FROM tasks
7610
+ WHERE status = 'needs_review'${rpScope.sql}`,
7611
+ args: [...rpScope.args]
6522
7612
  });
6523
7613
  return Number(result.rows[0]?.count ?? 0);
6524
7614
  },
@@ -6611,7 +7701,7 @@ __export(consolidation_exports, {
6611
7701
  selectUnconsolidated: () => selectUnconsolidated,
6612
7702
  storeConsolidation: () => storeConsolidation
6613
7703
  });
6614
- import { randomUUID as randomUUID3 } from "crypto";
7704
+ import { randomUUID as randomUUID4 } from "crypto";
6615
7705
  async function selectUnconsolidated(client, limit = 200) {
6616
7706
  const result = await client.execute({
6617
7707
  sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
@@ -6707,7 +7797,7 @@ async function consolidateCluster(cluster, model) {
6707
7797
  return textBlock?.text ?? "";
6708
7798
  }
6709
7799
  async function storeConsolidation(client, cluster, synthesisText, embedFn) {
6710
- const consolidatedId = randomUUID3();
7800
+ const consolidatedId = randomUUID4();
6711
7801
  const now = (/* @__PURE__ */ new Date()).toISOString();
6712
7802
  const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
6713
7803
 
@@ -6732,7 +7822,7 @@ ${synthesisText}`;
6732
7822
  const linkStmts = sourceIds.map((sourceId) => ({
6733
7823
  sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
6734
7824
  VALUES (?, ?, ?, ?)`,
6735
- args: [randomUUID3(), consolidatedId, sourceId, now]
7825
+ args: [randomUUID4(), consolidatedId, sourceId, now]
6736
7826
  }));
6737
7827
  const placeholders = sourceIds.map(() => "?").join(",");
6738
7828
  const markStmt = {
@@ -6847,7 +7937,7 @@ async function runConsolidation(client, options) {
6847
7937
  if (clustersProcessed >= options.maxCalls) break;
6848
7938
  if (cluster.memories.length < 3) continue;
6849
7939
  try {
6850
- const isCoordinator = cluster.agentId === "exe" || isCoordinatorName(cluster.agentId);
7940
+ const isCoordinator = isCoordinatorName(cluster.agentId);
6851
7941
  if (isCoordinator) {
6852
7942
  const synthesis = await consolidateCluster(cluster, options.model);
6853
7943
  if (!synthesis.trim()) continue;
@@ -6867,450 +7957,105 @@ async function runConsolidation(client, options) {
6867
7957
  if (sourceIds.length > 0) {
6868
7958
  const placeholders = sourceIds.map(() => "?").join(",");
6869
7959
  await client.execute({
6870
- sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
6871
- args: sourceIds
6872
- });
6873
- }
6874
- } else {
6875
- const dedupCount = await dedupCluster(client, cluster, options.embedFn);
6876
- memoriesConsolidated += dedupCount;
6877
- if (dedupCount === 0) continue;
6878
- }
6879
- clustersProcessed++;
6880
- memoriesConsolidated += isCoordinator ? cluster.memories.length : 0;
6881
- } catch (err) {
6882
- process.stderr.write(
6883
- `[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
6884
- `
6885
- );
6886
- }
6887
- }
6888
- return { clustersProcessed, memoriesConsolidated };
6889
- }
6890
- async function dedupCluster(client, cluster, embedFn) {
6891
- if (!embedFn || cluster.memories.length < 2) return 0;
6892
- const vectors = [];
6893
- for (const mem of cluster.memories) {
6894
- try {
6895
- const v = await embedFn(mem.raw_text.slice(0, 500));
6896
- vectors.push({ id: mem.id, vector: v });
6897
- } catch {
6898
- }
6899
- }
6900
- if (vectors.length < 2) return 0;
6901
- const toArchive = /* @__PURE__ */ new Set();
6902
- for (let i = 0; i < vectors.length; i++) {
6903
- if (toArchive.has(vectors[i].id)) continue;
6904
- for (let j = i + 1; j < vectors.length; j++) {
6905
- if (toArchive.has(vectors[j].id)) continue;
6906
- const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
6907
- if (sim > 0.95) {
6908
- toArchive.add(vectors[j].id);
6909
- }
6910
- }
6911
- }
6912
- if (toArchive.size === 0) return 0;
6913
- const ids = [...toArchive];
6914
- const placeholders = ids.map(() => "?").join(",");
6915
- await client.execute({
6916
- sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
6917
- args: ids
6918
- });
6919
- const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
6920
- if (survivors.length > 0) {
6921
- const survivorPlaceholders = survivors.map(() => "?").join(",");
6922
- await client.execute({
6923
- sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
6924
- args: survivors
6925
- });
6926
- }
6927
- return ids.length;
6928
- }
6929
- function cosineSimilarity(a, b) {
6930
- let dot = 0, normA = 0, normB = 0;
6931
- for (let i = 0; i < a.length; i++) {
6932
- dot += a[i] * b[i];
6933
- normA += a[i] * a[i];
6934
- normB += b[i] * b[i];
6935
- }
6936
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
6937
- return denom === 0 ? 0 : dot / denom;
6938
- }
6939
- async function isUserIdle(client, idleMinutes = 30) {
6940
- const result = await client.execute({
6941
- sql: `SELECT MAX(timestamp) as last_activity
6942
- FROM memories
6943
- WHERE tool_name != 'consolidation'
6944
- AND timestamp >= datetime('now', '-1 day')`,
6945
- args: []
6946
- });
6947
- const lastActivity = result.rows[0]?.last_activity;
6948
- if (!lastActivity) return true;
6949
- const lastMs = new Date(lastActivity).getTime();
6950
- const now = Date.now();
6951
- return now - lastMs >= idleMinutes * 60 * 1e3;
6952
- }
6953
- async function countUnconsolidated(client) {
6954
- const result = await client.execute({
6955
- sql: `SELECT COUNT(*) as cnt FROM memories
6956
- WHERE consolidated = 0
6957
- AND timestamp >= datetime('now', '-7 days')`,
6958
- args: []
6959
- });
6960
- return Number(result.rows[0]?.cnt ?? 0);
6961
- }
6962
- var WIKI_FETCH_TIMEOUT_MS;
6963
- var init_consolidation = __esm({
6964
- "src/lib/consolidation.ts"() {
6965
- "use strict";
6966
- init_store();
6967
- init_employees();
6968
- WIKI_FETCH_TIMEOUT_MS = 1e4;
6969
- }
6970
- });
6971
-
6972
- // src/lib/exe-daemon-client.ts
6973
- import net from "net";
6974
- import { spawn } from "child_process";
6975
- import { randomUUID as randomUUID4 } from "crypto";
6976
- import { existsSync as existsSync15, unlinkSync as unlinkSync6, readFileSync as readFileSync12, openSync, closeSync, statSync } from "fs";
6977
- import path17 from "path";
6978
- import { fileURLToPath as fileURLToPath2 } from "url";
6979
- function handleData(chunk) {
6980
- _buffer += chunk.toString();
6981
- if (_buffer.length > MAX_BUFFER) {
6982
- _buffer = "";
6983
- return;
6984
- }
6985
- let newlineIdx;
6986
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
6987
- const line = _buffer.slice(0, newlineIdx).trim();
6988
- _buffer = _buffer.slice(newlineIdx + 1);
6989
- if (!line) continue;
6990
- try {
6991
- const response = JSON.parse(line);
6992
- const entry = _pending.get(response.id);
6993
- if (entry) {
6994
- clearTimeout(entry.timer);
6995
- _pending.delete(response.id);
6996
- entry.resolve(response);
6997
- }
6998
- } catch {
6999
- }
7000
- }
7001
- }
7002
- function cleanupStaleFiles() {
7003
- if (existsSync15(PID_PATH)) {
7004
- try {
7005
- const pid = parseInt(readFileSync12(PID_PATH, "utf8").trim(), 10);
7006
- if (pid > 0) {
7007
- try {
7008
- process.kill(pid, 0);
7009
- return;
7010
- } catch {
7011
- }
7012
- }
7013
- } catch {
7014
- }
7015
- try {
7016
- unlinkSync6(PID_PATH);
7017
- } catch {
7018
- }
7019
- try {
7020
- unlinkSync6(SOCKET_PATH);
7021
- } catch {
7022
- }
7023
- }
7024
- }
7025
- function findPackageRoot() {
7026
- let dir = path17.dirname(fileURLToPath2(import.meta.url));
7027
- const { root } = path17.parse(dir);
7028
- while (dir !== root) {
7029
- if (existsSync15(path17.join(dir, "package.json"))) return dir;
7030
- dir = path17.dirname(dir);
7031
- }
7032
- return null;
7033
- }
7034
- function spawnDaemon() {
7035
- const pkgRoot = findPackageRoot();
7036
- if (!pkgRoot) {
7037
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
7038
- return;
7039
- }
7040
- const daemonPath = path17.join(pkgRoot, "dist", "lib", "exe-daemon.js");
7041
- if (!existsSync15(daemonPath)) {
7042
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
7043
- `);
7044
- return;
7045
- }
7046
- const resolvedPath = daemonPath;
7047
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
7048
- `);
7049
- const logPath = path17.join(path17.dirname(SOCKET_PATH), "exed.log");
7050
- let stderrFd = "ignore";
7051
- try {
7052
- stderrFd = openSync(logPath, "a");
7053
- } catch {
7054
- }
7055
- const child = spawn(process.execPath, [resolvedPath], {
7056
- detached: true,
7057
- stdio: ["ignore", "ignore", stderrFd],
7058
- env: {
7059
- ...process.env,
7060
- TMUX: void 0,
7061
- // Daemon is global — must not inherit session scope
7062
- TMUX_PANE: void 0,
7063
- // Prevents resolveExeSession() from scoping to one session
7064
- EXE_DAEMON_SOCK: SOCKET_PATH,
7065
- EXE_DAEMON_PID: PID_PATH
7066
- }
7067
- });
7068
- child.unref();
7069
- if (typeof stderrFd === "number") {
7070
- try {
7071
- closeSync(stderrFd);
7072
- } catch {
7073
- }
7074
- }
7075
- }
7076
- function acquireSpawnLock2() {
7077
- try {
7078
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
7079
- closeSync(fd);
7080
- return true;
7081
- } catch {
7082
- try {
7083
- const stat = statSync(SPAWN_LOCK_PATH);
7084
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
7085
- try {
7086
- unlinkSync6(SPAWN_LOCK_PATH);
7087
- } catch {
7088
- }
7089
- try {
7090
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
7091
- closeSync(fd);
7092
- return true;
7093
- } catch {
7094
- }
7095
- }
7096
- } catch {
7097
- }
7098
- return false;
7099
- }
7100
- }
7101
- function releaseSpawnLock2() {
7102
- try {
7103
- unlinkSync6(SPAWN_LOCK_PATH);
7104
- } catch {
7105
- }
7106
- }
7107
- function connectToSocket() {
7108
- return new Promise((resolve) => {
7109
- if (_socket && _connected) {
7110
- resolve(true);
7111
- return;
7112
- }
7113
- const socket = net.createConnection({ path: SOCKET_PATH });
7114
- const connectTimeout = setTimeout(() => {
7115
- socket.destroy();
7116
- resolve(false);
7117
- }, 2e3);
7118
- socket.on("connect", () => {
7119
- clearTimeout(connectTimeout);
7120
- _socket = socket;
7121
- _connected = true;
7122
- _buffer = "";
7123
- socket.on("data", handleData);
7124
- socket.on("close", () => {
7125
- _connected = false;
7126
- _socket = null;
7127
- for (const [id, entry] of _pending) {
7128
- clearTimeout(entry.timer);
7129
- _pending.delete(id);
7130
- entry.resolve({ error: "Connection closed" });
7131
- }
7132
- });
7133
- socket.on("error", () => {
7134
- _connected = false;
7135
- _socket = null;
7136
- });
7137
- resolve(true);
7138
- });
7139
- socket.on("error", () => {
7140
- clearTimeout(connectTimeout);
7141
- resolve(false);
7142
- });
7143
- });
7144
- }
7145
- async function connectEmbedDaemon() {
7146
- if (_socket && _connected) return true;
7147
- if (await connectToSocket()) return true;
7148
- if (acquireSpawnLock2()) {
7149
- try {
7150
- cleanupStaleFiles();
7151
- spawnDaemon();
7152
- } finally {
7153
- releaseSpawnLock2();
7154
- }
7155
- }
7156
- const start = Date.now();
7157
- let delay2 = 100;
7158
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
7159
- await new Promise((r) => setTimeout(r, delay2));
7160
- if (await connectToSocket()) return true;
7161
- delay2 = Math.min(delay2 * 2, 3e3);
7162
- }
7163
- return false;
7164
- }
7165
- function sendRequest(texts, priority) {
7166
- return new Promise((resolve) => {
7167
- if (!_socket || !_connected) {
7168
- resolve({ error: "Not connected" });
7169
- return;
7170
- }
7171
- const id = randomUUID4();
7172
- const timer = setTimeout(() => {
7173
- _pending.delete(id);
7174
- resolve({ error: "Request timeout" });
7175
- }, REQUEST_TIMEOUT_MS);
7176
- _pending.set(id, { resolve, timer });
7177
- try {
7178
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
7179
- } catch {
7180
- clearTimeout(timer);
7181
- _pending.delete(id);
7182
- resolve({ error: "Write failed" });
7183
- }
7184
- });
7185
- }
7186
- async function pingDaemon() {
7187
- if (!_socket || !_connected) return null;
7188
- return new Promise((resolve) => {
7189
- const id = randomUUID4();
7190
- const timer = setTimeout(() => {
7191
- _pending.delete(id);
7192
- resolve(null);
7193
- }, 5e3);
7194
- _pending.set(id, {
7195
- resolve: (data) => {
7196
- if (data.health) {
7197
- resolve(data.health);
7198
- } else {
7199
- resolve(null);
7200
- }
7201
- },
7202
- timer
7203
- });
7204
- try {
7205
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
7206
- } catch {
7207
- clearTimeout(timer);
7208
- _pending.delete(id);
7209
- resolve(null);
7960
+ sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
7961
+ args: sourceIds
7962
+ });
7963
+ }
7964
+ } else {
7965
+ const dedupCount = await dedupCluster(client, cluster, options.embedFn);
7966
+ memoriesConsolidated += dedupCount;
7967
+ if (dedupCount === 0) continue;
7968
+ }
7969
+ clustersProcessed++;
7970
+ memoriesConsolidated += isCoordinator ? cluster.memories.length : 0;
7971
+ } catch (err) {
7972
+ process.stderr.write(
7973
+ `[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
7974
+ `
7975
+ );
7210
7976
  }
7211
- });
7977
+ }
7978
+ return { clustersProcessed, memoriesConsolidated };
7212
7979
  }
7213
- function killAndRespawnDaemon() {
7214
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
7215
- if (existsSync15(PID_PATH)) {
7980
+ async function dedupCluster(client, cluster, embedFn) {
7981
+ if (!embedFn || cluster.memories.length < 2) return 0;
7982
+ const vectors = [];
7983
+ for (const mem of cluster.memories) {
7216
7984
  try {
7217
- const pid = parseInt(readFileSync12(PID_PATH, "utf8").trim(), 10);
7218
- if (pid > 0) {
7219
- try {
7220
- process.kill(pid, "SIGKILL");
7221
- } catch {
7222
- }
7223
- }
7985
+ const v = await embedFn(mem.raw_text.slice(0, 500));
7986
+ vectors.push({ id: mem.id, vector: v });
7224
7987
  } catch {
7225
7988
  }
7226
7989
  }
7227
- if (_socket) {
7228
- _socket.destroy();
7229
- _socket = null;
7230
- }
7231
- _connected = false;
7232
- _buffer = "";
7233
- try {
7234
- unlinkSync6(PID_PATH);
7235
- } catch {
7236
- }
7237
- try {
7238
- unlinkSync6(SOCKET_PATH);
7239
- } catch {
7240
- }
7241
- spawnDaemon();
7242
- }
7243
- async function embedViaClient(text, priority = "high") {
7244
- if (!_connected && !await connectEmbedDaemon()) return null;
7245
- _requestCount++;
7246
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
7247
- const health = await pingDaemon();
7248
- if (!health) {
7249
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
7250
- `);
7251
- killAndRespawnDaemon();
7252
- const start = Date.now();
7253
- let delay2 = 200;
7254
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
7255
- await new Promise((r) => setTimeout(r, delay2));
7256
- if (await connectToSocket()) break;
7257
- delay2 = Math.min(delay2 * 2, 3e3);
7990
+ if (vectors.length < 2) return 0;
7991
+ const toArchive = /* @__PURE__ */ new Set();
7992
+ for (let i = 0; i < vectors.length; i++) {
7993
+ if (toArchive.has(vectors[i].id)) continue;
7994
+ for (let j = i + 1; j < vectors.length; j++) {
7995
+ if (toArchive.has(vectors[j].id)) continue;
7996
+ const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
7997
+ if (sim > 0.95) {
7998
+ toArchive.add(vectors[j].id);
7258
7999
  }
7259
- if (!_connected) return null;
7260
8000
  }
7261
8001
  }
7262
- const result = await sendRequest([text], priority);
7263
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
7264
- if (result.error) {
7265
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
7266
- `);
7267
- killAndRespawnDaemon();
7268
- const start = Date.now();
7269
- let delay2 = 200;
7270
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
7271
- await new Promise((r) => setTimeout(r, delay2));
7272
- if (await connectToSocket()) break;
7273
- delay2 = Math.min(delay2 * 2, 3e3);
7274
- }
7275
- if (!_connected) return null;
7276
- const retry = await sendRequest([text], priority);
7277
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
7278
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
7279
- `);
8002
+ if (toArchive.size === 0) return 0;
8003
+ const ids = [...toArchive];
8004
+ const placeholders = ids.map(() => "?").join(",");
8005
+ await client.execute({
8006
+ sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
8007
+ args: ids
8008
+ });
8009
+ const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
8010
+ if (survivors.length > 0) {
8011
+ const survivorPlaceholders = survivors.map(() => "?").join(",");
8012
+ await client.execute({
8013
+ sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
8014
+ args: survivors
8015
+ });
7280
8016
  }
7281
- return null;
8017
+ return ids.length;
7282
8018
  }
7283
- function disconnectClient() {
7284
- if (_socket) {
7285
- _socket.destroy();
7286
- _socket = null;
7287
- }
7288
- _connected = false;
7289
- _buffer = "";
7290
- for (const [id, entry] of _pending) {
7291
- clearTimeout(entry.timer);
7292
- _pending.delete(id);
7293
- entry.resolve({ error: "Client disconnected" });
8019
+ function cosineSimilarity(a, b) {
8020
+ let dot = 0, normA = 0, normB = 0;
8021
+ for (let i = 0; i < a.length; i++) {
8022
+ dot += a[i] * b[i];
8023
+ normA += a[i] * a[i];
8024
+ normB += b[i] * b[i];
7294
8025
  }
8026
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
8027
+ return denom === 0 ? 0 : dot / denom;
7295
8028
  }
7296
- 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;
7297
- var init_exe_daemon_client = __esm({
7298
- "src/lib/exe-daemon-client.ts"() {
8029
+ async function isUserIdle(client, idleMinutes = 30) {
8030
+ const result = await client.execute({
8031
+ sql: `SELECT MAX(timestamp) as last_activity
8032
+ FROM memories
8033
+ WHERE tool_name != 'consolidation'
8034
+ AND timestamp >= datetime('now', '-1 day')`,
8035
+ args: []
8036
+ });
8037
+ const lastActivity = result.rows[0]?.last_activity;
8038
+ if (!lastActivity) return true;
8039
+ const lastMs = new Date(lastActivity).getTime();
8040
+ const now = Date.now();
8041
+ return now - lastMs >= idleMinutes * 60 * 1e3;
8042
+ }
8043
+ async function countUnconsolidated(client) {
8044
+ const result = await client.execute({
8045
+ sql: `SELECT COUNT(*) as cnt FROM memories
8046
+ WHERE consolidated = 0
8047
+ AND timestamp >= datetime('now', '-7 days')`,
8048
+ args: []
8049
+ });
8050
+ return Number(result.rows[0]?.cnt ?? 0);
8051
+ }
8052
+ var WIKI_FETCH_TIMEOUT_MS;
8053
+ var init_consolidation = __esm({
8054
+ "src/lib/consolidation.ts"() {
7299
8055
  "use strict";
7300
- init_config();
7301
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path17.join(EXE_AI_DIR, "exed.sock");
7302
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path17.join(EXE_AI_DIR, "exed.pid");
7303
- SPAWN_LOCK_PATH = path17.join(EXE_AI_DIR, "exed-spawn.lock");
7304
- SPAWN_LOCK_STALE_MS = 3e4;
7305
- CONNECT_TIMEOUT_MS = 15e3;
7306
- REQUEST_TIMEOUT_MS = 3e4;
7307
- _socket = null;
7308
- _connected = false;
7309
- _buffer = "";
7310
- _requestCount = 0;
7311
- HEALTH_CHECK_INTERVAL = 100;
7312
- _pending = /* @__PURE__ */ new Map();
7313
- MAX_BUFFER = 1e7;
8056
+ init_store();
8057
+ init_employees();
8058
+ WIKI_FETCH_TIMEOUT_MS = 1e4;
7314
8059
  }
7315
8060
  });
7316
8061
 
@@ -7352,8 +8097,8 @@ async function embedDirect(text) {
7352
8097
  const llamaCpp = await import("node-llama-cpp");
7353
8098
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
7354
8099
  const { existsSync: existsSync18 } = await import("fs");
7355
- const path21 = await import("path");
7356
- const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
8100
+ const path22 = await import("path");
8101
+ const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7357
8102
  if (!existsSync18(modelPath)) {
7358
8103
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
7359
8104
  }
@@ -8076,8 +8821,8 @@ __export(wiki_sync_exports, {
8076
8821
  listWorkspaces: () => listWorkspaces,
8077
8822
  syncMemories: () => syncMemories
8078
8823
  });
8079
- async function wikiRequest(config, path21, method = "GET", body) {
8080
- const url = `${config.wikiUrl}/api/v1${path21}`;
8824
+ async function wikiRequest(config, path22, method = "GET", body) {
8825
+ const url = `${config.wikiUrl}/api/v1${path22}`;
8081
8826
  const headers = {
8082
8827
  "Authorization": `Bearer ${config.wikiApiKey}`,
8083
8828
  "Content-Type": "application/json"
@@ -8089,7 +8834,7 @@ async function wikiRequest(config, path21, method = "GET", body) {
8089
8834
  signal: AbortSignal.timeout(3e4)
8090
8835
  });
8091
8836
  if (!response.ok) {
8092
- throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
8837
+ throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
8093
8838
  }
8094
8839
  return response.json();
8095
8840
  }
@@ -8193,6 +8938,162 @@ var init_wiki_sync = __esm({
8193
8938
  }
8194
8939
  });
8195
8940
 
8941
+ // src/lib/token-spend.ts
8942
+ var token_spend_exports = {};
8943
+ __export(token_spend_exports, {
8944
+ getAgentSpend: () => getAgentSpend
8945
+ });
8946
+ import { readdir } from "fs/promises";
8947
+ import { createReadStream } from "fs";
8948
+ import { createInterface } from "readline";
8949
+ import path18 from "path";
8950
+ import os10 from "os";
8951
+ function getPricing(model) {
8952
+ if (MODEL_PRICING[model]) return MODEL_PRICING[model];
8953
+ const stripped = model.replace(/-\d{8}$/, "");
8954
+ if (MODEL_PRICING[stripped]) return MODEL_PRICING[stripped];
8955
+ const sortedKeys = Object.keys(MODEL_PRICING).sort((a, b) => b.length - a.length);
8956
+ for (const key of sortedKeys) {
8957
+ if (model.includes(key)) return MODEL_PRICING[key];
8958
+ }
8959
+ return DEFAULT_PRICING;
8960
+ }
8961
+ async function getAgentSpend(period = "7d") {
8962
+ const cutoff = periodToCutoff(period);
8963
+ const client = getClient();
8964
+ const result = await client.execute({
8965
+ sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
8966
+ args: [cutoff]
8967
+ });
8968
+ if (result.rows.length === 0) return [];
8969
+ const sessionAgent = /* @__PURE__ */ new Map();
8970
+ for (const row of result.rows) {
8971
+ sessionAgent.set(row.session_uuid, row.agent_id);
8972
+ }
8973
+ const claudeDir = path18.join(os10.homedir(), ".claude", "projects");
8974
+ let projectDirs = [];
8975
+ try {
8976
+ const entries = await readdir(claudeDir);
8977
+ projectDirs = entries.map((e) => path18.join(claudeDir, e));
8978
+ } catch {
8979
+ return [];
8980
+ }
8981
+ const agentTotals = /* @__PURE__ */ new Map();
8982
+ for (const [sessionUuid, agentId] of sessionAgent) {
8983
+ for (const dir of projectDirs) {
8984
+ const jsonlPath = path18.join(dir, `${sessionUuid}.jsonl`);
8985
+ try {
8986
+ const usage = await extractSessionUsage(jsonlPath);
8987
+ if (usage.input === 0 && usage.output === 0) continue;
8988
+ const totals = agentTotals.get(agentId) ?? {
8989
+ input: 0,
8990
+ output: 0,
8991
+ cacheRead: 0,
8992
+ cacheCreate: 0,
8993
+ costUSD: 0,
8994
+ sessions: /* @__PURE__ */ new Set()
8995
+ };
8996
+ totals.input += usage.input;
8997
+ totals.output += usage.output;
8998
+ totals.cacheRead += usage.cacheRead;
8999
+ totals.cacheCreate += usage.cacheCreate;
9000
+ totals.costUSD += usage.costUSD;
9001
+ totals.sessions.add(sessionUuid);
9002
+ agentTotals.set(agentId, totals);
9003
+ break;
9004
+ } catch {
9005
+ }
9006
+ }
9007
+ }
9008
+ return Array.from(agentTotals.entries()).map(([agentId, t]) => ({
9009
+ agentId,
9010
+ inputTokens: t.input,
9011
+ outputTokens: t.output,
9012
+ cacheReadTokens: t.cacheRead,
9013
+ cacheCreationTokens: t.cacheCreate,
9014
+ costUSD: t.costUSD,
9015
+ sessions: t.sessions.size,
9016
+ period
9017
+ })).sort((a, b) => b.costUSD - a.costUSD);
9018
+ }
9019
+ async function extractSessionUsage(jsonlPath) {
9020
+ let input = 0;
9021
+ let output = 0;
9022
+ let cacheRead = 0;
9023
+ let cacheCreate = 0;
9024
+ let costUSD = 0;
9025
+ const seenMessageIds = /* @__PURE__ */ new Set();
9026
+ const rl = createInterface({
9027
+ input: createReadStream(jsonlPath, { encoding: "utf8" }),
9028
+ crlfDelay: Infinity
9029
+ });
9030
+ for await (const line of rl) {
9031
+ if (!line.includes('"type":"assistant"')) continue;
9032
+ try {
9033
+ const record = JSON.parse(line);
9034
+ if (record.type !== "assistant") continue;
9035
+ const messageId = record.message?.id;
9036
+ if (messageId) {
9037
+ if (seenMessageIds.has(messageId)) continue;
9038
+ seenMessageIds.add(messageId);
9039
+ }
9040
+ const usage = record.message?.usage;
9041
+ if (!usage) continue;
9042
+ const model = record.message?.model ?? "";
9043
+ const pricing = getPricing(model);
9044
+ const inp = usage.input_tokens ?? 0;
9045
+ const out = usage.output_tokens ?? 0;
9046
+ const cr = usage.cache_read_input_tokens ?? 0;
9047
+ const cc = usage.cache_creation_input_tokens ?? 0;
9048
+ input += inp;
9049
+ output += out;
9050
+ cacheRead += cr;
9051
+ cacheCreate += cc;
9052
+ if (pricing) {
9053
+ costUSD += inp * pricing.input + out * pricing.output + cr * pricing.cacheRead + cc * pricing.cacheWrite;
9054
+ }
9055
+ } catch {
9056
+ }
9057
+ }
9058
+ return { input, output, cacheRead, cacheCreate, costUSD };
9059
+ }
9060
+ function periodToCutoff(period) {
9061
+ const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
9062
+ return new Date(Date.now() - ms).toISOString();
9063
+ }
9064
+ var MODEL_PRICING, DEFAULT_PRICING;
9065
+ var init_token_spend = __esm({
9066
+ "src/lib/token-spend.ts"() {
9067
+ "use strict";
9068
+ init_database();
9069
+ MODEL_PRICING = {
9070
+ // Opus 4.5+ ($5/$25 — Anthropic price drop from original Opus 4)
9071
+ "claude-opus-4-7": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
9072
+ "claude-opus-4-6": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
9073
+ "claude-opus-4-5": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
9074
+ // Opus 4.0/4.1 (legacy $15/$75)
9075
+ "claude-opus-4-1": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
9076
+ "claude-opus-4": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
9077
+ // Sonnet 4.x
9078
+ "claude-sonnet-4": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
9079
+ // Sonnet 3.7/3.5
9080
+ "claude-3-7-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
9081
+ "claude-3-5-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
9082
+ // Haiku 4.5
9083
+ "claude-haiku-4-5": { input: 1 / 1e6, output: 5 / 1e6, cacheRead: 0.1 / 1e6, cacheWrite: 1.25 / 1e6 },
9084
+ // Haiku 3.5
9085
+ "claude-3-5-haiku": { input: 0.8 / 1e6, output: 4 / 1e6, cacheRead: 0.08 / 1e6, cacheWrite: 1 / 1e6 },
9086
+ // Opus 3
9087
+ "claude-3-opus": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
9088
+ // Sonnet 3
9089
+ "claude-3-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
9090
+ // Haiku 3
9091
+ "claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
9092
+ };
9093
+ DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
9094
+ }
9095
+ });
9096
+
8196
9097
  // src/lib/update-check.ts
8197
9098
  var update_check_exports = {};
8198
9099
  __export(update_check_exports, {
@@ -8202,9 +9103,9 @@ __export(update_check_exports, {
8202
9103
  });
8203
9104
  import { execSync as execSync11 } from "child_process";
8204
9105
  import { readFileSync as readFileSync13 } from "fs";
8205
- import path18 from "path";
9106
+ import path19 from "path";
8206
9107
  function getLocalVersion(packageRoot) {
8207
- const pkgPath = path18.join(packageRoot, "package.json");
9108
+ const pkgPath = path19.join(packageRoot, "package.json");
8208
9109
  const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
8209
9110
  return pkg.version;
8210
9111
  }
@@ -8276,9 +9177,9 @@ __export(device_registry_exports, {
8276
9177
  setFriendlyName: () => setFriendlyName
8277
9178
  });
8278
9179
  import crypto8 from "crypto";
8279
- import os9 from "os";
9180
+ import os11 from "os";
8280
9181
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16 } from "fs";
8281
- import path19 from "path";
9182
+ import path20 from "path";
8282
9183
  function getDeviceInfo() {
8283
9184
  if (existsSync16(DEVICE_JSON_PATH)) {
8284
9185
  try {
@@ -8290,13 +9191,13 @@ function getDeviceInfo() {
8290
9191
  } catch {
8291
9192
  }
8292
9193
  }
8293
- const hostname = os9.hostname();
9194
+ const hostname = os11.hostname();
8294
9195
  const info = {
8295
9196
  deviceId: crypto8.randomUUID(),
8296
9197
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
8297
9198
  hostname
8298
9199
  };
8299
- mkdirSync7(path19.dirname(DEVICE_JSON_PATH), { recursive: true });
9200
+ mkdirSync7(path20.dirname(DEVICE_JSON_PATH), { recursive: true });
8300
9201
  writeFileSync8(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
8301
9202
  return info;
8302
9203
  }
@@ -8337,7 +9238,7 @@ var init_device_registry = __esm({
8337
9238
  "src/lib/device-registry.ts"() {
8338
9239
  "use strict";
8339
9240
  init_config();
8340
- DEVICE_JSON_PATH = path19.join(EXE_AI_DIR, "device.json");
9241
+ DEVICE_JSON_PATH = path20.join(EXE_AI_DIR, "device.json");
8341
9242
  }
8342
9243
  });
8343
9244
 
@@ -8388,6 +9289,7 @@ function createWsClient(config, handlers) {
8388
9289
  let connected = false;
8389
9290
  let closed = false;
8390
9291
  let reconnectDelay = MIN_RECONNECT_MS;
9292
+ let reconnectAttempts = 0;
8391
9293
  let reconnectTimer = null;
8392
9294
  let heartbeatTimer = null;
8393
9295
  let currentAgents = [];
@@ -8404,6 +9306,7 @@ function createWsClient(config, handlers) {
8404
9306
  ws.onopen = () => {
8405
9307
  connected = true;
8406
9308
  reconnectDelay = MIN_RECONNECT_MS;
9309
+ reconnectAttempts = 0;
8407
9310
  handlers.onConnect();
8408
9311
  sendRaw({
8409
9312
  type: "register",
@@ -8470,6 +9373,14 @@ function createWsClient(config, handlers) {
8470
9373
  }
8471
9374
  function scheduleReconnect() {
8472
9375
  if (closed || reconnectTimer) return;
9376
+ reconnectAttempts++;
9377
+ if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
9378
+ process.stderr.write(
9379
+ `[ws-client] Cloud sync not available \u2014 gave up after ${MAX_RECONNECT_ATTEMPTS} attempts. Check your network or endpoint config.
9380
+ `
9381
+ );
9382
+ return;
9383
+ }
8473
9384
  const jitter = reconnectDelay * (0.75 + Math.random() * 0.5);
8474
9385
  reconnectTimer = setTimeout(() => {
8475
9386
  reconnectTimer = null;
@@ -8522,7 +9433,7 @@ function createWsClient(config, handlers) {
8522
9433
  }
8523
9434
  };
8524
9435
  }
8525
- var MIN_RECONNECT_MS, MAX_RECONNECT_MS, HEARTBEAT_INTERVAL_MS;
9436
+ var MIN_RECONNECT_MS, MAX_RECONNECT_MS, HEARTBEAT_INTERVAL_MS, MAX_RECONNECT_ATTEMPTS;
8526
9437
  var init_ws_client = __esm({
8527
9438
  "src/lib/ws-client.ts"() {
8528
9439
  "use strict";
@@ -8530,6 +9441,7 @@ var init_ws_client = __esm({
8530
9441
  MIN_RECONNECT_MS = 1e3;
8531
9442
  MAX_RECONNECT_MS = 3e4;
8532
9443
  HEARTBEAT_INTERVAL_MS = 3e4;
9444
+ MAX_RECONNECT_ATTEMPTS = 10;
8533
9445
  }
8534
9446
  });
8535
9447
 
@@ -8787,13 +9699,14 @@ var init_messaging = __esm({
8787
9699
  // src/lib/exe-daemon.ts
8788
9700
  init_config();
8789
9701
  init_memory();
9702
+ init_daemon_protocol();
8790
9703
  init_daemon_orchestration();
8791
9704
  import net2 from "net";
8792
9705
  import { writeFileSync as writeFileSync9, unlinkSync as unlinkSync7, mkdirSync as mkdirSync8, existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
8793
- import path20 from "path";
9706
+ import path21 from "path";
8794
9707
  import { getLlama } from "node-llama-cpp";
8795
- var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path20.join(EXE_AI_DIR, "exed.sock");
8796
- var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path20.join(EXE_AI_DIR, "exed.pid");
9708
+ var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path21.join(EXE_AI_DIR, "exed.sock");
9709
+ var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path21.join(EXE_AI_DIR, "exed.pid");
8797
9710
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
8798
9711
  var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
8799
9712
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
@@ -8819,7 +9732,7 @@ function enqueue(queue, entry) {
8819
9732
  queue.push(entry);
8820
9733
  }
8821
9734
  async function loadModel() {
8822
- const modelPath = path20.join(MODELS_DIR, MODEL_FILE);
9735
+ const modelPath = path21.join(MODELS_DIR, MODEL_FILE);
8823
9736
  if (!existsSync17(modelPath)) {
8824
9737
  process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
8825
9738
  `);
@@ -8926,13 +9839,15 @@ async function handleHealthCheck(socket, requestId) {
8926
9839
  testOk = false;
8927
9840
  }
8928
9841
  }
9842
+ const dbConnected = _storeInitialized;
8929
9843
  sendResponse(socket, {
8930
9844
  id: requestId,
8931
9845
  ...healthy && testOk ? {
8932
9846
  health: {
8933
9847
  status: "ok",
8934
9848
  uptime: Math.floor((Date.now() - _startedAt) / 1e3),
8935
- requests_served: _requestsServed
9849
+ requests_served: _requestsServed,
9850
+ db: { connected: dbConnected, totalDbRequests: _dbRequestsServed }
8936
9851
  }
8937
9852
  } : { error: "unhealthy: model not loaded or test embed failed" }
8938
9853
  });
@@ -8941,10 +9856,55 @@ async function handleHealthCheck(socket, requestId) {
8941
9856
  void shutdown();
8942
9857
  }
8943
9858
  }
9859
+ var _dbRequestsServed = 0;
9860
+ async function handleDbExecute(socket, requestId, sql, args) {
9861
+ try {
9862
+ if (!await ensureStoreForPolling()) {
9863
+ sendResponse(socket, { id: requestId, error: "DB not initialized" });
9864
+ return;
9865
+ }
9866
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
9867
+ const client = getClient2();
9868
+ const deserializedArgs = deserializeArgs(args);
9869
+ const result = await client.execute({ sql, args: deserializedArgs });
9870
+ _dbRequestsServed++;
9871
+ sendResponse(socket, { id: requestId, db: serializeResultSet(result) });
9872
+ } catch (err) {
9873
+ sendResponse(socket, {
9874
+ id: requestId,
9875
+ error: err instanceof Error ? err.message : String(err)
9876
+ });
9877
+ }
9878
+ }
9879
+ async function handleDbBatch(socket, requestId, statements, mode) {
9880
+ try {
9881
+ if (!await ensureStoreForPolling()) {
9882
+ sendResponse(socket, { id: requestId, error: "DB not initialized" });
9883
+ return;
9884
+ }
9885
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
9886
+ const client = getClient2();
9887
+ const stmts = statements.map((s) => ({
9888
+ sql: s.sql,
9889
+ args: deserializeArgs(s.args)
9890
+ }));
9891
+ const results = await client.batch(stmts, mode);
9892
+ _dbRequestsServed++;
9893
+ sendResponse(socket, {
9894
+ id: requestId,
9895
+ "db-batch": results.map(serializeResultSet)
9896
+ });
9897
+ } catch (err) {
9898
+ sendResponse(socket, {
9899
+ id: requestId,
9900
+ error: err instanceof Error ? err.message : String(err)
9901
+ });
9902
+ }
9903
+ }
8944
9904
  function startServer() {
8945
- mkdirSync8(path20.dirname(SOCKET_PATH2), { recursive: true });
9905
+ mkdirSync8(path21.dirname(SOCKET_PATH2), { recursive: true });
8946
9906
  for (const oldFile of ["embed.sock", "embed.pid"]) {
8947
- const oldPath = path20.join(path20.dirname(SOCKET_PATH2), oldFile);
9907
+ const oldPath = path21.join(path21.dirname(SOCKET_PATH2), oldFile);
8948
9908
  try {
8949
9909
  if (oldFile.endsWith(".pid")) {
8950
9910
  const pid = parseInt(readFileSync15(oldPath, "utf8").trim(), 10);
@@ -8983,6 +9943,22 @@ function startServer() {
8983
9943
  void handleHealthCheck(socket, request.id ?? "health");
8984
9944
  continue;
8985
9945
  }
9946
+ if (request.type === "db-execute") {
9947
+ if (!request.id || typeof request.sql !== "string") {
9948
+ sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid db-execute: missing id or sql" });
9949
+ continue;
9950
+ }
9951
+ void handleDbExecute(socket, request.id, request.sql, request.args ?? []);
9952
+ continue;
9953
+ }
9954
+ if (request.type === "db-batch") {
9955
+ if (!request.id || !Array.isArray(request.statements)) {
9956
+ sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid db-batch: missing id or statements" });
9957
+ continue;
9958
+ }
9959
+ void handleDbBatch(socket, request.id, request.statements, request.mode);
9960
+ continue;
9961
+ }
8986
9962
  if (!request.id || !Array.isArray(request.texts)) {
8987
9963
  sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
8988
9964
  continue;
@@ -9313,7 +10289,7 @@ function startWikiSync() {
9313
10289
  });
9314
10290
  }
9315
10291
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
9316
- var AGENT_STATS_PATH = path20.join(EXE_AI_DIR, "agent-stats.json");
10292
+ var AGENT_STATS_PATH = path21.join(EXE_AI_DIR, "agent-stats.json");
9317
10293
  async function writeAgentStats() {
9318
10294
  if (!await ensureStoreForPolling()) return;
9319
10295
  try {
@@ -9322,21 +10298,50 @@ async function writeAgentStats() {
9322
10298
  const result = await client.execute({
9323
10299
  sql: `SELECT agent_id,
9324
10300
  COUNT(*) as total,
9325
- SUM(CASE WHEN timestamp > datetime('now', '-7 days') THEN 1 ELSE 0 END) as growth_7d
10301
+ SUM(CASE WHEN timestamp > datetime('now', '-1 day') THEN 1 ELSE 0 END) as growth_24h,
10302
+ SUM(CASE WHEN timestamp > datetime('now', '-7 days') THEN 1 ELSE 0 END) as growth_7d,
10303
+ SUM(CASE WHEN timestamp > datetime('now', '-30 days') THEN 1 ELSE 0 END) as growth_30d
9326
10304
  FROM memories
9327
10305
  WHERE agent_id != 'default'
9328
10306
  GROUP BY agent_id
9329
10307
  ORDER BY total DESC`,
9330
10308
  args: []
9331
10309
  });
9332
- const agents = result.rows.map((row) => ({
10310
+ const agentsBase = result.rows.map((row) => ({
9333
10311
  id: row.agent_id,
9334
10312
  total: Number(row.total),
9335
- growth7d: Number(row.growth_7d)
10313
+ growth24h: Number(row.growth_24h),
10314
+ growth7d: Number(row.growth_7d),
10315
+ growth30d: Number(row.growth_30d),
10316
+ spend24h: { inputTokens: 0, outputTokens: 0, costUSD: 0, sessions: 0 },
10317
+ spend7d: { inputTokens: 0, outputTokens: 0, costUSD: 0, sessions: 0 },
10318
+ spend30d: { inputTokens: 0, outputTokens: 0, costUSD: 0, sessions: 0 }
9336
10319
  }));
10320
+ try {
10321
+ const { getAgentSpend: getAgentSpend2 } = await Promise.resolve().then(() => (init_token_spend(), token_spend_exports));
10322
+ const [spend24h, spend7d, spend30d] = await Promise.all([
10323
+ getAgentSpend2("24h"),
10324
+ getAgentSpend2("7d"),
10325
+ getAgentSpend2("30d")
10326
+ ]);
10327
+ const map24h = new Map(spend24h.map((s) => [s.agentId, s]));
10328
+ const map7d = new Map(spend7d.map((s) => [s.agentId, s]));
10329
+ const map30d = new Map(spend30d.map((s) => [s.agentId, s]));
10330
+ for (const agent of agentsBase) {
10331
+ const s24 = map24h.get(agent.id);
10332
+ const s7 = map7d.get(agent.id);
10333
+ const s30 = map30d.get(agent.id);
10334
+ if (s24) agent.spend24h = { inputTokens: s24.inputTokens, outputTokens: s24.outputTokens, costUSD: s24.costUSD, sessions: s24.sessions };
10335
+ if (s7) agent.spend7d = { inputTokens: s7.inputTokens, outputTokens: s7.outputTokens, costUSD: s7.costUSD, sessions: s7.sessions };
10336
+ if (s30) agent.spend30d = { inputTokens: s30.inputTokens, outputTokens: s30.outputTokens, costUSD: s30.costUSD, sessions: s30.sessions };
10337
+ }
10338
+ } catch (err) {
10339
+ process.stderr.write(`[exed] Agent spend merge: ${err instanceof Error ? err.message : String(err)}
10340
+ `);
10341
+ }
9337
10342
  const stats = {
9338
10343
  generated: (/* @__PURE__ */ new Date()).toISOString(),
9339
- agents,
10344
+ agents: agentsBase,
9340
10345
  daemon: {
9341
10346
  uptime: Math.floor((Date.now() - _startedAt) / 1e3),
9342
10347
  pid: process.pid
@@ -9459,6 +10464,32 @@ function startOrphanReaper() {
9459
10464
  process.stderr.write(`[exed] Orphan reaper started (every ${ORPHAN_REAP_INTERVAL_MS / 6e4}m)
9460
10465
  `);
9461
10466
  }
10467
+ var AUTO_WAKE_INTERVAL_MS = 60 * 1e3;
10468
+ function startAutoWake() {
10469
+ const tick = async () => {
10470
+ if (process.env.EXE_AUTO_WAKE === "0") return;
10471
+ if (!await ensureStoreForPolling()) return;
10472
+ try {
10473
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
10474
+ const { pollOrphanedTasks: pollOrphanedTasks2, createAutoWakeRealDeps: createAutoWakeRealDeps2 } = await Promise.resolve().then(() => (init_daemon_orchestration(), daemon_orchestration_exports));
10475
+ const { getCurrentSessionScope: getCurrentSessionScope2 } = await Promise.resolve().then(() => (init_task_scope(), task_scope_exports));
10476
+ const sessionScope = getCurrentSessionScope2() ?? "";
10477
+ const deps = createAutoWakeRealDeps2(getClient2, sessionScope, process.cwd());
10478
+ const woken = await pollOrphanedTasks2(deps);
10479
+ for (const agent of woken) {
10480
+ process.stderr.write(`[exed] Auto-wake: spawned ${agent}
10481
+ `);
10482
+ }
10483
+ } catch (err) {
10484
+ process.stderr.write(`[exed] Auto-wake error: ${err instanceof Error ? err.message : String(err)}
10485
+ `);
10486
+ }
10487
+ };
10488
+ const timer = setInterval(() => void tick(), AUTO_WAKE_INTERVAL_MS);
10489
+ timer.unref();
10490
+ process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
10491
+ `);
10492
+ }
9462
10493
  process.on("SIGINT", () => void shutdown());
9463
10494
  process.on("SIGTERM", () => void shutdown());
9464
10495
  function checkExistingDaemon() {
@@ -9532,6 +10563,7 @@ function startAutoUpdateCheck() {
9532
10563
  if (checkExistingDaemon()) {
9533
10564
  process.exit(0);
9534
10565
  }
10566
+ process.env.EXE_IS_DAEMON = "1";
9535
10567
  try {
9536
10568
  await loadModel();
9537
10569
  startServer();
@@ -9545,6 +10577,7 @@ try {
9545
10577
  startOrphanReaper();
9546
10578
  startAgentStats();
9547
10579
  startCapacityMonitoring();
10580
+ startAutoWake();
9548
10581
  startGraphExtraction();
9549
10582
  startWikiSync();
9550
10583
  startIntercomQueueDrain();