@askexenow/exe-os 0.8.33 → 0.8.37

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 (89) hide show
  1. package/dist/bin/backfill-conversations.js +341 -349
  2. package/dist/bin/backfill-responses.js +81 -13
  3. package/dist/bin/backfill-vectors.js +72 -12
  4. package/dist/bin/cleanup-stale-review-tasks.js +63 -3
  5. package/dist/bin/cli.js +1737 -1117
  6. package/dist/bin/exe-assign.js +89 -19
  7. package/dist/bin/exe-boot.js +951 -101
  8. package/dist/bin/exe-call.js +61 -2
  9. package/dist/bin/exe-dispatch.js +61 -13
  10. package/dist/bin/exe-doctor.js +63 -3
  11. package/dist/bin/exe-export-behaviors.js +71 -3
  12. package/dist/bin/exe-forget.js +69 -4
  13. package/dist/bin/exe-gateway.js +178 -45
  14. package/dist/bin/exe-heartbeat.js +79 -14
  15. package/dist/bin/exe-kill.js +71 -3
  16. package/dist/bin/exe-launch-agent.js +148 -14
  17. package/dist/bin/exe-link.js +1437 -0
  18. package/dist/bin/exe-new-employee.js +98 -13
  19. package/dist/bin/exe-pending-messages.js +74 -8
  20. package/dist/bin/exe-pending-notifications.js +63 -3
  21. package/dist/bin/exe-pending-reviews.js +77 -11
  22. package/dist/bin/exe-rename.js +1287 -0
  23. package/dist/bin/exe-review.js +73 -5
  24. package/dist/bin/exe-search.js +88 -14
  25. package/dist/bin/exe-session-cleanup.js +102 -28
  26. package/dist/bin/exe-status.js +64 -4
  27. package/dist/bin/exe-team.js +64 -4
  28. package/dist/bin/git-sweep.js +80 -5
  29. package/dist/bin/graph-backfill.js +71 -3
  30. package/dist/bin/graph-export.js +71 -3
  31. package/dist/bin/install.js +38 -8
  32. package/dist/bin/scan-tasks.js +80 -5
  33. package/dist/bin/setup.js +128 -10
  34. package/dist/bin/shard-migrate.js +71 -3
  35. package/dist/bin/wiki-sync.js +71 -3
  36. package/dist/gateway/index.js +179 -46
  37. package/dist/hooks/bug-report-worker.js +254 -28
  38. package/dist/hooks/commit-complete.js +80 -5
  39. package/dist/hooks/error-recall.js +89 -15
  40. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  41. package/dist/hooks/ingest-worker.js +185 -51
  42. package/dist/hooks/ingest.js +1 -1
  43. package/dist/hooks/instructions-loaded.js +81 -6
  44. package/dist/hooks/notification.js +81 -6
  45. package/dist/hooks/post-compact.js +81 -6
  46. package/dist/hooks/pre-compact.js +81 -6
  47. package/dist/hooks/pre-tool-use.js +423 -196
  48. package/dist/hooks/prompt-ingest-worker.js +91 -23
  49. package/dist/hooks/prompt-submit.js +159 -45
  50. package/dist/hooks/response-ingest-worker.js +96 -23
  51. package/dist/hooks/session-end.js +81 -6
  52. package/dist/hooks/session-start.js +89 -15
  53. package/dist/hooks/stop.js +81 -6
  54. package/dist/hooks/subagent-stop.js +81 -6
  55. package/dist/hooks/summary-worker.js +807 -55
  56. package/dist/index.js +198 -60
  57. package/dist/lib/cloud-sync.js +703 -18
  58. package/dist/lib/consolidation.js +4 -4
  59. package/dist/lib/database.js +64 -2
  60. package/dist/lib/device-registry.js +70 -3
  61. package/dist/lib/employee-templates.js +26 -0
  62. package/dist/lib/employees.js +34 -1
  63. package/dist/lib/exe-daemon.js +207 -74
  64. package/dist/lib/hybrid-search.js +88 -14
  65. package/dist/lib/identity-templates.js +51 -0
  66. package/dist/lib/identity.js +3 -3
  67. package/dist/lib/messaging.js +65 -17
  68. package/dist/lib/reminders.js +3 -3
  69. package/dist/lib/schedules.js +63 -3
  70. package/dist/lib/skill-learning.js +3 -3
  71. package/dist/lib/status-brief.js +63 -5
  72. package/dist/lib/store.js +73 -4
  73. package/dist/lib/task-router.js +4 -2
  74. package/dist/lib/tasks.js +95 -28
  75. package/dist/lib/tmux-routing.js +92 -23
  76. package/dist/mcp/server.js +800 -74
  77. package/dist/mcp/tools/complete-reminder.js +3 -3
  78. package/dist/mcp/tools/create-reminder.js +3 -3
  79. package/dist/mcp/tools/create-task.js +198 -31
  80. package/dist/mcp/tools/deactivate-behavior.js +4 -4
  81. package/dist/mcp/tools/list-reminders.js +3 -3
  82. package/dist/mcp/tools/list-tasks.js +19 -9
  83. package/dist/mcp/tools/send-message.js +69 -21
  84. package/dist/mcp/tools/update-task.js +28 -18
  85. package/dist/runtime/index.js +166 -28
  86. package/dist/tui/App.js +193 -40
  87. package/package.json +7 -3
  88. package/src/commands/exe/afk.md +116 -0
  89. package/src/commands/exe/rename.md +12 -0
@@ -454,11 +454,11 @@ async function connectEmbedDaemon() {
454
454
  }
455
455
  }
456
456
  const start = Date.now();
457
- let delay = 100;
457
+ let delay2 = 100;
458
458
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
459
- await new Promise((r) => setTimeout(r, delay));
459
+ await new Promise((r) => setTimeout(r, delay2));
460
460
  if (await connectToSocket()) return true;
461
- delay = Math.min(delay * 2, 3e3);
461
+ delay2 = Math.min(delay2 * 2, 3e3);
462
462
  }
463
463
  return false;
464
464
  }
@@ -550,11 +550,11 @@ async function embedViaClient(text, priority = "high") {
550
550
  `);
551
551
  killAndRespawnDaemon();
552
552
  const start = Date.now();
553
- let delay = 200;
553
+ let delay2 = 200;
554
554
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
555
- await new Promise((r) => setTimeout(r, delay));
555
+ await new Promise((r) => setTimeout(r, delay2));
556
556
  if (await connectToSocket()) break;
557
- delay = Math.min(delay * 2, 3e3);
557
+ delay2 = Math.min(delay2 * 2, 3e3);
558
558
  }
559
559
  if (!_connected) return null;
560
560
  }
@@ -566,11 +566,11 @@ async function embedViaClient(text, priority = "high") {
566
566
  `);
567
567
  killAndRespawnDaemon();
568
568
  const start = Date.now();
569
- let delay = 200;
569
+ let delay2 = 200;
570
570
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
571
- await new Promise((r) => setTimeout(r, delay));
571
+ await new Promise((r) => setTimeout(r, delay2));
572
572
  if (await connectToSocket()) break;
573
- delay = Math.min(delay * 2, 3e3);
573
+ delay2 = Math.min(delay2 * 2, 3e3);
574
574
  }
575
575
  if (!_connected) return null;
576
576
  const retry = await sendRequest([text], priority);
@@ -681,12 +681,68 @@ var init_embedder = __esm({
681
681
  }
682
682
  });
683
683
 
