@askexenow/exe-os 0.8.32 → 0.8.36

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 (87) hide show
  1. package/dist/bin/backfill-conversations.js +332 -348
  2. package/dist/bin/backfill-responses.js +72 -12
  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 +1518 -1122
  6. package/dist/bin/exe-agent.js +4 -4
  7. package/dist/bin/exe-assign.js +80 -18
  8. package/dist/bin/exe-boot.js +408 -89
  9. package/dist/bin/exe-call.js +83 -24
  10. package/dist/bin/exe-dispatch.js +18 -10
  11. package/dist/bin/exe-doctor.js +63 -3
  12. package/dist/bin/exe-export-behaviors.js +64 -3
  13. package/dist/bin/exe-forget.js +69 -4
  14. package/dist/bin/exe-gateway.js +121 -36
  15. package/dist/bin/exe-heartbeat.js +77 -13
  16. package/dist/bin/exe-kill.js +64 -3
  17. package/dist/bin/exe-launch-agent.js +162 -35
  18. package/dist/bin/exe-link.js +946 -0
  19. package/dist/bin/exe-new-employee.js +121 -36
  20. package/dist/bin/exe-pending-messages.js +72 -7
  21. package/dist/bin/exe-pending-notifications.js +63 -3
  22. package/dist/bin/exe-pending-reviews.js +75 -10
  23. package/dist/bin/exe-rename.js +1287 -0
  24. package/dist/bin/exe-review.js +64 -4
  25. package/dist/bin/exe-search.js +79 -13
  26. package/dist/bin/exe-session-cleanup.js +91 -26
  27. package/dist/bin/exe-status.js +64 -4
  28. package/dist/bin/exe-team.js +64 -4
  29. package/dist/bin/git-sweep.js +71 -4
  30. package/dist/bin/graph-backfill.js +64 -3
  31. package/dist/bin/graph-export.js +64 -3
  32. package/dist/bin/install.js +3 -3
  33. package/dist/bin/scan-tasks.js +71 -4
  34. package/dist/bin/setup.js +156 -38
  35. package/dist/bin/shard-migrate.js +64 -3
  36. package/dist/bin/wiki-sync.js +64 -3
  37. package/dist/gateway/index.js +122 -37
  38. package/dist/hooks/bug-report-worker.js +209 -23
  39. package/dist/hooks/commit-complete.js +71 -4
  40. package/dist/hooks/error-recall.js +79 -13
  41. package/dist/hooks/ingest-worker.js +129 -43
  42. package/dist/hooks/instructions-loaded.js +71 -4
  43. package/dist/hooks/notification.js +71 -4
  44. package/dist/hooks/post-compact.js +71 -4
  45. package/dist/hooks/pre-compact.js +71 -4
  46. package/dist/hooks/pre-tool-use.js +413 -194
  47. package/dist/hooks/prompt-ingest-worker.js +82 -22
  48. package/dist/hooks/prompt-submit.js +103 -37
  49. package/dist/hooks/response-ingest-worker.js +87 -22
  50. package/dist/hooks/session-end.js +71 -4
  51. package/dist/hooks/session-start.js +79 -13
  52. package/dist/hooks/stop.js +71 -4
  53. package/dist/hooks/subagent-stop.js +71 -4
  54. package/dist/hooks/summary-worker.js +303 -50
  55. package/dist/index.js +134 -46
  56. package/dist/lib/cloud-sync.js +209 -15
  57. package/dist/lib/consolidation.js +4 -4
  58. package/dist/lib/database.js +64 -2
  59. package/dist/lib/device-registry.js +70 -3
  60. package/dist/lib/employee-templates.js +48 -22
  61. package/dist/lib/employees.js +34 -1
  62. package/dist/lib/exe-daemon.js +136 -53
  63. package/dist/lib/hybrid-search.js +79 -13
  64. package/dist/lib/identity-templates.js +57 -6
  65. package/dist/lib/identity.js +3 -3
  66. package/dist/lib/messaging.js +22 -14
  67. package/dist/lib/reminders.js +3 -3
  68. package/dist/lib/schedules.js +63 -3
  69. package/dist/lib/skill-learning.js +3 -3
  70. package/dist/lib/status-brief.js +63 -5
  71. package/dist/lib/store.js +64 -3
  72. package/dist/lib/task-router.js +4 -2
  73. package/dist/lib/tasks.js +48 -21
  74. package/dist/lib/tmux-routing.js +47 -20
  75. package/dist/mcp/server.js +727 -58
  76. package/dist/mcp/tools/complete-reminder.js +3 -3
  77. package/dist/mcp/tools/create-reminder.js +3 -3
  78. package/dist/mcp/tools/create-task.js +151 -24
  79. package/dist/mcp/tools/deactivate-behavior.js +3 -3
  80. package/dist/mcp/tools/list-reminders.js +3 -3
  81. package/dist/mcp/tools/list-tasks.js +17 -8
  82. package/dist/mcp/tools/send-message.js +24 -16
  83. package/dist/mcp/tools/update-task.js +25 -16
  84. package/dist/runtime/index.js +112 -24
  85. package/dist/tui/App.js +139 -36
  86. package/package.json +6 -2
  87. 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 {
@@ -2890,10 +2956,40 @@ var init_active_agent = __esm({
2890
2956
  });
2891
2957
 
2892
2958
  // src/lib/employees.ts
2959
+ var employees_exports = {};
2960
+ __export(employees_exports, {
2961
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
2962
+ addEmployee: () => addEmployee,
2963
+ getEmployee: () => getEmployee,
2964
+ getEmployeeByRole: () => getEmployeeByRole,
2965
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
2966
+ hasRole: () => hasRole,
2967
+ isMultiInstance: () => isMultiInstance,
2968
+ loadEmployees: () => loadEmployees,
2969
+ loadEmployeesSync: () => loadEmployeesSync,
2970
+ registerBinSymlinks: () => registerBinSymlinks,
2971
+ saveEmployees: () => saveEmployees,
2972
+ validateEmployeeName: () => validateEmployeeName
2973
+ });
2893
2974
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2894
- import { existsSync as existsSync7, symlinkSync, readlinkSync } from "fs";
2975
+ import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync5 } from "fs";
2895
2976
  import { execSync as execSync5 } from "child_process";
2896
2977
  import path9 from "path";
2978
+ function validateEmployeeName(name) {
2979
+ if (!name) {
2980
+ return { valid: false, error: "Name is required" };
2981
+ }
2982
+ if (name.length > 32) {
2983
+ return { valid: false, error: "Name must be 32 characters or fewer" };
2984
+ }
2985
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
2986
+ return {
2987
+ valid: false,
2988
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
2989
+ };
2990
+ }
2991
+ return { valid: true };
2992
+ }
2897
2993
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
2898
2994
  if (!existsSync7(employeesPath)) {
2899
2995
  return [];
@@ -2909,6 +3005,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
2909
3005
  await mkdir3(path9.dirname(employeesPath), { recursive: true });
2910
3006
  await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
2911
3007
  }
3008
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
3009
+ if (!existsSync7(employeesPath)) return [];
3010
+ try {
3011
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
3012
+ } catch {
3013
+ return [];
3014
+ }
3015
+ }
3016
+ function getEmployee(employees, name) {
3017
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
3018
+ }
3019
+ function getEmployeeByRole(employees, role) {
3020
+ const lower = role.toLowerCase();
3021
+ return employees.find((e) => e.role.toLowerCase() === lower);
3022
+ }
3023
+ function getEmployeeNamesByRole(employees, role) {
3024
+ const lower = role.toLowerCase();
3025
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
3026
+ }
3027
+ function hasRole(agentName, role) {
3028
+ const employees = loadEmployeesSync();
3029
+ const emp = getEmployee(employees, agentName);
3030
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
3031
+ }
3032
+ function isMultiInstance(agentName, employees) {
3033
+ const roster = employees ?? loadEmployeesSync();
3034
+ const emp = getEmployee(roster, agentName);
3035
+ if (!emp) return false;
3036
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
3037
+ }
2912
3038
  function addEmployee(employees, employee) {
2913
3039
  const normalized = { ...employee, name: employee.name.toLowerCase() };
2914
3040
  if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
@@ -2916,12 +3042,48 @@ function addEmployee(employees, employee) {
2916
3042
  }
2917
3043
  return [...employees, normalized];
2918
3044
  }
2919
- var EMPLOYEES_PATH;
3045
+ function registerBinSymlinks(name) {
3046
+ const created = [];
3047
+ const skipped = [];
3048
+ const errors = [];
3049
+ let exeBinPath;
3050
+ try {
3051
+ exeBinPath = execSync5("which exe", { encoding: "utf-8" }).trim();
3052
+ } catch {
3053
+ errors.push("Could not find 'exe' in PATH");
3054
+ return { created, skipped, errors };
3055
+ }
3056
+ const binDir = path9.dirname(exeBinPath);
3057
+ let target;
3058
+ try {
3059
+ target = readlinkSync(exeBinPath);
3060
+ } catch {
3061
+ errors.push("Could not read 'exe' symlink");
3062
+ return { created, skipped, errors };
3063
+ }
3064
+ for (const suffix of ["", "-opencode"]) {
3065
+ const linkName = `${name}${suffix}`;
3066
+ const linkPath = path9.join(binDir, linkName);
3067
+ if (existsSync7(linkPath)) {
3068
+ skipped.push(linkName);
3069
+ continue;
3070
+ }
3071
+ try {
3072
+ symlinkSync(target, linkPath);
3073
+ created.push(linkName);
3074
+ } catch (err) {
3075
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
3076
+ }
3077
+ }
3078
+ return { created, skipped, errors };
3079
+ }
3080
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
2920
3081
  var init_employees = __esm({
2921
3082
  "src/lib/employees.ts"() {
2922
3083
  "use strict";
2923
3084
  init_config();
2924
3085
  EMPLOYEES_PATH = path9.join(EXE_AI_DIR, "exe-employees.json");
3086
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
2925
3087
  }
2926
3088
  });
2927
3089
 
@@ -2940,7 +3102,7 @@ __export(license_exports, {
2940
3102
  saveLicense: () => saveLicense,
2941
3103
  validateLicense: () => validateLicense
2942
3104
  });
2943
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3105
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
2944
3106
  import { randomUUID as randomUUID2 } from "crypto";
2945
3107
  import path10 from "path";
2946
3108
  import { jwtVerify, importSPKI } from "jose";