684
+ // src/lib/db-retry.ts
685
+ function isBusyError(err) {
686
+ if (err instanceof Error) {
687
+ const msg = err.message.toLowerCase();
688
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
689
+ }
690
+ return false;
691
+ }
692
+ function delay(ms) {
693
+ return new Promise((resolve) => setTimeout(resolve, ms));
694
+ }
695
+ async function retryOnBusy(fn, label) {
696
+ let lastError;
697
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
698
+ try {
699
+ return await fn();
700
+ } catch (err) {
701
+ lastError = err;
702
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
703
+ throw err;
704
+ }
705
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
706
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
707
+ process.stderr.write(
708
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
709
+ `
710
+ );
711
+ await delay(backoff + jitter);
712
+ }
713
+ }
714
+ throw lastError;
715
+ }
716
+ function wrapWithRetry(client) {
717
+ return new Proxy(client, {
718
+ get(target, prop, receiver) {
719
+ if (prop === "execute") {
720
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
721
+ }
722
+ if (prop === "batch") {
723
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
724
+ }
725
+ return Reflect.get(target, prop, receiver);
726
+ }
727
+ });
728
+ }
729
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
730
+ var init_db_retry = __esm({
731
+ "src/lib/db-retry.ts"() {
732
+ "use strict";
733
+ MAX_RETRIES = 3;
734
+ BASE_DELAY_MS = 200;
735
+ MAX_JITTER_MS = 300;
736
+ }
737
+ });
738
+
684
739
  // src/lib/database.ts
685
740
  import { createClient } from "@libsql/client";
686
741
  async function initDatabase(config2) {
687
742
  if (_client) {
688
743
  _client.close();
689
744
  _client = null;
745
+ _resilientClient = null;
690
746
  }
691
747
  const opts = {
692
748
  url: `file:${config2.dbPath}`
@@ -695,20 +751,27 @@ async function initDatabase(config2) {
695
751
  opts.encryptionKey = config2.encryptionKey;
696
752
  }
697
753
  _client = createClient(opts);
754
+ _resilientClient = wrapWithRetry(_client);
698
755
  }
699
756
  function isInitialized() {
700
757
  return _client !== null;
701
758
  }
702
759
  function getClient() {
760
+ if (!_resilientClient) {
761
+ throw new Error("Database client not initialized. Call initDatabase() first.");
762
+ }
763
+ return _resilientClient;
764
+ }
765
+ function getRawClient() {
703
766
  if (!_client) {
704
767
  throw new Error("Database client not initialized. Call initDatabase() first.");
705
768
  }
706
769
  return _client;
707
770
  }
708
771
  async function ensureSchema() {
709
- const client = getClient();
772
+ const client = getRawClient();
710
773
  await client.execute("PRAGMA journal_mode = WAL");
711
- await client.execute("PRAGMA busy_timeout = 5000");
774
+ await client.execute("PRAGMA busy_timeout = 30000");
712
775
  try {
713
776
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
714
777
  } catch {
@@ -1501,13 +1564,16 @@ async function disposeDatabase() {
1501
1564
  if (_client) {
1502
1565
  _client.close();
1503
1566
  _client = null;
1567
+ _resilientClient = null;
1504
1568
  }
1505
1569
  }
1506
- var _client, initTurso, disposeTurso;
1570
+ var _client, _resilientClient, initTurso, disposeTurso;
1507
1571
  var init_database = __esm({
1508
1572
  "src/lib/database.ts"() {
1509
1573
  "use strict";
1574
+ init_db_retry();
1510
1575
  _client = null;
1576
+ _resilientClient = null;
1511
1577
  initTurso = initDatabase;
1512
1578
  disposeTurso = disposeDatabase;
1513
1579
  }
@@ -1620,7 +1686,7 @@ function listShards() {
1620
1686
  }
1621
1687
  async function ensureShardSchema(client) {
1622
1688
  await client.execute("PRAGMA journal_mode = WAL");
1623
- await client.execute("PRAGMA busy_timeout = 5000");
1689
+ await client.execute("PRAGMA busy_timeout = 30000");
1624
1690
  try {
1625
1691
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1626
1692
  } catch {
@@ -1881,7 +1947,8 @@ async function writeMemory(record) {
1881
1947
  has_error: record.has_error ? 1 : 0,
1882
1948
  raw_text: record.raw_text,
1883
1949
  vector: record.vector,
1884
- version: _nextVersion++,
1950
+ version: 0,
1951
+ // Placeholder — assigned atomically at flush time
1885
1952
  task_id: record.task_id ?? null,
1886
1953
  importance: record.importance ?? 5,
1887
1954
  status: record.status ?? "active",
@@ -1915,6 +1982,13 @@ async function flushBatch() {
1915
1982
  _flushing = true;
1916
1983
  try {
1917
1984
  const batch = _pendingRecords.slice(0);
1985
+ const client = getClient();
1986
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1987
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1988
+ for (const row of batch) {
1989
+ row.version = baseVersion++;
1990
+ }
1991
+ _nextVersion = baseVersion;
1918
1992
  const buildStmt = (row) => {
1919
1993
  const hasVector = row.vector !== null;
1920
1994
  const taskId = row.task_id ?? null;
@@ -2817,7 +2891,7 @@ function getActiveAgent() {
2817
2891
  "tmux display-message -p '#{session_name}' 2>/dev/null",
2818
2892
  { encoding: "utf8", timeout: 2e3 }
2819
2893
  ).trim();
2820
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
2894
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
2821
2895
  if (empMatch && empMatch[1] !== "exe") {
2822
2896
  return { agentId: empMatch[1], agentRole: "employee" };
2823
2897
  }
@@ -2890,10 +2964,40 @@ var init_active_agent = __esm({
2890
2964
  });
2891
2965
 
2892
2966
  // src/lib/employees.ts
2967
+ var employees_exports = {};
2968
+ __export(employees_exports, {
2969
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
2970
+ addEmployee: () => addEmployee,
2971
+ getEmployee: () => getEmployee,
2972
+ getEmployeeByRole: () => getEmployeeByRole,
2973
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
2974
+ hasRole: () => hasRole,
2975
+ isMultiInstance: () => isMultiInstance,
2976
+ loadEmployees: () => loadEmployees,
2977
+ loadEmployeesSync: () => loadEmployeesSync,
2978
+ registerBinSymlinks: () => registerBinSymlinks,
2979
+ saveEmployees: () => saveEmployees,
2980
+ validateEmployeeName: () => validateEmployeeName
2981
+ });
2893
2982
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2894
- import { existsSync as existsSync7, symlinkSync, readlinkSync } from "fs";
2983
+ import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync5 } from "fs";
2895
2984
  import { execSync as execSync5 } from "child_process";
2896
2985
  import path9 from "path";
2986
+ function validateEmployeeName(name) {
2987
+ if (!name) {
2988
+ return { valid: false, error: "Name is required" };
2989
+ }
2990
+ if (name.length > 32) {
2991
+ return { valid: false, error: "Name must be 32 characters or fewer" };
2992
+ }
2993
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
2994
+ return {
2995
+ valid: false,
2996
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
2997
+ };
2998
+ }
2999
+ return { valid: true };
3000
+ }
2897
3001
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
2898
3002
  if (!existsSync7(employeesPath)) {
2899
3003
  return [];
@@ -2909,6 +3013,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
2909
3013
  await mkdir3(path9.dirname(employeesPath), { recursive: true });
2910
3014
  await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
2911
3015
  }
3016
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
3017
+ if (!existsSync7(employeesPath)) return [];
3018
+ try {
3019
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
3020
+ } catch {
3021
+ return [];
3022
+ }
3023
+ }
3024
+ function getEmployee(employees, name) {
3025
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
3026
+ }
3027
+ function getEmployeeByRole(employees, role) {
3028
+ const lower = role.toLowerCase();
3029
+ return employees.find((e) => e.role.toLowerCase() === lower);
3030
+ }
3031
+ function getEmployeeNamesByRole(employees, role) {
3032
+ const lower = role.toLowerCase();
3033
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
3034
+ }
3035
+ function hasRole(agentName, role) {
3036
+ const employees = loadEmployeesSync();
3037
+ const emp = getEmployee(employees, agentName);
3038
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
3039
+ }
3040
+ function isMultiInstance(agentName, employees) {
3041
+ const roster = employees ?? loadEmployeesSync();
3042
+ const emp = getEmployee(roster, agentName);
3043
+ if (!emp) return false;
3044
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
3045
+ }
2912
3046
  function addEmployee(employees, employee) {
2913
3047
  const normalized = { ...employee, name: employee.name.toLowerCase() };
2914
3048
  if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
@@ -2916,12 +3050,48 @@ function addEmployee(employees, employee) {
2916
3050
  }
2917
3051
  return [...employees, normalized];
2918
3052
  }
2919
- var EMPLOYEES_PATH;
3053
+ function registerBinSymlinks(name) {
3054
+ const created = [];
3055
+ const skipped = [];
3056
+ const errors = [];
3057
+ let exeBinPath;
3058
+ try {
3059
+ exeBinPath = execSync5("which exe", { encoding: "utf-8" }).trim();
3060
+ } catch {
3061
+ errors.push("Could not find 'exe' in PATH");
3062
+ return { created, skipped, errors };
3063
+ }
3064
+ const binDir = path9.dirname(exeBinPath);
3065
+ let target;
3066
+ try {
3067
+ target = readlinkSync(exeBinPath);
3068
+ } catch {
3069
+ errors.push("Could not read 'exe' symlink");
3070
+ return { created, skipped, errors };
3071
+ }
3072
+ for (const suffix of ["", "-opencode"]) {
3073
+ const linkName = `${name}${suffix}`;
3074
+ const linkPath = path9.join(binDir, linkName);
3075
+ if (existsSync7(linkPath)) {
3076
+ skipped.push(linkName);
3077
+ continue;
3078
+ }
3079
+ try {
3080
+ symlinkSync(target, linkPath);
3081
+ created.push(linkName);
3082
+ } catch (err) {
3083
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
3084
+ }
3085
+ }
3086
+ return { created, skipped, errors };
3087
+ }
3088
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
2920
3089
  var init_employees = __esm({
2921
3090
  "src/lib/employees.ts"() {
2922
3091
  "use strict";
2923
3092
  init_config();
2924
3093
  EMPLOYEES_PATH = path9.join(EXE_AI_DIR, "exe-employees.json");
3094
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
2925
3095
  }
2926
3096
  });
2927
3097
 
@@ -2940,7 +3110,7 @@ __export(license_exports, {
2940
3110
  saveLicense: () => saveLicense,
2941
3111
  validateLicense: () => validateLicense
2942
3112
  });
2943
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3113
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
2944
3114
  import { randomUUID as randomUUID2 } from "crypto";
2945
3115
  import path10 from "path";
2946
3116
  import { jwtVerify, importSPKI } from "jose";
@@ -2948,14 +3118,14 @@ function loadDeviceId() {
2948
3118
  const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
2949
3119
  try {
2950
3120
  if (existsSync8(deviceJsonPath)) {
2951
- const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
3121
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
2952
3122
  if (data.deviceId) return data.deviceId;
2953
3123
  }
2954
3124
  } catch {
2955
3125
  }
2956
3126
  try {
2957
3127
  if (existsSync8(DEVICE_ID_PATH)) {
2958
- const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
3128
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
2959
3129
  if (id2) return id2;
2960
3130
  }
2961
3131
  } catch {
@@ -2968,7 +3138,7 @@ function loadDeviceId() {
2968
3138
  function loadLicense() {
2969
3139
  try {
2970
3140
  if (!existsSync8(LICENSE_PATH)) return null;
2971
- return readFileSync5(LICENSE_PATH, "utf8").trim();
3141
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
2972
3142
  } catch {
2973
3143
  return null;
2974
3144
  }
@@ -3002,7 +3172,7 @@ async function verifyLicenseJwt(token) {
3002
3172
  async function getCachedLicense() {
3003
3173
  try {
3004
3174
  if (!existsSync8(CACHE_PATH)) return null;
3005
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
3175
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3006
3176
  if (!raw.token || typeof raw.token !== "string") return null;
3007
3177
  return await verifyLicenseJwt(raw.token);
3008
3178
  } catch {
@@ -3012,7 +3182,7 @@ async function getCachedLicense() {
3012
3182
  function readCachedToken() {
3013
3183
  try {
3014
3184
  if (!existsSync8(CACHE_PATH)) return null;
3015
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
3185
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3016
3186
  return typeof raw.token === "string" ? raw.token : null;
3017
3187
  } catch {
3018
3188
  return null;
@@ -3234,12 +3404,12 @@ __export(plan_limits_exports, {
3234
3404
  countActiveMemories: () => countActiveMemories,
3235
3405
  getLicenseSync: () => getLicenseSync
3236
3406
  });
3237
- import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
3407
+ import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
3238
3408
  import path11 from "path";
3239
3409
  function getLicenseSync() {
3240
3410
  try {
3241
3411
  if (!existsSync9(CACHE_PATH2)) return freeLicense();
3242
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
3412
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
3243
3413
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3244
3414
  const parts = raw.token.split(".");
3245
3415
  if (parts.length !== 3) return freeLicense();
@@ -3307,7 +3477,7 @@ function assertEmployeeLimitSync(rosterPath) {
3307
3477
  let count = 0;
3308
3478
  try {
3309
3479
  if (existsSync9(filePath)) {
3310
- const raw = readFileSync6(filePath, "utf8");
3480
+ const raw = readFileSync7(filePath, "utf8");
3311
3481
  const employees = JSON.parse(raw);
3312
3482
  count = Array.isArray(employees) ? employees.length : 0;
3313
3483
  }
@@ -3353,7 +3523,7 @@ import crypto5 from "crypto";
3353
3523
  import path14 from "path";
3354
3524
  import os2 from "os";
3355
3525
  import {
3356
- readFileSync as readFileSync7,
3526
+ readFileSync as readFileSync8,
3357
3527
  readdirSync as readdirSync3,
3358
3528
  unlinkSync as unlinkSync3,
3359
3529
  existsSync as existsSync10,
@@ -3405,7 +3575,7 @@ import crypto6 from "crypto";
3405
3575
  import path15 from "path";
3406
3576
  import { execSync as execSync6 } from "child_process";
3407
3577
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3408
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
3578
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
3409
3579
  async function writeCheckpoint(input) {
3410
3580
  const client = getClient();
3411
3581
  const row = await resolveTask(client, input.taskId);
@@ -3782,7 +3952,7 @@ async function ensureGitignoreExe(baseDir) {
3782
3952
  const gitignorePath = path15.join(baseDir, ".gitignore");
3783
3953
  try {
3784
3954
  if (existsSync11(gitignorePath)) {
3785
- const content = readFileSync8(gitignorePath, "utf-8");
3955
+ const content = readFileSync9(gitignorePath, "utf-8");
3786
3956
  if (/^\/?exe\/?$/m.test(content)) return;
3787
3957
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3788
3958
  } else {
@@ -3802,7 +3972,7 @@ var init_tasks_crud = __esm({
3802
3972
  });
3803
3973
 
3804
3974
  // src/lib/session-registry.ts
3805
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync12 } from "fs";
3975
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync12 } from "fs";
3806
3976
  import path16 from "path";
3807
3977
  import os3 from "os";
3808
3978
  function registerSession(entry) {
@@ -3821,7 +3991,7 @@ function registerSession(entry) {
3821
3991
  }
3822
3992
  function listSessions() {
3823
3993
  try {
3824
- const raw = readFileSync9(REGISTRY_PATH, "utf8");
3994
+ const raw = readFileSync10(REGISTRY_PATH, "utf8");
3825
3995
  return JSON.parse(raw);
3826
3996
  } catch {
3827
3997
  return [];
@@ -4016,7 +4186,7 @@ var init_provider_table = __esm({
4016
4186
  });
4017
4187
 
4018
4188
  // src/lib/intercom-queue.ts
4019
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
4189
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
4020
4190
  import path17 from "path";
4021
4191
  import os4 from "os";
4022
4192
  function ensureDir() {
@@ -4026,7 +4196,7 @@ function ensureDir() {
4026
4196
  function readQueue() {
4027
4197
  try {
4028
4198
  if (!existsSync13(QUEUE_PATH)) return [];
4029
- return JSON.parse(readFileSync10(QUEUE_PATH, "utf8"));
4199
+ return JSON.parse(readFileSync11(QUEUE_PATH, "utf8"));
4030
4200
  } catch {
4031
4201
  return [];
4032
4202
  }
@@ -4066,10 +4236,46 @@ var init_intercom_queue = __esm({
4066
4236
 
4067
4237
  // src/lib/tmux-routing.ts
4068
4238
  import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
4069
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync } from "fs";
4239
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync } from "fs";
4070
4240
  import path18 from "path";
4071
4241
  import os5 from "os";
4072
4242
  import { fileURLToPath as fileURLToPath2 } from "url";
4243
+ import { unlinkSync as unlinkSync4 } from "fs";
4244
+ function spawnLockPath(sessionName) {
4245
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4246
+ }
4247
+ function isProcessAlive(pid) {
4248
+ try {
4249
+ process.kill(pid, 0);
4250
+ return true;
4251
+ } catch {
4252
+ return false;
4253
+ }
4254
+ }
4255
+ function acquireSpawnLock2(sessionName) {
4256
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
4257
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4258
+ }
4259
+ const lockFile = spawnLockPath(sessionName);
4260
+ if (existsSync14(lockFile)) {
4261
+ try {
4262
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4263
+ const age = Date.now() - lock.timestamp;
4264
+ if (isProcessAlive(lock.pid) && age < 6e4) {
4265
+ return false;
4266
+ }
4267
+ } catch {
4268
+ }
4269
+ }
4270
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4271
+ return true;
4272
+ }
4273
+ function releaseSpawnLock2(sessionName) {
4274
+ try {
4275
+ unlinkSync4(spawnLockPath(sessionName));
4276
+ } catch {
4277
+ }
4278
+ }
4073
4279
  function resolveBehaviorsExporterScript() {
4074
4280
  try {
4075
4281
  const thisFile = fileURLToPath2(import.meta.url);
@@ -4115,7 +4321,7 @@ function extractRootExe(name) {
4115
4321
  }
4116
4322
  function getParentExe(sessionKey) {
4117
4323
  try {
4118
- const data = JSON.parse(readFileSync11(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4324
+ const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4119
4325
  return data.parentExe || null;
4120
4326
  } catch {
4121
4327
  return null;
@@ -4123,7 +4329,7 @@ function getParentExe(sessionKey) {
4123
4329
  }
4124
4330
  function getDispatchedBy(sessionKey) {
4125
4331
  try {
4126
- const data = JSON.parse(readFileSync11(
4332
+ const data = JSON.parse(readFileSync12(
4127
4333
  path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4128
4334
  "utf8"
4129
4335
  ));
@@ -4150,17 +4356,17 @@ function isEmployeeAlive(sessionName) {
4150
4356
  }
4151
4357
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
4152
4358
  const base = employeeSessionName(employeeName, exeSession);
4153
- if (!isAlive(base)) return 0;
4359
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
4154
4360
  for (let i = 2; i <= maxInstances; i++) {
4155
4361
  const candidate = employeeSessionName(employeeName, exeSession, i);
4156
- if (!isAlive(candidate)) return i;
4362
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
4157
4363
  }
4158
4364
  return null;
4159
4365
  }
4160
4366
  function readDebounceState() {
4161
4367
  try {
4162
4368
  if (!existsSync14(DEBOUNCE_FILE)) return {};
4163
- return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4369
+ return JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
4164
4370
  } catch {
4165
4371
  return {};
4166
4372
  }
@@ -4356,7 +4562,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4356
4562
  const claudeJsonPath = path18.join(os5.homedir(), ".claude.json");
4357
4563
  let claudeJson = {};
4358
4564
  try {
4359
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4565
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
4360
4566
  } catch {
4361
4567
  }
4362
4568
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4374,7 +4580,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4374
4580
  const settingsPath = path18.join(projSettingsDir, "settings.json");
4375
4581
  let settings = {};
4376
4582
  try {
4377
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4583
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
4378
4584
  } catch {
4379
4585
  }
4380
4586
  const perms = settings.permissions ?? {};
@@ -4487,6 +4693,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4487
4693
  command: spawnCommand
4488
4694
  });
4489
4695
  if (spawnResult.error) {
4696
+ releaseSpawnLock2(sessionName);
4490
4697
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4491
4698
  }
4492
4699
  transport.pipeLog(sessionName, logFile);
@@ -4524,6 +4731,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4524
4731
  }
4525
4732
  }
4526
4733
  if (!booted) {
4734
+ releaseSpawnLock2(sessionName);
4527
4735
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4528
4736
  }
4529
4737
  if (!useExeAgent) {
@@ -4540,9 +4748,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4540
4748
  pid: 0,
4541
4749
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4542
4750
  });
4751
+ releaseSpawnLock2(sessionName);
4543
4752
  return { sessionName };
4544
4753
  }
4545
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
4754
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
4546
4755
  var init_tmux_routing = __esm({
4547
4756
  "src/lib/tmux-routing.ts"() {
4548
4757
  "use strict";
@@ -4554,6 +4763,7 @@ var init_tmux_routing = __esm({
4554
4763
  init_provider_table();
4555
4764
  init_intercom_queue();
4556
4765
  init_plan_limits();
4766
+ SPAWN_LOCK_DIR = path18.join(os5.homedir(), ".exe-os", "spawn-locks");
4557
4767
  SESSION_CACHE = path18.join(os5.homedir(), ".exe-os", "session-cache");
4558
4768
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4559
4769
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -4566,7 +4776,7 @@ var init_tmux_routing = __esm({
4566
4776
 
4567
4777
  // src/lib/tasks-review.ts
4568
4778
  import path19 from "path";
4569
- import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
4779
+ import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as unlinkSync5 } from "fs";
4570
4780
  async function countPendingReviews() {
4571
4781
  const client = getClient();
4572
4782
  const result = await client.execute({
@@ -4690,7 +4900,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4690
4900
  if (existsSync15(cacheDir)) {
4691
4901
  for (const f of readdirSync4(cacheDir)) {
4692
4902
  if (f.startsWith("review-notified-")) {
4693
- unlinkSync4(path19.join(cacheDir, f));
4903
+ unlinkSync5(path19.join(cacheDir, f));
4694
4904
  }
4695
4905
  }
4696
4906
  }
@@ -4879,7 +5089,7 @@ async function dispatchTaskToEmployee(input) {
4879
5089
  } else {
4880
5090
  const projectDir = input.projectDir ?? process.cwd();
4881
5091
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
4882
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
5092
+ autoInstance: isMultiInstance(input.assignedTo)
4883
5093
  });
4884
5094
  if (result.status === "failed") {
4885
5095
  process.stderr.write(
@@ -4914,6 +5124,7 @@ var init_tasks_notify = __esm({
4914
5124
  init_session_key();
4915
5125
  init_notifications();
4916
5126
  init_transport();
5127
+ init_employees();
4917
5128
  }
4918
5129
  });
4919
5130
 
@@ -5276,7 +5487,7 @@ __export(tasks_exports, {
5276
5487
  writeCheckpoint: () => writeCheckpoint
5277
5488
  });
5278
5489
  import path21 from "path";
5279
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
5490
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
5280
5491
  async function createTask(input) {
5281
5492
  const result = await createTaskCore(input);
5282
5493
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5302,7 +5513,7 @@ async function updateTask(input) {
5302
5513
  writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5303
5514
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
5304
5515
  try {
5305
- unlinkSync5(cachePath);
5516
+ unlinkSync6(cachePath);
5306
5517
  } catch {
5307
5518
  }
5308
5519
  }
@@ -5611,7 +5822,7 @@ async function deliverLocalMessage(messageId) {
5611
5822
  return true;
5612
5823
  } catch {
5613
5824
  const newRetryCount = msg.retryCount + 1;
5614
- if (newRetryCount >= MAX_RETRIES) {
5825
+ if (newRetryCount >= MAX_RETRIES2) {
5615
5826
  await markFailed(messageId, "session unavailable after 10 retries");
5616
5827
  } else {
5617
5828
  await client.execute({
@@ -5629,25 +5840,25 @@ async function markFailed(messageId, reason) {
5629
5840
  args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
5630
5841
  });
5631
5842
  }
5632
- var MAX_RETRIES, _wsClientSend;
5843
+ var MAX_RETRIES2, _wsClientSend;
5633
5844
  var init_messaging = __esm({
5634
5845
  "src/lib/messaging.ts"() {
5635
5846
  "use strict";
5636
5847
  init_database();
5637
5848
  init_tmux_routing();
5638
- MAX_RETRIES = 10;
5849
+ MAX_RETRIES2 = 10;
5639
5850
  _wsClientSend = null;
5640
5851
  }
5641
5852
  });
5642
5853
 
5643
5854
  // src/gateway/whatsapp-accounts.ts
5644
- import { readFileSync as readFileSync13 } from "fs";
5855
+ import { readFileSync as readFileSync14 } from "fs";
5645
5856
  import { join } from "path";
5646
5857
  import { homedir } from "os";
5647
5858
  function loadAccounts() {
5648
5859
  if (cachedAccounts !== null) return cachedAccounts;
5649
5860
  try {
5650
- const raw = readFileSync13(CONFIG_PATH2, "utf8");
5861
+ const raw = readFileSync14(CONFIG_PATH2, "utf8");
5651
5862
  const parsed = JSON.parse(raw);
5652
5863
  if (!Array.isArray(parsed)) {
5653
5864
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -5680,6 +5891,376 @@ var init_whatsapp_accounts = __esm({
5680
5891
  }
5681
5892
  });
5682
5893
 
5894
+ // src/lib/consolidation.ts
5895
+ var consolidation_exports = {};
5896
+ __export(consolidation_exports, {
5897
+ buildConsolidationPrompt: () => buildConsolidationPrompt,
5898
+ consolidateCluster: () => consolidateCluster,
5899
+ countUnconsolidated: () => countUnconsolidated,
5900
+ groupMemories: () => groupMemories,
5901
+ isUserIdle: () => isUserIdle,
5902
+ pushToWiki: () => pushToWiki,
5903
+ runConsolidation: () => runConsolidation,
5904
+ selectUnconsolidated: () => selectUnconsolidated,
5905
+ storeConsolidation: () => storeConsolidation
5906
+ });
5907
+ import { randomUUID as randomUUID4 } from "crypto";
5908
+ async function selectUnconsolidated(client, limit = 200) {
5909
+ const result = await client.execute({
5910
+ sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
5911
+ FROM memories
5912
+ WHERE consolidated = 0
5913
+ AND timestamp >= datetime('now', '-7 days')
5914
+ ORDER BY timestamp DESC
5915
+ LIMIT ?`,
5916
+ args: [limit]
5917
+ });
5918
+ return result.rows.map((row) => ({
5919
+ id: row.id,
5920
+ agent_id: row.agent_id,
5921
+ project_name: row.project_name,
5922
+ tool_name: row.tool_name,
5923
+ raw_text: row.raw_text,
5924
+ timestamp: row.timestamp
5925
+ }));
5926
+ }
5927
+ function groupMemories(memories) {
5928
+ const byProject = /* @__PURE__ */ new Map();
5929
+ for (const mem of memories) {
5930
+ const key = `${mem.agent_id}::${mem.project_name}`;
5931
+ const list = byProject.get(key) ?? [];
5932
+ list.push(mem);
5933
+ byProject.set(key, list);
5934
+ }
5935
+ const clusters = [];
5936
+ for (const [key, projectMemories] of byProject) {
5937
+ const [agentId, projectName] = key.split("::");
5938
+ const byDay = /* @__PURE__ */ new Map();
5939
+ for (const mem of projectMemories) {
5940
+ const day = mem.timestamp.slice(0, 10);
5941
+ const list = byDay.get(day) ?? [];
5942
+ list.push(mem);
5943
+ byDay.set(day, list);
5944
+ }
5945
+ for (const [day, dayMemories] of byDay) {
5946
+ if (dayMemories.length <= 15) {
5947
+ clusters.push({
5948
+ agentId,
5949
+ projectName,
5950
+ dateRange: day,
5951
+ memories: dayMemories
5952
+ });
5953
+ } else {
5954
+ for (let i = 0; i < dayMemories.length; i += 12) {
5955
+ const chunk = dayMemories.slice(i, i + 12);
5956
+ clusters.push({
5957
+ agentId,
5958
+ projectName,
5959
+ dateRange: day,
5960
+ memories: chunk
5961
+ });
5962
+ }
5963
+ }
5964
+ }
5965
+ }
5966
+ return clusters;
5967
+ }
5968
+ function buildConsolidationPrompt(cluster) {
5969
+ const snippets = cluster.memories.map((m, i) => {
5970
+ const text = m.raw_text.length > 200 ? m.raw_text.slice(0, 200) + "..." : m.raw_text;
5971
+ return `${i + 1}. [${m.tool_name}] ${text}`;
5972
+ }).join("\n");
5973
+ return `You are reviewing a set of work memories from an AI coding agent.
5974
+ These are raw tool call records from ${cluster.dateRange}.
5975
+
5976
+ Agent: ${cluster.agentId} | Project: ${cluster.projectName} | Date: ${cluster.dateRange}
5977
+
5978
+ MEMORIES:
5979
+ ${snippets}
5980
+
5981
+ Extract EXACTLY THREE types of insights:
5982
+
5983
+ 1. KEY DECISIONS \u2014 choices that were made and why (at most 3)
5984
+ 2. RECURRING PATTERNS \u2014 actions or approaches that repeated (at most 3)
5985
+ 3. OPEN QUESTIONS \u2014 things that seem unresolved or risky (at most 2)
5986
+
5987
+ Format each as a single sentence. Be specific \u2014 include file names,
5988
+ function names, and concrete details. Skip if no insight for a category.`;
5989
+ }
5990
+ async function consolidateCluster(cluster, model) {
5991
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
5992
+ const client = new Anthropic();
5993
+ const prompt = buildConsolidationPrompt(cluster);
5994
+ const response = await client.messages.create({
5995
+ model,
5996
+ max_tokens: 300,
5997
+ messages: [{ role: "user", content: prompt }]
5998
+ });
5999
+ const textBlock = response.content.find((b) => b.type === "text");
6000
+ return textBlock?.text ?? "";
6001
+ }
6002
+ async function storeConsolidation(client, cluster, synthesisText, embedFn) {
6003
+ const consolidatedId = randomUUID4();
6004
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6005
+ const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
6006
+
6007
+ ${synthesisText}`;
6008
+ let vector = null;
6009
+ if (embedFn) {
6010
+ try {
6011
+ vector = await embedFn(rawText);
6012
+ } catch {
6013
+ }
6014
+ }
6015
+ const insertSql = vector ? `INSERT INTO memories
6016
+ (id, agent_id, agent_role, session_id, timestamp,
6017
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
6018
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1, 9)` : `INSERT INTO memories
6019
+ (id, agent_id, agent_role, session_id, timestamp,
6020
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
6021
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1, 9)`;
6022
+ const insertArgs = vector ? [consolidatedId, cluster.agentId, now, cluster.projectName, rawText, vectorToBlob(vector)] : [consolidatedId, cluster.agentId, now, cluster.projectName, rawText];
6023
+ await client.execute({ sql: insertSql, args: insertArgs });
6024
+ const sourceIds = cluster.memories.map((m) => m.id);
6025
+ const linkStmts = sourceIds.map((sourceId) => ({
6026
+ sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
6027
+ VALUES (?, ?, ?, ?)`,
6028
+ args: [randomUUID4(), consolidatedId, sourceId, now]
6029
+ }));
6030
+ const placeholders = sourceIds.map(() => "?").join(",");
6031
+ const markStmt = {
6032
+ sql: `UPDATE memories SET consolidated = 1 WHERE id IN (${placeholders})`,
6033
+ args: sourceIds
6034
+ };
6035
+ await client.batch([...linkStmts, markStmt], "write");
6036
+ return { consolidatedMemoryId: consolidatedId, sourceIds, rawText };
6037
+ }
6038
+ async function pushToWiki(consolidation, config2) {
6039
+ if (!config2.wikiEnabled || !config2.wikiAutoUpdate) {
6040
+ return { updated: false };
6041
+ }
6042
+ const apiUrl = config2.wikiUrl || process.env.EXE_WIKI_API_URL;
6043
+ const apiKey = config2.wikiApiKey || process.env.EXE_WIKI_API_KEY;
6044
+ if (!apiUrl || !apiKey) {
6045
+ return { updated: false };
6046
+ }
6047
+ const workspace = config2.wikiWorkspaceMapping[consolidation.projectName] ?? consolidation.projectName;
6048
+ try {
6049
+ const listRes = await fetch(
6050
+ `${apiUrl}/v1/workspace/${encodeURIComponent(workspace)}/documents`,
6051
+ {
6052
+ headers: { Authorization: `Bearer ${apiKey}` },
6053
+ signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
6054
+ }
6055
+ );
6056
+ if (!listRes.ok) {
6057
+ return { updated: false, error: `Workspace "${workspace}" not accessible (${listRes.status})` };
6058
+ }
6059
+ const listJson = await listRes.json();
6060
+ const docs = listJson.documents ?? listJson.localFiles?.items ?? [];
6061
+ const contentLines = consolidation.rawText.split("\n").filter((l) => l.trim() && !l.startsWith("CONSOLIDATION") && !l.match(/^[A-Z\s]+:$/)).join(" ");
6062
+ const keywords = contentLines.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
6063
+ let bestMatch = null;
6064
+ for (const doc of docs) {
6065
+ if (!doc.id || !doc.title) continue;
6066
+ const titleWords = doc.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
6067
+ if (titleWords.length === 0) continue;
6068
+ const matchCount = titleWords.filter(
6069
+ (tw) => keywords.some((k) => k.includes(tw) || tw.includes(k))
6070
+ ).length;
6071
+ const score = matchCount / titleWords.length;
6072
+ if (score > (bestMatch?.score ?? 0)) {
6073
+ bestMatch = { id: doc.id, title: doc.title, score };
6074
+ }
6075
+ }
6076
+ if (bestMatch && bestMatch.score >= config2.wikiAutoUpdateThreshold) {
6077
+ const updateRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
6078
+ method: "POST",
6079
+ headers: {
6080
+ "Content-Type": "application/json",
6081
+ Authorization: `Bearer ${apiKey}`
6082
+ },
6083
+ body: JSON.stringify({
6084
+ textContent: consolidation.rawText,
6085
+ metadata: { title: bestMatch.title, appendTo: bestMatch.id },
6086
+ workspaceSlugs: [workspace]
6087
+ }),
6088
+ signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
6089
+ });
6090
+ if (updateRes.ok) {
6091
+ process.stderr.write(
6092
+ `[consolidation] Wiki auto-updated: ${workspace}/${bestMatch.title}
6093
+ `
6094
+ );
6095
+ return { updated: true, action: "updated", page: bestMatch.title };
6096
+ }
6097
+ return { updated: false, error: `Update failed (${updateRes.status})` };
6098
+ }
6099
+ if (config2.wikiAutoUpdateCreateNew) {
6100
+ const title = `Consolidated Insights \u2014 ${consolidation.projectName} (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`;
6101
+ const createRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
6102
+ method: "POST",
6103
+ headers: {
6104
+ "Content-Type": "application/json",
6105
+ Authorization: `Bearer ${apiKey}`
6106
+ },
6107
+ body: JSON.stringify({
6108
+ textContent: consolidation.rawText,
6109
+ metadata: { title },
6110
+ workspaceSlugs: [workspace]
6111
+ }),
6112
+ signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
6113
+ });
6114
+ if (createRes.ok) {
6115
+ process.stderr.write(
6116
+ `[consolidation] Wiki page created: ${workspace}/${title}
6117
+ `
6118
+ );
6119
+ return { updated: true, action: "created", page: title };
6120
+ }
6121
+ return { updated: false, error: `Create failed (${createRes.status})` };
6122
+ }
6123
+ return { updated: false };
6124
+ } catch (err) {
6125
+ const msg = err instanceof Error ? err.message : String(err);
6126
+ process.stderr.write(`[consolidation] Wiki push failed: ${msg}
6127
+ `);
6128
+ return { updated: false, error: msg };
6129
+ }
6130
+ }
6131
+ async function runConsolidation(client, options) {
6132
+ const memories = await selectUnconsolidated(client);
6133
+ if (memories.length < 20) {
6134
+ return { clustersProcessed: 0, memoriesConsolidated: 0 };
6135
+ }
6136
+ const clusters = groupMemories(memories);
6137
+ let clustersProcessed = 0;
6138
+ let memoriesConsolidated = 0;
6139
+ for (const cluster of clusters) {
6140
+ if (clustersProcessed >= options.maxCalls) break;
6141
+ if (cluster.memories.length < 3) continue;
6142
+ try {
6143
+ const isExe = cluster.agentId === "exe";
6144
+ if (isExe) {
6145
+ const synthesis = await consolidateCluster(cluster, options.model);
6146
+ if (!synthesis.trim()) continue;
6147
+ const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
6148
+ if (options.wikiConfig) {
6149
+ await pushToWiki(
6150
+ { ...result, projectName: cluster.projectName },
6151
+ options.wikiConfig
6152
+ ).catch((err) => {
6153
+ process.stderr.write(
6154
+ `[consolidation] Wiki push error (non-fatal): ${err instanceof Error ? err.message : String(err)}
6155
+ `
6156
+ );
6157
+ });
6158
+ }
6159
+ const sourceIds = result.sourceIds;
6160
+ if (sourceIds.length > 0) {
6161
+ const placeholders = sourceIds.map(() => "?").join(",");
6162
+ await client.execute({
6163
+ sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
6164
+ args: sourceIds
6165
+ });
6166
+ }
6167
+ } else {
6168
+ const dedupCount = await dedupCluster(client, cluster, options.embedFn);
6169
+ memoriesConsolidated += dedupCount;
6170
+ if (dedupCount === 0) continue;
6171
+ }
6172
+ clustersProcessed++;
6173
+ memoriesConsolidated += isExe ? cluster.memories.length : 0;
6174
+ } catch (err) {
6175
+ process.stderr.write(
6176
+ `[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
6177
+ `
6178
+ );
6179
+ }
6180
+ }
6181
+ return { clustersProcessed, memoriesConsolidated };
6182
+ }
6183
+ async function dedupCluster(client, cluster, embedFn) {
6184
+ if (!embedFn || cluster.memories.length < 2) return 0;
6185
+ const vectors = [];
6186
+ for (const mem of cluster.memories) {
6187
+ try {
6188
+ const v = await embedFn(mem.raw_text.slice(0, 500));
6189
+ vectors.push({ id: mem.id, vector: v });
6190
+ } catch {
6191
+ }
6192
+ }
6193
+ if (vectors.length < 2) return 0;
6194
+ const toArchive = /* @__PURE__ */ new Set();
6195
+ for (let i = 0; i < vectors.length; i++) {
6196
+ if (toArchive.has(vectors[i].id)) continue;
6197
+ for (let j = i + 1; j < vectors.length; j++) {
6198
+ if (toArchive.has(vectors[j].id)) continue;
6199
+ const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
6200
+ if (sim > 0.95) {
6201
+ toArchive.add(vectors[j].id);
6202
+ }
6203
+ }
6204
+ }
6205
+ if (toArchive.size === 0) return 0;
6206
+ const ids = [...toArchive];
6207
+ const placeholders = ids.map(() => "?").join(",");
6208
+ await client.execute({
6209
+ sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
6210
+ args: ids
6211
+ });
6212
+ const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
6213
+ if (survivors.length > 0) {
6214
+ const survivorPlaceholders = survivors.map(() => "?").join(",");
6215
+ await client.execute({
6216
+ sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
6217
+ args: survivors
6218
+ });
6219
+ }
6220
+ return ids.length;
6221
+ }
6222
+ function cosineSimilarity(a, b) {
6223
+ let dot = 0, normA = 0, normB = 0;
6224
+ for (let i = 0; i < a.length; i++) {
6225
+ dot += a[i] * b[i];
6226
+ normA += a[i] * a[i];
6227
+ normB += b[i] * b[i];
6228
+ }
6229
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
6230
+ return denom === 0 ? 0 : dot / denom;
6231
+ }
6232
+ async function isUserIdle(client, idleMinutes = 30) {
6233
+ const result = await client.execute({
6234
+ sql: `SELECT MAX(timestamp) as last_activity
6235
+ FROM memories
6236
+ WHERE tool_name != 'consolidation'
6237
+ AND timestamp >= datetime('now', '-1 day')`,
6238
+ args: []
6239
+ });
6240
+ const lastActivity = result.rows[0]?.last_activity;
6241
+ if (!lastActivity) return true;
6242
+ const lastMs = new Date(lastActivity).getTime();
6243
+ const now = Date.now();
6244
+ return now - lastMs >= idleMinutes * 60 * 1e3;
6245
+ }
6246
+ async function countUnconsolidated(client) {
6247
+ const result = await client.execute({
6248
+ sql: `SELECT COUNT(*) as cnt FROM memories
6249
+ WHERE consolidated = 0
6250
+ AND timestamp >= datetime('now', '-7 days')`,
6251
+ args: []
6252
+ });
6253
+ return Number(result.rows[0]?.cnt ?? 0);
6254
+ }
6255
+ var WIKI_FETCH_TIMEOUT_MS;
6256
+ var init_consolidation = __esm({
6257
+ "src/lib/consolidation.ts"() {
6258
+ "use strict";
6259
+ init_store();
6260
+ WIKI_FETCH_TIMEOUT_MS = 1e4;
6261
+ }
6262
+ });
6263
+
5683
6264
  // src/mcp/server.ts