@@ -2948,14 +3110,14 @@ function loadDeviceId() {
2948
3110
  const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
2949
3111
  try {
2950
3112
  if (existsSync8(deviceJsonPath)) {
2951
- const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
3113
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
2952
3114
  if (data.deviceId) return data.deviceId;
2953
3115
  }
2954
3116
  } catch {
2955
3117
  }
2956
3118
  try {
2957
3119
  if (existsSync8(DEVICE_ID_PATH)) {
2958
- const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
3120
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
2959
3121
  if (id2) return id2;
2960
3122
  }
2961
3123
  } catch {
@@ -2968,7 +3130,7 @@ function loadDeviceId() {
2968
3130
  function loadLicense() {
2969
3131
  try {
2970
3132
  if (!existsSync8(LICENSE_PATH)) return null;
2971
- return readFileSync5(LICENSE_PATH, "utf8").trim();
3133
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
2972
3134
  } catch {
2973
3135
  return null;
2974
3136
  }
@@ -3002,7 +3164,7 @@ async function verifyLicenseJwt(token) {
3002
3164
  async function getCachedLicense() {
3003
3165
  try {
3004
3166
  if (!existsSync8(CACHE_PATH)) return null;
3005
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
3167
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3006
3168
  if (!raw.token || typeof raw.token !== "string") return null;
3007
3169
  return await verifyLicenseJwt(raw.token);
3008
3170
  } catch {
@@ -3012,7 +3174,7 @@ async function getCachedLicense() {
3012
3174
  function readCachedToken() {
3013
3175
  try {
3014
3176
  if (!existsSync8(CACHE_PATH)) return null;
3015
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
3177
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3016
3178
  return typeof raw.token === "string" ? raw.token : null;
3017
3179
  } catch {
3018
3180
  return null;
@@ -3234,12 +3396,12 @@ __export(plan_limits_exports, {
3234
3396
  countActiveMemories: () => countActiveMemories,
3235
3397
  getLicenseSync: () => getLicenseSync
3236
3398
  });
3237
- import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
3399
+ import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
3238
3400
  import path11 from "path";
3239
3401
  function getLicenseSync() {
3240
3402
  try {
3241
3403
  if (!existsSync9(CACHE_PATH2)) return freeLicense();
3242
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
3404
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
3243
3405
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3244
3406
  const parts = raw.token.split(".");
3245
3407
  if (parts.length !== 3) return freeLicense();
@@ -3307,7 +3469,7 @@ function assertEmployeeLimitSync(rosterPath) {
3307
3469
  let count = 0;
3308
3470
  try {
3309
3471
  if (existsSync9(filePath)) {
3310
- const raw = readFileSync6(filePath, "utf8");
3472
+ const raw = readFileSync7(filePath, "utf8");
3311
3473
  const employees = JSON.parse(raw);
3312
3474
  count = Array.isArray(employees) ? employees.length : 0;
3313
3475
  }
@@ -3353,7 +3515,7 @@ import crypto5 from "crypto";
3353
3515
  import path14 from "path";
3354
3516
  import os2 from "os";
3355
3517
  import {
3356
- readFileSync as readFileSync7,
3518
+ readFileSync as readFileSync8,
3357
3519
  readdirSync as readdirSync3,
3358
3520
  unlinkSync as unlinkSync3,
3359
3521
  existsSync as existsSync10,
@@ -3405,7 +3567,7 @@ import crypto6 from "crypto";
3405
3567
  import path15 from "path";
3406
3568
  import { execSync as execSync6 } from "child_process";
3407
3569
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3408
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
3570
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
3409
3571
  async function writeCheckpoint(input) {
3410
3572
  const client = getClient();
3411
3573
  const row = await resolveTask(client, input.taskId);
@@ -3782,7 +3944,7 @@ async function ensureGitignoreExe(baseDir) {
3782
3944
  const gitignorePath = path15.join(baseDir, ".gitignore");
3783
3945
  try {
3784
3946
  if (existsSync11(gitignorePath)) {
3785
- const content = readFileSync8(gitignorePath, "utf-8");
3947
+ const content = readFileSync9(gitignorePath, "utf-8");
3786
3948
  if (/^\/?exe\/?$/m.test(content)) return;
3787
3949
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3788
3950
  } else {
@@ -3802,7 +3964,7 @@ var init_tasks_crud = __esm({
3802
3964
  });
3803
3965
 
3804
3966
  // src/lib/session-registry.ts
3805
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync12 } from "fs";
3967
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync12 } from "fs";
3806
3968
  import path16 from "path";
3807
3969
  import os3 from "os";
3808
3970
  function registerSession(entry) {
@@ -3821,7 +3983,7 @@ function registerSession(entry) {
3821
3983
  }
3822
3984
  function listSessions() {
3823
3985
  try {
3824
- const raw = readFileSync9(REGISTRY_PATH, "utf8");
3986
+ const raw = readFileSync10(REGISTRY_PATH, "utf8");
3825
3987
  return JSON.parse(raw);
3826
3988
  } catch {
3827
3989
  return [];
@@ -4016,7 +4178,7 @@ var init_provider_table = __esm({
4016
4178
  });
4017
4179
 
4018
4180
  // src/lib/intercom-queue.ts
4019
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
4181
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
4020
4182
  import path17 from "path";
4021
4183
  import os4 from "os";
4022
4184
  function ensureDir() {
@@ -4026,7 +4188,7 @@ function ensureDir() {
4026
4188
  function readQueue() {
4027
4189
  try {
4028
4190
  if (!existsSync13(QUEUE_PATH)) return [];
4029
- return JSON.parse(readFileSync10(QUEUE_PATH, "utf8"));
4191
+ return JSON.parse(readFileSync11(QUEUE_PATH, "utf8"));
4030
4192
  } catch {
4031
4193
  return [];
4032
4194
  }
@@ -4066,7 +4228,7 @@ var init_intercom_queue = __esm({
4066
4228
 
4067
4229
  // src/lib/tmux-routing.ts
4068
4230
  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";
4231
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync } from "fs";
4070
4232
  import path18 from "path";
4071
4233
  import os5 from "os";
4072
4234
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -4115,7 +4277,7 @@ function extractRootExe(name) {
4115
4277
  }
4116
4278
  function getParentExe(sessionKey) {
4117
4279
  try {
4118
- const data = JSON.parse(readFileSync11(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4280
+ const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4119
4281
  return data.parentExe || null;
4120
4282
  } catch {
4121
4283
  return null;
@@ -4123,7 +4285,7 @@ function getParentExe(sessionKey) {
4123
4285
  }
4124
4286
  function getDispatchedBy(sessionKey) {
4125
4287
  try {
4126
- const data = JSON.parse(readFileSync11(
4288
+ const data = JSON.parse(readFileSync12(
4127
4289
  path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4128
4290
  "utf8"
4129
4291
  ));
@@ -4160,7 +4322,7 @@ function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive =
4160
4322
  function readDebounceState() {
4161
4323
  try {
4162
4324
  if (!existsSync14(DEBOUNCE_FILE)) return {};
4163
- return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4325
+ return JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
4164
4326
  } catch {
4165
4327
  return {};
4166
4328
  }
@@ -4356,7 +4518,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4356
4518
  const claudeJsonPath = path18.join(os5.homedir(), ".claude.json");
4357
4519
  let claudeJson = {};
4358
4520
  try {
4359
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4521
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
4360
4522
  } catch {
4361
4523
  }
4362
4524
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4374,7 +4536,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4374
4536
  const settingsPath = path18.join(projSettingsDir, "settings.json");
4375
4537
  let settings = {};
4376
4538
  try {
4377
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4539
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
4378
4540
  } catch {
4379
4541
  }
4380
4542
  const perms = settings.permissions ?? {};
@@ -4879,7 +5041,7 @@ async function dispatchTaskToEmployee(input) {
4879
5041
  } else {
4880
5042
  const projectDir = input.projectDir ?? process.cwd();
4881
5043
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
4882
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
5044
+ autoInstance: isMultiInstance(input.assignedTo)
4883
5045
  });
4884
5046
  if (result.status === "failed") {
4885
5047
  process.stderr.write(
@@ -4914,6 +5076,7 @@ var init_tasks_notify = __esm({
4914
5076
  init_session_key();
4915
5077
  init_notifications();
4916
5078
  init_transport();
5079
+ init_employees();
4917
5080
  }
4918
5081
  });
4919
5082
 
@@ -5611,7 +5774,7 @@ async function deliverLocalMessage(messageId) {
5611
5774
  return true;
5612
5775
  } catch {
5613
5776
  const newRetryCount = msg.retryCount + 1;
5614
- if (newRetryCount >= MAX_RETRIES) {
5777
+ if (newRetryCount >= MAX_RETRIES2) {
5615
5778
  await markFailed(messageId, "session unavailable after 10 retries");
5616
5779
  } else {
5617
5780
  await client.execute({
@@ -5629,25 +5792,25 @@ async function markFailed(messageId, reason) {
5629
5792
  args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
5630
5793
  });
5631
5794
  }
5632
- var MAX_RETRIES, _wsClientSend;
5795
+ var MAX_RETRIES2, _wsClientSend;
5633
5796
  var init_messaging = __esm({
5634
5797
  "src/lib/messaging.ts"() {
5635
5798
  "use strict";
5636
5799
  init_database();
5637
5800
  init_tmux_routing();
5638
- MAX_RETRIES = 10;
5801
+ MAX_RETRIES2 = 10;
5639
5802
  _wsClientSend = null;
5640
5803
  }
5641
5804
  });
5642
5805
 
5643
5806
  // src/gateway/whatsapp-accounts.ts
5644
- import { readFileSync as readFileSync13 } from "fs";
5807
+ import { readFileSync as readFileSync14 } from "fs";
5645
5808
  import { join } from "path";
5646
5809
  import { homedir } from "os";
5647
5810
  function loadAccounts() {
5648
5811
  if (cachedAccounts !== null) return cachedAccounts;
5649
5812
  try {
5650
- const raw = readFileSync13(CONFIG_PATH2, "utf8");
5813
+ const raw = readFileSync14(CONFIG_PATH2, "utf8");
5651
5814
  const parsed = JSON.parse(raw);
5652
5815
  if (!Array.isArray(parsed)) {
5653
5816
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -5680,6 +5843,376 @@ var init_whatsapp_accounts = __esm({
5680
5843
  }
5681
5844
  });
5682
5845
 
5846
+ // src/lib/consolidation.ts
5847
+ var consolidation_exports = {};
5848
+ __export(consolidation_exports, {
5849
+ buildConsolidationPrompt: () => buildConsolidationPrompt,
5850
+ consolidateCluster: () => consolidateCluster,
5851
+ countUnconsolidated: () => countUnconsolidated,
5852
+ groupMemories: () => groupMemories,
5853
+ isUserIdle: () => isUserIdle,
5854
+ pushToWiki: () => pushToWiki,
5855
+ runConsolidation: () => runConsolidation,
5856
+ selectUnconsolidated: () => selectUnconsolidated,
5857
+ storeConsolidation: () => storeConsolidation
5858
+ });
5859
+ import { randomUUID as randomUUID4 } from "crypto";
5860
+ async function selectUnconsolidated(client, limit = 200) {
5861
+ const result = await client.execute({
5862
+ sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
5863
+ FROM memories
5864
+ WHERE consolidated = 0
5865
+ AND timestamp >= datetime('now', '-7 days')
5866
+ ORDER BY timestamp DESC
5867
+ LIMIT ?`,
5868
+ args: [limit]
5869
+ });
5870
+ return result.rows.map((row) => ({
5871
+ id: row.id,
5872
+ agent_id: row.agent_id,
5873
+ project_name: row.project_name,
5874
+ tool_name: row.tool_name,
5875
+ raw_text: row.raw_text,
5876
+ timestamp: row.timestamp
5877
+ }));
5878
+ }
5879
+ function groupMemories(memories) {
5880
+ const byProject = /* @__PURE__ */ new Map();
5881
+ for (const mem of memories) {
5882
+ const key = `${mem.agent_id}::${mem.project_name}`;
5883
+ const list = byProject.get(key) ?? [];
5884
+ list.push(mem);
5885
+ byProject.set(key, list);
5886
+ }
5887
+ const clusters = [];
5888
+ for (const [key, projectMemories] of byProject) {
5889
+ const [agentId, projectName] = key.split("::");
5890
+ const byDay = /* @__PURE__ */ new Map();
5891
+ for (const mem of projectMemories) {
5892
+ const day = mem.timestamp.slice(0, 10);
5893
+ const list = byDay.get(day) ?? [];
5894
+ list.push(mem);
5895
+ byDay.set(day, list);
5896
+ }
5897
+ for (const [day, dayMemories] of byDay) {
5898
+ if (dayMemories.length <= 15) {
5899
+ clusters.push({
5900
+ agentId,
5901
+ projectName,
5902
+ dateRange: day,
5903
+ memories: dayMemories
5904
+ });
5905
+ } else {
5906
+ for (let i = 0; i < dayMemories.length; i += 12) {
5907
+ const chunk = dayMemories.slice(i, i + 12);
5908
+ clusters.push({
5909
+ agentId,
5910
+ projectName,
5911
+ dateRange: day,
5912
+ memories: chunk
5913
+ });
5914
+ }
5915
+ }
5916
+ }
5917
+ }
5918
+ return clusters;
5919
+ }
5920
+ function buildConsolidationPrompt(cluster) {
5921
+ const snippets = cluster.memories.map((m, i) => {
5922
+ const text = m.raw_text.length > 200 ? m.raw_text.slice(0, 200) + "..." : m.raw_text;
5923
+ return `${i + 1}. [${m.tool_name}] ${text}`;
5924
+ }).join("\n");
5925
+ return `You are reviewing a set of work memories from an AI coding agent.
5926
+ These are raw tool call records from ${cluster.dateRange}.
5927
+
5928
+ Agent: ${cluster.agentId} | Project: ${cluster.projectName} | Date: ${cluster.dateRange}
5929
+
5930
+ MEMORIES:
5931
+ ${snippets}
5932
+
5933
+ Extract EXACTLY THREE types of insights:
5934
+
5935
+ 1. KEY DECISIONS \u2014 choices that were made and why (at most 3)
5936
+ 2. RECURRING PATTERNS \u2014 actions or approaches that repeated (at most 3)
5937
+ 3. OPEN QUESTIONS \u2014 things that seem unresolved or risky (at most 2)
5938
+
5939
+ Format each as a single sentence. Be specific \u2014 include file names,
5940
+ function names, and concrete details. Skip if no insight for a category.`;
5941
+ }
5942
+ async function consolidateCluster(cluster, model) {
5943
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
5944
+ const client = new Anthropic();
5945
+ const prompt = buildConsolidationPrompt(cluster);
5946
+ const response = await client.messages.create({
5947
+ model,
5948
+ max_tokens: 300,
5949
+ messages: [{ role: "user", content: prompt }]
5950
+ });
5951
+ const textBlock = response.content.find((b) => b.type === "text");
5952
+ return textBlock?.text ?? "";
5953
+ }
5954
+ async function storeConsolidation(client, cluster, synthesisText, embedFn) {
5955
+ const consolidatedId = randomUUID4();
5956
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5957
+ const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
5958
+
5959
+ ${synthesisText}`;
5960
+ let vector = null;
5961
+ if (embedFn) {
5962
+ try {
5963
+ vector = await embedFn(rawText);
5964
+ } catch {
5965
+ }
5966
+ }
5967
+ const insertSql = vector ? `INSERT INTO memories
5968
+ (id, agent_id, agent_role, session_id, timestamp,
5969
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
5970
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1, 9)` : `INSERT INTO memories
5971
+ (id, agent_id, agent_role, session_id, timestamp,
5972
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
5973
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1, 9)`;
5974
+ const insertArgs = vector ? [consolidatedId, cluster.agentId, now, cluster.projectName, rawText, vectorToBlob(vector)] : [consolidatedId, cluster.agentId, now, cluster.projectName, rawText];
5975
+ await client.execute({ sql: insertSql, args: insertArgs });
5976
+ const sourceIds = cluster.memories.map((m) => m.id);
5977
+ const linkStmts = sourceIds.map((sourceId) => ({
5978
+ sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
5979
+ VALUES (?, ?, ?, ?)`,
5980
+ args: [randomUUID4(), consolidatedId, sourceId, now]
5981
+ }));
5982
+ const placeholders = sourceIds.map(() => "?").join(",");
5983
+ const markStmt = {
5984
+ sql: `UPDATE memories SET consolidated = 1 WHERE id IN (${placeholders})`,
5985
+ args: sourceIds
5986
+ };
5987
+ await client.batch([...linkStmts, markStmt], "write");
5988
+ return { consolidatedMemoryId: consolidatedId, sourceIds, rawText };
5989
+ }
5990
+ async function pushToWiki(consolidation, config2) {
5991
+ if (!config2.wikiEnabled || !config2.wikiAutoUpdate) {
5992
+ return { updated: false };
5993
+ }
5994
+ const apiUrl = config2.wikiUrl || process.env.EXE_WIKI_API_URL;
5995
+ const apiKey = config2.wikiApiKey || process.env.EXE_WIKI_API_KEY;
5996
+ if (!apiUrl || !apiKey) {
5997
+ return { updated: false };
5998
+ }
5999
+ const workspace = config2.wikiWorkspaceMapping[consolidation.projectName] ?? consolidation.projectName;
6000
+ try {
6001
+ const listRes = await fetch(
6002
+ `${apiUrl}/v1/workspace/${encodeURIComponent(workspace)}/documents`,
6003
+ {
6004
+ headers: { Authorization: `Bearer ${apiKey}` },
6005
+ signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
6006
+ }
6007
+ );
6008
+ if (!listRes.ok) {
6009
+ return { updated: false, error: `Workspace "${workspace}" not accessible (${listRes.status})` };
6010
+ }
6011
+ const listJson = await listRes.json();
6012
+ const docs = listJson.documents ?? listJson.localFiles?.items ?? [];
6013
+ const contentLines = consolidation.rawText.split("\n").filter((l) => l.trim() && !l.startsWith("CONSOLIDATION") && !l.match(/^[A-Z\s]+:$/)).join(" ");
6014
+ const keywords = contentLines.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
6015
+ let bestMatch = null;
6016
+ for (const doc of docs) {
6017
+ if (!doc.id || !doc.title) continue;
6018
+ const titleWords = doc.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
6019
+ if (titleWords.length === 0) continue;
6020
+ const matchCount = titleWords.filter(
6021
+ (tw) => keywords.some((k) => k.includes(tw) || tw.includes(k))
6022
+ ).length;
6023
+ const score = matchCount / titleWords.length;
6024
+ if (score > (bestMatch?.score ?? 0)) {
6025
+ bestMatch = { id: doc.id, title: doc.title, score };
6026
+ }
6027
+ }
6028
+ if (bestMatch && bestMatch.score >= config2.wikiAutoUpdateThreshold) {
6029
+ const updateRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
6030
+ method: "POST",
6031
+ headers: {
6032
+ "Content-Type": "application/json",
6033
+ Authorization: `Bearer ${apiKey}`
6034
+ },
6035
+ body: JSON.stringify({
6036
+ textContent: consolidation.rawText,
6037
+ metadata: { title: bestMatch.title, appendTo: bestMatch.id },
6038
+ workspaceSlugs: [workspace]
6039
+ }),
6040
+ signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
6041
+ });
6042
+ if (updateRes.ok) {
6043
+ process.stderr.write(
6044
+ `[consolidation] Wiki auto-updated: ${workspace}/${bestMatch.title}
6045
+ `
6046
+ );
6047
+ return { updated: true, action: "updated", page: bestMatch.title };
6048
+ }
6049
+ return { updated: false, error: `Update failed (${updateRes.status})` };
6050
+ }
6051
+ if (config2.wikiAutoUpdateCreateNew) {
6052
+ const title = `Consolidated Insights \u2014 ${consolidation.projectName} (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`;
6053
+ const createRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
6054
+ method: "POST",
6055
+ headers: {
6056
+ "Content-Type": "application/json",
6057
+ Authorization: `Bearer ${apiKey}`
6058
+ },
6059
+ body: JSON.stringify({
6060
+ textContent: consolidation.rawText,
6061
+ metadata: { title },
6062
+ workspaceSlugs: [workspace]
6063
+ }),
6064
+ signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
6065
+ });
6066
+ if (createRes.ok) {
6067
+ process.stderr.write(
6068
+ `[consolidation] Wiki page created: ${workspace}/${title}
6069
+ `
6070
+ );
6071
+ return { updated: true, action: "created", page: title };
6072
+ }
6073
+ return { updated: false, error: `Create failed (${createRes.status})` };
6074
+ }
6075
+ return { updated: false };
6076
+ } catch (err) {
6077
+ const msg = err instanceof Error ? err.message : String(err);
6078
+ process.stderr.write(`[consolidation] Wiki push failed: ${msg}
6079
+ `);
6080
+ return { updated: false, error: msg };
6081
+ }
6082
+ }
6083
+ async function runConsolidation(client, options) {
6084
+ const memories = await selectUnconsolidated(client);
6085
+ if (memories.length < 20) {
6086
+ return { clustersProcessed: 0, memoriesConsolidated: 0 };
6087
+ }
6088
+ const clusters = groupMemories(memories);
6089
+ let clustersProcessed = 0;
6090
+ let memoriesConsolidated = 0;
6091
+ for (const cluster of clusters) {
6092
+ if (clustersProcessed >= options.maxCalls) break;
6093
+ if (cluster.memories.length < 3) continue;
6094
+ try {
6095
+ const isExe = cluster.agentId === "exe";
6096
+ if (isExe) {
6097
+ const synthesis = await consolidateCluster(cluster, options.model);
6098
+ if (!synthesis.trim()) continue;
6099
+ const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
6100
+ if (options.wikiConfig) {
6101
+ await pushToWiki(
6102
+ { ...result, projectName: cluster.projectName },
6103
+ options.wikiConfig
6104
+ ).catch((err) => {
6105
+ process.stderr.write(
6106
+ `[consolidation] Wiki push error (non-fatal): ${err instanceof Error ? err.message : String(err)}
6107
+ `
6108
+ );
6109
+ });
6110
+ }
6111
+ const sourceIds = result.sourceIds;
6112
+ if (sourceIds.length > 0) {
6113
+ const placeholders = sourceIds.map(() => "?").join(",");
6114
+ await client.execute({
6115
+ sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
6116
+ args: sourceIds
6117
+ });
6118
+ }
6119
+ } else {
6120
+ const dedupCount = await dedupCluster(client, cluster, options.embedFn);
6121
+ memoriesConsolidated += dedupCount;
6122
+ if (dedupCount === 0) continue;
6123
+ }
6124
+ clustersProcessed++;
6125
+ memoriesConsolidated += isExe ? cluster.memories.length : 0;
6126
+ } catch (err) {
6127
+ process.stderr.write(
6128
+ `[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
6129
+ `
6130
+ );
6131
+ }
6132
+ }
6133
+ return { clustersProcessed, memoriesConsolidated };
6134
+ }
6135
+ async function dedupCluster(client, cluster, embedFn) {
6136
+ if (!embedFn || cluster.memories.length < 2) return 0;
6137
+ const vectors = [];
6138
+ for (const mem of cluster.memories) {
6139
+ try {
6140
+ const v = await embedFn(mem.raw_text.slice(0, 500));
6141
+ vectors.push({ id: mem.id, vector: v });
6142
+ } catch {
6143
+ }
6144
+ }
6145
+ if (vectors.length < 2) return 0;
6146
+ const toArchive = /* @__PURE__ */ new Set();
6147
+ for (let i = 0; i < vectors.length; i++) {
6148
+ if (toArchive.has(vectors[i].id)) continue;
6149
+ for (let j = i + 1; j < vectors.length; j++) {
6150
+ if (toArchive.has(vectors[j].id)) continue;
6151
+ const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
6152
+ if (sim > 0.95) {
6153
+ toArchive.add(vectors[j].id);
6154
+ }
6155
+ }
6156
+ }
6157
+ if (toArchive.size === 0) return 0;
6158
+ const ids = [...toArchive];
6159
+ const placeholders = ids.map(() => "?").join(",");
6160
+ await client.execute({
6161
+ sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
6162
+ args: ids
6163
+ });
6164
+ const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
6165
+ if (survivors.length > 0) {
6166
+ const survivorPlaceholders = survivors.map(() => "?").join(",");
6167
+ await client.execute({
6168
+ sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
6169
+ args: survivors
6170
+ });
6171
+ }
6172
+ return ids.length;
6173
+ }
6174
+ function cosineSimilarity(a, b) {
6175
+ let dot = 0, normA = 0, normB = 0;
6176
+ for (let i = 0; i < a.length; i++) {
6177
+ dot += a[i] * b[i];
6178
+ normA += a[i] * a[i];
6179
+ normB += b[i] * b[i];
6180
+ }
6181
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
6182
+ return denom === 0 ? 0 : dot / denom;
6183
+ }
6184
+ async function isUserIdle(client, idleMinutes = 30) {
6185
+ const result = await client.execute({
6186
+ sql: `SELECT MAX(timestamp) as last_activity
6187
+ FROM memories
6188
+ WHERE tool_name != 'consolidation'
6189
+ AND timestamp >= datetime('now', '-1 day')`,
6190
+ args: []
6191
+ });
6192
+ const lastActivity = result.rows[0]?.last_activity;
6193
+ if (!lastActivity) return true;
6194
+ const lastMs = new Date(lastActivity).getTime();
6195
+ const now = Date.now();
6196
+ return now - lastMs >= idleMinutes * 60 * 1e3;
6197
+ }
6198
+ async function countUnconsolidated(client) {
6199
+ const result = await client.execute({
6200
+ sql: `SELECT COUNT(*) as cnt FROM memories
6201
+ WHERE consolidated = 0
6202
+ AND timestamp >= datetime('now', '-7 days')`,
6203
+ args: []
6204
+ });
6205
+ return Number(result.rows[0]?.cnt ?? 0);
6206
+ }
6207
+ var WIKI_FETCH_TIMEOUT_MS;
6208
+ var init_consolidation = __esm({
6209
+ "src/lib/consolidation.ts"() {
6210
+ "use strict";
6211
+ init_store();
6212
+ WIKI_FETCH_TIMEOUT_MS = 1e4;
6213
+ }
6214
+ });
6215
+
5683
6216
  // src/mcp/server.ts
5684
6217
  init_embedder();
5685
6218
  init_store();
@@ -6779,8 +7312,8 @@ function registerCreateTask(server2) {
6779
7312
  try {
6780
7313
  const exeSession = resolveExeSession();
6781
7314
  if (exeSession) {
6782
- const MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["tom", "sasha"]);
6783
- const useAutoInstance = MULTI_INSTANCE_ROLES.has(assigned_to);
7315
+ const { isMultiInstance: isMultiInstance2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
7316
+ const useAutoInstance = isMultiInstance2(assigned_to);
6784
7317
  const { loadConfigSync: loadConfigSync2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6785
7318
  const cfg = loadConfigSync2();
6786
7319
  const result = ensureEmployee(assigned_to, exeSession, process.cwd(), {
@@ -7588,7 +8121,7 @@ import { z as z20 } from "zod";
7588
8121
  // src/lib/identity.ts
7589
8122
  init_config();
7590
8123
  init_database();
7591
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
8124
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
7592
8125
  import { readdirSync as readdirSync5 } from "fs";
7593
8126
  import path22 from "path";
7594
8127
  import { createHash } from "crypto";
@@ -7641,7 +8174,7 @@ function contentHash(content) {
7641
8174
  function getIdentity(agentId) {
7642
8175
  const filePath = identityPath(agentId);
7643
8176
  if (!existsSync16(filePath)) return null;
7644
- const raw = readFileSync12(filePath, "utf-8");
8177
+ const raw = readFileSync13(filePath, "utf-8");
7645
8178
  const { frontmatter, body } = parseFrontmatter(raw);
7646
8179
  return {
7647
8180
  agentId,
@@ -8297,7 +8830,6 @@ function registerAcknowledgeMessages(server2) {
8297
8830
  const agent = getActiveAgent();
8298
8831
  const agentId = agent.agentId || "exe";
8299
8832
  const client = getClient();
8300
- await client.execute({ sql: "PRAGMA busy_timeout = 5000", args: [] });
8301
8833
  const result = await client.execute({
8302
8834
  sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
8303
8835
  WHERE target_agent = ? AND status IN ('pending', 'delivered')`,
@@ -8627,7 +9159,7 @@ function registerSendWhatsapp(server2) {
8627
9159
  import { z as z29 } from "zod";
8628
9160
 
8629
9161
  // src/automation/trigger-engine.ts
8630
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
9162
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
8631
9163
  import { randomUUID as randomUUID3 } from "crypto";
8632
9164
  import path23 from "path";
8633
9165
  import os6 from "os";
@@ -8635,7 +9167,7 @@ var TRIGGERS_PATH = path23.join(os6.homedir(), ".exe-os", "triggers.json");
8635
9167
  function loadTriggers(project) {
8636
9168
  if (!existsSync17(TRIGGERS_PATH)) return [];
8637
9169
  try {
8638
- const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
9170
+ const raw = readFileSync15(TRIGGERS_PATH, "utf-8");
8639
9171
  const all = JSON.parse(raw);
8640
9172
  if (!Array.isArray(all)) return [];
8641
9173
  if (project) {
@@ -8933,7 +9465,7 @@ function registerListTriggers(server2) {
8933
9465
  import { z as z31 } from "zod";
8934
9466
 
8935
9467
  // src/automation/starter-packs/index.ts
8936
- import { readFileSync as readFileSync15, readdirSync as readdirSync6, existsSync as existsSync18 } from "fs";
9468
+ import { readFileSync as readFileSync16, readdirSync as readdirSync6, existsSync as existsSync18 } from "fs";
8937
9469
  import path24 from "path";
8938
9470
  import { fileURLToPath as fileURLToPath3 } from "url";
8939
9471
  var __dirname = path24.dirname(fileURLToPath3(import.meta.url));
@@ -8955,7 +9487,7 @@ function loadPack(industry) {
8955
9487
  let customObjects = [];
8956
9488
  try {
8957
9489
  customObjects = JSON.parse(
8958
- readFileSync15(objectsPath, "utf-8")
9490
+ readFileSync16(objectsPath, "utf-8")
8959
9491
  );
8960
9492
  } catch {
8961
9493
  customObjects = [];
@@ -8964,7 +9496,7 @@ function loadPack(industry) {
8964
9496
  if (existsSync18(triggersPath)) {
8965
9497
  try {
8966
9498
  triggers = JSON.parse(
8967
- readFileSync15(triggersPath, "utf-8")
9499
+ readFileSync16(triggersPath, "utf-8")
8968
9500
  );
8969
9501
  } catch {
8970
9502
  triggers = [];
@@ -8974,7 +9506,7 @@ function loadPack(industry) {
8974
9506
  if (existsSync18(wikiDir)) {
8975
9507
  const files = readdirSync6(wikiDir).filter((f) => f.endsWith(".md"));
8976
9508
  for (const file of files) {
8977
- const content = readFileSync15(path24.join(wikiDir, file), "utf-8");
9509
+ const content = readFileSync16(path24.join(wikiDir, file), "utf-8");
8978
9510
  const titleMatch = content.match(/^#\s+(.+)/m);
8979
9511
  wikiSeeds.push({
8980
9512
  filename: file,
@@ -8986,7 +9518,7 @@ function loadPack(industry) {
8986
9518
  let manifest = {};
8987
9519
  if (existsSync18(manifestPath)) {
8988
9520
  try {
8989
- manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
9521
+ manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
8990
9522
  } catch {
8991
9523
  manifest = {};
8992
9524
  }
@@ -8994,7 +9526,7 @@ function loadPack(industry) {
8994
9526
  let identityContext = null;
8995
9527
  if (existsSync18(identityContextPath)) {
8996
9528
  try {
8997
- identityContext = readFileSync15(identityContextPath, "utf-8");
9529
+ identityContext = readFileSync16(identityContextPath, "utf-8");
8998
9530
  } catch {
8999
9531
  identityContext = null;
9000
9532
  }
@@ -10474,7 +11006,7 @@ function registerQueryConversations(server2) {
10474
11006
 
10475
11007
  // src/mcp/tools/load-skill.ts
10476
11008
  import { z as z39 } from "zod";
10477
- import { readFileSync as readFileSync16, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
11009
+ import { readFileSync as readFileSync17, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
10478
11010
  import path27 from "path";
10479
11011
  import { homedir as homedir2 } from "os";
10480
11012
  var SKILLS_DIR = path27.join(homedir2(), ".claude", "skills");
@@ -10531,7 +11063,7 @@ ${skills.map((s) => `- ${s}`).join("\n")}`
10531
11063
  const sanitized = path27.basename(skill_name);
10532
11064
  const skillFile = path27.join(SKILLS_DIR, sanitized, "SKILL.md");
10533
11065
  try {
10534
- const content = readFileSync16(skillFile, "utf-8");
11066
+ const content = readFileSync17(skillFile, "utf-8");
10535
11067
  return {
10536
11068
  content: [{
10537
11069
  type: "text",
@@ -10557,12 +11089,133 @@ ${available.map((s) => `- ${s}`).join("\n")}` : "\n\nNo skills found in ~/.claud
10557
11089
  );
10558
11090
  }
10559
11091
 
11092
+ // src/mcp/tools/consolidate-memories.ts
11093
+ init_database();
11094
+ init_active_agent();
11095
+ import { z as z40 } from "zod";
11096
+ function registerConsolidateMemories(server2) {
11097
+ server2.registerTool(
11098
+ "consolidate_memories",
11099
+ {
11100
+ title: "Consolidate Memories",
11101
+ 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.",
11102
+ inputSchema: {
11103
+ max_clusters: z40.coerce.number().optional().default(5).describe("Maximum number of clusters to consolidate (default 5)"),
11104
+ model: z40.string().optional().describe("LLM model for summarization (defaults to config value)")
11105
+ }
11106
+ },
11107
+ async ({ max_clusters, model }) => {
11108
+ const { agentId } = getActiveAgent();
11109
+ try {
11110
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
11111
+ const config2 = await loadConfig2();
11112
+ const consolidationModel = model ?? config2.consolidationModel ?? "claude-haiku-4-5-20251001";
11113
+ const { runConsolidation: runConsolidation2 } = await Promise.resolve().then(() => (init_consolidation(), consolidation_exports));
11114
+ const client = getClient();
11115
+ let embedFn;
11116
+ try {
11117
+ const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
11118
+ embedFn = embed2;
11119
+ } catch {
11120
+ }
11121
+ const result = await runConsolidation2(client, {
11122
+ model: consolidationModel,
11123
+ maxCalls: max_clusters,
11124
+ embedFn
11125
+ });
11126
+ if (result.clustersProcessed === 0) {
11127
+ return {
11128
+ content: [{
11129
+ type: "text",
11130
+ text: `No consolidation needed \u2014 fewer than 20 unconsolidated memories for ${agentId}.`
11131
+ }]
11132
+ };
11133
+ }
11134
+ return {
11135
+ content: [{
11136
+ type: "text",
11137
+ text: `Consolidation complete:
11138
+ - Clusters processed: ${result.clustersProcessed}
11139
+ - Memories consolidated: ${result.memoriesConsolidated}
11140
+
11141
+ Consolidated summaries stored as tier-1 (importance=9) memories.`
11142
+ }]
11143
+ };
11144
+ } catch (err) {
11145
+ return {
11146
+ content: [{
11147
+ type: "text",
11148
+ text: `Consolidation failed: ${err instanceof Error ? err.message : String(err)}`
11149
+ }]
11150
+ };
11151
+ }
11152
+ }
11153
+ );
11154
+ }
11155
+
11156
+ // src/lib/telemetry.ts
11157
+ var ENABLED = process.env.EXE_TELEMETRY === "1";
11158
+ var initialized = false;
11159
+ async function ensureInit() {
11160
+ if (initialized || !ENABLED) return;
11161
+ initialized = true;
11162
+ try {
11163
+ const { NodeSDK } = await import("@opentelemetry/sdk-node");
11164
+ const { ConsoleSpanExporter } = await import("@opentelemetry/sdk-trace-base");
11165
+ const sdk = new NodeSDK({
11166
+ serviceName: "exe-os",
11167
+ traceExporter: new ConsoleSpanExporter()
11168
+ });
11169
+ sdk.start();
11170
+ process.stderr.write("[exe-os] OpenTelemetry tracing enabled\n");
11171
+ } catch (err) {
11172
+ process.stderr.write(
11173
+ `[exe-os] OpenTelemetry init failed: ${err instanceof Error ? err.message : String(err)}
11174
+ `
11175
+ );
11176
+ }
11177
+ }
11178
+ async function withTrace(toolName, fn) {
11179
+ if (!ENABLED) return fn();
11180
+ await ensureInit();
11181
+ const { trace, SpanStatusCode } = await import("@opentelemetry/api");
11182
+ const tracer = trace.getTracer("exe-os", "1.0.0");
11183
+ return tracer.startActiveSpan(`mcp.tool.${toolName}`, async (span) => {
11184
+ span.setAttribute("mcp.tool.name", toolName);
11185
+ try {
11186
+ const result = await fn();
11187
+ span.setStatus({ code: SpanStatusCode.OK });
11188
+ return result;
11189
+ } catch (err) {
11190
+ span.setStatus({
11191
+ code: SpanStatusCode.ERROR,
11192
+ message: err instanceof Error ? err.message : String(err)
11193
+ });
11194
+ span.recordException(
11195
+ err instanceof Error ? err : new Error(String(err))
11196
+ );
11197
+ throw err;
11198
+ } finally {
11199
+ span.end();
11200
+ }
11201
+ });
11202
+ }
11203
+ function instrumentServer(server2) {
11204
+ if (!ENABLED) return;
11205
+ const original = server2.registerTool.bind(server2);
11206
+ server2.registerTool = (name, config2, handler) => {
11207
+ const traced = async (...args) => withTrace(name, () => handler(...args));
11208
+ return original(name, config2, traced);
11209
+ };
11210
+ }
11211
+
10560
11212
  // src/mcp/server.ts
10561
11213
  var server = new McpServer({
10562
11214
  name: "exe-os",
10563
11215
  version: "1.3.0"
10564
11216
  });
10565
11217
  var _backfillTimer = null;
11218
+ instrumentServer(server);
10566
11219
  registerRecallMyMemory(server);
10567
11220
  registerAskTeamMemory(server);
10568
11221
  registerGetSessionContext(server);
@@ -10603,6 +11256,7 @@ registerUpdateWikiPage(server);
10603
11256
  registerDeployClient(server);
10604
11257
  registerQueryConversations(server);
10605
11258
  registerLoadSkill(server);
11259
+ registerConsolidateMemories(server);
10606
11260
  try {
10607
11261
  await initStore();
10608
11262
  process.stderr.write("[exe-os] MCP server starting...\n");
@@ -10678,3 +11332,18 @@ async function shutdown() {
10678
11332
  }
10679
11333
  process.on("SIGINT", () => void shutdown());
10680
11334
  process.on("SIGTERM", () => void shutdown());
11335
+ process.on("uncaughtException", (err) => {
11336
+ process.stderr.write(
11337
+ `[exe-os] uncaughtException (non-fatal): ${err.message}
11338
+ `
11339
+ );
11340
+ if (err.stack) {
11341
+ process.stderr.write(`[exe-os] ${err.stack.split("\n").slice(1, 3).join("\n ")}
11342
+ `);
11343
+ }
11344
+ });
11345
+ process.on("unhandledRejection", (reason) => {
11346
+ const msg = reason instanceof Error ? reason.message : String(reason);
11347
+ process.stderr.write(`[exe-os] unhandledRejection (non-fatal): ${msg}
11348
+ `);
11349
+ });