5684
6265
  init_embedder();
5685
6266
  init_store();
@@ -6229,7 +6810,8 @@ function registerGetSessionContext(server2) {
6229
6810
  has_error, raw_text, vector, task_id
6230
6811
  FROM memories
6231
6812
  WHERE session_id = ?
6232
- ORDER BY timestamp ASC`,
6813
+ ORDER BY timestamp ASC
6814
+ LIMIT 500`,
6233
6815
  args: [session_id]
6234
6816
  });
6235
6817
  if (result.rows.length === 0) {
@@ -6779,8 +7361,8 @@ function registerCreateTask(server2) {
6779
7361
  try {
6780
7362
  const exeSession = resolveExeSession();
6781
7363
  if (exeSession) {
6782
- const MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["tom", "sasha"]);
6783
- const useAutoInstance = MULTI_INSTANCE_ROLES.has(assigned_to);
7364
+ const { isMultiInstance: isMultiInstance2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
7365
+ const useAutoInstance = isMultiInstance2(assigned_to);
6784
7366
  const { loadConfigSync: loadConfigSync2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6785
7367
  const cfg = loadConfigSync2();
6786
7368
  const result = ensureEmployee(assigned_to, exeSession, process.cwd(), {
@@ -7588,7 +8170,7 @@ import { z as z20 } from "zod";
7588
8170
  // src/lib/identity.ts
7589
8171
  init_config();
7590
8172
  init_database();
7591
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
8173
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
7592
8174
  import { readdirSync as readdirSync5 } from "fs";
7593
8175
  import path22 from "path";
7594
8176
  import { createHash } from "crypto";
@@ -7641,7 +8223,7 @@ function contentHash(content) {
7641
8223
  function getIdentity(agentId) {
7642
8224
  const filePath = identityPath(agentId);
7643
8225
  if (!existsSync16(filePath)) return null;
7644
- const raw = readFileSync12(filePath, "utf-8");
8226
+ const raw = readFileSync13(filePath, "utf-8");
7645
8227
  const { frontmatter, body } = parseFrontmatter(raw);
7646
8228
  return {
7647
8229
  agentId,
@@ -7932,12 +8514,20 @@ async function resolveWorkspace(slug_or_id, name) {
7932
8514
  };
7933
8515
  }
7934
8516
  async function embedTexts(texts) {
7935
- const settled = await Promise.allSettled(
7936
- texts.map((text) => embed(text))
7937
- );
7938
- return settled.map(
7939
- (result) => result.status === "fulfilled" ? result.value : null
7940
- );
8517
+ const BATCH_SIZE = 50;
8518
+ const results = [];
8519
+ for (let i = 0; i < texts.length; i += BATCH_SIZE) {
8520
+ const batch = texts.slice(i, i + BATCH_SIZE);
8521
+ const settled = await Promise.allSettled(
8522
+ batch.map((text) => embed(text))
8523
+ );
8524
+ results.push(
8525
+ ...settled.map(
8526
+ (result) => result.status === "fulfilled" ? result.value : null
8527
+ )
8528
+ );
8529
+ }
8530
+ return results;
7941
8531
  }
7942
8532
  function buildChunkStatement(input) {
7943
8533
  const hasVector = input.vector !== null;
@@ -8297,7 +8887,6 @@ function registerAcknowledgeMessages(server2) {
8297
8887
  const agent = getActiveAgent();
8298
8888
  const agentId = agent.agentId || "exe";
8299
8889
  const client = getClient();
8300
- await client.execute({ sql: "PRAGMA busy_timeout = 5000", args: [] });
8301
8890
  const result = await client.execute({
8302
8891
  sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
8303
8892
  WHERE target_agent = ? AND status IN ('pending', 'delivered')`,
@@ -8627,7 +9216,7 @@ function registerSendWhatsapp(server2) {
8627
9216
  import { z as z29 } from "zod";
8628
9217
 
8629
9218
  // src/automation/trigger-engine.ts
8630
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
9219
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
8631
9220
  import { randomUUID as randomUUID3 } from "crypto";
8632
9221
  import path23 from "path";
8633
9222
  import os6 from "os";
@@ -8635,7 +9224,7 @@ var TRIGGERS_PATH = path23.join(os6.homedir(), ".exe-os", "triggers.json");
8635
9224
  function loadTriggers(project) {
8636
9225
  if (!existsSync17(TRIGGERS_PATH)) return [];
8637
9226
  try {
8638
- const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
9227
+ const raw = readFileSync15(TRIGGERS_PATH, "utf-8");
8639
9228
  const all = JSON.parse(raw);
8640
9229
  if (!Array.isArray(all)) return [];
8641
9230
  if (project) {
@@ -8933,7 +9522,7 @@ function registerListTriggers(server2) {
8933
9522
  import { z as z31 } from "zod";
8934
9523
 
8935
9524
  // src/automation/starter-packs/index.ts
8936
- import { readFileSync as readFileSync15, readdirSync as readdirSync6, existsSync as existsSync18 } from "fs";
9525
+ import { readFileSync as readFileSync16, readdirSync as readdirSync6, existsSync as existsSync18 } from "fs";
8937
9526
  import path24 from "path";
8938
9527
  import { fileURLToPath as fileURLToPath3 } from "url";
8939
9528
  var __dirname = path24.dirname(fileURLToPath3(import.meta.url));
@@ -8955,7 +9544,7 @@ function loadPack(industry) {
8955
9544
  let customObjects = [];
8956
9545
  try {
8957
9546
  customObjects = JSON.parse(
8958
- readFileSync15(objectsPath, "utf-8")
9547
+ readFileSync16(objectsPath, "utf-8")
8959
9548
  );
8960
9549
  } catch {
8961
9550
  customObjects = [];
@@ -8964,7 +9553,7 @@ function loadPack(industry) {
8964
9553
  if (existsSync18(triggersPath)) {
8965
9554
  try {
8966
9555
  triggers = JSON.parse(
8967
- readFileSync15(triggersPath, "utf-8")
9556
+ readFileSync16(triggersPath, "utf-8")
8968
9557
  );
8969
9558
  } catch {
8970
9559
  triggers = [];
@@ -8974,7 +9563,7 @@ function loadPack(industry) {
8974
9563
  if (existsSync18(wikiDir)) {
8975
9564
  const files = readdirSync6(wikiDir).filter((f) => f.endsWith(".md"));
8976
9565
  for (const file of files) {
8977
- const content = readFileSync15(path24.join(wikiDir, file), "utf-8");
9566
+ const content = readFileSync16(path24.join(wikiDir, file), "utf-8");
8978
9567
  const titleMatch = content.match(/^#\s+(.+)/m);
8979
9568
  wikiSeeds.push({
8980
9569
  filename: file,
@@ -8986,7 +9575,7 @@ function loadPack(industry) {
8986
9575
  let manifest = {};
8987
9576
  if (existsSync18(manifestPath)) {
8988
9577
  try {
8989
- manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
9578
+ manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
8990
9579
  } catch {
8991
9580
  manifest = {};
8992
9581
  }
@@ -8994,7 +9583,7 @@ function loadPack(industry) {
8994
9583
  let identityContext = null;
8995
9584
  if (existsSync18(identityContextPath)) {
8996
9585
  try {
8997
- identityContext = readFileSync15(identityContextPath, "utf-8");
9586
+ identityContext = readFileSync16(identityContextPath, "utf-8");
8998
9587
  } catch {
8999
9588
  identityContext = null;
9000
9589
  }
@@ -10474,7 +11063,7 @@ function registerQueryConversations(server2) {
10474
11063
 
10475
11064
  // src/mcp/tools/load-skill.ts
10476
11065
  import { z as z39 } from "zod";
10477
- import { readFileSync as readFileSync16, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
11066
+ import { readFileSync as readFileSync17, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
10478
11067
  import path27 from "path";
10479
11068
  import { homedir as homedir2 } from "os";
10480
11069
  var SKILLS_DIR = path27.join(homedir2(), ".claude", "skills");
@@ -10531,7 +11120,7 @@ ${skills.map((s) => `- ${s}`).join("\n")}`
10531
11120
  const sanitized = path27.basename(skill_name);
10532
11121
  const skillFile = path27.join(SKILLS_DIR, sanitized, "SKILL.md");
10533
11122
  try {
10534
- const content = readFileSync16(skillFile, "utf-8");
11123
+ const content = readFileSync17(skillFile, "utf-8");
10535
11124
  return {
10536
11125
  content: [{
10537
11126
  type: "text",
@@ -10557,12 +11146,133 @@ ${available.map((s) => `- ${s}`).join("\n")}` : "\n\nNo skills found in ~/.claud
10557
11146
  );
10558
11147
  }
10559
11148
 
11149
+ // src/mcp/tools/consolidate-memories.ts
11150
+ init_database();
11151
+ init_active_agent();
11152
+ import { z as z40 } from "zod";
11153
+ function registerConsolidateMemories(server2) {
11154
+ server2.registerTool(
11155
+ "consolidate_memories",
11156
+ {
11157
+ title: "Consolidate Memories",
11158
+ description: "Trigger content-aware memory consolidation. Groups recent memories by project, uses LLM to extract key facts, decisions, and patterns, then stores a high-importance summary and archives the originals. Runs automatically during idle time, but can be triggered manually when you want to compress your working memory.",
11159
+ inputSchema: {
11160
+ max_clusters: z40.coerce.number().optional().default(5).describe("Maximum number of clusters to consolidate (default 5)"),
11161
+ model: z40.string().optional().describe("LLM model for summarization (defaults to config value)")
11162
+ }
11163
+ },
11164
+ async ({ max_clusters, model }) => {
11165
+ const { agentId } = getActiveAgent();
11166
+ try {
11167
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
11168
+ const config2 = await loadConfig2();
11169
+ const consolidationModel = model ?? config2.consolidationModel ?? "claude-haiku-4-5-20251001";
11170
+ const { runConsolidation: runConsolidation2 } = await Promise.resolve().then(() => (init_consolidation(), consolidation_exports));
11171
+ const client = getClient();
11172
+ let embedFn;
11173
+ try {
11174
+ const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
11175
+ embedFn = embed2;
11176
+ } catch {
11177
+ }
11178
+ const result = await runConsolidation2(client, {
11179
+ model: consolidationModel,
11180
+ maxCalls: max_clusters,
11181
+ embedFn
11182
+ });
11183
+ if (result.clustersProcessed === 0) {
11184
+ return {
11185
+ content: [{
11186
+ type: "text",
11187
+ text: `No consolidation needed \u2014 fewer than 20 unconsolidated memories for ${agentId}.`
11188
+ }]
11189
+ };
11190
+ }
11191
+ return {
11192
+ content: [{
11193
+ type: "text",
11194
+ text: `Consolidation complete:
11195
+ - Clusters processed: ${result.clustersProcessed}
11196
+ - Memories consolidated: ${result.memoriesConsolidated}
11197
+
11198
+ Consolidated summaries stored as tier-1 (importance=9) memories.`
11199
+ }]
11200
+ };
11201
+ } catch (err) {
11202
+ return {
11203
+ content: [{
11204
+ type: "text",
11205
+ text: `Consolidation failed: ${err instanceof Error ? err.message : String(err)}`
11206
+ }]
11207
+ };
11208
+ }
11209
+ }
11210
+ );
11211
+ }
11212
+
11213
+ // src/lib/telemetry.ts
11214
+ var ENABLED = process.env.EXE_TELEMETRY === "1";
11215
+ var initialized = false;
11216
+ async function ensureInit() {
11217
+ if (initialized || !ENABLED) return;
11218
+ initialized = true;
11219
+ try {
11220
+ const { NodeSDK } = await import("@opentelemetry/sdk-node");
11221
+ const { ConsoleSpanExporter } = await import("@opentelemetry/sdk-trace-base");
11222
+ const sdk = new NodeSDK({
11223
+ serviceName: "exe-os",
11224
+ traceExporter: new ConsoleSpanExporter()
11225
+ });
11226
+ sdk.start();
11227
+ process.stderr.write("[exe-os] OpenTelemetry tracing enabled\n");
11228
+ } catch (err) {
11229
+ process.stderr.write(
11230
+ `[exe-os] OpenTelemetry init failed: ${err instanceof Error ? err.message : String(err)}
11231
+ `
11232
+ );
11233
+ }
11234
+ }
11235
+ async function withTrace(toolName, fn) {
11236
+ if (!ENABLED) return fn();
11237
+ await ensureInit();
11238
+ const { trace, SpanStatusCode } = await import("@opentelemetry/api");
11239
+ const tracer = trace.getTracer("exe-os", "1.0.0");
11240
+ return tracer.startActiveSpan(`mcp.tool.${toolName}`, async (span) => {
11241
+ span.setAttribute("mcp.tool.name", toolName);
11242
+ try {
11243
+ const result = await fn();
11244
+ span.setStatus({ code: SpanStatusCode.OK });
11245
+ return result;
11246
+ } catch (err) {
11247
+ span.setStatus({
11248
+ code: SpanStatusCode.ERROR,
11249
+ message: err instanceof Error ? err.message : String(err)
11250
+ });
11251
+ span.recordException(
11252
+ err instanceof Error ? err : new Error(String(err))
11253
+ );
11254
+ throw err;
11255
+ } finally {
11256
+ span.end();
11257
+ }
11258
+ });
11259
+ }
11260
+ function instrumentServer(server2) {
11261
+ if (!ENABLED) return;
11262
+ const original = server2.registerTool.bind(server2);
11263
+ server2.registerTool = (name, config2, handler) => {
11264
+ const traced = async (...args) => withTrace(name, () => handler(...args));
11265
+ return original(name, config2, traced);
11266
+ };
11267
+ }
11268
+
10560
11269
  // src/mcp/server.ts
10561
11270
  var server = new McpServer({
10562
11271
  name: "exe-os",
10563
11272
  version: "1.3.0"
10564
11273
  });
10565
11274
  var _backfillTimer = null;
11275
+ instrumentServer(server);
10566
11276
  registerRecallMyMemory(server);
10567
11277
  registerAskTeamMemory(server);
10568
11278
  registerGetSessionContext(server);
@@ -10603,6 +11313,7 @@ registerUpdateWikiPage(server);
10603
11313
  registerDeployClient(server);
10604
11314
  registerQueryConversations(server);
10605
11315
  registerLoadSkill(server);
11316
+ registerConsolidateMemories(server);
10606
11317
  try {
10607
11318
  await initStore();
10608
11319
  process.stderr.write("[exe-os] MCP server starting...\n");
@@ -10678,3 +11389,18 @@ async function shutdown() {
10678
11389
  }
10679
11390
  process.on("SIGINT", () => void shutdown());
10680
11391
  process.on("SIGTERM", () => void shutdown());
11392
+ process.on("uncaughtException", (err) => {
11393
+ process.stderr.write(
11394
+ `[exe-os] uncaughtException (non-fatal): ${err.message}
11395
+ `
11396
+ );
11397
+ if (err.stack) {
11398
+ process.stderr.write(`[exe-os] ${err.stack.split("\n").slice(1, 3).join("\n ")}
11399
+ `);
11400
+ }
11401
+ });
11402
+ process.on("unhandledRejection", (reason) => {
11403
+ const msg = reason instanceof Error ? reason.message : String(reason);
11404
+ process.stderr.write(`[exe-os] unhandledRejection (non-fatal): ${msg}
11405
+ `);
11406
+ });