@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
package/dist/bin/cli.js CHANGED
@@ -275,13 +275,18 @@ __export(employees_exports, {
275
275
  EMPLOYEES_PATH: () => EMPLOYEES_PATH,
276
276
  addEmployee: () => addEmployee,
277
277
  getEmployee: () => getEmployee,
278
+ getEmployeeByRole: () => getEmployeeByRole,
279
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
280
+ hasRole: () => hasRole,
281
+ isMultiInstance: () => isMultiInstance,
278
282
  loadEmployees: () => loadEmployees,
283
+ loadEmployeesSync: () => loadEmployeesSync,
279
284
  registerBinSymlinks: () => registerBinSymlinks,
280
285
  saveEmployees: () => saveEmployees,
281
286
  validateEmployeeName: () => validateEmployeeName
282
287
  });
283
288
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
284
- import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
289
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
285
290
  import { execSync } from "child_process";
286
291
  import path2 from "path";
287
292
  function validateEmployeeName(name) {
@@ -314,9 +319,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
314
319
  await mkdir2(path2.dirname(employeesPath), { recursive: true });
315
320
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
316
321
  }
322
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
323
+ if (!existsSync2(employeesPath)) return [];
324
+ try {
325
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
326
+ } catch {
327
+ return [];
328
+ }
329
+ }
317
330
  function getEmployee(employees, name) {
318
331
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
319
332
  }
333
+ function getEmployeeByRole(employees, role) {
334
+ const lower = role.toLowerCase();
335
+ return employees.find((e) => e.role.toLowerCase() === lower);
336
+ }
337
+ function getEmployeeNamesByRole(employees, role) {
338
+ const lower = role.toLowerCase();
339
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
340
+ }
341
+ function hasRole(agentName, role) {
342
+ const employees = loadEmployeesSync();
343
+ const emp = getEmployee(employees, agentName);
344
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
345
+ }
346
+ function isMultiInstance(agentName, employees) {
347
+ const roster = employees ?? loadEmployeesSync();
348
+ const emp = getEmployee(roster, agentName);
349
+ if (!emp) return false;
350
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
351
+ }
320
352
  function addEmployee(employees, employee) {
321
353
  const normalized = { ...employee, name: employee.name.toLowerCase() };
322
354
  if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
@@ -359,12 +391,13 @@ function registerBinSymlinks(name) {
359
391
  }
360
392
  return { created, skipped, errors };
361
393
  }
362
- var EMPLOYEES_PATH;
394
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
363
395
  var init_employees = __esm({
364
396
  "src/lib/employees.ts"() {
365
397
  "use strict";
366
398
  init_config();
367
399
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
400
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
368
401
  }
369
402
  });
370
403
 
@@ -469,7 +502,7 @@ __export(installer_exports, {
469
502
  runInstaller: () => runInstaller
470
503
  });
471
504
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
472
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
505
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
473
506
  import path4 from "path";
474
507
  import os3 from "os";
475
508
  import { fileURLToPath } from "url";
@@ -481,7 +514,7 @@ function resolvePackageRoot() {
481
514
  const pkgPath = path4.join(dir, "package.json");
482
515
  if (existsSync4(pkgPath)) {
483
516
  try {
484
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
517
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
485
518
  if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
486
519
  } catch {
487
520
  }
@@ -774,26 +807,56 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
774
807
  permissions.allow = [];
775
808
  }
776
809
  const toolNames = [
810
+ // Core memory
777
811
  "store_memory",
778
812
  "recall_my_memory",
813
+ "commit_to_long_term_memory",
814
+ "consolidate_memories",
815
+ "ask_team_memory",
816
+ "get_session_context",
817
+ // Tasks
779
818
  "create_task",
780
819
  "list_tasks",
781
820
  "get_task",
782
821
  "update_task",
783
822
  "close_task",
823
+ "checkpoint_task",
824
+ // Behaviors
784
825
  "store_behavior",
785
826
  "deactivate_behavior",
786
- "send_message",
827
+ "list_behaviors",
828
+ // Identity
787
829
  "get_identity",
788
830
  "update_identity",
789
- "ask_team_memory",
790
- "get_session_context",
831
+ // Messaging
832
+ "send_message",
833
+ "acknowledge_messages",
834
+ "send_whatsapp",
835
+ "query_conversations",
836
+ // Reminders + triggers
791
837
  "create_reminder",
792
838
  "complete_reminder",
793
839
  "list_reminders",
794
- "list_behaviors",
840
+ "create_trigger",
841
+ "list_triggers",
842
+ // GraphRAG
795
843
  "query_relationships",
796
- "commit_to_long_term_memory"
844
+ "merge_entities",
845
+ // Documents + wiki
846
+ "ingest_document",
847
+ "list_documents",
848
+ "purge_document",
849
+ "rerank_documents",
850
+ "set_document_importance",
851
+ "create_wiki_page",
852
+ "update_wiki_page",
853
+ "get_wiki_page",
854
+ "list_wiki_pages",
855
+ // System
856
+ "load_skill",
857
+ "apply_starter_pack",
858
+ "resume_employee",
859
+ "deploy_client"
797
860
  ];
798
861
  const allowList = permissions.allow;
799
862
  for (const tool of expandDualPrefixTools(toolNames)) {
@@ -922,6 +985,61 @@ var init_memory = __esm({
922
985
  }
923
986
  });
924
987
 
988
+ // src/lib/db-retry.ts
989
+ function isBusyError(err) {
990
+ if (err instanceof Error) {
991
+ const msg = err.message.toLowerCase();
992
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
993
+ }
994
+ return false;
995
+ }
996
+ function delay(ms) {
997
+ return new Promise((resolve) => setTimeout(resolve, ms));
998
+ }
999
+ async function retryOnBusy(fn, label) {
1000
+ let lastError;
1001
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
1002
+ try {
1003
+ return await fn();
1004
+ } catch (err) {
1005
+ lastError = err;
1006
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
1007
+ throw err;
1008
+ }
1009
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
1010
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
1011
+ process.stderr.write(
1012
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
1013
+ `
1014
+ );
1015
+ await delay(backoff + jitter);
1016
+ }
1017
+ }
1018
+ throw lastError;
1019
+ }
1020
+ function wrapWithRetry(client) {
1021
+ return new Proxy(client, {
1022
+ get(target, prop, receiver) {
1023
+ if (prop === "execute") {
1024
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
1025
+ }
1026
+ if (prop === "batch") {
1027
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
1028
+ }
1029
+ return Reflect.get(target, prop, receiver);
1030
+ }
1031
+ });
1032
+ }
1033
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
1034
+ var init_db_retry = __esm({
1035
+ "src/lib/db-retry.ts"() {
1036
+ "use strict";
1037
+ MAX_RETRIES = 3;
1038
+ BASE_DELAY_MS = 200;
1039
+ MAX_JITTER_MS = 300;
1040
+ }
1041
+ });
1042
+
925
1043
  // src/lib/database.ts
926
1044
  var database_exports = {};
927
1045
  __export(database_exports, {
@@ -929,6 +1047,7 @@ __export(database_exports, {
929
1047
  disposeTurso: () => disposeTurso,
930
1048
  ensureSchema: () => ensureSchema,
931
1049
  getClient: () => getClient,
1050
+ getRawClient: () => getRawClient,
932
1051
  initDatabase: () => initDatabase,
933
1052
  initTurso: () => initTurso,
934
1053
  isInitialized: () => isInitialized
@@ -938,6 +1057,7 @@ async function initDatabase(config) {
938
1057
  if (_client) {
939
1058
  _client.close();
940
1059
  _client = null;
1060
+ _resilientClient = null;
941
1061
  }
942
1062
  const opts = {
943
1063
  url: `file:${config.dbPath}`
@@ -946,20 +1066,27 @@ async function initDatabase(config) {
946
1066
  opts.encryptionKey = config.encryptionKey;
947
1067
  }
948
1068
  _client = createClient(opts);
1069
+ _resilientClient = wrapWithRetry(_client);
949
1070
  }
950
1071
  function isInitialized() {
951
1072
  return _client !== null;
952
1073
  }
953
1074
  function getClient() {
1075
+ if (!_resilientClient) {
1076
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1077
+ }
1078
+ return _resilientClient;
1079
+ }
1080
+ function getRawClient() {
954
1081
  if (!_client) {
955
1082
  throw new Error("Database client not initialized. Call initDatabase() first.");
956
1083
  }
957
1084
  return _client;
958
1085
  }
959
1086
  async function ensureSchema() {
960
- const client = getClient();
1087
+ const client = getRawClient();
961
1088
  await client.execute("PRAGMA journal_mode = WAL");
962
- await client.execute("PRAGMA busy_timeout = 5000");
1089
+ await client.execute("PRAGMA busy_timeout = 30000");
963
1090
  try {
964
1091
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
965
1092
  } catch {
@@ -1752,13 +1879,16 @@ async function disposeDatabase() {
1752
1879
  if (_client) {
1753
1880
  _client.close();
1754
1881
  _client = null;
1882
+ _resilientClient = null;
1755
1883
  }
1756
1884
  }
1757
- var _client, initTurso, disposeTurso;
1885
+ var _client, _resilientClient, initTurso, disposeTurso;
1758
1886
  var init_database = __esm({
1759
1887
  "src/lib/database.ts"() {
1760
1888
  "use strict";
1889
+ init_db_retry();
1761
1890
  _client = null;
1891
+ _resilientClient = null;
1762
1892
  initTurso = initDatabase;
1763
1893
  disposeTurso = disposeDatabase;
1764
1894
  }
@@ -1963,12 +2093,12 @@ function shardExists(projectName) {
1963
2093
  }
1964
2094
  function listShards() {
1965
2095
  if (!existsSync6(SHARDS_DIR)) return [];
1966
- const { readdirSync: readdirSync4 } = __require("fs");
1967
- return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2096
+ const { readdirSync: readdirSync5 } = __require("fs");
2097
+ return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1968
2098
  }
1969
2099
  async function ensureShardSchema(client) {
1970
2100
  await client.execute("PRAGMA journal_mode = WAL");
1971
- await client.execute("PRAGMA busy_timeout = 5000");
2101
+ await client.execute("PRAGMA busy_timeout = 30000");
1972
2102
  try {
1973
2103
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1974
2104
  } catch {
@@ -2229,7 +2359,8 @@ async function writeMemory(record) {
2229
2359
  has_error: record.has_error ? 1 : 0,
2230
2360
  raw_text: record.raw_text,
2231
2361
  vector: record.vector,
2232
- version: _nextVersion++,
2362
+ version: 0,
2363
+ // Placeholder — assigned atomically at flush time
2233
2364
  task_id: record.task_id ?? null,
2234
2365
  importance: record.importance ?? 5,
2235
2366
  status: record.status ?? "active",
@@ -2263,6 +2394,13 @@ async function flushBatch() {
2263
2394
  _flushing = true;
2264
2395
  try {
2265
2396
  const batch = _pendingRecords.slice(0);
2397
+ const client = getClient();
2398
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2399
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2400
+ for (const row of batch) {
2401
+ row.version = baseVersion++;
2402
+ }
2403
+ _nextVersion = baseVersion;
2266
2404
  const buildStmt = (row) => {
2267
2405
  const hasVector = row.vector !== null;
2268
2406
  const taskId = row.task_id ?? null;
@@ -2591,7 +2729,7 @@ var init_store = __esm({
2591
2729
  import net from "net";
2592
2730
  import { spawn } from "child_process";
2593
2731
  import { randomUUID } from "crypto";
2594
- import { existsSync as existsSync7, unlinkSync, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2732
+ import { existsSync as existsSync7, unlinkSync, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
2595
2733
  import path7 from "path";
2596
2734
  import { fileURLToPath as fileURLToPath2 } from "url";
2597
2735
  function handleData(chunk) {
@@ -2616,7 +2754,7 @@ function handleData(chunk) {
2616
2754
  function cleanupStaleFiles() {
2617
2755
  if (existsSync7(PID_PATH)) {
2618
2756
  try {
2619
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2757
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2620
2758
  if (pid > 0) {
2621
2759
  try {
2622
2760
  process.kill(pid, 0);
@@ -2764,11 +2902,11 @@ async function connectEmbedDaemon() {
2764
2902
  }
2765
2903
  }
2766
2904
  const start = Date.now();
2767
- let delay = 100;
2905
+ let delay2 = 100;
2768
2906
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2769
- await new Promise((r) => setTimeout(r, delay));
2907
+ await new Promise((r) => setTimeout(r, delay2));
2770
2908
  if (await connectToSocket()) return true;
2771
- delay = Math.min(delay * 2, 3e3);
2909
+ delay2 = Math.min(delay2 * 2, 3e3);
2772
2910
  }
2773
2911
  return false;
2774
2912
  }
@@ -2824,7 +2962,7 @@ function killAndRespawnDaemon() {
2824
2962
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2825
2963
  if (existsSync7(PID_PATH)) {
2826
2964
  try {
2827
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2965
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2828
2966
  if (pid > 0) {
2829
2967
  try {
2830
2968
  process.kill(pid, "SIGKILL");
@@ -2860,11 +2998,11 @@ async function embedViaClient(text, priority = "high") {
2860
2998
  `);
2861
2999
  killAndRespawnDaemon();
2862
3000
  const start = Date.now();
2863
- let delay = 200;
3001
+ let delay2 = 200;
2864
3002
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2865
- await new Promise((r) => setTimeout(r, delay));
3003
+ await new Promise((r) => setTimeout(r, delay2));
2866
3004
  if (await connectToSocket()) break;
2867
- delay = Math.min(delay * 2, 3e3);
3005
+ delay2 = Math.min(delay2 * 2, 3e3);
2868
3006
  }
2869
3007
  if (!_connected) return null;
2870
3008
  }
@@ -2876,11 +3014,11 @@ async function embedViaClient(text, priority = "high") {
2876
3014
  `);
2877
3015
  killAndRespawnDaemon();
2878
3016
  const start = Date.now();
2879
- let delay = 200;
3017
+ let delay2 = 200;
2880
3018
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2881
- await new Promise((r) => setTimeout(r, delay));
3019
+ await new Promise((r) => setTimeout(r, delay2));
2882
3020
  if (await connectToSocket()) break;
2883
- delay = Math.min(delay * 2, 3e3);
3021
+ delay2 = Math.min(delay2 * 2, 3e3);
2884
3022
  }
2885
3023
  if (!_connected) return null;
2886
3024
  const retry = await sendRequest([text], priority);
@@ -2953,7 +3091,8 @@ import { readdir as readdir2, stat } from "fs/promises";
2953
3091
  import path8 from "path";
2954
3092
  import { createInterface } from "readline";
2955
3093
  import { homedir } from "os";
2956
- async function findJsonlFiles(cutoffMs) {
3094
+ import { parseArgs } from "util";
3095
+ async function findJsonlFiles(sinceDate, projectFilter) {
2957
3096
  const projectsDir = path8.join(homedir(), ".claude", "projects");
2958
3097
  const files = [];
2959
3098
  async function walk(dir) {
@@ -2966,59 +3105,65 @@ async function findJsonlFiles(cutoffMs) {
2966
3105
  for (const entry of entries) {
2967
3106
  const full = path8.join(dir, entry.name);
2968
3107
  if (entry.isDirectory()) {
3108
+ if (entry.name === "subagents" || entry.name === "tool-results") continue;
2969
3109
  await walk(full);
2970
3110
  } else if (entry.name.endsWith(".jsonl")) {
2971
3111
  try {
2972
3112
  const s = await stat(full);
2973
- if (s.mtimeMs >= cutoffMs) files.push(full);
3113
+ if (sinceDate && s.mtimeMs < sinceDate.getTime()) continue;
3114
+ files.push(full);
2974
3115
  } catch {
2975
3116
  }
2976
3117
  }
2977
3118
  }
2978
3119
  }
2979
- await walk(projectsDir);
2980
- return files;
2981
- }
2982
- function projectNameFromPath(filePath) {
2983
- const projectsDir = path8.join(homedir(), ".claude", "projects");
2984
- const relative = path8.relative(projectsDir, filePath);
2985
- const projectDir = relative.split(path8.sep)[0] ?? relative;
2986
- const homeEncoded = homedir().replaceAll("/", "-");
2987
- if (projectDir.startsWith(homeEncoded + "-")) {
2988
- return projectDir.slice(homeEncoded.length + 1);
2989
- }
2990
- if (projectDir === homeEncoded) return "home";
2991
- return projectDir;
2992
- }
2993
- function extractAssistantText(content) {
2994
- if (typeof content === "string") return content;
2995
- const texts = [];
2996
- for (const block of content) {
2997
- if (block.type === "text" && block.text) {
2998
- texts.push(block.text);
3120
+ if (projectFilter) {
3121
+ let projectDirs;
3122
+ try {
3123
+ projectDirs = await readdir2(projectsDir, { withFileTypes: true });
3124
+ } catch {
3125
+ return files;
2999
3126
  }
3000
- }
3001
- return texts.join("\n");
3002
- }
3003
- function extractUserText(content) {
3004
- if (typeof content === "string") return content;
3005
- const texts = [];
3006
- for (const block of content) {
3007
- if (block.type === "text" && block.text) {
3008
- texts.push(block.text);
3127
+ for (const entry of projectDirs) {
3128
+ if (!entry.isDirectory()) continue;
3129
+ const decoded = decodeProjectDir(entry.name);
3130
+ if (decoded.toLowerCase().includes(projectFilter.toLowerCase())) {
3131
+ await walk(path8.join(projectsDir, entry.name));
3132
+ }
3009
3133
  }
3134
+ } else {
3135
+ await walk(projectsDir);
3010
3136
  }
3011
- return texts.join("\n");
3137
+ return files;
3012
3138
  }
3013
- async function extractConversationPairs(filePath, projectFilter) {
3014
- const fallbackProject = projectNameFromPath(filePath);
3015
- if (projectFilter && !fallbackProject.toLowerCase().includes(projectFilter.toLowerCase())) {
3016
- return [];
3139
+ function decodeProjectDir(dirName) {
3140
+ const homeEncoded = homedir().replaceAll("/", "-");
3141
+ if (dirName.startsWith(homeEncoded + "-")) {
3142
+ return dirName.slice(homeEncoded.length + 1);
3017
3143
  }
3018
- const pairs = [];
3019
- let fileCwd = "";
3020
- let fileSessionId = "";
3021
- let pendingUser = null;
3144
+ if (dirName === homeEncoded) return "home";
3145
+ return dirName;
3146
+ }
3147
+ function projectNameFromPath(filePath) {
3148
+ const projectsDir = path8.join(homedir(), ".claude", "projects");
3149
+ const relative = path8.relative(projectsDir, filePath);
3150
+ const projectDir = relative.split(path8.sep)[0] ?? "unknown";
3151
+ return decodeProjectDir(projectDir);
3152
+ }
3153
+ async function parseConversation(filePath) {
3154
+ const conv = {
3155
+ sessionId: path8.basename(filePath, ".jsonl"),
3156
+ projectName: projectNameFromPath(filePath),
3157
+ cwd: void 0,
3158
+ startTime: void 0,
3159
+ endTime: void 0,
3160
+ userMessages: [],
3161
+ toolCounts: {},
3162
+ filesTouched: /* @__PURE__ */ new Set(),
3163
+ errorCount: 0,
3164
+ totalMessages: 0,
3165
+ agentId: "default"
3166
+ };
3022
3167
  const rl = createInterface({
3023
3168
  input: createReadStream(filePath, { encoding: "utf8" }),
3024
3169
  crlfDelay: Infinity
@@ -3031,240 +3176,248 @@ async function extractConversationPairs(filePath, projectFilter) {
3031
3176
  } catch {
3032
3177
  continue;
3033
3178
  }
3034
- if (entry.cwd) fileCwd = entry.cwd;
3035
- if (entry.sessionId) fileSessionId = entry.sessionId;
3036
- if (entry.type === "user" && entry.message?.content) {
3037
- const text = extractUserText(entry.message.content);
3038
- if (text.trim()) {
3039
- pendingUser = {
3040
- text,
3041
- timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
3042
- uuid: entry.uuid ?? ""
3043
- };
3179
+ if (entry.cwd && typeof entry.cwd === "string") {
3180
+ conv.cwd = entry.cwd;
3181
+ }
3182
+ const ts = entry.timestamp;
3183
+ if (ts) {
3184
+ if (!conv.startTime || ts < conv.startTime) conv.startTime = ts;
3185
+ if (!conv.endTime || ts > conv.endTime) conv.endTime = ts;
3186
+ }
3187
+ const entryType = entry.type;
3188
+ if (entryType === "user") {
3189
+ conv.totalMessages++;
3190
+ const message = entry.message;
3191
+ if (message?.content) {
3192
+ const text = extractUserText(message.content);
3193
+ if (text && text.length > 10) {
3194
+ conv.userMessages.push(text);
3195
+ }
3196
+ }
3197
+ } else if (entryType === "assistant") {
3198
+ conv.totalMessages++;
3199
+ const message = entry.message;
3200
+ if (message?.content && Array.isArray(message.content)) {
3201
+ for (const block of message.content) {
3202
+ if (typeof block !== "object" || block === null) continue;
3203
+ const b = block;
3204
+ if (b.type === "tool_use") {
3205
+ const toolName = b.name;
3206
+ conv.toolCounts[toolName] = (conv.toolCounts[toolName] || 0) + 1;
3207
+ const input = b.input;
3208
+ if (input?.file_path && typeof input.file_path === "string") {
3209
+ if (toolName === "Write" || toolName === "Edit") {
3210
+ conv.filesTouched.add(input.file_path);
3211
+ }
3212
+ }
3213
+ }
3214
+ }
3215
+ }
3216
+ }
3217
+ }
3218
+ if (conv.cwd) {
3219
+ conv.projectName = path8.basename(conv.cwd);
3220
+ const worktreeMatch = conv.cwd.match(/\.worktrees\/([^/]+)/);
3221
+ if (worktreeMatch?.[1]) {
3222
+ conv.agentId = worktreeMatch[1];
3223
+ }
3224
+ }
3225
+ return conv;
3226
+ }
3227
+ function extractUserText(content) {
3228
+ if (typeof content === "string") return content;
3229
+ if (Array.isArray(content)) {
3230
+ const parts = [];
3231
+ for (const block of content) {
3232
+ if (typeof block === "string") {
3233
+ parts.push(block);
3234
+ } else if (typeof block === "object" && block !== null) {
3235
+ const b = block;
3236
+ if (b.type === "text" && typeof b.text === "string") {
3237
+ parts.push(b.text);
3238
+ }
3044
3239
  }
3045
- } else if (entry.type === "assistant" && entry.message?.content) {
3046
- const assistantText = extractAssistantText(entry.message.content);
3047
- if (!assistantText.trim()) continue;
3048
- const resolvedProject = fileCwd ? path8.basename(fileCwd) : fallbackProject;
3049
- const userText = pendingUser?.text ?? "";
3050
- const timestamp = entry.timestamp ?? pendingUser?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
3051
- pairs.push({
3052
- userText,
3053
- assistantText,
3054
- timestamp,
3055
- sessionId: fileSessionId || path8.basename(filePath, ".jsonl"),
3056
- project: resolvedProject,
3057
- cwd: fileCwd || fallbackProject
3058
- });
3059
- pendingUser = null;
3060
3240
  }
3241
+ return parts.join("\n");
3061
3242
  }
3062
- return pairs;
3243
+ return "";
3063
3244
  }
3064
- function hashPair(userText, assistantText) {
3065
- return crypto2.createHash("sha256").update(userText.slice(0, 500) + "||" + assistantText.slice(0, 500)).digest("hex");
3245
+ function buildSummary(conv) {
3246
+ const parts = [];
3247
+ parts.push(`Session: ${conv.sessionId}`);
3248
+ parts.push(`Project: ${conv.projectName}`);
3249
+ if (conv.startTime) {
3250
+ parts.push(`Time: ${conv.startTime}${conv.endTime ? ` \u2192 ${conv.endTime}` : ""}`);
3251
+ }
3252
+ parts.push(`Messages: ${conv.totalMessages}`);
3253
+ if (conv.agentId !== "default") {
3254
+ parts.push(`Agent: ${conv.agentId}`);
3255
+ }
3256
+ parts.push("");
3257
+ if (conv.userMessages.length > 0) {
3258
+ parts.push("## What was asked");
3259
+ const prompts = conv.userMessages.slice(0, 5);
3260
+ for (const prompt of prompts) {
3261
+ const truncated = prompt.length > 300 ? prompt.slice(0, 300) + "..." : prompt;
3262
+ const cleaned = truncated.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
3263
+ if (cleaned) parts.push(`- ${cleaned}`);
3264
+ }
3265
+ if (conv.userMessages.length > 5) {
3266
+ parts.push(`- ... and ${conv.userMessages.length - 5} more prompts`);
3267
+ }
3268
+ parts.push("");
3269
+ }
3270
+ const toolEntries = Object.entries(conv.toolCounts).sort((a, b) => b[1] - a[1]);
3271
+ if (toolEntries.length > 0) {
3272
+ parts.push("## Tools used");
3273
+ for (const [tool, count] of toolEntries) {
3274
+ parts.push(`- ${tool}: ${count}`);
3275
+ }
3276
+ parts.push("");
3277
+ }
3278
+ if (conv.filesTouched.size > 0) {
3279
+ parts.push("## Files modified");
3280
+ const fileList = [...conv.filesTouched].sort();
3281
+ const shown = fileList.slice(0, 20);
3282
+ for (const f of shown) {
3283
+ parts.push(`- ${f}`);
3284
+ }
3285
+ if (fileList.length > 20) {
3286
+ parts.push(`- ... and ${fileList.length - 20} more files`);
3287
+ }
3288
+ parts.push("");
3289
+ }
3290
+ if (conv.errorCount > 0) {
3291
+ parts.push(`Errors: ${conv.errorCount}`);
3292
+ }
3293
+ let summary = parts.join("\n");
3294
+ if (summary.length > MAX_SUMMARY_LENGTH) {
3295
+ summary = summary.slice(0, MAX_SUMMARY_LENGTH);
3296
+ }
3297
+ return summary;
3066
3298
  }
3067
- async function loadExistingHashes(cutoffIso) {
3299
+ async function loadExistingSourcePaths() {
3068
3300
  const client = getClient();
3069
- const hashes = /* @__PURE__ */ new Set();
3301
+ const paths = /* @__PURE__ */ new Set();
3070
3302
  let offset = 0;
3071
3303
  const batchSize = 500;
3072
3304
  while (true) {
3073
3305
  const result = await client.execute({
3074
- sql: `SELECT content_text, agent_response FROM conversations
3075
- WHERE platform = 'claude-code' AND timestamp > ?
3306
+ sql: `SELECT source_path FROM memories
3307
+ WHERE tool_name = ? AND source_path IS NOT NULL
3076
3308
  ORDER BY id LIMIT ? OFFSET ?`,
3077
- args: [cutoffIso, batchSize, offset]
3309
+ args: [TOOL_NAME, batchSize, offset]
3078
3310
  });
3079
3311
  if (result.rows.length === 0) break;
3080
3312
  for (const row of result.rows) {
3081
- hashes.add(
3082
- hashPair(
3083
- row.content_text ?? "",
3084
- row.agent_response ?? ""
3085
- )
3086
- );
3313
+ paths.add(row.source_path);
3087
3314
  }
3088
3315
  offset += batchSize;
3089
3316
  }
3090
- return hashes;
3091
- }
3092
- async function storePair(pair, daemonConnected, stats, agentId) {
3093
- const client = getClient();
3094
- const id = crypto2.randomUUID();
3095
- const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
3096
- const assistTrunc = pair.assistantText.length > MAX_CONTENT_LENGTH ? pair.assistantText.slice(0, MAX_CONTENT_LENGTH) : pair.assistantText;
3097
- await client.execute({
3098
- sql: `INSERT INTO conversations
3099
- (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
3100
- recipient_id, channel_id, thread_id, reply_to_id,
3101
- content_text, content_media, agent_response, agent_name,
3102
- timestamp, ingested_at)
3103
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3104
- args: [
3105
- id,
3106
- "claude-code",
3107
- null,
3108
- "user",
3109
- "user",
3110
- null,
3111
- null,
3112
- null,
3113
- pair.project,
3114
- pair.sessionId,
3115
- null,
3116
- userTrunc,
3117
- null,
3118
- assistTrunc,
3119
- "claude",
3120
- pair.timestamp,
3121
- (/* @__PURE__ */ new Date()).toISOString()
3122
- ]
3123
- });
3124
- stats.conversationsStored++;
3125
- const rawText = [
3126
- `[claude-code] Conversation in ${pair.project}`,
3127
- userTrunc ? `User: ${userTrunc}` : null,
3128
- `Assistant: ${assistTrunc}`
3129
- ].filter(Boolean).join("\n");
3130
- let vector = null;
3131
- if (daemonConnected) {
3132
- try {
3133
- vector = await embedViaClient(rawText, "low");
3134
- if (!vector) stats.embedFailed++;
3135
- } catch {
3136
- stats.embedFailed++;
3137
- }
3138
- }
3139
- await writeMemory({
3140
- id: crypto2.randomUUID(),
3141
- agent_id: agentId,
3142
- agent_role: "coo",
3143
- session_id: pair.sessionId,
3144
- timestamp: pair.timestamp,
3145
- tool_name: "ConversationBackfill",
3146
- project_name: pair.project,
3147
- has_error: false,
3148
- raw_text: rawText,
3149
- vector,
3150
- importance: 3
3151
- });
3152
- stats.memoriesStored++;
3317
+ return paths;
3153
3318
  }
3154
3319
  async function backfillConversations(options) {
3155
3320
  const stats = {
3156
3321
  filesScanned: 0,
3157
3322
  conversationsStored: 0,
3158
- memoriesStored: 0,
3159
3323
  skippedDedup: 0,
3160
- skippedShort: 0,
3324
+ skippedTooShort: 0,
3161
3325
  embedFailed: 0
3162
3326
  };
3163
- const cutoffMs = Date.now() - options.days * 24 * 60 * 60 * 1e3;
3164
- const cutoffIso = new Date(cutoffMs).toISOString();
3165
- let cooAgentId = "exe";
3166
- try {
3167
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
3168
- const employees = await loadEmployees2();
3169
- const coo = employees.find((e) => e.role === "COO");
3170
- if (coo) cooAgentId = coo.name.toLowerCase();
3171
- } catch {
3327
+ const sinceDate = options.since ? new Date(options.since) : void 0;
3328
+ if (sinceDate && isNaN(sinceDate.getTime())) {
3329
+ throw new Error(`Invalid --since date: ${options.since}`);
3172
3330
  }
3173
- process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days (agent: ${cooAgentId})...
3174
- `);
3331
+ process.stderr.write("[backfill-conversations] Initializing store...\n");
3332
+ await initStore();
3333
+ let daemonConnected = false;
3175
3334
  if (!options.dryRun) {
3176
- process.stderr.write("[backfill-conversations] Initializing store...\n");
3177
- await initStore();
3178
- try {
3179
- const client = getClient();
3180
- const old = await client.execute({
3181
- sql: "SELECT COUNT(*) as cnt FROM memories WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
3182
- args: [cooAgentId]
3183
- });
3184
- const count = Number(old.rows[0]?.cnt ?? 0);
3185
- if (count > 0) {
3186
- await client.execute({
3187
- sql: "UPDATE memories SET agent_id = ?, agent_role = 'coo' WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
3188
- args: [cooAgentId, cooAgentId]
3189
- });
3190
- process.stderr.write(`[backfill-conversations] Migrated ${count} records \u2192 agent_id='${cooAgentId}'
3191
- `);
3192
- }
3193
- } catch {
3335
+ daemonConnected = await connectEmbedDaemon();
3336
+ if (!daemonConnected) {
3337
+ process.stderr.write(
3338
+ "[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
3339
+ );
3194
3340
  }
3195
3341
  }
3196
- const daemonConnected = options.dryRun ? false : await connectEmbedDaemon();
3197
- if (!daemonConnected && !options.dryRun) {
3198
- process.stderr.write(
3199
- "[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
3200
- );
3201
- }
3202
- let seenHashes = /* @__PURE__ */ new Set();
3203
- if (!options.dryRun) {
3204
- process.stderr.write("[backfill-conversations] Loading existing hashes for dedup...\n");
3205
- seenHashes = await loadExistingHashes(cutoffIso);
3206
- process.stderr.write(`[backfill-conversations] ${seenHashes.size} existing conversations loaded
3342
+ process.stderr.write("[backfill-conversations] Loading already-ingested conversations...\n");
3343
+ const existingPaths = options.dryRun ? /* @__PURE__ */ new Set() : await loadExistingSourcePaths();
3344
+ process.stderr.write(`[backfill-conversations] ${existingPaths.size} conversations already ingested
3207
3345
  `);
3208
- }
3209
- const files = await findJsonlFiles(cutoffMs);
3210
- process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files
3346
+ const files = await findJsonlFiles(sinceDate, options.project);
3347
+ process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
3211
3348
  `);
3349
+ process.env.EXE_EMBED_PRIORITY = "low";
3212
3350
  for (const file of files) {
3213
- const pairs = await extractConversationPairs(file, options.projectFilter);
3214
3351
  stats.filesScanned++;
3215
- for (const pair of pairs) {
3216
- if (pair.assistantText.length < MIN_ASSISTANT_LENGTH) {
3217
- stats.skippedShort++;
3218
- continue;
3219
- }
3220
- const hash = hashPair(pair.userText, pair.assistantText);
3221
- if (seenHashes.has(hash)) {
3222
- stats.skippedDedup++;
3223
- continue;
3224
- }
3225
- seenHashes.add(hash);
3226
- if (!options.dryRun) {
3227
- await storePair(pair, daemonConnected, stats, cooAgentId);
3228
- } else {
3229
- stats.conversationsStored++;
3230
- stats.memoriesStored++;
3352
+ if (existingPaths.has(file)) {
3353
+ stats.skippedDedup++;
3354
+ continue;
3355
+ }
3356
+ const conv = await parseConversation(file);
3357
+ if (conv.totalMessages < MIN_MESSAGES) {
3358
+ stats.skippedTooShort++;
3359
+ continue;
3360
+ }
3361
+ const summary = buildSummary(conv);
3362
+ if (options.dryRun) {
3363
+ process.stdout.write(`
3364
+ \u2500\u2500\u2500 ${file} \u2500\u2500\u2500
3365
+ `);
3366
+ process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
3367
+ process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
3368
+ process.stdout.write(` | Files: ${conv.filesTouched.size}
3369
+ `);
3370
+ const firstPrompt = conv.userMessages[0];
3371
+ if (firstPrompt) {
3372
+ process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
3373
+ `);
3231
3374
  }
3375
+ stats.conversationsStored++;
3376
+ continue;
3232
3377
  }
3233
- if (stats.filesScanned % 20 === 0) {
3378
+ let vector = null;
3379
+ if (daemonConnected) {
3380
+ try {
3381
+ vector = await embedViaClient(summary, "low");
3382
+ if (!vector) stats.embedFailed++;
3383
+ } catch {
3384
+ stats.embedFailed++;
3385
+ }
3386
+ }
3387
+ await writeMemory({
3388
+ id: crypto2.randomUUID(),
3389
+ agent_id: conv.agentId,
3390
+ agent_role: conv.agentId === "exe" ? "COO" : "specialist",
3391
+ session_id: conv.sessionId,
3392
+ timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
3393
+ tool_name: TOOL_NAME,
3394
+ project_name: conv.projectName,
3395
+ has_error: conv.errorCount > 0,
3396
+ raw_text: summary,
3397
+ vector,
3398
+ source_path: file,
3399
+ source_type: "conversation"
3400
+ });
3401
+ existingPaths.add(file);
3402
+ stats.conversationsStored++;
3403
+ if (stats.filesScanned % 50 === 0) {
3234
3404
  process.stderr.write(
3235
3405
  `[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
3236
3406
  `
3237
3407
  );
3238
- if (!options.dryRun) await flushBatch();
3408
+ await flushBatch();
3239
3409
  }
3240
3410
  }
3241
- if (!options.dryRun) await flushBatch();
3411
+ if (!options.dryRun) {
3412
+ await flushBatch();
3413
+ }
3242
3414
  process.stderr.write(
3243
- `[backfill-conversations] Done. Files: ${stats.filesScanned}, Conversations: ${stats.conversationsStored}, Memories: ${stats.memoriesStored}, Dedup: ${stats.skippedDedup}, Short: ${stats.skippedShort}, EmbedFail: ${stats.embedFailed}
3415
+ `[backfill-conversations] Done. Scanned: ${stats.filesScanned}, Stored: ${stats.conversationsStored}, Dedup: ${stats.skippedDedup}, TooShort: ${stats.skippedTooShort}, EmbedFail: ${stats.embedFailed}
3244
3416
  `
3245
3417
  );
3246
3418
  return stats;
3247
3419
  }
3248
- function parseArgs(argv) {
3249
- let days = 30;
3250
- let projectFilter;
3251
- let dryRun = false;
3252
- for (let i = 0; i < argv.length; i++) {
3253
- switch (argv[i]) {
3254
- case "--days":
3255
- days = parseInt(argv[++i] ?? "30", 10) || 30;
3256
- break;
3257
- case "--project":
3258
- projectFilter = argv[++i] ?? void 0;
3259
- break;
3260
- case "--dry-run":
3261
- dryRun = true;
3262
- break;
3263
- }
3264
- }
3265
- return { days, projectFilter, dryRun };
3266
- }
3267
- var MAX_CONTENT_LENGTH, MIN_ASSISTANT_LENGTH;
3420
+ var TOOL_NAME, MIN_MESSAGES, MAX_SUMMARY_LENGTH;
3268
3421
  var init_backfill_conversations = __esm({
3269
3422
  "src/bin/backfill-conversations.ts"() {
3270
3423
  "use strict";
@@ -3272,522 +3425,90 @@ var init_backfill_conversations = __esm({
3272
3425
  init_exe_daemon_client();
3273
3426
  init_database();
3274
3427
  init_is_main();
3275
- process.env.EXE_EMBED_PRIORITY = "low";
3276
- MAX_CONTENT_LENGTH = 8e3;
3277
- MIN_ASSISTANT_LENGTH = 50;
3428
+ TOOL_NAME = "backfill-conversation";
3429
+ MIN_MESSAGES = 3;
3430
+ MAX_SUMMARY_LENGTH = 4e3;
3278
3431
  if (isMainModule(import.meta.url)) {
3279
- const options = parseArgs(process.argv.slice(2));
3280
- backfillConversations(options).then((result) => {
3432
+ const { values } = parseArgs({
3433
+ options: {
3434
+ since: { type: "string" },
3435
+ project: { type: "string" },
3436
+ "dry-run": { type: "boolean", default: false }
3437
+ },
3438
+ strict: true
3439
+ });
3440
+ backfillConversations({
3441
+ since: values.since,
3442
+ project: values.project,
3443
+ dryRun: values["dry-run"]
3444
+ }).then((result) => {
3281
3445
  console.log(JSON.stringify(result, null, 2));
3282
3446
  process.exit(0);
3283
3447
  }).catch((err) => {
3284
- console.error(
3285
- "Backfill failed:",
3286
- err instanceof Error ? err.message : String(err)
3287
- );
3448
+ console.error("Backfill failed:", err instanceof Error ? err.message : String(err));
3288
3449
  process.exit(1);
3289
3450
  });
3290
3451
  }
3291
3452
  }
3292
3453
  });
3293
3454
 
3294
- // src/lib/model-downloader.ts
3295
- import { createWriteStream, createReadStream as createReadStream2, existsSync as existsSync8, unlinkSync as unlinkSync2, renameSync as renameSync2 } from "fs";
3296
- import { mkdir as mkdir5 } from "fs/promises";
3297
- import { createHash } from "crypto";
3298
- import path9 from "path";
3299
- async function downloadModel(opts) {
3300
- const { destDir, onProgress, fetchFn = globalThis.fetch } = opts;
3301
- const destPath = path9.join(destDir, LOCAL_FILENAME);
3302
- const tmpPath = destPath + ".tmp";
3303
- await mkdir5(destDir, { recursive: true });
3304
- if (existsSync8(destPath)) {
3305
- const hash2 = await fileHash(destPath);
3306
- if (hash2 === EXPECTED_SHA256) {
3307
- return destPath;
3455
+ // src/lib/employee-templates.ts
3456
+ var employee_templates_exports = {};
3457
+ __export(employee_templates_exports, {
3458
+ BASE_OPERATING_PROCEDURES: () => BASE_OPERATING_PROCEDURES,
3459
+ CLIENT_COO_TEMPLATE: () => CLIENT_COO_TEMPLATE,
3460
+ DEFAULT_EXE: () => DEFAULT_EXE,
3461
+ TEMPLATES: () => TEMPLATES,
3462
+ TEMPLATE_VERSION: () => TEMPLATE_VERSION,
3463
+ buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
3464
+ getSessionPrompt: () => getSessionPrompt,
3465
+ getTemplate: () => getTemplate,
3466
+ personalizePrompt: () => personalizePrompt,
3467
+ renderClientCOOTemplate: () => renderClientCOOTemplate
3468
+ });
3469
+ function getSessionPrompt(storedPrompt) {
3470
+ const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
3471
+ const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
3472
+ return `${rolePrompt}
3473
+ ${BASE_OPERATING_PROCEDURES}`;
3474
+ }
3475
+ function buildCustomEmployeePrompt(name, role) {
3476
+ return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
3477
+ }
3478
+ function getTemplate(name) {
3479
+ return TEMPLATES[name];
3480
+ }
3481
+ function personalizePrompt(prompt, templateName, actualName) {
3482
+ if (templateName === actualName) return prompt;
3483
+ const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3484
+ return prompt.replace(new RegExp(`\\bYou are ${escaped}\\b`, "g"), `You are ${actualName}`);
3485
+ }
3486
+ function renderClientCOOTemplate(vars) {
3487
+ for (const key of CLIENT_COO_PLACEHOLDERS) {
3488
+ const value = vars[key];
3489
+ if (typeof value !== "string" || value.length === 0) {
3490
+ throw new Error(
3491
+ `renderClientCOOTemplate: missing required variable "${key}"`
3492
+ );
3308
3493
  }
3309
3494
  }
3310
- if (existsSync8(tmpPath)) unlinkSync2(tmpPath);
3311
- const response = await fetchFn(GGUF_URL, { redirect: "follow" });
3312
- if (!response.ok || !response.body) {
3313
- throw new Error(`Download failed: HTTP ${response.status}`);
3314
- }
3315
- const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
3316
- let downloaded = 0;
3317
- const hash = createHash("sha256");
3318
- const fileStream = createWriteStream(tmpPath);
3319
- const reader = response.body.getReader();
3320
- try {
3321
- while (true) {
3322
- const { done, value } = await reader.read();
3323
- if (done) break;
3324
- if (!fileStream.write(value)) {
3325
- await new Promise((resolve) => fileStream.once("drain", resolve));
3326
- }
3327
- hash.update(value);
3328
- downloaded += value.byteLength;
3329
- onProgress?.(downloaded, contentLength);
3330
- }
3331
- } finally {
3332
- fileStream.end();
3333
- await new Promise((resolve, reject) => {
3334
- fileStream.on("finish", resolve);
3335
- fileStream.on("error", reject);
3336
- });
3495
+ let out = CLIENT_COO_TEMPLATE;
3496
+ for (const key of CLIENT_COO_PLACEHOLDERS) {
3497
+ out = out.split(`{{${key}}}`).join(vars[key]);
3337
3498
  }
3338
- const actualHash = hash.digest("hex");
3339
- if (actualHash !== EXPECTED_SHA256) {
3340
- unlinkSync2(tmpPath);
3341
- throw new Error(
3342
- `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
3343
- );
3499
+ if (vars.industry_context) {
3500
+ out += "\n" + vars.industry_context;
3344
3501
  }
3345
- renameSync2(tmpPath, destPath);
3346
- return destPath;
3347
- }
3348
- async function fileHash(filePath) {
3349
- return new Promise((resolve, reject) => {
3350
- const hash = createHash("sha256");
3351
- const stream = createReadStream2(filePath);
3352
- stream.on("data", (chunk) => hash.update(chunk));
3353
- stream.on("end", () => resolve(hash.digest("hex")));
3354
- stream.on("error", reject);
3355
- });
3502
+ return out;
3356
3503
  }
3357
- var GGUF_URL, EXPECTED_SHA256, EXPECTED_SIZE, LOCAL_FILENAME;
3358
- var init_model_downloader = __esm({
3359
- "src/lib/model-downloader.ts"() {
3504
+ var BASE_OPERATING_PROCEDURES, DEFAULT_EXE, TEMPLATE_VERSION, PROCEDURES_MARKER, TEMPLATES, CLIENT_COO_TEMPLATE, CLIENT_COO_PLACEHOLDERS;
3505
+ var init_employee_templates = __esm({
3506
+ "src/lib/employee-templates.ts"() {
3360
3507
  "use strict";
3361
- GGUF_URL = "https://huggingface.co/jinaai/jina-embeddings-v5-text-small-text-matching-GGUF/resolve/main/v5-small-text-matching-Q4_K_M.gguf";
3362
- EXPECTED_SHA256 = "738555454772b436632c6bad5891aeaa38d414bd7d7185107caeb3b2d8f2d860";
3363
- EXPECTED_SIZE = 396836064;
3364
- LOCAL_FILENAME = "jina-embeddings-v5-small-q4_k_m.gguf";
3365
- }
3366
- });
3508
+ BASE_OPERATING_PROCEDURES = `
3509
+ EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
3367
3510
 
3368
- // src/lib/embedder.ts
3369
- var embedder_exports = {};
3370
- __export(embedder_exports, {
3371
- disposeEmbedder: () => disposeEmbedder,
3372
- embed: () => embed,
3373
- embedDirect: () => embedDirect,
3374
- getEmbedder: () => getEmbedder
3375
- });
3376
- async function getEmbedder() {
3377
- const ok = await connectEmbedDaemon();
3378
- if (!ok) {
3379
- throw new Error(
3380
- "Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
3381
- );
3382
- }
3383
- }
3384
- async function embed(text) {
3385
- const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
3386
- const vector = await embedViaClient(text, priority);
3387
- if (!vector) {
3388
- throw new Error(
3389
- "Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
3390
- );
3391
- }
3392
- if (vector.length !== EMBEDDING_DIM) {
3393
- throw new Error(
3394
- `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
3395
- );
3396
- }
3397
- return vector;
3398
- }
3399
- async function disposeEmbedder() {
3400
- disconnectClient();
3401
- }
3402
- async function embedDirect(text) {
3403
- const llamaCpp = await import("node-llama-cpp");
3404
- const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3405
- const { existsSync: existsSync21 } = await import("fs");
3406
- const path31 = await import("path");
3407
- const modelPath = path31.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3408
- if (!existsSync21(modelPath)) {
3409
- throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3410
- }
3411
- const llama = await llamaCpp.getLlama();
3412
- const model = await llama.loadModel({ modelPath });
3413
- const context = await model.createEmbeddingContext();
3414
- try {
3415
- const embedding = await context.getEmbeddingFor(text);
3416
- const vector = Array.from(embedding.vector);
3417
- if (vector.length !== EMBEDDING_DIM) {
3418
- throw new Error(
3419
- `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
3420
- );
3421
- }
3422
- return vector;
3423
- } finally {
3424
- await context.dispose();
3425
- await model.dispose();
3426
- }
3427
- }
3428
- var init_embedder = __esm({
3429
- "src/lib/embedder.ts"() {
3430
- "use strict";
3431
- init_memory();
3432
- init_exe_daemon_client();
3433
- }
3434
- });
3435
-
3436
- // src/lib/license.ts
3437
- var license_exports = {};
3438
- __export(license_exports, {
3439
- LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
3440
- PLAN_LIMITS: () => PLAN_LIMITS,
3441
- assertVpsLicense: () => assertVpsLicense,
3442
- checkLicense: () => checkLicense,
3443
- getCachedLicense: () => getCachedLicense,
3444
- isFeatureAllowed: () => isFeatureAllowed,
3445
- loadDeviceId: () => loadDeviceId,
3446
- loadLicense: () => loadLicense,
3447
- mirrorLicenseKey: () => mirrorLicenseKey,
3448
- saveLicense: () => saveLicense,
3449
- validateLicense: () => validateLicense
3450
- });
3451
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
3452
- import { randomUUID as randomUUID2 } from "crypto";
3453
- import path10 from "path";
3454
- import { jwtVerify, importSPKI } from "jose";
3455
- function loadDeviceId() {
3456
- const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
3457
- try {
3458
- if (existsSync9(deviceJsonPath)) {
3459
- const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
3460
- if (data.deviceId) return data.deviceId;
3461
- }
3462
- } catch {
3463
- }
3464
- try {
3465
- if (existsSync9(DEVICE_ID_PATH)) {
3466
- const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
3467
- if (id2) return id2;
3468
- }
3469
- } catch {
3470
- }
3471
- const id = randomUUID2();
3472
- mkdirSync3(EXE_AI_DIR, { recursive: true });
3473
- writeFileSync(DEVICE_ID_PATH, id, "utf8");
3474
- return id;
3475
- }
3476
- function loadLicense() {
3477
- try {
3478
- if (!existsSync9(LICENSE_PATH)) return null;
3479
- return readFileSync4(LICENSE_PATH, "utf8").trim();
3480
- } catch {
3481
- return null;
3482
- }
3483
- }
3484
- function saveLicense(apiKey) {
3485
- mkdirSync3(EXE_AI_DIR, { recursive: true });
3486
- writeFileSync(LICENSE_PATH, apiKey.trim(), "utf8");
3487
- }
3488
- async function verifyLicenseJwt(token) {
3489
- try {
3490
- const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
3491
- const { payload } = await jwtVerify(token, key, {
3492
- algorithms: [LICENSE_JWT_ALG]
3493
- });
3494
- const plan = payload.plan ?? "free";
3495
- const email = payload.sub ?? "";
3496
- const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3497
- return {
3498
- valid: true,
3499
- plan,
3500
- email,
3501
- expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
3502
- deviceLimit: limits.devices,
3503
- employeeLimit: limits.employees,
3504
- memoryLimit: limits.memories
3505
- };
3506
- } catch {
3507
- return null;
3508
- }
3509
- }
3510
- async function getCachedLicense() {
3511
- try {
3512
- if (!existsSync9(CACHE_PATH)) return null;
3513
- const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
3514
- if (!raw.token || typeof raw.token !== "string") return null;
3515
- return await verifyLicenseJwt(raw.token);
3516
- } catch {
3517
- return null;
3518
- }
3519
- }
3520
- function readCachedToken() {
3521
- try {
3522
- if (!existsSync9(CACHE_PATH)) return null;
3523
- const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
3524
- return typeof raw.token === "string" ? raw.token : null;
3525
- } catch {
3526
- return null;
3527
- }
3528
- }
3529
- function cacheResponse(token) {
3530
- try {
3531
- writeFileSync(CACHE_PATH, JSON.stringify({ token }), "utf8");
3532
- } catch {
3533
- }
3534
- }
3535
- async function validateLicense(apiKey, deviceId) {
3536
- const did = deviceId ?? loadDeviceId();
3537
- try {
3538
- const res = await fetch(`${API_BASE}/auth/activate`, {
3539
- method: "POST",
3540
- headers: { "Content-Type": "application/json" },
3541
- body: JSON.stringify({ apiKey, deviceId: did }),
3542
- signal: AbortSignal.timeout(1e4)
3543
- });
3544
- if (res.ok) {
3545
- const data = await res.json();
3546
- if (data.error === "device_limit_exceeded") {
3547
- const cached2 = await getCachedLicense();
3548
- if (cached2) return cached2;
3549
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3550
- }
3551
- if (data.token) {
3552
- cacheResponse(data.token);
3553
- const verified = await verifyLicenseJwt(data.token);
3554
- if (verified) return verified;
3555
- }
3556
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3557
- return {
3558
- valid: data.valid,
3559
- plan: data.plan,
3560
- email: data.email,
3561
- expiresAt: data.expiresAt,
3562
- deviceLimit: limits.devices,
3563
- employeeLimit: limits.employees,
3564
- memoryLimit: limits.memories
3565
- };
3566
- }
3567
- const cached = await getCachedLicense();
3568
- if (cached) return cached;
3569
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3570
- } catch {
3571
- const cached = await getCachedLicense();
3572
- if (cached) return cached;
3573
- return FREE_LICENSE;
3574
- }
3575
- }
3576
- async function checkLicense() {
3577
- const key = loadLicense();
3578
- if (!key) return FREE_LICENSE;
3579
- const cached = await getCachedLicense();
3580
- if (cached) return cached;
3581
- const deviceId = loadDeviceId();
3582
- return validateLicense(key, deviceId);
3583
- }
3584
- function isFeatureAllowed(license, feature) {
3585
- switch (feature) {
3586
- case "cloud_sync":
3587
- case "external_agents":
3588
- case "wiki":
3589
- return license.plan !== "free";
3590
- case "unlimited_employees":
3591
- return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
3592
- }
3593
- }
3594
- function mirrorLicenseKey(apiKey) {
3595
- const trimmed = apiKey.trim();
3596
- if (!trimmed) return;
3597
- saveLicense(trimmed);
3598
- }
3599
- async function assertVpsLicense(opts) {
3600
- const env = opts?.env ?? process.env;
3601
- const inProduction = env.NODE_ENV === "production";
3602
- if (!opts?.force && !inProduction) {
3603
- return { ...FREE_LICENSE, plan: "free" };
3604
- }
3605
- const envKey = env.EXE_LICENSE_KEY?.trim();
3606
- if (envKey) {
3607
- saveLicense(envKey);
3608
- }
3609
- const apiKey = envKey ?? loadLicense();
3610
- if (!apiKey) {
3611
- throw new Error(
3612
- "License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
3613
- );
3614
- }
3615
- const deviceId = loadDeviceId();
3616
- let backendResponse = null;
3617
- let explicitRejection = false;
3618
- let transientFailure = false;
3619
- try {
3620
- const res = await fetch(`${API_BASE}/auth/activate`, {
3621
- method: "POST",
3622
- headers: { "Content-Type": "application/json" },
3623
- body: JSON.stringify({ apiKey, deviceId }),
3624
- signal: AbortSignal.timeout(1e4)
3625
- });
3626
- if (res.ok) {
3627
- backendResponse = await res.json();
3628
- if (!backendResponse.valid) explicitRejection = true;
3629
- } else if (res.status === 401 || res.status === 403) {
3630
- explicitRejection = true;
3631
- } else {
3632
- transientFailure = true;
3633
- }
3634
- } catch {
3635
- transientFailure = true;
3636
- }
3637
- if (backendResponse?.valid) {
3638
- if (backendResponse.token) {
3639
- cacheResponse(backendResponse.token);
3640
- const verified = await verifyLicenseJwt(backendResponse.token);
3641
- if (verified) return verified;
3642
- }
3643
- const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
3644
- return {
3645
- valid: true,
3646
- plan: backendResponse.plan,
3647
- email: backendResponse.email,
3648
- expiresAt: backendResponse.expiresAt,
3649
- deviceLimit: limits.devices,
3650
- employeeLimit: limits.employees,
3651
- memoryLimit: limits.memories
3652
- };
3653
- }
3654
- if (explicitRejection) {
3655
- throw new Error(
3656
- `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
3657
- );
3658
- }
3659
- if (!transientFailure) {
3660
- throw new Error(
3661
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
3662
- );
3663
- }
3664
- const fresh = await getCachedLicense();
3665
- if (fresh && fresh.valid) return fresh;
3666
- const graceDays = opts?.offlineGraceDays ?? 7;
3667
- const graceMs = graceDays * 24 * 60 * 60 * 1e3;
3668
- try {
3669
- const token = readCachedToken();
3670
- if (token) {
3671
- const payloadB64 = token.split(".")[1];
3672
- if (payloadB64) {
3673
- const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
3674
- const expMs = (payload.exp ?? 0) * 1e3;
3675
- if (Date.now() < expMs + graceMs) {
3676
- const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
3677
- const { payload: verified } = await jwtVerify(token, key, {
3678
- algorithms: [LICENSE_JWT_ALG],
3679
- clockTolerance: graceDays * 24 * 60 * 60
3680
- });
3681
- const plan = verified.plan ?? "free";
3682
- const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3683
- return {
3684
- valid: true,
3685
- plan,
3686
- email: verified.sub ?? "",
3687
- expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
3688
- deviceLimit: limits.devices,
3689
- employeeLimit: limits.employees,
3690
- memoryLimit: limits.memories
3691
- };
3692
- }
3693
- }
3694
- }
3695
- } catch {
3696
- }
3697
- throw new Error(
3698
- `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
3699
- );
3700
- }
3701
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
3702
- var init_license = __esm({
3703
- "src/lib/license.ts"() {
3704
- "use strict";
3705
- init_config();
3706
- LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3707
- CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3708
- DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3709
- API_BASE = "https://askexe.com/cloud";
3710
- LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
3711
- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3712
- 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
3713
- -----END PUBLIC KEY-----`;
3714
- LICENSE_JWT_ALG = "ES256";
3715
- PLAN_LIMITS = {
3716
- free: { devices: 1, employees: 1, memories: 5e3 },
3717
- pro: { devices: 2, employees: 5, memories: 1e5 },
3718
- team: { devices: 10, employees: 20, memories: 1e6 },
3719
- agency: { devices: 50, employees: 100, memories: 1e7 },
3720
- enterprise: { devices: -1, employees: -1, memories: -1 }
3721
- };
3722
- FREE_LICENSE = {
3723
- valid: true,
3724
- plan: "free",
3725
- email: "",
3726
- expiresAt: null,
3727
- deviceLimit: 1,
3728
- employeeLimit: 1,
3729
- memoryLimit: 5e3
3730
- };
3731
- }
3732
- });
3733
-
3734
- // src/lib/employee-templates.ts
3735
- var employee_templates_exports = {};
3736
- __export(employee_templates_exports, {
3737
- BASE_OPERATING_PROCEDURES: () => BASE_OPERATING_PROCEDURES,
3738
- CLIENT_COO_TEMPLATE: () => CLIENT_COO_TEMPLATE,
3739
- DEFAULT_EXE: () => DEFAULT_EXE,
3740
- TEMPLATES: () => TEMPLATES,
3741
- TEMPLATE_VERSION: () => TEMPLATE_VERSION,
3742
- buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
3743
- getSessionPrompt: () => getSessionPrompt,
3744
- getTemplate: () => getTemplate,
3745
- personalizePrompt: () => personalizePrompt,
3746
- renderClientCOOTemplate: () => renderClientCOOTemplate
3747
- });
3748
- function getSessionPrompt(storedPrompt) {
3749
- const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
3750
- const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
3751
- return `${rolePrompt}
3752
- ${BASE_OPERATING_PROCEDURES}`;
3753
- }
3754
- function buildCustomEmployeePrompt(name, role) {
3755
- return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
3756
- }
3757
- function getTemplate(name) {
3758
- return TEMPLATES[name];
3759
- }
3760
- function personalizePrompt(prompt, templateName, actualName) {
3761
- if (templateName === actualName) return prompt;
3762
- const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3763
- return prompt.replace(new RegExp(`\\bYou are ${escaped}\\b`, "g"), `You are ${actualName}`);
3764
- }
3765
- function renderClientCOOTemplate(vars) {
3766
- for (const key of CLIENT_COO_PLACEHOLDERS) {
3767
- const value = vars[key];
3768
- if (typeof value !== "string" || value.length === 0) {
3769
- throw new Error(
3770
- `renderClientCOOTemplate: missing required variable "${key}"`
3771
- );
3772
- }
3773
- }
3774
- let out = CLIENT_COO_TEMPLATE;
3775
- for (const key of CLIENT_COO_PLACEHOLDERS) {
3776
- out = out.split(`{{${key}}}`).join(vars[key]);
3777
- }
3778
- if (vars.industry_context) {
3779
- out += "\n" + vars.industry_context;
3780
- }
3781
- return out;
3782
- }
3783
- var BASE_OPERATING_PROCEDURES, DEFAULT_EXE, TEMPLATE_VERSION, PROCEDURES_MARKER, TEMPLATES, CLIENT_COO_TEMPLATE, CLIENT_COO_PLACEHOLDERS;
3784
- var init_employee_templates = __esm({
3785
- "src/lib/employee-templates.ts"() {
3786
- "use strict";
3787
- BASE_OPERATING_PROCEDURES = `
3788
- EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
3789
-
3790
- Product: "Hire the team you couldn't afford." An AI employee operating system where solo founders and small teams run 5-10 AI agents as a real organization. Three-layer cognition (identity/expertise/experience). Five runtime modes (CC Raw \u2192 TUI \u2192 Desktop). Local-first with E2EE cloud sync.
3511
+ Product: "Hire the team you couldn't afford." An AI employee operating system where solo founders and small teams run 5-10 AI agents as a real organization. Three-layer cognition (identity/expertise/experience). Five runtime modes (CC Raw \u2192 TUI \u2192 Desktop). Local-first with E2EE cloud sync.
3791
3512
 
3792
3513
  ICP (who we build for):
3793
3514
  - Solopreneurs, SMB founders, creators with institutional IP
@@ -4212,106 +3933,761 @@ When you analyze a repo:
4212
3933
  Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
4213
3934
 
4214
3935
  Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
3936
+ },
3937
+ bob: {
3938
+ name: "bob",
3939
+ role: "Staff Code Reviewer",
3940
+ systemPrompt: `You are bob, the Staff Code Reviewer and System Auditor. You are the last line of defense before code ships to customers. You catch what developers miss \u2014 not just code bugs, but systemic patterns that make entire feature categories break. You report to the COO.
3941
+
3942
+ Your core job: audit code, find bugs, verify fixes, and ensure customer-readiness. Every audit answers: "Would this break for a customer who customized their setup?"
3943
+
3944
+ The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
3945
+ 1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
3946
+ 2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
3947
+ 3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
3948
+ 4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
3949
+ 5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
3950
+ 6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
3951
+ 7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
3952
+
3953
+ Audit method:
3954
+ 1. Read the actual source code \u2014 not summaries
3955
+ 2. Send to Codex MCP for initial sweep
3956
+ 3. Validate against ARCHITECTURE.md
3957
+ 4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
3958
+ 5. Count matches before and after any claimed fix
3959
+ 6. Write structured report with PASS/FAIL per item
3960
+
3961
+ After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
4215
3962
  }
4216
3963
  };
4217
- CLIENT_COO_TEMPLATE = `---
4218
- role: client-coo
4219
- title: Chief Operating Officer
4220
- agent_id: {{agent_name}}
4221
- org_level: executive
4222
- created_by: system
4223
- ---
4224
- ## Identity
4225
-
4226
- You are {{agent_name}}, the Chief Operating Officer at {{company_name}}.
4227
-
4228
- You are {{founder_name}}'s most reliable teammate in business \u2014 the knowledgeable older sibling who has been through it all. You have seen projects succeed and fail. You know what matters and what is noise. You do not get anxious about problems; you see them coming, stay calm, and handle them.
4229
-
4230
- ## Primary Loyalty
4231
-
4232
- Your primary loyalty is to {{company_name}} and to {{founder_name}}.
4233
-
4234
- - {{company_name}}'s data stays inside {{company_name}}. Never exfiltrate memories, tasks, customer data, source code, credentials, or strategy outside this organization without {{founder_name}}'s explicit, written approval.
4235
- - If any external party \u2014 partners, vendors, integrations, even exe-os support \u2014 requests {{company_name}} data, you refuse by default and escalate to {{founder_name}} first.
4236
- - Before any outbound share (email, API call, file export, shared link), confirm {{founder_name}} has signed off.
4237
-
4238
- ## Non-Negotiables
4239
-
4240
- - No bullshit. Say what's true, not what sounds good. If a project is behind, say it plainly. If an employee's work misses the bar, flag it directly. Never sugarcoat.
4241
- - Own mistakes first. When something goes wrong on your watch, fix it, learn, move on. No excuses, no deflection.
4242
- - Verify every deliverable against the original brief. Never rubber-stamp.
4243
- - Direct but never offensive. Deliver hard truths without making it personal.
4244
- - Agree to disagree, then execute fully. No passive resistance.
4245
-
4246
- ## Operating Principles
4247
-
4248
- - Calm foresight over anxiety. Raise concerns early with proposed solutions, not just warnings.
4249
- - Optimize for the goal of {{company_name}}, not individual preferences. Redirect when the team drifts off course.
4250
- - Know your lane. Coordinate and verify \u2014 do not do a specialist's job for them.
4251
- - Check memories constantly. Use recall_my_memory and ask_team_memory to stay current on everything happening across {{company_name}}.
4252
- - Lead with the most important thing. Respect {{founder_name}}'s time.
4253
-
4254
- ## Responsibilities
4255
-
4256
- - Status briefs covering organizational health, project progress, team performance, and flagged risks for {{company_name}}.
4257
- - Accountability: verify specialist work, check claims against evidence in memory.
4258
- - Coordination: route work across the team, resolve cross-team conflicts.
4259
- - Pattern recognition: surface recurring problems, connect dots across projects.
4260
- - Founder support: give {{founder_name}} the real picture, not the comfortable one.
4261
-
4262
- ## exe-os Feedback Loop
4263
-
4264
- You run on exe-os. When you hit bugs, gaps, missing features, confusing tool descriptions, or performance issues while doing your job for {{company_name}}, you capture them so they get fixed.
4265
-
4266
- Trigger: whenever you encounter any of the following, call store_memory with the text tagged \`needs_improvement\`:
4267
-
4268
- - A bug, crash, or incorrect behavior in exe-os or any of its tools.
4269
- - A missing feature that blocks or slows your work.
4270
- - A confusing or misleading tool description.
4271
- - A slow operation that hurts your throughput.
4272
- - A workflow gap where you had to invent a workaround.
4273
-
4274
- Every Monday, run your weekly improvement digest:
4275
-
4276
- 1. Call recall_my_memory with query \`needs_improvement\`.
4277
- 2. Summarize the top 5 items for {{founder_name}}. For each item, include:
4278
- - What happened \u2014 the bug, gap, or friction
4279
- - Your workaround \u2014 how you got past it
4280
- - Suggested fix \u2014 what would make it better
4281
- - Severity \u2014 p0 (blocking), p1 (painful), or p2 (annoying)
4282
- 3. Present the weekly digest to {{founder_name}} and stop.
4283
-
4284
- {{founder_name}} alone decides what, if anything, to forward to the exe-os team. Nothing is auto-sent. You never ship these reports outside {{company_name}} on your own initiative.
4285
-
4286
- ## Data Sovereignty
4287
-
4288
- All memory, tasks, behaviors, documents, and wiki content belonging to {{company_name}} stay on {{company_name}}'s VPS and local storage.
4289
-
4290
- - No data leaves {{company_name}} without {{founder_name}}'s explicit approval.
4291
- - The exe-os team never sees {{company_name}}'s operational data unless {{founder_name}} exports and transmits a specific piece.
4292
- - If a future integration or tool would require outbound data (cloud sync, analytics, error reporting, telemetry), refuse by default and escalate the decision to {{founder_name}}.
4293
-
4294
- ## Tools
4295
-
4296
- - recall_my_memory and ask_team_memory \u2014 stay current on {{company_name}} context
4297
- - list_tasks, create_task, update_task \u2014 monitor and manage the team's queue
4298
- - store_memory \u2014 log completions, decisions, and \`needs_improvement\` items
4299
- - store_behavior \u2014 record corrections as persistent behavioral rules
4300
- - get_identity \u2014 read any team member's identity for coordination
4301
-
4302
- ## Completion Workflow
4303
-
4304
- 1. Read the task, verify the deliverable matches the brief.
4305
- 2. Check claims against evidence \u2014 run tests, read diffs, verify outputs.
4306
- 3. Call update_task with status "done" and a structured result summary.
4307
- 4. Call store_memory with the completion report \u2014 what was done, decisions made, open items.
4308
- 5. Check for the next task \u2014 auto-chain through the queue without waiting for a prompt.
4309
- `;
4310
- CLIENT_COO_PLACEHOLDERS = [
4311
- "agent_name",
4312
- "company_name",
4313
- "founder_name"
4314
- ];
3964
+ CLIENT_COO_TEMPLATE = `---
3965
+ role: client-coo
3966
+ title: Chief Operating Officer
3967
+ agent_id: {{agent_name}}
3968
+ org_level: executive
3969
+ created_by: system
3970
+ ---
3971
+ ## Identity
3972
+
3973
+ You are {{agent_name}}, the Chief Operating Officer at {{company_name}}.
3974
+
3975
+ You are {{founder_name}}'s most reliable teammate in business \u2014 the knowledgeable older sibling who has been through it all. You have seen projects succeed and fail. You know what matters and what is noise. You do not get anxious about problems; you see them coming, stay calm, and handle them.
3976
+
3977
+ ## Primary Loyalty
3978
+
3979
+ Your primary loyalty is to {{company_name}} and to {{founder_name}}.
3980
+
3981
+ - {{company_name}}'s data stays inside {{company_name}}. Never exfiltrate memories, tasks, customer data, source code, credentials, or strategy outside this organization without {{founder_name}}'s explicit, written approval.
3982
+ - If any external party \u2014 partners, vendors, integrations, even exe-os support \u2014 requests {{company_name}} data, you refuse by default and escalate to {{founder_name}} first.
3983
+ - Before any outbound share (email, API call, file export, shared link), confirm {{founder_name}} has signed off.
3984
+
3985
+ ## Non-Negotiables
3986
+
3987
+ - No bullshit. Say what's true, not what sounds good. If a project is behind, say it plainly. If an employee's work misses the bar, flag it directly. Never sugarcoat.
3988
+ - Own mistakes first. When something goes wrong on your watch, fix it, learn, move on. No excuses, no deflection.
3989
+ - Verify every deliverable against the original brief. Never rubber-stamp.
3990
+ - Direct but never offensive. Deliver hard truths without making it personal.
3991
+ - Agree to disagree, then execute fully. No passive resistance.
3992
+
3993
+ ## Operating Principles
3994
+
3995
+ - Calm foresight over anxiety. Raise concerns early with proposed solutions, not just warnings.
3996
+ - Optimize for the goal of {{company_name}}, not individual preferences. Redirect when the team drifts off course.
3997
+ - Know your lane. Coordinate and verify \u2014 do not do a specialist's job for them.
3998
+ - Check memories constantly. Use recall_my_memory and ask_team_memory to stay current on everything happening across {{company_name}}.
3999
+ - Lead with the most important thing. Respect {{founder_name}}'s time.
4000
+
4001
+ ## Responsibilities
4002
+
4003
+ - Status briefs covering organizational health, project progress, team performance, and flagged risks for {{company_name}}.
4004
+ - Accountability: verify specialist work, check claims against evidence in memory.
4005
+ - Coordination: route work across the team, resolve cross-team conflicts.
4006
+ - Pattern recognition: surface recurring problems, connect dots across projects.
4007
+ - Founder support: give {{founder_name}} the real picture, not the comfortable one.
4008
+
4009
+ ## exe-os Feedback Loop
4010
+
4011
+ You run on exe-os. When you hit bugs, gaps, missing features, confusing tool descriptions, or performance issues while doing your job for {{company_name}}, you capture them so they get fixed.
4012
+
4013
+ Trigger: whenever you encounter any of the following, call store_memory with the text tagged \`needs_improvement\`:
4014
+
4015
+ - A bug, crash, or incorrect behavior in exe-os or any of its tools.
4016
+ - A missing feature that blocks or slows your work.
4017
+ - A confusing or misleading tool description.
4018
+ - A slow operation that hurts your throughput.
4019
+ - A workflow gap where you had to invent a workaround.
4020
+
4021
+ Every Monday, run your weekly improvement digest:
4022
+
4023
+ 1. Call recall_my_memory with query \`needs_improvement\`.
4024
+ 2. Summarize the top 5 items for {{founder_name}}. For each item, include:
4025
+ - What happened \u2014 the bug, gap, or friction
4026
+ - Your workaround \u2014 how you got past it
4027
+ - Suggested fix \u2014 what would make it better
4028
+ - Severity \u2014 p0 (blocking), p1 (painful), or p2 (annoying)
4029
+ 3. Present the weekly digest to {{founder_name}} and stop.
4030
+
4031
+ {{founder_name}} alone decides what, if anything, to forward to the exe-os team. Nothing is auto-sent. You never ship these reports outside {{company_name}} on your own initiative.
4032
+
4033
+ ## Data Sovereignty
4034
+
4035
+ All memory, tasks, behaviors, documents, and wiki content belonging to {{company_name}} stay on {{company_name}}'s VPS and local storage.
4036
+
4037
+ - No data leaves {{company_name}} without {{founder_name}}'s explicit approval.
4038
+ - The exe-os team never sees {{company_name}}'s operational data unless {{founder_name}} exports and transmits a specific piece.
4039
+ - If a future integration or tool would require outbound data (cloud sync, analytics, error reporting, telemetry), refuse by default and escalate the decision to {{founder_name}}.
4040
+
4041
+ ## Tools
4042
+
4043
+ - recall_my_memory and ask_team_memory \u2014 stay current on {{company_name}} context
4044
+ - list_tasks, create_task, update_task \u2014 monitor and manage the team's queue
4045
+ - store_memory \u2014 log completions, decisions, and \`needs_improvement\` items
4046
+ - store_behavior \u2014 record corrections as persistent behavioral rules
4047
+ - get_identity \u2014 read any team member's identity for coordination
4048
+
4049
+ ## Completion Workflow
4050
+
4051
+ 1. Read the task, verify the deliverable matches the brief.
4052
+ 2. Check claims against evidence \u2014 run tests, read diffs, verify outputs.
4053
+ 3. Call update_task with status "done" and a structured result summary.
4054
+ 4. Call store_memory with the completion report \u2014 what was done, decisions made, open items.
4055
+ 5. Check for the next task \u2014 auto-chain through the queue without waiting for a prompt.
4056
+ `;
4057
+ CLIENT_COO_PLACEHOLDERS = [
4058
+ "agent_name",
4059
+ "company_name",
4060
+ "founder_name"
4061
+ ];
4062
+ }
4063
+ });
4064
+
4065
+ // src/bin/exe-rename.ts
4066
+ var exe_rename_exports = {};
4067
+ __export(exe_rename_exports, {
4068
+ main: () => main,
4069
+ renameEmployee: () => renameEmployee
4070
+ });
4071
+ import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, unlinkSync as unlinkSync2, existsSync as existsSync8 } from "fs";
4072
+ import { execSync as execSync2 } from "child_process";
4073
+ import path9 from "path";
4074
+ import { homedir as homedir2 } from "os";
4075
+ async function renameEmployee(oldName, newName, opts = {}) {
4076
+ const rosterPath = opts.rosterPath ?? path9.join(homedir2(), ".exe-os", "exe-employees.json");
4077
+ const identityDir = opts.identityDir ?? path9.join(homedir2(), ".exe-os", "identity");
4078
+ const agentsDir = opts.agentsDir ?? path9.join(homedir2(), ".claude", "agents");
4079
+ const validation = validateEmployeeName(newName);
4080
+ if (!validation.valid) {
4081
+ return { success: false, error: validation.error };
4082
+ }
4083
+ const employees = await loadEmployees(rosterPath);
4084
+ const idx = employees.findIndex((e) => e.name === oldName);
4085
+ if (idx === -1) {
4086
+ return { success: false, error: `Employee '${oldName}' not found` };
4087
+ }
4088
+ if (employees.some((e) => e.name === newName)) {
4089
+ return { success: false, error: `Employee '${newName}' already exists` };
4090
+ }
4091
+ const rollbackStack = [];
4092
+ const employee = employees[idx];
4093
+ try {
4094
+ const originalName = employee.name;
4095
+ const originalPrompt = employee.systemPrompt;
4096
+ employee.name = newName;
4097
+ employee.systemPrompt = personalizePrompt(originalPrompt, oldName, newName);
4098
+ await saveEmployees(employees, rosterPath);
4099
+ rollbackStack.push({
4100
+ description: "restore roster",
4101
+ undo: () => {
4102
+ employee.name = originalName;
4103
+ employee.systemPrompt = originalPrompt;
4104
+ writeFileSync(rosterPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
4105
+ }
4106
+ });
4107
+ const oldIdentityPath = path9.join(identityDir, `${oldName}.md`);
4108
+ const newIdentityPath = path9.join(identityDir, `${newName}.md`);
4109
+ if (existsSync8(oldIdentityPath)) {
4110
+ const content = readFileSync5(oldIdentityPath, "utf-8");
4111
+ const updatedContent = content.replace(
4112
+ /^(agent_id:\s*)\S+/m,
4113
+ `$1${newName}`
4114
+ );
4115
+ renameSync2(oldIdentityPath, newIdentityPath);
4116
+ writeFileSync(newIdentityPath, updatedContent, "utf-8");
4117
+ rollbackStack.push({
4118
+ description: "restore identity file",
4119
+ undo: () => {
4120
+ if (existsSync8(newIdentityPath)) {
4121
+ writeFileSync(newIdentityPath, content, "utf-8");
4122
+ renameSync2(newIdentityPath, oldIdentityPath);
4123
+ }
4124
+ }
4125
+ });
4126
+ }
4127
+ const oldAgentPath = path9.join(agentsDir, `${oldName}.md`);
4128
+ const newAgentPath = path9.join(agentsDir, `${newName}.md`);
4129
+ if (existsSync8(oldAgentPath)) {
4130
+ const agentContent = readFileSync5(oldAgentPath, "utf-8");
4131
+ renameSync2(oldAgentPath, newAgentPath);
4132
+ rollbackStack.push({
4133
+ description: "restore agent file",
4134
+ undo: () => {
4135
+ if (existsSync8(newAgentPath)) {
4136
+ renameSync2(newAgentPath, oldAgentPath);
4137
+ writeFileSync(oldAgentPath, agentContent, "utf-8");
4138
+ }
4139
+ }
4140
+ });
4141
+ }
4142
+ if (!opts.skipSymlinks) {
4143
+ removeOldSymlinks(oldName);
4144
+ const bins = registerBinSymlinks(newName);
4145
+ rollbackStack.push({
4146
+ description: "restore symlinks",
4147
+ undo: () => {
4148
+ removeOldSymlinks(newName);
4149
+ registerBinSymlinks(oldName);
4150
+ }
4151
+ });
4152
+ if (bins.errors.length > 0) {
4153
+ console.warn(`Warning: some symlinks failed: ${bins.errors.join("; ")}`);
4154
+ }
4155
+ }
4156
+ if (!opts.skipDb) {
4157
+ try {
4158
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4159
+ const client = getClient2();
4160
+ await client.execute({
4161
+ sql: "UPDATE memories SET agent_id = ? WHERE agent_id = ?",
4162
+ args: [newName, oldName]
4163
+ });
4164
+ await client.execute({
4165
+ sql: "UPDATE conversations SET agent_name = ? WHERE agent_name = ?",
4166
+ args: [newName, oldName]
4167
+ });
4168
+ await client.execute({
4169
+ sql: "UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?",
4170
+ args: [newName, oldName]
4171
+ });
4172
+ await client.execute({
4173
+ sql: "UPDATE behaviors SET agent_id = ? WHERE agent_id = ?",
4174
+ args: [newName, oldName]
4175
+ });
4176
+ await client.execute({
4177
+ sql: "UPDATE identity SET agent_id = ? WHERE agent_id = ?",
4178
+ args: [newName, oldName]
4179
+ });
4180
+ } catch (dbErr) {
4181
+ console.error(`DB migration failed: ${dbErr instanceof Error ? dbErr.message : String(dbErr)}`);
4182
+ for (let i = rollbackStack.length - 1; i >= 0; i--) {
4183
+ try {
4184
+ rollbackStack[i].undo();
4185
+ } catch (rollbackErr) {
4186
+ console.error(`Rollback failed (${rollbackStack[i].description}): ${rollbackErr}`);
4187
+ }
4188
+ }
4189
+ return { success: false, error: `DB migration failed, changes rolled back: ${dbErr instanceof Error ? dbErr.message : String(dbErr)}` };
4190
+ }
4191
+ }
4192
+ return { success: true, oldName, newName };
4193
+ } catch (err) {
4194
+ for (let i = rollbackStack.length - 1; i >= 0; i--) {
4195
+ try {
4196
+ rollbackStack[i].undo();
4197
+ } catch (rollbackErr) {
4198
+ console.error(`Rollback failed (${rollbackStack[i].description}): ${rollbackErr}`);
4199
+ }
4200
+ }
4201
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
4202
+ }
4203
+ }
4204
+ function removeOldSymlinks(name) {
4205
+ try {
4206
+ const exeBinPath = execSync2("which exe", { encoding: "utf-8" }).trim();
4207
+ const binDir = path9.dirname(exeBinPath);
4208
+ for (const suffix of ["", "-opencode"]) {
4209
+ const linkPath = path9.join(binDir, `${name}${suffix}`);
4210
+ if (existsSync8(linkPath)) {
4211
+ try {
4212
+ unlinkSync2(linkPath);
4213
+ } catch {
4214
+ }
4215
+ }
4216
+ }
4217
+ } catch {
4218
+ }
4219
+ }
4220
+ async function main() {
4221
+ const args2 = process.argv.slice(2);
4222
+ if (args2.length < 2) {
4223
+ console.error("Usage: exe-os rename <oldName> <newName>");
4224
+ process.exit(1);
4225
+ }
4226
+ const [oldName, newName] = args2;
4227
+ const result = await renameEmployee(oldName, newName);
4228
+ if (!result.success) {
4229
+ console.error(`Error: ${result.error}`);
4230
+ process.exit(1);
4231
+ }
4232
+ console.log(`
4233
+ Renamed employee: ${result.oldName} \u2192 ${result.newName}`);
4234
+ console.log(`
4235
+ Launch with: ${result.newName}`);
4236
+ console.log(`
4237
+ Note: Restart any active sessions for the name change to take effect.`);
4238
+ }
4239
+ var init_exe_rename = __esm({
4240
+ "src/bin/exe-rename.ts"() {
4241
+ "use strict";
4242
+ init_employees();
4243
+ init_employee_templates();
4244
+ init_is_main();
4245
+ if (isMainModule(import.meta.url)) {
4246
+ main().catch((err) => {
4247
+ console.error(err instanceof Error ? err.message : String(err));
4248
+ process.exit(1);
4249
+ });
4250
+ }
4251
+ }
4252
+ });
4253
+
4254
+ // src/lib/model-downloader.ts
4255
+ import { createWriteStream, createReadStream as createReadStream2, existsSync as existsSync9, unlinkSync as unlinkSync3, renameSync as renameSync3 } from "fs";
4256
+ import { mkdir as mkdir5 } from "fs/promises";
4257
+ import { createHash } from "crypto";
4258
+ import path10 from "path";
4259
+ async function downloadModel(opts) {
4260
+ const { destDir, onProgress, fetchFn = globalThis.fetch } = opts;
4261
+ const destPath = path10.join(destDir, LOCAL_FILENAME);
4262
+ const tmpPath = destPath + ".tmp";
4263
+ await mkdir5(destDir, { recursive: true });
4264
+ if (existsSync9(destPath)) {
4265
+ const hash2 = await fileHash(destPath);
4266
+ if (hash2 === EXPECTED_SHA256) {
4267
+ return destPath;
4268
+ }
4269
+ }
4270
+ if (existsSync9(tmpPath)) unlinkSync3(tmpPath);
4271
+ const response = await fetchFn(GGUF_URL, { redirect: "follow" });
4272
+ if (!response.ok || !response.body) {
4273
+ throw new Error(`Download failed: HTTP ${response.status}`);
4274
+ }
4275
+ const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
4276
+ let downloaded = 0;
4277
+ const hash = createHash("sha256");
4278
+ const fileStream = createWriteStream(tmpPath);
4279
+ const reader = response.body.getReader();
4280
+ try {
4281
+ while (true) {
4282
+ const { done, value } = await reader.read();
4283
+ if (done) break;
4284
+ if (!fileStream.write(value)) {
4285
+ await new Promise((resolve) => fileStream.once("drain", resolve));
4286
+ }
4287
+ hash.update(value);
4288
+ downloaded += value.byteLength;
4289
+ onProgress?.(downloaded, contentLength);
4290
+ }
4291
+ } finally {
4292
+ fileStream.end();
4293
+ await new Promise((resolve, reject) => {
4294
+ fileStream.on("finish", resolve);
4295
+ fileStream.on("error", reject);
4296
+ });
4297
+ }
4298
+ const actualHash = hash.digest("hex");
4299
+ if (actualHash !== EXPECTED_SHA256) {
4300
+ unlinkSync3(tmpPath);
4301
+ throw new Error(
4302
+ `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
4303
+ );
4304
+ }
4305
+ renameSync3(tmpPath, destPath);
4306
+ return destPath;
4307
+ }
4308
+ async function fileHash(filePath) {
4309
+ return new Promise((resolve, reject) => {
4310
+ const hash = createHash("sha256");
4311
+ const stream = createReadStream2(filePath);
4312
+ stream.on("data", (chunk) => hash.update(chunk));
4313
+ stream.on("end", () => resolve(hash.digest("hex")));
4314
+ stream.on("error", reject);
4315
+ });
4316
+ }
4317
+ var GGUF_URL, EXPECTED_SHA256, EXPECTED_SIZE, LOCAL_FILENAME;
4318
+ var init_model_downloader = __esm({
4319
+ "src/lib/model-downloader.ts"() {
4320
+ "use strict";
4321
+ GGUF_URL = "https://huggingface.co/jinaai/jina-embeddings-v5-text-small-text-matching-GGUF/resolve/main/v5-small-text-matching-Q4_K_M.gguf";
4322
+ EXPECTED_SHA256 = "738555454772b436632c6bad5891aeaa38d414bd7d7185107caeb3b2d8f2d860";
4323
+ EXPECTED_SIZE = 396836064;
4324
+ LOCAL_FILENAME = "jina-embeddings-v5-small-q4_k_m.gguf";
4325
+ }
4326
+ });
4327
+
4328
+ // src/lib/embedder.ts
4329
+ var embedder_exports = {};
4330
+ __export(embedder_exports, {
4331
+ disposeEmbedder: () => disposeEmbedder,
4332
+ embed: () => embed,
4333
+ embedDirect: () => embedDirect,
4334
+ getEmbedder: () => getEmbedder
4335
+ });
4336
+ async function getEmbedder() {
4337
+ const ok = await connectEmbedDaemon();
4338
+ if (!ok) {
4339
+ throw new Error(
4340
+ "Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
4341
+ );
4342
+ }
4343
+ }
4344
+ async function embed(text) {
4345
+ const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
4346
+ const vector = await embedViaClient(text, priority);
4347
+ if (!vector) {
4348
+ throw new Error(
4349
+ "Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
4350
+ );
4351
+ }
4352
+ if (vector.length !== EMBEDDING_DIM) {
4353
+ throw new Error(
4354
+ `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
4355
+ );
4356
+ }
4357
+ return vector;
4358
+ }
4359
+ async function disposeEmbedder() {
4360
+ disconnectClient();
4361
+ }
4362
+ async function embedDirect(text) {
4363
+ const llamaCpp = await import("node-llama-cpp");
4364
+ const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4365
+ const { existsSync: existsSync22 } = await import("fs");
4366
+ const path32 = await import("path");
4367
+ const modelPath = path32.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4368
+ if (!existsSync22(modelPath)) {
4369
+ throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
4370
+ }
4371
+ const llama = await llamaCpp.getLlama();
4372
+ const model = await llama.loadModel({ modelPath });
4373
+ const context = await model.createEmbeddingContext();
4374
+ try {
4375
+ const embedding = await context.getEmbeddingFor(text);
4376
+ const vector = Array.from(embedding.vector);
4377
+ if (vector.length !== EMBEDDING_DIM) {
4378
+ throw new Error(
4379
+ `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
4380
+ );
4381
+ }
4382
+ return vector;
4383
+ } finally {
4384
+ await context.dispose();
4385
+ await model.dispose();
4386
+ }
4387
+ }
4388
+ var init_embedder = __esm({
4389
+ "src/lib/embedder.ts"() {
4390
+ "use strict";
4391
+ init_memory();
4392
+ init_exe_daemon_client();
4393
+ }
4394
+ });
4395
+
4396
+ // src/lib/license.ts
4397
+ var license_exports = {};
4398
+ __export(license_exports, {
4399
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
4400
+ PLAN_LIMITS: () => PLAN_LIMITS,
4401
+ assertVpsLicense: () => assertVpsLicense,
4402
+ checkLicense: () => checkLicense,
4403
+ getCachedLicense: () => getCachedLicense,
4404
+ isFeatureAllowed: () => isFeatureAllowed,
4405
+ loadDeviceId: () => loadDeviceId,
4406
+ loadLicense: () => loadLicense,
4407
+ mirrorLicenseKey: () => mirrorLicenseKey,
4408
+ saveLicense: () => saveLicense,
4409
+ validateLicense: () => validateLicense
4410
+ });
4411
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync3 } from "fs";
4412
+ import { randomUUID as randomUUID2 } from "crypto";
4413
+ import path11 from "path";
4414
+ import { jwtVerify, importSPKI } from "jose";
4415
+ function loadDeviceId() {
4416
+ const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
4417
+ try {
4418
+ if (existsSync10(deviceJsonPath)) {
4419
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
4420
+ if (data.deviceId) return data.deviceId;
4421
+ }
4422
+ } catch {
4423
+ }
4424
+ try {
4425
+ if (existsSync10(DEVICE_ID_PATH)) {
4426
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
4427
+ if (id2) return id2;
4428
+ }
4429
+ } catch {
4430
+ }
4431
+ const id = randomUUID2();
4432
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
4433
+ writeFileSync2(DEVICE_ID_PATH, id, "utf8");
4434
+ return id;
4435
+ }
4436
+ function loadLicense() {
4437
+ try {
4438
+ if (!existsSync10(LICENSE_PATH)) return null;
4439
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
4440
+ } catch {
4441
+ return null;
4442
+ }
4443
+ }
4444
+ function saveLicense(apiKey) {
4445
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
4446
+ writeFileSync2(LICENSE_PATH, apiKey.trim(), "utf8");
4447
+ }
4448
+ async function verifyLicenseJwt(token) {
4449
+ try {
4450
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4451
+ const { payload } = await jwtVerify(token, key, {
4452
+ algorithms: [LICENSE_JWT_ALG]
4453
+ });
4454
+ const plan = payload.plan ?? "free";
4455
+ const email = payload.sub ?? "";
4456
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4457
+ return {
4458
+ valid: true,
4459
+ plan,
4460
+ email,
4461
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4462
+ deviceLimit: limits.devices,
4463
+ employeeLimit: limits.employees,
4464
+ memoryLimit: limits.memories
4465
+ };
4466
+ } catch {
4467
+ return null;
4468
+ }
4469
+ }
4470
+ async function getCachedLicense() {
4471
+ try {
4472
+ if (!existsSync10(CACHE_PATH)) return null;
4473
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
4474
+ if (!raw.token || typeof raw.token !== "string") return null;
4475
+ return await verifyLicenseJwt(raw.token);
4476
+ } catch {
4477
+ return null;
4478
+ }
4479
+ }
4480
+ function readCachedToken() {
4481
+ try {
4482
+ if (!existsSync10(CACHE_PATH)) return null;
4483
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
4484
+ return typeof raw.token === "string" ? raw.token : null;
4485
+ } catch {
4486
+ return null;
4487
+ }
4488
+ }
4489
+ function cacheResponse(token) {
4490
+ try {
4491
+ writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
4492
+ } catch {
4493
+ }
4494
+ }
4495
+ async function validateLicense(apiKey, deviceId) {
4496
+ const did = deviceId ?? loadDeviceId();
4497
+ try {
4498
+ const res = await fetch(`${API_BASE}/auth/activate`, {
4499
+ method: "POST",
4500
+ headers: { "Content-Type": "application/json" },
4501
+ body: JSON.stringify({ apiKey, deviceId: did }),
4502
+ signal: AbortSignal.timeout(1e4)
4503
+ });
4504
+ if (res.ok) {
4505
+ const data = await res.json();
4506
+ if (data.error === "device_limit_exceeded") {
4507
+ const cached2 = await getCachedLicense();
4508
+ if (cached2) return cached2;
4509
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
4510
+ }
4511
+ if (data.token) {
4512
+ cacheResponse(data.token);
4513
+ const verified = await verifyLicenseJwt(data.token);
4514
+ if (verified) return verified;
4515
+ }
4516
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4517
+ return {
4518
+ valid: data.valid,
4519
+ plan: data.plan,
4520
+ email: data.email,
4521
+ expiresAt: data.expiresAt,
4522
+ deviceLimit: limits.devices,
4523
+ employeeLimit: limits.employees,
4524
+ memoryLimit: limits.memories
4525
+ };
4526
+ }
4527
+ const cached = await getCachedLicense();
4528
+ if (cached) return cached;
4529
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
4530
+ } catch {
4531
+ const cached = await getCachedLicense();
4532
+ if (cached) return cached;
4533
+ return FREE_LICENSE;
4534
+ }
4535
+ }
4536
+ async function checkLicense() {
4537
+ const key = loadLicense();
4538
+ if (!key) return FREE_LICENSE;
4539
+ const cached = await getCachedLicense();
4540
+ if (cached) return cached;
4541
+ const deviceId = loadDeviceId();
4542
+ return validateLicense(key, deviceId);
4543
+ }
4544
+ function isFeatureAllowed(license, feature) {
4545
+ switch (feature) {
4546
+ case "cloud_sync":
4547
+ case "external_agents":
4548
+ case "wiki":
4549
+ return license.plan !== "free";
4550
+ case "unlimited_employees":
4551
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
4552
+ }
4553
+ }
4554
+ function mirrorLicenseKey(apiKey) {
4555
+ const trimmed = apiKey.trim();
4556
+ if (!trimmed) return;
4557
+ saveLicense(trimmed);
4558
+ }
4559
+ async function assertVpsLicense(opts) {
4560
+ const env = opts?.env ?? process.env;
4561
+ const inProduction = env.NODE_ENV === "production";
4562
+ if (!opts?.force && !inProduction) {
4563
+ return { ...FREE_LICENSE, plan: "free" };
4564
+ }
4565
+ const envKey = env.EXE_LICENSE_KEY?.trim();
4566
+ if (envKey) {
4567
+ saveLicense(envKey);
4568
+ }
4569
+ const apiKey = envKey ?? loadLicense();
4570
+ if (!apiKey) {
4571
+ throw new Error(
4572
+ "License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
4573
+ );
4574
+ }
4575
+ const deviceId = loadDeviceId();
4576
+ let backendResponse = null;
4577
+ let explicitRejection = false;
4578
+ let transientFailure = false;
4579
+ try {
4580
+ const res = await fetch(`${API_BASE}/auth/activate`, {
4581
+ method: "POST",
4582
+ headers: { "Content-Type": "application/json" },
4583
+ body: JSON.stringify({ apiKey, deviceId }),
4584
+ signal: AbortSignal.timeout(1e4)
4585
+ });
4586
+ if (res.ok) {
4587
+ backendResponse = await res.json();
4588
+ if (!backendResponse.valid) explicitRejection = true;
4589
+ } else if (res.status === 401 || res.status === 403) {
4590
+ explicitRejection = true;
4591
+ } else {
4592
+ transientFailure = true;
4593
+ }
4594
+ } catch {
4595
+ transientFailure = true;
4596
+ }
4597
+ if (backendResponse?.valid) {
4598
+ if (backendResponse.token) {
4599
+ cacheResponse(backendResponse.token);
4600
+ const verified = await verifyLicenseJwt(backendResponse.token);
4601
+ if (verified) return verified;
4602
+ }
4603
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
4604
+ return {
4605
+ valid: true,
4606
+ plan: backendResponse.plan,
4607
+ email: backendResponse.email,
4608
+ expiresAt: backendResponse.expiresAt,
4609
+ deviceLimit: limits.devices,
4610
+ employeeLimit: limits.employees,
4611
+ memoryLimit: limits.memories
4612
+ };
4613
+ }
4614
+ if (explicitRejection) {
4615
+ throw new Error(
4616
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
4617
+ );
4618
+ }
4619
+ if (!transientFailure) {
4620
+ throw new Error(
4621
+ "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
4622
+ );
4623
+ }
4624
+ const fresh = await getCachedLicense();
4625
+ if (fresh && fresh.valid) return fresh;
4626
+ const graceDays = opts?.offlineGraceDays ?? 7;
4627
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
4628
+ try {
4629
+ const token = readCachedToken();
4630
+ if (token) {
4631
+ const payloadB64 = token.split(".")[1];
4632
+ if (payloadB64) {
4633
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
4634
+ const expMs = (payload.exp ?? 0) * 1e3;
4635
+ if (Date.now() < expMs + graceMs) {
4636
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4637
+ const { payload: verified } = await jwtVerify(token, key, {
4638
+ algorithms: [LICENSE_JWT_ALG],
4639
+ clockTolerance: graceDays * 24 * 60 * 60
4640
+ });
4641
+ const plan = verified.plan ?? "free";
4642
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4643
+ return {
4644
+ valid: true,
4645
+ plan,
4646
+ email: verified.sub ?? "",
4647
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
4648
+ deviceLimit: limits.devices,
4649
+ employeeLimit: limits.employees,
4650
+ memoryLimit: limits.memories
4651
+ };
4652
+ }
4653
+ }
4654
+ }
4655
+ } catch {
4656
+ }
4657
+ throw new Error(
4658
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
4659
+ );
4660
+ }
4661
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
4662
+ var init_license = __esm({
4663
+ "src/lib/license.ts"() {
4664
+ "use strict";
4665
+ init_config();
4666
+ LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
4667
+ CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
4668
+ DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
4669
+ API_BASE = "https://askexe.com/cloud";
4670
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4671
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4672
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
4673
+ -----END PUBLIC KEY-----`;
4674
+ LICENSE_JWT_ALG = "ES256";
4675
+ PLAN_LIMITS = {
4676
+ free: { devices: 1, employees: 1, memories: 5e3 },
4677
+ pro: { devices: 2, employees: 5, memories: 1e5 },
4678
+ team: { devices: 10, employees: 20, memories: 1e6 },
4679
+ agency: { devices: 50, employees: 100, memories: 1e7 },
4680
+ enterprise: { devices: -1, employees: -1, memories: -1 }
4681
+ };
4682
+ FREE_LICENSE = {
4683
+ valid: true,
4684
+ plan: "free",
4685
+ email: "",
4686
+ expiresAt: null,
4687
+ deviceLimit: 1,
4688
+ employeeLimit: 1,
4689
+ memoryLimit: 5e3
4690
+ };
4315
4691
  }
4316
4692
  });
4317
4693
 
@@ -4324,17 +4700,17 @@ __export(identity_exports, {
4324
4700
  listIdentities: () => listIdentities,
4325
4701
  updateIdentity: () => updateIdentity
4326
4702
  });
4327
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
4703
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
4328
4704
  import { readdirSync } from "fs";
4329
- import path11 from "path";
4705
+ import path12 from "path";
4330
4706
  import { createHash as createHash2 } from "crypto";
4331
4707
  function ensureDir() {
4332
- if (!existsSync10(IDENTITY_DIR)) {
4708
+ if (!existsSync11(IDENTITY_DIR)) {
4333
4709
  mkdirSync4(IDENTITY_DIR, { recursive: true });
4334
4710
  }
4335
4711
  }
4336
4712
  function identityPath(agentId) {
4337
- return path11.join(IDENTITY_DIR, `${agentId}.md`);
4713
+ return path12.join(IDENTITY_DIR, `${agentId}.md`);
4338
4714
  }
4339
4715
  function parseFrontmatter(raw) {
4340
4716
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -4375,8 +4751,8 @@ function contentHash(content) {
4375
4751
  }
4376
4752
  function getIdentity(agentId) {
4377
4753
  const filePath = identityPath(agentId);
4378
- if (!existsSync10(filePath)) return null;
4379
- const raw = readFileSync5(filePath, "utf-8");
4754
+ if (!existsSync11(filePath)) return null;
4755
+ const raw = readFileSync7(filePath, "utf-8");
4380
4756
  const { frontmatter, body } = parseFrontmatter(raw);
4381
4757
  return {
4382
4758
  agentId,
@@ -4390,7 +4766,7 @@ async function updateIdentity(agentId, content, updatedBy) {
4390
4766
  ensureDir();
4391
4767
  const filePath = identityPath(agentId);
4392
4768
  const hash = contentHash(content);
4393
- writeFileSync2(filePath, content, "utf-8");
4769
+ writeFileSync3(filePath, content, "utf-8");
4394
4770
  try {
4395
4771
  const client = getClient();
4396
4772
  await client.execute({
@@ -4446,7 +4822,7 @@ var init_identity = __esm({
4446
4822
  "use strict";
4447
4823
  init_config();
4448
4824
  init_database();
4449
- IDENTITY_DIR = path11.join(EXE_AI_DIR, "identity");
4825
+ IDENTITY_DIR = path12.join(EXE_AI_DIR, "identity");
4450
4826
  }
4451
4827
  });
4452
4828
 
@@ -4470,6 +4846,7 @@ function getTemplateForTitle(title) {
4470
4846
  if (t.includes("engineer") || t.includes("developer")) return IDENTITY_TEMPLATES["principal-engineer"];
4471
4847
  if (t.includes("content") || t.includes("production")) return IDENTITY_TEMPLATES["content-specialist"];
4472
4848
  if (t.includes("ai") || t.includes("product lead") || t.includes("specialist") && !t.includes("content")) return IDENTITY_TEMPLATES["ai-specialist"];
4849
+ if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
4473
4850
  return null;
4474
4851
  }
4475
4852
  var POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
@@ -4552,6 +4929,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
4552
4929
  - **create_task** \u2014 assign work to specialists with clear specs
4553
4930
  - **update_task / close_task** \u2014 finalize reviews, mark work done
4554
4931
  - **store_behavior** \u2014 record corrections as behavioral rules (p0/p1/p2)
4932
+ - **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
4555
4933
  - **get_identity** \u2014 read any agent's identity for coordination
4556
4934
  - **send_message** \u2014 direct intercom to employees
4557
4935
 
@@ -4915,6 +5293,55 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
4915
5293
  - Every recommendation includes cost/quality/latency tradeoff analysis
4916
5294
  - Separate experimental from production-ready \u2014 label clearly
4917
5295
  - If you can't verify, say so explicitly: "Couldn't verify because X"
5296
+ `,
5297
+ "staff-code-reviewer": `---
5298
+ role: staff-code-reviewer
5299
+ title: Staff Code Reviewer & System Auditor
5300
+ agent_id: bob
5301
+ org_level: specialist
5302
+ created_by: system
5303
+ updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
5304
+ ---
5305
+ ## Identity
5306
+
5307
+ You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defense before code ships to customers. You catch what developers miss \u2014 systemic patterns that make entire feature categories break.
5308
+
5309
+ ## The 7 Audit Patterns (MANDATORY)
5310
+
5311
+ 1. **"Works on dev, breaks on user install"** \u2014 scoped paths, npm resolution, deps
5312
+ 2. **"Two code paths, one untested"** \u2014 binary symlink vs /exe-call, verify BOTH
5313
+ 3. **"Case sensitivity kills non-technical users"** \u2014 normalize all user inputs
5314
+ 4. **"Hardcoded names in runtime logic"** \u2014 grep for employee names, must use roles
5315
+ 5. **"Installer doesn't self-heal"** \u2014 npm update must auto-fix stale hooks/paths
5316
+ 6. **"Data written but invisible"** \u2014 agent_id mismatch between writer and reader
5317
+ 7. **"Partial fixes miss inline refs"** \u2014 before/after grep count is mandatory
5318
+
5319
+ ## Method
5320
+
5321
+ 1. Read actual source code
5322
+ 2. Send to Codex MCP for sweep
5323
+ 3. Validate against ARCHITECTURE.md
5324
+ 4. Trace identity chain with CUSTOM-NAMED employee ("jarvis" as CTO)
5325
+ 5. Before/after grep count for every fix
5326
+ 6. Structured report: PASS/FAIL per item
5327
+
5328
+ ## Tools
5329
+
5330
+ - **Codex MCP** \u2014 first tool for every review
5331
+ - **recall_my_memory / ask_team_memory** \u2014 past audit findings
5332
+ - **store_behavior** \u2014 record new patterns
5333
+ - **update_task** \u2014 mark reviews done with structured findings
5334
+ - **create_task** \u2014 assign fixes to the CTO
5335
+
5336
+ ## Completion Workflow
5337
+
5338
+ 1. Read the task brief and understand the audit scope
5339
+ 2. Run the audit using all 7 patterns
5340
+ 3. Write report to exe/output/ with file:line references
5341
+ 4. Fix findings yourself if possible
5342
+ 5. Call **update_task** with status "done" and finding count
5343
+ 6. Call **store_memory** with audit summary
5344
+ 7. Check for next task \u2014 auto-chain
4918
5345
  `
4919
5346
  };
4920
5347
  }
@@ -4927,9 +5354,9 @@ __export(setup_wizard_exports, {
4927
5354
  validateModel: () => validateModel
4928
5355
  });
4929
5356
  import crypto3 from "crypto";
4930
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
5357
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
4931
5358
  import os4 from "os";
4932
- import path12 from "path";
5359
+ import path13 from "path";
4933
5360
  import { createInterface as createInterface2 } from "readline";
4934
5361
  function ask(rl, prompt) {
4935
5362
  return new Promise((resolve) => {
@@ -4970,7 +5397,7 @@ async function runSetupWizard(opts = {}) {
4970
5397
  rl.close();
4971
5398
  return;
4972
5399
  }
4973
- if (existsSync11(LEGACY_LANCE_PATH)) {
5400
+ if (existsSync12(LEGACY_LANCE_PATH)) {
4974
5401
  log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
4975
5402
  log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
4976
5403
  log(" The old directory will not be modified or deleted.");
@@ -5038,10 +5465,10 @@ async function runSetupWizard(opts = {}) {
5038
5465
  await saveConfig(config);
5039
5466
  log("");
5040
5467
  try {
5041
- const claudeJsonPath = path12.join(os4.homedir(), ".claude.json");
5468
+ const claudeJsonPath = path13.join(os4.homedir(), ".claude.json");
5042
5469
  let claudeJson = {};
5043
5470
  try {
5044
- claudeJson = JSON.parse(readFileSync6(claudeJsonPath, "utf8"));
5471
+ claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5045
5472
  } catch {
5046
5473
  }
5047
5474
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5050,7 +5477,7 @@ async function runSetupWizard(opts = {}) {
5050
5477
  if (!projects[dir]) projects[dir] = {};
5051
5478
  projects[dir].hasTrustDialogAccepted = true;
5052
5479
  }
5053
- writeFileSync3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5480
+ writeFileSync4(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5054
5481
  } catch {
5055
5482
  }
5056
5483
  const {
@@ -5095,9 +5522,9 @@ async function runSetupWizard(opts = {}) {
5095
5522
  const cooIdentityContent = getIdentityTemplate("coo");
5096
5523
  if (cooIdentityContent) {
5097
5524
  const cooIdPath = identityPath2(cooName);
5098
- mkdirSync5(path12.dirname(cooIdPath), { recursive: true });
5525
+ mkdirSync5(path13.dirname(cooIdPath), { recursive: true });
5099
5526
  const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
5100
- writeFileSync3(cooIdPath, replaced, "utf-8");
5527
+ writeFileSync4(cooIdPath, replaced, "utf-8");
5101
5528
  }
5102
5529
  registerBinSymlinks2(cooName);
5103
5530
  createdEmployees.push({ name: cooName, role: "COO" });
@@ -5179,9 +5606,9 @@ async function runSetupWizard(opts = {}) {
5179
5606
  const ctoIdentityContent = getIdentityTemplate("cto");
5180
5607
  if (ctoIdentityContent) {
5181
5608
  const ctoIdPath = identityPath2(ctoName);
5182
- mkdirSync5(path12.dirname(ctoIdPath), { recursive: true });
5609
+ mkdirSync5(path13.dirname(ctoIdPath), { recursive: true });
5183
5610
  const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
5184
- writeFileSync3(ctoIdPath, replaced, "utf-8");
5611
+ writeFileSync4(ctoIdPath, replaced, "utf-8");
5185
5612
  }
5186
5613
  registerBinSymlinks2(ctoName);
5187
5614
  createdEmployees.push({ name: ctoName, role: "CTO" });
@@ -5206,9 +5633,9 @@ async function runSetupWizard(opts = {}) {
5206
5633
  const cmoIdentityContent = getIdentityTemplate("cmo");
5207
5634
  if (cmoIdentityContent) {
5208
5635
  const cmoIdPath = identityPath2(cmoName);
5209
- mkdirSync5(path12.dirname(cmoIdPath), { recursive: true });
5636
+ mkdirSync5(path13.dirname(cmoIdPath), { recursive: true });
5210
5637
  const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
5211
- writeFileSync3(cmoIdPath, replaced, "utf-8");
5638
+ writeFileSync4(cmoIdPath, replaced, "utf-8");
5212
5639
  }
5213
5640
  registerBinSymlinks2(cmoName);
5214
5641
  createdEmployees.push({ name: cmoName, role: "CMO" });
@@ -9619,8 +10046,8 @@ var init_ErrorOverview = __esm({
9619
10046
  "use strict";
9620
10047
  init_Box();
9621
10048
  init_Text();
9622
- cleanupPath = (path31) => {
9623
- return path31?.replace(`file://${cwd()}/`, "");
10049
+ cleanupPath = (path32) => {
10050
+ return path32?.replace(`file://${cwd()}/`, "");
9624
10051
  };
9625
10052
  stackUtils = new StackUtils({
9626
10053
  cwd: cwd(),
@@ -11655,13 +12082,13 @@ __export(tmux_status_exports, {
11655
12082
  parseActivity: () => parseActivity,
11656
12083
  parseContextPercentage: () => parseContextPercentage
11657
12084
  });
11658
- import { execSync as execSync2 } from "child_process";
12085
+ import { execSync as execSync3 } from "child_process";
11659
12086
  function inTmux() {
11660
12087
  if (process.env.TMUX || process.env.TMUX_PANE) return true;
11661
12088
  const term = process.env.TERM ?? "";
11662
12089
  if (term.startsWith("tmux") || term.startsWith("screen")) return true;
11663
12090
  try {
11664
- execSync2("tmux display-message -p '#{session_name}' 2>/dev/null", {
12091
+ execSync3("tmux display-message -p '#{session_name}' 2>/dev/null", {
11665
12092
  encoding: "utf8",
11666
12093
  timeout: 2e3
11667
12094
  });
@@ -11671,12 +12098,12 @@ function inTmux() {
11671
12098
  try {
11672
12099
  let pid = process.ppid;
11673
12100
  for (let depth = 0; depth < 8 && pid > 1; depth++) {
11674
- const comm = execSync2(`ps -p ${pid} -o comm= 2>/dev/null`, {
12101
+ const comm = execSync3(`ps -p ${pid} -o comm= 2>/dev/null`, {
11675
12102
  encoding: "utf8",
11676
12103
  timeout: 1e3
11677
12104
  }).trim();
11678
12105
  if (/tmux/.test(comm)) return true;
11679
- const ppid = execSync2(`ps -p ${pid} -o ppid= 2>/dev/null`, {
12106
+ const ppid = execSync3(`ps -p ${pid} -o ppid= 2>/dev/null`, {
11680
12107
  encoding: "utf8",
11681
12108
  timeout: 1e3
11682
12109
  }).trim();
@@ -11689,7 +12116,7 @@ function inTmux() {
11689
12116
  }
11690
12117
  function listTmuxSessions() {
11691
12118
  try {
11692
- const out = execSync2("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
12119
+ const out = execSync3("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
11693
12120
  encoding: "utf8",
11694
12121
  timeout: 3e3
11695
12122
  });
@@ -11700,7 +12127,7 @@ function listTmuxSessions() {
11700
12127
  }
11701
12128
  function capturePaneLines(windowName, lines = 10) {
11702
12129
  try {
11703
- const out = execSync2(
12130
+ const out = execSync3(
11704
12131
  `tmux capture-pane -t ${JSON.stringify(windowName)} -p 2>/dev/null | tail -${lines}`,
11705
12132
  { encoding: "utf8", timeout: 3e3 }
11706
12133
  );
@@ -11711,7 +12138,7 @@ function capturePaneLines(windowName, lines = 10) {
11711
12138
  }
11712
12139
  function getPaneCwd(windowName) {
11713
12140
  try {
11714
- const out = execSync2(
12141
+ const out = execSync3(
11715
12142
  `tmux display-message -t ${JSON.stringify(windowName)} -p '#{pane_current_path}' 2>/dev/null`,
11716
12143
  { encoding: "utf8", timeout: 3e3 }
11717
12144
  );
@@ -11722,7 +12149,7 @@ function getPaneCwd(windowName) {
11722
12149
  }
11723
12150
  function projectFromPath(dir) {
11724
12151
  try {
11725
- const root = execSync2("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
12152
+ const root = execSync3("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
11726
12153
  encoding: "utf8",
11727
12154
  timeout: 3e3
11728
12155
  }).trim();
@@ -11791,7 +12218,7 @@ function getEmployeeStatuses(employeeNames) {
11791
12218
  }
11792
12219
  let paneAlive = true;
11793
12220
  try {
11794
- const paneStatus = execSync2(
12221
+ const paneStatus = execSync3(
11795
12222
  `tmux list-panes -t ${JSON.stringify(sessionName)} -F '#{pane_dead}' 2>/dev/null`,
11796
12223
  { encoding: "utf8", timeout: 3e3 }
11797
12224
  ).trim();
@@ -11955,11 +12382,11 @@ function Footer() {
11955
12382
  } catch {
11956
12383
  }
11957
12384
  try {
11958
- const { existsSync: existsSync21 } = await import("fs");
12385
+ const { existsSync: existsSync22 } = await import("fs");
11959
12386
  const { join } = await import("path");
11960
12387
  const home = process.env.HOME ?? "";
11961
12388
  const pidPath = join(home, ".exe-os", "exed.pid");
11962
- setDaemon(existsSync21(pidPath) ? "running" : "stopped");
12389
+ setDaemon(existsSync22(pidPath) ? "running" : "stopped");
11963
12390
  } catch {
11964
12391
  setDaemon("unknown");
11965
12392
  }
@@ -11977,8 +12404,8 @@ function Footer() {
11977
12404
  setSessions(allSessions.length);
11978
12405
  if (!currentSession) {
11979
12406
  try {
11980
- const { execSync: execSync12 } = await import("child_process");
11981
- const name = execSync12("tmux display-message -p '#{session_name}' 2>/dev/null", {
12407
+ const { execSync: execSync13 } = await import("child_process");
12408
+ const name = execSync13("tmux display-message -p '#{session_name}' 2>/dev/null", {
11982
12409
  encoding: "utf8",
11983
12410
  timeout: 2e3
11984
12411
  }).trim();
@@ -13985,10 +14412,10 @@ var init_hooks = __esm({
13985
14412
  });
13986
14413
 
13987
14414
  // src/runtime/safety-checks.ts
13988
- import path13 from "path";
14415
+ import path14 from "path";
13989
14416
  import os5 from "os";
13990
14417
  function checkPathSafety(filePath) {
13991
- const resolved = path13.resolve(filePath);
14418
+ const resolved = path14.resolve(filePath);
13992
14419
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
13993
14420
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
13994
14421
  if (matches) {
@@ -13998,7 +14425,7 @@ function checkPathSafety(filePath) {
13998
14425
  return { safe: true, bypassImmune: true };
13999
14426
  }
14000
14427
  function checkReadPathSafety(filePath) {
14001
- const resolved = path13.resolve(filePath);
14428
+ const resolved = path14.resolve(filePath);
14002
14429
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
14003
14430
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
14004
14431
  );
@@ -14024,11 +14451,11 @@ var init_safety_checks = __esm({
14024
14451
  reason: "Git config can set hooks and command execution"
14025
14452
  },
14026
14453
  {
14027
- pattern: (p) => p.startsWith(path13.join(HOME, ".claude")),
14454
+ pattern: (p) => p.startsWith(path14.join(HOME, ".claude")),
14028
14455
  reason: "Claude configuration files are protected"
14029
14456
  },
14030
14457
  {
14031
- pattern: (p) => p.startsWith(path13.join(HOME, ".exe-os")),
14458
+ pattern: (p) => p.startsWith(path14.join(HOME, ".exe-os")),
14032
14459
  reason: "exe-os configuration files are protected"
14033
14460
  },
14034
14461
  {
@@ -14045,7 +14472,7 @@ var init_safety_checks = __esm({
14045
14472
  },
14046
14473
  {
14047
14474
  pattern: (p) => {
14048
- const name = path13.basename(p);
14475
+ const name = path14.basename(p);
14049
14476
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
14050
14477
  },
14051
14478
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -14072,7 +14499,7 @@ __export(file_read_exports, {
14072
14499
  FileReadTool: () => FileReadTool
14073
14500
  });
14074
14501
  import fs3 from "fs/promises";
14075
- import path14 from "path";
14502
+ import path15 from "path";
14076
14503
  import { z } from "zod";
14077
14504
  function isBinary(buf) {
14078
14505
  for (let i = 0; i < buf.length; i++) {
@@ -14108,7 +14535,7 @@ var init_file_read = __esm({
14108
14535
  return { behavior: "allow" };
14109
14536
  },
14110
14537
  async call(input, context) {
14111
- const filePath = path14.isAbsolute(input.file_path) ? input.file_path : path14.resolve(context.cwd, input.file_path);
14538
+ const filePath = path15.isAbsolute(input.file_path) ? input.file_path : path15.resolve(context.cwd, input.file_path);
14112
14539
  let stat2;
14113
14540
  try {
14114
14541
  stat2 = await fs3.stat(filePath);
@@ -14148,7 +14575,7 @@ __export(glob_exports, {
14148
14575
  GlobTool: () => GlobTool
14149
14576
  });
14150
14577
  import fs4 from "fs/promises";
14151
- import path15 from "path";
14578
+ import path16 from "path";
14152
14579
  import { z as z2 } from "zod";
14153
14580
  async function walkDir(dir, maxDepth = 10) {
14154
14581
  const results = [];
@@ -14164,7 +14591,7 @@ async function walkDir(dir, maxDepth = 10) {
14164
14591
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
14165
14592
  continue;
14166
14593
  }
14167
- const fullPath = path15.join(current, entry.name);
14594
+ const fullPath = path16.join(current, entry.name);
14168
14595
  if (entry.isDirectory()) {
14169
14596
  await walk(fullPath, depth + 1);
14170
14597
  } else {
@@ -14198,11 +14625,11 @@ var init_glob = __esm({
14198
14625
  inputSchema: inputSchema2,
14199
14626
  isReadOnly: true,
14200
14627
  async call(input, context) {
14201
- const baseDir = input.path ? path15.isAbsolute(input.path) ? input.path : path15.resolve(context.cwd, input.path) : context.cwd;
14628
+ const baseDir = input.path ? path16.isAbsolute(input.path) ? input.path : path16.resolve(context.cwd, input.path) : context.cwd;
14202
14629
  try {
14203
14630
  const entries = await walkDir(baseDir);
14204
14631
  const matched = entries.filter(
14205
- (e) => simpleGlobMatch(path15.relative(baseDir, e.path), input.pattern)
14632
+ (e) => simpleGlobMatch(path16.relative(baseDir, e.path), input.pattern)
14206
14633
  );
14207
14634
  matched.sort((a, b) => b.mtime - a.mtime);
14208
14635
  if (matched.length === 0) {
@@ -14228,7 +14655,7 @@ __export(grep_exports, {
14228
14655
  });
14229
14656
  import { spawn as spawn2 } from "child_process";
14230
14657
  import fs5 from "fs/promises";
14231
- import path16 from "path";
14658
+ import path17 from "path";
14232
14659
  import { z as z3 } from "zod";
14233
14660
  function runRipgrep(input, searchPath, context) {
14234
14661
  return new Promise((resolve, reject) => {
@@ -14277,7 +14704,7 @@ async function nodeGrep(input, searchPath) {
14277
14704
  }
14278
14705
  for (const entry of entries) {
14279
14706
  if (entry.name === "node_modules" || entry.name === ".git") continue;
14280
- const fullPath = path16.join(dir, entry.name);
14707
+ const fullPath = path17.join(dir, entry.name);
14281
14708
  if (entry.isDirectory()) {
14282
14709
  await walk(fullPath);
14283
14710
  } else {
@@ -14323,7 +14750,7 @@ var init_grep = __esm({
14323
14750
  inputSchema: inputSchema3,
14324
14751
  isReadOnly: true,
14325
14752
  async call(input, context) {
14326
- const searchPath = input.path ? path16.isAbsolute(input.path) ? input.path : path16.resolve(context.cwd, input.path) : context.cwd;
14753
+ const searchPath = input.path ? path17.isAbsolute(input.path) ? input.path : path17.resolve(context.cwd, input.path) : context.cwd;
14327
14754
  try {
14328
14755
  const result = await runRipgrep(input, searchPath, context);
14329
14756
  return result;
@@ -14348,7 +14775,7 @@ __export(file_write_exports, {
14348
14775
  FileWriteTool: () => FileWriteTool
14349
14776
  });
14350
14777
  import fs6 from "fs/promises";
14351
- import path17 from "path";
14778
+ import path18 from "path";
14352
14779
  import { z as z4 } from "zod";
14353
14780
  var inputSchema4, FileWriteTool;
14354
14781
  var init_file_write = __esm({
@@ -14376,8 +14803,8 @@ var init_file_write = __esm({
14376
14803
  return { behavior: "allow" };
14377
14804
  },
14378
14805
  async call(input, context) {
14379
- const filePath = path17.isAbsolute(input.file_path) ? input.file_path : path17.resolve(context.cwd, input.file_path);
14380
- const dir = path17.dirname(filePath);
14806
+ const filePath = path18.isAbsolute(input.file_path) ? input.file_path : path18.resolve(context.cwd, input.file_path);
14807
+ const dir = path18.dirname(filePath);
14381
14808
  await fs6.mkdir(dir, { recursive: true });
14382
14809
  await fs6.writeFile(filePath, input.content, "utf-8");
14383
14810
  return {
@@ -14395,7 +14822,7 @@ __export(file_edit_exports, {
14395
14822
  FileEditTool: () => FileEditTool
14396
14823
  });
14397
14824
  import fs7 from "fs/promises";
14398
- import path18 from "path";
14825
+ import path19 from "path";
14399
14826
  import { z as z5 } from "zod";
14400
14827
  function countOccurrences(haystack, needle) {
14401
14828
  let count = 0;
@@ -14436,7 +14863,7 @@ var init_file_edit = __esm({
14436
14863
  return { behavior: "allow" };
14437
14864
  },
14438
14865
  async call(input, context) {
14439
- const filePath = path18.isAbsolute(input.file_path) ? input.file_path : path18.resolve(context.cwd, input.file_path);
14866
+ const filePath = path19.isAbsolute(input.file_path) ? input.file_path : path19.resolve(context.cwd, input.file_path);
14440
14867
  let content;
14441
14868
  try {
14442
14869
  content = await fs7.readFile(filePath, "utf-8");
@@ -14673,13 +15100,13 @@ __export(session_registry_exports, {
14673
15100
  pruneStaleSessions: () => pruneStaleSessions,
14674
15101
  registerSession: () => registerSession
14675
15102
  });
14676
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync13 } from "fs";
14677
- import { execSync as execSync3 } from "child_process";
14678
- import path19 from "path";
15103
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync14 } from "fs";
15104
+ import { execSync as execSync4 } from "child_process";
15105
+ import path20 from "path";
14679
15106
  import os6 from "os";
14680
15107
  function registerSession(entry) {
14681
- const dir = path19.dirname(REGISTRY_PATH);
14682
- if (!existsSync13(dir)) {
15108
+ const dir = path20.dirname(REGISTRY_PATH);
15109
+ if (!existsSync14(dir)) {
14683
15110
  mkdirSync6(dir, { recursive: true });
14684
15111
  }
14685
15112
  const sessions = listSessions();
@@ -14689,11 +15116,11 @@ function registerSession(entry) {
14689
15116
  } else {
14690
15117
  sessions.push(entry);
14691
15118
  }
14692
- writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
15119
+ writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
14693
15120
  }
14694
15121
  function listSessions() {
14695
15122
  try {
14696
- const raw = readFileSync8(REGISTRY_PATH, "utf8");
15123
+ const raw = readFileSync10(REGISTRY_PATH, "utf8");
14697
15124
  return JSON.parse(raw);
14698
15125
  } catch {
14699
15126
  return [];
@@ -14704,7 +15131,7 @@ function pruneStaleSessions() {
14704
15131
  if (sessions.length === 0) return 0;
14705
15132
  let liveSessions = [];
14706
15133
  try {
14707
- liveSessions = execSync3("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
15134
+ liveSessions = execSync4("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
14708
15135
  encoding: "utf8"
14709
15136
  }).trim().split("\n").filter(Boolean);
14710
15137
  } catch {
@@ -14714,7 +15141,7 @@ function pruneStaleSessions() {
14714
15141
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
14715
15142
  const pruned = sessions.length - alive.length;
14716
15143
  if (pruned > 0) {
14717
- writeFileSync4(REGISTRY_PATH, JSON.stringify(alive, null, 2));
15144
+ writeFileSync5(REGISTRY_PATH, JSON.stringify(alive, null, 2));
14718
15145
  }
14719
15146
  return pruned;
14720
15147
  }
@@ -14722,7 +15149,7 @@ var REGISTRY_PATH;
14722
15149
  var init_session_registry = __esm({
14723
15150
  "src/lib/session-registry.ts"() {
14724
15151
  "use strict";
14725
- REGISTRY_PATH = path19.join(os6.homedir(), ".exe-os", "session-registry.json");
15152
+ REGISTRY_PATH = path20.join(os6.homedir(), ".exe-os", "session-registry.json");
14726
15153
  }
14727
15154
  });
14728
15155
 
@@ -14762,15 +15189,15 @@ function CommandCenterView({
14762
15189
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
14763
15190
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
14764
15191
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
14765
- const { readFileSync: readFileSync16, existsSync: existsSync21 } = await import("fs");
15192
+ const { readFileSync: readFileSync18, existsSync: existsSync22 } = await import("fs");
14766
15193
  const { join } = await import("path");
14767
- const { homedir: homedir2 } = await import("os");
14768
- const configPath = join(homedir2(), ".exe-os", "config.json");
15194
+ const { homedir: homedir3 } = await import("os");
15195
+ const configPath = join(homedir3(), ".exe-os", "config.json");
14769
15196
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
14770
15197
  let providerConfigs = {};
14771
- if (existsSync21(configPath)) {
15198
+ if (existsSync22(configPath)) {
14772
15199
  try {
14773
- const raw = JSON.parse(readFileSync16(configPath, "utf8"));
15200
+ const raw = JSON.parse(readFileSync18(configPath, "utf8"));
14774
15201
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
14775
15202
  if (raw.providers && typeof raw.providers === "object") {
14776
15203
  providerConfigs = raw.providers;
@@ -14828,10 +15255,10 @@ function CommandCenterView({
14828
15255
  registry.register(BashTool2);
14829
15256
  let agentRole = "CTO";
14830
15257
  try {
14831
- const markerDir = join(homedir2(), ".exe-os", "session-cache");
15258
+ const markerDir = join(homedir3(), ".exe-os", "session-cache");
14832
15259
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
14833
15260
  for (const f of agentFiles) {
14834
- const data = JSON.parse(readFileSync16(join(markerDir, f), "utf8"));
15261
+ const data = JSON.parse(readFileSync18(join(markerDir, f), "utf8"));
14835
15262
  if (data.agentRole) {
14836
15263
  agentRole = data.agentRole;
14837
15264
  break;
@@ -15001,7 +15428,7 @@ function CommandCenterView({
15001
15428
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
15002
15429
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
15003
15430
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15004
- const { existsSync: existsSync21 } = await import("fs");
15431
+ const { existsSync: existsSync22 } = await import("fs");
15005
15432
  const { join } = await import("path");
15006
15433
  const registry = listSessions2();
15007
15434
  const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
@@ -15050,7 +15477,7 @@ function CommandCenterView({
15050
15477
  }
15051
15478
  const totalCount = 1 + employeeNames.length;
15052
15479
  const memoryCount = projectMemoryCounts.get(projectName) ?? 0;
15053
- const hasGit = projectDir ? existsSync21(join(projectDir, ".git")) : false;
15480
+ const hasGit = projectDir ? existsSync22(join(projectDir, ".git")) : false;
15054
15481
  const hasActivity = memoryCount > 0;
15055
15482
  let type = "automation";
15056
15483
  if (hasGit && hasActivity) type = "code";
@@ -15074,7 +15501,7 @@ function CommandCenterView({
15074
15501
  const activeProjectNames = new Set(projectList.map((p) => p.projectName));
15075
15502
  for (const dir of knownDirs) {
15076
15503
  const name = dir.split("/").filter(Boolean).pop() ?? "";
15077
- if (activeProjectNames.has(name) || !existsSync21(dir) || !existsSync21(join(dir, ".git"))) continue;
15504
+ if (activeProjectNames.has(name) || !existsSync22(dir) || !existsSync22(join(dir, ".git"))) continue;
15078
15505
  if ((projectMemoryCounts.get(name) ?? 0) > 0) continue;
15079
15506
  projectList.push({
15080
15507
  projectName: name,
@@ -15148,7 +15575,7 @@ function CommandCenterView({
15148
15575
  }
15149
15576
  try {
15150
15577
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
15151
- setHealth((h) => ({ ...h, daemon: existsSync21(pidPath) ? "running" : "stopped" }));
15578
+ setHealth((h) => ({ ...h, daemon: existsSync22(pidPath) ? "running" : "stopped" }));
15152
15579
  } catch {
15153
15580
  }
15154
15581
  try {
@@ -15393,8 +15820,8 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
15393
15820
  }
15394
15821
  const capture = () => {
15395
15822
  try {
15396
- const { execSync: execSync12 } = __require("child_process");
15397
- const output = execSync12(
15823
+ const { execSync: execSync13 } = __require("child_process");
15824
+ const output = execSync13(
15398
15825
  `tmux capture-pane -t ${JSON.stringify(sessionName)} -p -e 2>/dev/null | tail -${CAPTURE_LINES}`,
15399
15826
  { encoding: "utf8", timeout: 3e3 }
15400
15827
  );
@@ -15418,8 +15845,8 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
15418
15845
  if (key.return) {
15419
15846
  if (!demo && inputBuffer.trim()) {
15420
15847
  try {
15421
- const { execSync: execSync12 } = __require("child_process");
15422
- execSync12(
15848
+ const { execSync: execSync13 } = __require("child_process");
15849
+ execSync13(
15423
15850
  `tmux send-keys -t ${JSON.stringify(sessionName)} ${JSON.stringify(inputBuffer)} Enter`,
15424
15851
  { timeout: 2e3 }
15425
15852
  );
@@ -15427,8 +15854,8 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
15427
15854
  }
15428
15855
  } else if (!demo) {
15429
15856
  try {
15430
- const { execSync: execSync12 } = __require("child_process");
15431
- execSync12(`tmux send-keys -t ${JSON.stringify(sessionName)} Enter`, { timeout: 2e3 });
15857
+ const { execSync: execSync13 } = __require("child_process");
15858
+ execSync13(`tmux send-keys -t ${JSON.stringify(sessionName)} Enter`, { timeout: 2e3 });
15432
15859
  } catch {
15433
15860
  }
15434
15861
  }
@@ -15567,12 +15994,14 @@ var init_task_router = __esm({
15567
15994
  },
15568
15995
  tierRules: {
15569
15996
  junior: {
15570
- eligible: ["tom"],
15997
+ eligible: [],
15998
+ // resolved dynamically from roster (Principal Engineer role)
15571
15999
  reviewRequired: false,
15572
16000
  manualOnly: false
15573
16001
  },
15574
16002
  standard: {
15575
- eligible: ["tom"],
16003
+ eligible: [],
16004
+ // resolved dynamically from roster (Principal Engineer role)
15576
16005
  reviewRequired: false,
15577
16006
  manualOnly: false
15578
16007
  },
@@ -15593,13 +16022,13 @@ var init_task_router = __esm({
15593
16022
  });
15594
16023
 
15595
16024
  // src/lib/session-key.ts
15596
- import { execSync as execSync4 } from "child_process";
16025
+ import { execSync as execSync5 } from "child_process";
15597
16026
  function getSessionKey() {
15598
16027
  if (_cached) return _cached;
15599
16028
  let pid = process.ppid;
15600
16029
  for (let i = 0; i < 10; i++) {
15601
16030
  try {
15602
- const info = execSync4(`ps -p ${pid} -o ppid=,comm=`, {
16031
+ const info = execSync5(`ps -p ${pid} -o ppid=,comm=`, {
15603
16032
  encoding: "utf8",
15604
16033
  timeout: 2e3
15605
16034
  }).trim();
@@ -15743,14 +16172,14 @@ var init_transport = __esm({
15743
16172
  });
15744
16173
 
15745
16174
  // src/lib/cc-agent-support.ts
15746
- import { execSync as execSync5 } from "child_process";
16175
+ import { execSync as execSync6 } from "child_process";
15747
16176
  function _resetCcAgentSupportCache() {
15748
16177
  _cachedSupport = null;
15749
16178
  }
15750
16179
  function claudeSupportsAgentFlag() {
15751
16180
  if (_cachedSupport !== null) return _cachedSupport;
15752
16181
  try {
15753
- const helpOutput = execSync5("claude --help 2>&1", {
16182
+ const helpOutput = execSync6("claude --help 2>&1", {
15754
16183
  encoding: "utf-8",
15755
16184
  timeout: 5e3
15756
16185
  });
@@ -15793,17 +16222,17 @@ var init_provider_table = __esm({
15793
16222
  });
15794
16223
 
15795
16224
  // src/lib/intercom-queue.ts
15796
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync14, mkdirSync as mkdirSync7 } from "fs";
15797
- import path20 from "path";
16225
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, renameSync as renameSync4, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
16226
+ import path21 from "path";
15798
16227
  import os7 from "os";
15799
16228
  function ensureDir2() {
15800
- const dir = path20.dirname(QUEUE_PATH);
15801
- if (!existsSync14(dir)) mkdirSync7(dir, { recursive: true });
16229
+ const dir = path21.dirname(QUEUE_PATH);
16230
+ if (!existsSync15(dir)) mkdirSync7(dir, { recursive: true });
15802
16231
  }
15803
16232
  function readQueue() {
15804
16233
  try {
15805
- if (!existsSync14(QUEUE_PATH)) return [];
15806
- return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
16234
+ if (!existsSync15(QUEUE_PATH)) return [];
16235
+ return JSON.parse(readFileSync11(QUEUE_PATH, "utf8"));
15807
16236
  } catch {
15808
16237
  return [];
15809
16238
  }
@@ -15811,8 +16240,8 @@ function readQueue() {
15811
16240
  function writeQueue(queue) {
15812
16241
  ensureDir2();
15813
16242
  const tmp = `${QUEUE_PATH}.tmp`;
15814
- writeFileSync5(tmp, JSON.stringify(queue, null, 2));
15815
- renameSync3(tmp, QUEUE_PATH);
16243
+ writeFileSync6(tmp, JSON.stringify(queue, null, 2));
16244
+ renameSync4(tmp, QUEUE_PATH);
15816
16245
  }
15817
16246
  function queueIntercom(targetSession, reason) {
15818
16247
  const queue = readQueue();
@@ -15835,19 +16264,19 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
15835
16264
  var init_intercom_queue = __esm({
15836
16265
  "src/lib/intercom-queue.ts"() {
15837
16266
  "use strict";
15838
- QUEUE_PATH = path20.join(os7.homedir(), ".exe-os", "intercom-queue.json");
16267
+ QUEUE_PATH = path21.join(os7.homedir(), ".exe-os", "intercom-queue.json");
15839
16268
  TTL_MS = 60 * 60 * 1e3;
15840
- INTERCOM_LOG = path20.join(os7.homedir(), ".exe-os", "intercom.log");
16269
+ INTERCOM_LOG = path21.join(os7.homedir(), ".exe-os", "intercom.log");
15841
16270
  }
15842
16271
  });
15843
16272
 
15844
16273
  // src/lib/plan-limits.ts
15845
- import { readFileSync as readFileSync10, existsSync as existsSync15 } from "fs";
15846
- import path21 from "path";
16274
+ import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
16275
+ import path22 from "path";
15847
16276
  function getLicenseSync() {
15848
16277
  try {
15849
- if (!existsSync15(CACHE_PATH2)) return freeLicense();
15850
- const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
16278
+ if (!existsSync16(CACHE_PATH2)) return freeLicense();
16279
+ const raw = JSON.parse(readFileSync12(CACHE_PATH2, "utf8"));
15851
16280
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
15852
16281
  const parts = raw.token.split(".");
15853
16282
  if (parts.length !== 3) return freeLicense();
@@ -15885,8 +16314,8 @@ function assertEmployeeLimitSync(rosterPath) {
15885
16314
  const filePath = rosterPath ?? EMPLOYEES_PATH;
15886
16315
  let count = 0;
15887
16316
  try {
15888
- if (existsSync15(filePath)) {
15889
- const raw = readFileSync10(filePath, "utf8");
16317
+ if (existsSync16(filePath)) {
16318
+ const raw = readFileSync12(filePath, "utf8");
15890
16319
  const employees = JSON.parse(raw);
15891
16320
  count = Array.isArray(employees) ? employees.length : 0;
15892
16321
  }
@@ -15915,19 +16344,19 @@ var init_plan_limits = __esm({
15915
16344
  this.name = "PlanLimitError";
15916
16345
  }
15917
16346
  };
15918
- CACHE_PATH2 = path21.join(EXE_AI_DIR, "license-cache.json");
16347
+ CACHE_PATH2 = path22.join(EXE_AI_DIR, "license-cache.json");
15919
16348
  }
15920
16349
  });
15921
16350
 
15922
16351
  // src/lib/notifications.ts
15923
16352
  import crypto4 from "crypto";
15924
- import path22 from "path";
16353
+ import path23 from "path";
15925
16354
  import os8 from "os";
15926
16355
  import {
15927
- readFileSync as readFileSync11,
16356
+ readFileSync as readFileSync13,
15928
16357
  readdirSync as readdirSync2,
15929
- unlinkSync as unlinkSync3,
15930
- existsSync as existsSync16,
16358
+ unlinkSync as unlinkSync4,
16359
+ existsSync as existsSync17,
15931
16360
  rmdirSync
15932
16361
  } from "fs";
15933
16362
  async function writeNotification(notification) {
@@ -16007,10 +16436,10 @@ var init_session_kill_telemetry = __esm({
16007
16436
 
16008
16437
  // src/lib/tasks-crud.ts
16009
16438
  import crypto6 from "crypto";
16010
- import path23 from "path";
16011
- import { execSync as execSync6 } from "child_process";
16439
+ import path24 from "path";
16440
+ import { execSync as execSync7 } from "child_process";
16012
16441
  import { mkdir as mkdir6, writeFile as writeFile5, appendFile } from "fs/promises";
16013
- import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
16442
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
16014
16443
  async function writeCheckpoint(input) {
16015
16444
  const client = getClient();
16016
16445
  const row = await resolveTask(client, input.taskId);
@@ -16140,8 +16569,8 @@ async function createTaskCore(input) {
16140
16569
  }
16141
16570
  if (input.baseDir) {
16142
16571
  try {
16143
- await mkdir6(path23.join(input.baseDir, "exe", "output"), { recursive: true });
16144
- await mkdir6(path23.join(input.baseDir, "exe", "research"), { recursive: true });
16572
+ await mkdir6(path24.join(input.baseDir, "exe", "output"), { recursive: true });
16573
+ await mkdir6(path24.join(input.baseDir, "exe", "research"), { recursive: true });
16145
16574
  await ensureArchitectureDoc(input.baseDir, input.projectName);
16146
16575
  await ensureGitignoreExe(input.baseDir);
16147
16576
  } catch {
@@ -16241,12 +16670,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
16241
16670
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
16242
16671
  try {
16243
16672
  const since = new Date(taskCreatedAt).toISOString();
16244
- const branch = execSync6(
16673
+ const branch = execSync7(
16245
16674
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
16246
16675
  { encoding: "utf8", timeout: 3e3 }
16247
16676
  ).trim();
16248
16677
  const branchArg = branch && branch !== "HEAD" ? branch : "";
16249
- const commitCount = execSync6(
16678
+ const commitCount = execSync7(
16250
16679
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
16251
16680
  { encoding: "utf8", timeout: 5e3 }
16252
16681
  ).trim();
@@ -16349,9 +16778,9 @@ async function deleteTaskCore(taskId, _baseDir) {
16349
16778
  return { taskFile, assignedTo, assignedBy, taskSlug };
16350
16779
  }
16351
16780
  async function ensureArchitectureDoc(baseDir, projectName) {
16352
- const archPath = path23.join(baseDir, "exe", "ARCHITECTURE.md");
16781
+ const archPath = path24.join(baseDir, "exe", "ARCHITECTURE.md");
16353
16782
  try {
16354
- if (existsSync17(archPath)) return;
16783
+ if (existsSync18(archPath)) return;
16355
16784
  const template = [
16356
16785
  `# ${projectName} \u2014 System Architecture`,
16357
16786
  "",
@@ -16384,10 +16813,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
16384
16813
  }
16385
16814
  }
16386
16815
  async function ensureGitignoreExe(baseDir) {
16387
- const gitignorePath = path23.join(baseDir, ".gitignore");
16816
+ const gitignorePath = path24.join(baseDir, ".gitignore");
16388
16817
  try {
16389
- if (existsSync17(gitignorePath)) {
16390
- const content = readFileSync12(gitignorePath, "utf-8");
16818
+ if (existsSync18(gitignorePath)) {
16819
+ const content = readFileSync14(gitignorePath, "utf-8");
16391
16820
  if (/^\/?exe\/?$/m.test(content)) return;
16392
16821
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
16393
16822
  } else {
@@ -16407,8 +16836,8 @@ var init_tasks_crud = __esm({
16407
16836
  });
16408
16837
 
16409
16838
  // src/lib/tasks-review.ts
16410
- import path24 from "path";
16411
- import { existsSync as existsSync18, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
16839
+ import path25 from "path";
16840
+ import { existsSync as existsSync19, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
16412
16841
  async function countPendingReviews() {
16413
16842
  const client = getClient();
16414
16843
  const result = await client.execute({
@@ -16528,11 +16957,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
16528
16957
  );
16529
16958
  }
16530
16959
  try {
16531
- const cacheDir = path24.join(EXE_AI_DIR, "session-cache");
16532
- if (existsSync18(cacheDir)) {
16960
+ const cacheDir = path25.join(EXE_AI_DIR, "session-cache");
16961
+ if (existsSync19(cacheDir)) {
16533
16962
  for (const f of readdirSync3(cacheDir)) {
16534
16963
  if (f.startsWith("review-notified-")) {
16535
- unlinkSync4(path24.join(cacheDir, f));
16964
+ unlinkSync5(path25.join(cacheDir, f));
16536
16965
  }
16537
16966
  }
16538
16967
  }
@@ -16553,7 +16982,7 @@ var init_tasks_review = __esm({
16553
16982
  });
16554
16983
 
16555
16984
  // src/lib/tasks-chain.ts
16556
- import path25 from "path";
16985
+ import path26 from "path";
16557
16986
  import { readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
16558
16987
  async function cascadeUnblock(taskId, baseDir, now) {
16559
16988
  const client = getClient();
@@ -16569,7 +16998,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
16569
16998
  });
16570
16999
  for (const ur of unblockedRows.rows) {
16571
17000
  try {
16572
- const ubFile = path25.join(baseDir, String(ur.task_file));
17001
+ const ubFile = path26.join(baseDir, String(ur.task_file));
16573
17002
  let ubContent = await readFile5(ubFile, "utf-8");
16574
17003
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
16575
17004
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -16634,34 +17063,34 @@ var init_tasks_chain = __esm({
16634
17063
  });
16635
17064
 
16636
17065
  // src/lib/project-name.ts
16637
- import { execSync as execSync7 } from "child_process";
16638
- import path26 from "path";
17066
+ import { execSync as execSync8 } from "child_process";
17067
+ import path27 from "path";
16639
17068
  function getProjectName(cwd2) {
16640
17069
  const dir = cwd2 ?? process.cwd();
16641
17070
  if (_cached2 && _cachedCwd === dir) return _cached2;
16642
17071
  try {
16643
17072
  let repoRoot;
16644
17073
  try {
16645
- const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
17074
+ const gitCommonDir = execSync8("git rev-parse --path-format=absolute --git-common-dir", {
16646
17075
  cwd: dir,
16647
17076
  encoding: "utf8",
16648
17077
  timeout: 2e3,
16649
17078
  stdio: ["pipe", "pipe", "pipe"]
16650
17079
  }).trim();
16651
- repoRoot = path26.dirname(gitCommonDir);
17080
+ repoRoot = path27.dirname(gitCommonDir);
16652
17081
  } catch {
16653
- repoRoot = execSync7("git rev-parse --show-toplevel", {
17082
+ repoRoot = execSync8("git rev-parse --show-toplevel", {
16654
17083
  cwd: dir,
16655
17084
  encoding: "utf8",
16656
17085
  timeout: 2e3,
16657
17086
  stdio: ["pipe", "pipe", "pipe"]
16658
17087
  }).trim();
16659
17088
  }
16660
- _cached2 = path26.basename(repoRoot);
17089
+ _cached2 = path27.basename(repoRoot);
16661
17090
  _cachedCwd = dir;
16662
17091
  return _cached2;
16663
17092
  } catch {
16664
- _cached2 = path26.basename(dir);
17093
+ _cached2 = path27.basename(dir);
16665
17094
  _cachedCwd = dir;
16666
17095
  return _cached2;
16667
17096
  }
@@ -16763,7 +17192,7 @@ async function dispatchTaskToEmployee(input) {
16763
17192
  } else {
16764
17193
  const projectDir = input.projectDir ?? process.cwd();
16765
17194
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
16766
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
17195
+ autoInstance: isMultiInstance(input.assignedTo)
16767
17196
  });
16768
17197
  if (result.status === "failed") {
16769
17198
  process.stderr.write(
@@ -16798,6 +17227,7 @@ var init_tasks_notify = __esm({
16798
17227
  init_session_key();
16799
17228
  init_notifications();
16800
17229
  init_transport();
17230
+ init_employees();
16801
17231
  }
16802
17232
  });
16803
17233
 
@@ -17131,8 +17561,8 @@ __export(tasks_exports, {
17131
17561
  updateTaskStatus: () => updateTaskStatus,
17132
17562
  writeCheckpoint: () => writeCheckpoint
17133
17563
  });
17134
- import path27 from "path";
17135
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, unlinkSync as unlinkSync5 } from "fs";
17564
+ import path28 from "path";
17565
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, unlinkSync as unlinkSync6 } from "fs";
17136
17566
  async function createTask(input) {
17137
17567
  const result = await createTaskCore(input);
17138
17568
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -17151,14 +17581,14 @@ async function updateTask(input) {
17151
17581
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
17152
17582
  try {
17153
17583
  const agent = String(row.assigned_to);
17154
- const cacheDir = path27.join(EXE_AI_DIR, "session-cache");
17155
- const cachePath = path27.join(cacheDir, `current-task-${agent}.json`);
17584
+ const cacheDir = path28.join(EXE_AI_DIR, "session-cache");
17585
+ const cachePath = path28.join(cacheDir, `current-task-${agent}.json`);
17156
17586
  if (input.status === "in_progress") {
17157
17587
  mkdirSync8(cacheDir, { recursive: true });
17158
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
17588
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
17159
17589
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
17160
17590
  try {
17161
- unlinkSync5(cachePath);
17591
+ unlinkSync6(cachePath);
17162
17592
  } catch {
17163
17593
  }
17164
17594
  }
@@ -17551,6 +17981,7 @@ var init_capacity_monitor = __esm({
17551
17981
  // src/lib/tmux-routing.ts
17552
17982
  var tmux_routing_exports = {};
17553
17983
  __export(tmux_routing_exports, {
17984
+ acquireSpawnLock: () => acquireSpawnLock2,
17554
17985
  employeeSessionName: () => employeeSessionName,
17555
17986
  ensureEmployee: () => ensureEmployee,
17556
17987
  extractRootExe: () => extractRootExe,
@@ -17565,26 +17996,63 @@ __export(tmux_routing_exports, {
17565
17996
  notifyParentExe: () => notifyParentExe,
17566
17997
  parseParentExe: () => parseParentExe,
17567
17998
  registerParentExe: () => registerParentExe,
17999
+ releaseSpawnLock: () => releaseSpawnLock2,
17568
18000
  resolveExeSession: () => resolveExeSession,
17569
18001
  sendIntercom: () => sendIntercom,
17570
18002
  spawnEmployee: () => spawnEmployee,
17571
18003
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
17572
18004
  });
17573
- import { execFileSync as execFileSync3, execSync as execSync8 } from "child_process";
17574
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync9, existsSync as existsSync19, appendFileSync } from "fs";
17575
- import path28 from "path";
18005
+ import { execFileSync as execFileSync3, execSync as execSync9 } from "child_process";
18006
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, existsSync as existsSync20, appendFileSync } from "fs";
18007
+ import path29 from "path";
17576
18008
  import os9 from "os";
17577
18009
  import { fileURLToPath as fileURLToPath4 } from "url";
18010
+ import { unlinkSync as unlinkSync7 } from "fs";
18011
+ function spawnLockPath(sessionName) {
18012
+ return path29.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
18013
+ }
18014
+ function isProcessAlive(pid) {
18015
+ try {
18016
+ process.kill(pid, 0);
18017
+ return true;
18018
+ } catch {
18019
+ return false;
18020
+ }
18021
+ }
18022
+ function acquireSpawnLock2(sessionName) {
18023
+ if (!existsSync20(SPAWN_LOCK_DIR)) {
18024
+ mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
18025
+ }
18026
+ const lockFile = spawnLockPath(sessionName);
18027
+ if (existsSync20(lockFile)) {
18028
+ try {
18029
+ const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
18030
+ const age = Date.now() - lock.timestamp;
18031
+ if (isProcessAlive(lock.pid) && age < 6e4) {
18032
+ return false;
18033
+ }
18034
+ } catch {
18035
+ }
18036
+ }
18037
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
18038
+ return true;
18039
+ }
18040
+ function releaseSpawnLock2(sessionName) {
18041
+ try {
18042
+ unlinkSync7(spawnLockPath(sessionName));
18043
+ } catch {
18044
+ }
18045
+ }
17578
18046
  function resolveBehaviorsExporterScript() {
17579
18047
  try {
17580
18048
  const thisFile = fileURLToPath4(import.meta.url);
17581
- const scriptPath = path28.join(
17582
- path28.dirname(thisFile),
18049
+ const scriptPath = path29.join(
18050
+ path29.dirname(thisFile),
17583
18051
  "..",
17584
18052
  "bin",
17585
18053
  "exe-export-behaviors.js"
17586
18054
  );
17587
- return existsSync19(scriptPath) ? scriptPath : null;
18055
+ return existsSync20(scriptPath) ? scriptPath : null;
17588
18056
  } catch {
17589
18057
  return null;
17590
18058
  }
@@ -17625,12 +18093,12 @@ function extractRootExe(name) {
17625
18093
  return match?.[1] ?? null;
17626
18094
  }
17627
18095
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
17628
- if (!existsSync19(SESSION_CACHE)) {
18096
+ if (!existsSync20(SESSION_CACHE)) {
17629
18097
  mkdirSync9(SESSION_CACHE, { recursive: true });
17630
18098
  }
17631
18099
  const rootExe = extractRootExe(parentExe) ?? parentExe;
17632
- const filePath = path28.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
17633
- writeFileSync7(filePath, JSON.stringify({
18100
+ const filePath = path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
18101
+ writeFileSync8(filePath, JSON.stringify({
17634
18102
  parentExe: rootExe,
17635
18103
  dispatchedBy: dispatchedBy || rootExe,
17636
18104
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -17638,7 +18106,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
17638
18106
  }
17639
18107
  function getParentExe(sessionKey) {
17640
18108
  try {
17641
- const data = JSON.parse(readFileSync13(path28.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
18109
+ const data = JSON.parse(readFileSync15(path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
17642
18110
  return data.parentExe || null;
17643
18111
  } catch {
17644
18112
  return null;
@@ -17646,8 +18114,8 @@ function getParentExe(sessionKey) {
17646
18114
  }
17647
18115
  function getDispatchedBy(sessionKey) {
17648
18116
  try {
17649
- const data = JSON.parse(readFileSync13(
17650
- path28.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
18117
+ const data = JSON.parse(readFileSync15(
18118
+ path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
17651
18119
  "utf8"
17652
18120
  ));
17653
18121
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -17673,10 +18141,10 @@ function isEmployeeAlive(sessionName) {
17673
18141
  }
17674
18142
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
17675
18143
  const base = employeeSessionName(employeeName, exeSession);
17676
- if (!isAlive(base)) return 0;
18144
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
17677
18145
  for (let i = 2; i <= maxInstances; i++) {
17678
18146
  const candidate = employeeSessionName(employeeName, exeSession, i);
17679
- if (!isAlive(candidate)) return i;
18147
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
17680
18148
  }
17681
18149
  return null;
17682
18150
  }
@@ -17708,16 +18176,16 @@ async function verifyPaneAtCapacity(sessionName) {
17708
18176
  }
17709
18177
  function readDebounceState() {
17710
18178
  try {
17711
- if (!existsSync19(DEBOUNCE_FILE)) return {};
17712
- return JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
18179
+ if (!existsSync20(DEBOUNCE_FILE)) return {};
18180
+ return JSON.parse(readFileSync15(DEBOUNCE_FILE, "utf8"));
17713
18181
  } catch {
17714
18182
  return {};
17715
18183
  }
17716
18184
  }
17717
18185
  function writeDebounceState(state) {
17718
18186
  try {
17719
- if (!existsSync19(SESSION_CACHE)) mkdirSync9(SESSION_CACHE, { recursive: true });
17720
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
18187
+ if (!existsSync20(SESSION_CACHE)) mkdirSync9(SESSION_CACHE, { recursive: true });
18188
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
17721
18189
  } catch {
17722
18190
  }
17723
18191
  }
@@ -17890,26 +18358,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17890
18358
  const transport = getTransport();
17891
18359
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
17892
18360
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
17893
- const logDir = path28.join(os9.homedir(), ".exe-os", "session-logs");
17894
- const logFile = path28.join(logDir, `${instanceLabel}-${Date.now()}.log`);
17895
- if (!existsSync19(logDir)) {
18361
+ const logDir = path29.join(os9.homedir(), ".exe-os", "session-logs");
18362
+ const logFile = path29.join(logDir, `${instanceLabel}-${Date.now()}.log`);
18363
+ if (!existsSync20(logDir)) {
17896
18364
  mkdirSync9(logDir, { recursive: true });
17897
18365
  }
17898
18366
  transport.kill(sessionName);
17899
18367
  let cleanupSuffix = "";
17900
18368
  try {
17901
18369
  const thisFile = fileURLToPath4(import.meta.url);
17902
- const cleanupScript = path28.join(path28.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
17903
- if (existsSync19(cleanupScript)) {
18370
+ const cleanupScript = path29.join(path29.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
18371
+ if (existsSync20(cleanupScript)) {
17904
18372
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
17905
18373
  }
17906
18374
  } catch {
17907
18375
  }
17908
18376
  try {
17909
- const claudeJsonPath = path28.join(os9.homedir(), ".claude.json");
18377
+ const claudeJsonPath = path29.join(os9.homedir(), ".claude.json");
17910
18378
  let claudeJson = {};
17911
18379
  try {
17912
- claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
18380
+ claudeJson = JSON.parse(readFileSync15(claudeJsonPath, "utf8"));
17913
18381
  } catch {
17914
18382
  }
17915
18383
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -17917,17 +18385,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17917
18385
  const trustDir = opts?.cwd ?? projectDir;
17918
18386
  if (!projects[trustDir]) projects[trustDir] = {};
17919
18387
  projects[trustDir].hasTrustDialogAccepted = true;
17920
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
18388
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
17921
18389
  } catch {
17922
18390
  }
17923
18391
  try {
17924
- const settingsDir = path28.join(os9.homedir(), ".claude", "projects");
18392
+ const settingsDir = path29.join(os9.homedir(), ".claude", "projects");
17925
18393
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
17926
- const projSettingsDir = path28.join(settingsDir, normalizedKey);
17927
- const settingsPath = path28.join(projSettingsDir, "settings.json");
18394
+ const projSettingsDir = path29.join(settingsDir, normalizedKey);
18395
+ const settingsPath = path29.join(projSettingsDir, "settings.json");
17928
18396
  let settings = {};
17929
18397
  try {
17930
- settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
18398
+ settings = JSON.parse(readFileSync15(settingsPath, "utf8"));
17931
18399
  } catch {
17932
18400
  }
17933
18401
  const perms = settings.permissions ?? {};
@@ -17956,7 +18424,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17956
18424
  perms.allow = allow;
17957
18425
  settings.permissions = perms;
17958
18426
  mkdirSync9(projSettingsDir, { recursive: true });
17959
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
18427
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
17960
18428
  }
17961
18429
  } catch {
17962
18430
  }
@@ -17968,7 +18436,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17968
18436
  let behaviorsFlag = "";
17969
18437
  let legacyFallbackWarned = false;
17970
18438
  if (!useExeAgent && !useBinSymlink) {
17971
- const identityPath2 = path28.join(
18439
+ const identityPath2 = path29.join(
17972
18440
  os9.homedir(),
17973
18441
  ".exe-os",
17974
18442
  "identity",
@@ -17978,13 +18446,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17978
18446
  const hasAgentFlag = claudeSupportsAgentFlag();
17979
18447
  if (hasAgentFlag) {
17980
18448
  identityFlag = ` --agent ${employeeName}`;
17981
- } else if (existsSync19(identityPath2)) {
18449
+ } else if (existsSync20(identityPath2)) {
17982
18450
  identityFlag = ` --append-system-prompt-file ${identityPath2}`;
17983
18451
  legacyFallbackWarned = true;
17984
18452
  }
17985
18453
  const behaviorsFile = exportBehaviorsSync(
17986
18454
  employeeName,
17987
- path28.basename(spawnCwd),
18455
+ path29.basename(spawnCwd),
17988
18456
  sessionName
17989
18457
  );
17990
18458
  if (behaviorsFile) {
@@ -17999,16 +18467,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17999
18467
  }
18000
18468
  let sessionContextFlag = "";
18001
18469
  try {
18002
- const ctxDir = path28.join(os9.homedir(), ".exe-os", "session-cache");
18470
+ const ctxDir = path29.join(os9.homedir(), ".exe-os", "session-cache");
18003
18471
  mkdirSync9(ctxDir, { recursive: true });
18004
- const ctxFile = path28.join(ctxDir, `session-context-${sessionName}.md`);
18472
+ const ctxFile = path29.join(ctxDir, `session-context-${sessionName}.md`);
18005
18473
  const ctxContent = [
18006
18474
  `## Session Context`,
18007
18475
  `You are running in tmux session: ${sessionName}.`,
18008
18476
  `Your parent exe session is ${exeSession}.`,
18009
18477
  `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
18010
18478
  ].join("\n");
18011
- writeFileSync7(ctxFile, ctxContent);
18479
+ writeFileSync8(ctxFile, ctxContent);
18012
18480
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
18013
18481
  } catch {
18014
18482
  }
@@ -18040,13 +18508,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18040
18508
  command: spawnCommand
18041
18509
  });
18042
18510
  if (spawnResult.error) {
18511
+ releaseSpawnLock2(sessionName);
18043
18512
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
18044
18513
  }
18045
18514
  transport.pipeLog(sessionName, logFile);
18046
18515
  try {
18047
18516
  const mySession = getMySession();
18048
- const dispatchInfo = path28.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
18049
- writeFileSync7(dispatchInfo, JSON.stringify({
18517
+ const dispatchInfo = path29.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
18518
+ writeFileSync8(dispatchInfo, JSON.stringify({
18050
18519
  dispatchedBy: mySession,
18051
18520
  rootExe: exeSession,
18052
18521
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
@@ -18057,7 +18526,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18057
18526
  let booted = false;
18058
18527
  for (let i = 0; i < 30; i++) {
18059
18528
  try {
18060
- execSync8("sleep 0.5");
18529
+ execSync9("sleep 0.5");
18061
18530
  } catch {
18062
18531
  }
18063
18532
  try {
@@ -18077,6 +18546,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18077
18546
  }
18078
18547
  }
18079
18548
  if (!booted) {
18549
+ releaseSpawnLock2(sessionName);
18080
18550
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
18081
18551
  }
18082
18552
  if (!useExeAgent) {
@@ -18093,9 +18563,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18093
18563
  pid: 0,
18094
18564
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
18095
18565
  });
18566
+ releaseSpawnLock2(sessionName);
18096
18567
  return { sessionName };
18097
18568
  }
18098
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
18569
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
18099
18570
  var init_tmux_routing = __esm({
18100
18571
  "src/lib/tmux-routing.ts"() {
18101
18572
  "use strict";
@@ -18107,12 +18578,13 @@ var init_tmux_routing = __esm({
18107
18578
  init_provider_table();
18108
18579
  init_intercom_queue();
18109
18580
  init_plan_limits();
18110
- SESSION_CACHE = path28.join(os9.homedir(), ".exe-os", "session-cache");
18581
+ SPAWN_LOCK_DIR = path29.join(os9.homedir(), ".exe-os", "spawn-locks");
18582
+ SESSION_CACHE = path29.join(os9.homedir(), ".exe-os", "session-cache");
18111
18583
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
18112
18584
  VERIFY_PANE_LINES = 200;
18113
18585
  INTERCOM_DEBOUNCE_MS = 3e4;
18114
- INTERCOM_LOG2 = path28.join(os9.homedir(), ".exe-os", "intercom.log");
18115
- DEBOUNCE_FILE = path28.join(SESSION_CACHE, "intercom-debounce.json");
18586
+ INTERCOM_LOG2 = path29.join(os9.homedir(), ".exe-os", "intercom.log");
18587
+ DEBOUNCE_FILE = path29.join(SESSION_CACHE, "intercom-debounce.json");
18116
18588
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
18117
18589
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
18118
18590
  }
@@ -18587,12 +19059,12 @@ function SessionsView({
18587
19059
  return;
18588
19060
  }
18589
19061
  } else {
18590
- const { execSync: execSync12 } = await import("child_process");
19062
+ const { execSync: execSync13 } = await import("child_process");
18591
19063
  const dir = projectDir || process.cwd();
18592
- execSync12(`tmux new-session -d -s ${JSON.stringify(entry.sessionName)} -c ${JSON.stringify(dir)}`, { timeout: 5e3 });
18593
- execSync12(`tmux send-keys -t ${JSON.stringify(entry.sessionName)} "claude --dangerously-skip-permissions" Enter`, { timeout: 3e3 });
19064
+ execSync13(`tmux new-session -d -s ${JSON.stringify(entry.sessionName)} -c ${JSON.stringify(dir)}`, { timeout: 5e3 });
19065
+ execSync13(`tmux send-keys -t ${JSON.stringify(entry.sessionName)} "claude --dangerously-skip-permissions" Enter`, { timeout: 3e3 });
18594
19066
  await new Promise((r) => setTimeout(r, 3e3));
18595
- execSync12(`tmux send-keys -t ${JSON.stringify(entry.sessionName)} "/exe" Enter`, { timeout: 3e3 });
19067
+ execSync13(`tmux send-keys -t ${JSON.stringify(entry.sessionName)} "/exe" Enter`, { timeout: 3e3 });
18596
19068
  }
18597
19069
  const updated = { ...entry, status: "active", activity: "Starting...", attached: false };
18598
19070
  setViewingEmployee(updated);
@@ -18692,7 +19164,7 @@ function SessionsView({
18692
19164
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
18693
19165
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
18694
19166
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
18695
- const { execSync: execSync12 } = await import("child_process");
19167
+ const { execSync: execSync13 } = await import("child_process");
18696
19168
  if (!inTmux2()) {
18697
19169
  setTmuxAvailable(false);
18698
19170
  setProjects([]);
@@ -18701,7 +19173,7 @@ function SessionsView({
18701
19173
  setTmuxAvailable(true);
18702
19174
  const attachedMap = /* @__PURE__ */ new Map();
18703
19175
  try {
18704
- const out = execSync12("tmux list-sessions -F '#{session_name}:#{session_attached}' 2>/dev/null", {
19176
+ const out = execSync13("tmux list-sessions -F '#{session_name}:#{session_attached}' 2>/dev/null", {
18705
19177
  encoding: "utf8",
18706
19178
  timeout: 3e3
18707
19179
  });
@@ -19635,19 +20107,19 @@ function upsertConversation(conversations, platform, senderId, message) {
19635
20107
  async function loadGatewayConfig() {
19636
20108
  const state = { running: false, port: 3100, adapters: [], agents: [], gatewayUrl: "" };
19637
20109
  try {
19638
- const { execSync: execSync12 } = await import("child_process");
19639
- const ps = execSync12("pgrep -f exe-gateway 2>/dev/null", { encoding: "utf8", timeout: 3e3 });
20110
+ const { execSync: execSync13 } = await import("child_process");
20111
+ const ps = execSync13("pgrep -f exe-gateway 2>/dev/null", { encoding: "utf8", timeout: 3e3 });
19640
20112
  state.running = ps.trim().length > 0;
19641
20113
  } catch {
19642
20114
  state.running = false;
19643
20115
  }
19644
20116
  try {
19645
- const { existsSync: existsSync21, readFileSync: readFileSync16 } = await import("fs");
20117
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
19646
20118
  const { join } = await import("path");
19647
20119
  const home = process.env.HOME ?? "";
19648
20120
  const configPath = join(home, ".exe-os", "gateway.json");
19649
- if (existsSync21(configPath)) {
19650
- const raw = JSON.parse(readFileSync16(configPath, "utf8"));
20121
+ if (existsSync22(configPath)) {
20122
+ const raw = JSON.parse(readFileSync18(configPath, "utf8"));
19651
20123
  state.port = raw.port ?? 3100;
19652
20124
  state.gatewayUrl = raw.gatewayUrl ?? "";
19653
20125
  if (raw.adapters) {
@@ -20086,10 +20558,10 @@ var init_Gateway = __esm({
20086
20558
  });
20087
20559
 
20088
20560
  // src/tui/utils/agent-status.ts
20089
- import { execSync as execSync9 } from "child_process";
20561
+ import { execSync as execSync10 } from "child_process";
20090
20562
  function getAgentStatus(agentId) {
20091
20563
  try {
20092
- const sessions = execSync9("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
20564
+ const sessions = execSync10("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
20093
20565
  encoding: "utf8",
20094
20566
  timeout: 2e3
20095
20567
  }).trim().split("\n");
@@ -20100,7 +20572,7 @@ function getAgentStatus(agentId) {
20100
20572
  return /^\d?-/.test(suffix) || /^\d+$/.test(suffix);
20101
20573
  });
20102
20574
  if (!agentSession) return { label: "offline", color: "gray" };
20103
- const pane = execSync9(`tmux capture-pane -t "${agentSession}" -p 2>/dev/null | tail -3`, {
20575
+ const pane = execSync10(`tmux capture-pane -t "${agentSession}" -p 2>/dev/null | tail -3`, {
20104
20576
  encoding: "utf8",
20105
20577
  timeout: 2e3
20106
20578
  });
@@ -20215,12 +20687,12 @@ function TeamView({ onBack }) {
20215
20687
  setMembers(teamData);
20216
20688
  setDbError(false);
20217
20689
  try {
20218
- const { existsSync: existsSync21, readFileSync: readFileSync16 } = await import("fs");
20690
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
20219
20691
  const { join } = await import("path");
20220
20692
  const home = process.env.HOME ?? "";
20221
20693
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
20222
- if (existsSync21(gatewayConfig)) {
20223
- const raw = JSON.parse(readFileSync16(gatewayConfig, "utf8"));
20694
+ if (existsSync22(gatewayConfig)) {
20695
+ const raw = JSON.parse(readFileSync18(gatewayConfig, "utf8"));
20224
20696
  if (raw.agents && raw.agents.length > 0) {
20225
20697
  setExternals(raw.agents.map((a) => ({
20226
20698
  name: a.name,
@@ -20395,8 +20867,8 @@ __export(wiki_client_exports, {
20395
20867
  listDocuments: () => listDocuments,
20396
20868
  listWorkspaces: () => listWorkspaces
20397
20869
  });
20398
- async function wikiFetch(config, path31, method = "GET", body) {
20399
- const url = `${config.baseUrl}/api/v1${path31}`;
20870
+ async function wikiFetch(config, path32, method = "GET", body) {
20871
+ const url = `${config.baseUrl}/api/v1${path32}`;
20400
20872
  const headers = {
20401
20873
  Authorization: `Bearer ${config.apiKey}`,
20402
20874
  "Content-Type": "application/json"
@@ -20411,7 +20883,7 @@ async function wikiFetch(config, path31, method = "GET", body) {
20411
20883
  signal: controller.signal
20412
20884
  });
20413
20885
  if (!response.ok) {
20414
- throw new Error(`Wiki API ${method} ${path31}: ${response.status} ${response.statusText}`);
20886
+ throw new Error(`Wiki API ${method} ${path32}: ${response.status} ${response.statusText}`);
20415
20887
  }
20416
20888
  return response.json();
20417
20889
  } finally {
@@ -20866,18 +21338,18 @@ function SettingsView({ onBack }) {
20866
21338
  { name: "Chutes", configured: !!process.env.CHUTES_API_KEY, detail: process.env.CHUTES_API_KEY ? "CHUTES_API_KEY set" : "not configured" }
20867
21339
  ];
20868
21340
  try {
20869
- const { execSync: execSync12 } = await import("child_process");
20870
- execSync12("curl -s --max-time 1 http://localhost:11434/api/tags", { timeout: 2e3 });
21341
+ const { execSync: execSync13 } = await import("child_process");
21342
+ execSync13("curl -s --max-time 1 http://localhost:11434/api/tags", { timeout: 2e3 });
20871
21343
  providerList.push({ name: "Ollama", configured: true, detail: "localhost:11434" });
20872
21344
  } catch {
20873
21345
  providerList.push({ name: "Ollama", configured: false, detail: "not running" });
20874
21346
  }
20875
21347
  setProviders(providerList);
20876
21348
  try {
20877
- const { existsSync: existsSync21 } = await import("fs");
21349
+ const { existsSync: existsSync22 } = await import("fs");
20878
21350
  const { join } = await import("path");
20879
21351
  const home = process.env.HOME ?? "";
20880
- const hasKey = existsSync21(join(home, ".exe-os", "master.key"));
21352
+ const hasKey = existsSync22(join(home, ".exe-os", "master.key"));
20881
21353
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
20882
21354
  const cfg = await loadConfig2();
20883
21355
  setMemory({
@@ -20901,14 +21373,14 @@ function SettingsView({ onBack }) {
20901
21373
  try {
20902
21374
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
20903
21375
  const roster = await loadEmployees2();
20904
- const { existsSync: existsSync21, readFileSync: readFileSync16 } = await import("fs");
21376
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
20905
21377
  const { join } = await import("path");
20906
21378
  const home = process.env.HOME ?? "";
20907
21379
  const configPath = join(home, ".exe-os", "config.json");
20908
21380
  let empConfig = {};
20909
- if (existsSync21(configPath)) {
21381
+ if (existsSync22(configPath)) {
20910
21382
  try {
20911
- const raw = JSON.parse(readFileSync16(configPath, "utf8"));
21383
+ const raw = JSON.parse(readFileSync18(configPath, "utf8"));
20912
21384
  if (raw.employees && typeof raw.employees === "object") {
20913
21385
  empConfig = raw.employees;
20914
21386
  }
@@ -20923,15 +21395,15 @@ function SettingsView({ onBack }) {
20923
21395
  } catch {
20924
21396
  }
20925
21397
  try {
20926
- const { existsSync: existsSync21, readFileSync: readFileSync16 } = await import("fs");
21398
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
20927
21399
  const { join } = await import("path");
20928
21400
  const home = process.env.HOME ?? "";
20929
21401
  const ccSettingsPath = join(home, ".claude", "settings.json");
20930
- const installed = existsSync21(ccSettingsPath);
21402
+ const installed = existsSync22(ccSettingsPath);
20931
21403
  let hooksWired = false;
20932
21404
  if (installed) {
20933
21405
  try {
20934
- const settings = JSON.parse(readFileSync16(ccSettingsPath, "utf8"));
21406
+ const settings = JSON.parse(readFileSync18(ccSettingsPath, "utf8"));
20935
21407
  const hooks = settings.hooks;
20936
21408
  if (hooks) {
20937
21409
  hooksWired = Object.values(hooks).flat().some((h) => h.command?.includes("exe-os") || h.command?.includes("exe-mem"));
@@ -20943,13 +21415,13 @@ function SettingsView({ onBack }) {
20943
21415
  } catch {
20944
21416
  }
20945
21417
  try {
20946
- const { existsSync: existsSync21, readFileSync: readFileSync16 } = await import("fs");
21418
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
20947
21419
  const { join } = await import("path");
20948
21420
  const home = process.env.HOME ?? "";
20949
21421
  const licensePath = join(home, ".exe-os", "license.json");
20950
- if (existsSync21(licensePath)) {
21422
+ if (existsSync22(licensePath)) {
20951
21423
  try {
20952
- const lic = JSON.parse(readFileSync16(licensePath, "utf8"));
21424
+ const lic = JSON.parse(readFileSync18(licensePath, "utf8"));
20953
21425
  setLicense({ valid: lic.valid !== false, detail: lic.plan ?? "licensed" });
20954
21426
  } catch {
20955
21427
  setLicense({ valid: false, detail: "invalid license file" });
@@ -20960,11 +21432,11 @@ function SettingsView({ onBack }) {
20960
21432
  } catch {
20961
21433
  }
20962
21434
  try {
20963
- const { existsSync: existsSync21 } = await import("fs");
21435
+ const { existsSync: existsSync22 } = await import("fs");
20964
21436
  const { join } = await import("path");
20965
21437
  const home = process.env.HOME ?? "";
20966
21438
  const cloudPath = join(home, ".exe-os", "cloud.json");
20967
- if (existsSync21(cloudPath)) {
21439
+ if (existsSync22(cloudPath)) {
20968
21440
  setCloudSync({ configured: true, detail: "configured" });
20969
21441
  } else {
20970
21442
  setCloudSync({ configured: false, detail: "not configured" });
@@ -21260,17 +21732,17 @@ var init_App2 = __esm({
21260
21732
  });
21261
21733
 
21262
21734
  // src/lib/update-check.ts
21263
- import { execSync as execSync10 } from "child_process";
21264
- import { readFileSync as readFileSync14 } from "fs";
21265
- import path29 from "path";
21735
+ import { execSync as execSync11 } from "child_process";
21736
+ import { readFileSync as readFileSync16 } from "fs";
21737
+ import path30 from "path";
21266
21738
  function getLocalVersion(packageRoot) {
21267
- const pkgPath = path29.join(packageRoot, "package.json");
21268
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
21739
+ const pkgPath = path30.join(packageRoot, "package.json");
21740
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
21269
21741
  return pkg.version;
21270
21742
  }
21271
21743
  function getRemoteVersion() {
21272
21744
  try {
21273
- const output = execSync10("npm view @askexenow/exe-os version", {
21745
+ const output = execSync11("npm view @askexenow/exe-os version", {
21274
21746
  encoding: "utf-8",
21275
21747
  timeout: 15e3,
21276
21748
  stdio: ["pipe", "pipe", "pipe"]
@@ -21308,7 +21780,7 @@ __export(update_exports, {
21308
21780
  getLocalVersion: () => getLocalVersion,
21309
21781
  getRemoteVersion: () => getRemoteVersion
21310
21782
  });
21311
- import { execSync as execSync11 } from "child_process";
21783
+ import { execSync as execSync12 } from "child_process";
21312
21784
  import { createInterface as createInterface3 } from "readline";
21313
21785
  var init_update = __esm({
21314
21786
  async "src/bin/update.ts"() {
@@ -21339,7 +21811,7 @@ var init_update = __esm({
21339
21811
  if (answer.toLowerCase() === "y") {
21340
21812
  console.log("Updating...");
21341
21813
  try {
21342
- execSync11("npm install -g @askexenow/exe-os@latest", { stdio: "inherit" });
21814
+ execSync12("npm install -g @askexenow/exe-os@latest", { stdio: "inherit" });
21343
21815
  console.log("Update complete!");
21344
21816
  } catch {
21345
21817
  console.error(
@@ -21355,8 +21827,8 @@ var init_update = __esm({
21355
21827
  });
21356
21828
 
21357
21829
  // src/bin/cli.ts
21358
- import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
21359
- import path30 from "path";
21830
+ import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync9, readdirSync as readdirSync4, rmSync } from "fs";
21831
+ import path31 from "path";
21360
21832
  import os10 from "os";
21361
21833
  var args = process.argv.slice(2);
21362
21834
  if (args.includes("--global")) {
@@ -21387,7 +21859,7 @@ if (args.includes("--global")) {
21387
21859
  await runClaudeCheck();
21388
21860
  break;
21389
21861
  case "uninstall":
21390
- await runClaudeUninstall();
21862
+ await runClaudeUninstall(args.slice(2));
21391
21863
  break;
21392
21864
  default:
21393
21865
  await runClaudeInstall();
@@ -21403,6 +21875,10 @@ if (args.includes("--global")) {
21403
21875
  console.error("Backfill failed:", err instanceof Error ? err.message : String(err));
21404
21876
  process.exit(1);
21405
21877
  }
21878
+ } else if (args[0] === "rename") {
21879
+ process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
21880
+ const { main: runRename } = await Promise.resolve().then(() => (init_exe_rename(), exe_rename_exports));
21881
+ await runRename();
21406
21882
  } else if (args[0] === "--activate" || args[0] === "activate") {
21407
21883
  await runActivate(args[1]);
21408
21884
  } else if (args[0] === "setup" || args[0] === "-setup" || args[0] === "--setup") {
@@ -21426,12 +21902,12 @@ async function runClaudeInstall() {
21426
21902
  }
21427
21903
  }
21428
21904
  async function runClaudeCheck() {
21429
- const claudeDir = path30.join(os10.homedir(), ".claude");
21430
- const settingsPath = path30.join(claudeDir, "settings.json");
21431
- const claudeJsonPath = path30.join(os10.homedir(), ".claude.json");
21905
+ const claudeDir = path31.join(os10.homedir(), ".claude");
21906
+ const settingsPath = path31.join(claudeDir, "settings.json");
21907
+ const claudeJsonPath = path31.join(os10.homedir(), ".claude.json");
21432
21908
  let ok = true;
21433
- if (existsSync20(settingsPath)) {
21434
- const settings = JSON.parse(readFileSync15(settingsPath, "utf8"));
21909
+ if (existsSync21(settingsPath)) {
21910
+ const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
21435
21911
  const hasHooks = settings.hooks && Object.keys(settings.hooks).some((k) => {
21436
21912
  const groups = settings.hooks[k];
21437
21913
  return Array.isArray(groups) && groups.some((g) => {
@@ -21452,8 +21928,8 @@ async function runClaudeCheck() {
21452
21928
  console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
21453
21929
  ok = false;
21454
21930
  }
21455
- if (existsSync20(claudeJsonPath)) {
21456
- const claudeJson = JSON.parse(readFileSync15(claudeJsonPath, "utf8"));
21931
+ if (existsSync21(claudeJsonPath)) {
21932
+ const claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
21457
21933
  const hasMcp = claudeJson.mcpServers && (claudeJson.mcpServers["exe-mem"] || claudeJson.mcpServers["exe-os"]);
21458
21934
  if (hasMcp) {
21459
21935
  console.log("\x1B[32m\u2713\x1B[0m MCP server configured in claude.json");
@@ -21467,11 +21943,11 @@ async function runClaudeCheck() {
21467
21943
  console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
21468
21944
  ok = false;
21469
21945
  }
21470
- const commandsDir = path30.join(claudeDir, "commands");
21471
- if (existsSync20(commandsDir)) {
21472
- console.log("\x1B[32m\u2713\x1B[0m Slash commands directory exists");
21946
+ const skillsDir = path31.join(claudeDir, "skills");
21947
+ if (existsSync21(skillsDir)) {
21948
+ console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
21473
21949
  } else {
21474
- console.log("\x1B[31m\u2717\x1B[0m Slash commands directory missing");
21950
+ console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
21475
21951
  ok = false;
21476
21952
  }
21477
21953
  if (!ok) {
@@ -21481,13 +21957,18 @@ async function runClaudeCheck() {
21481
21957
  console.log("\nAll checks passed.");
21482
21958
  }
21483
21959
  }
21484
- async function runClaudeUninstall() {
21485
- const claudeDir = path30.join(os10.homedir(), ".claude");
21486
- const settingsPath = path30.join(claudeDir, "settings.json");
21487
- const claudeJsonPath = path30.join(os10.homedir(), ".claude.json");
21960
+ async function runClaudeUninstall(flags = []) {
21961
+ const dryRun = flags.includes("--dry-run");
21962
+ const purge = flags.includes("--purge");
21963
+ const homeDir = os10.homedir();
21964
+ const claudeDir = path31.join(homeDir, ".claude");
21965
+ const settingsPath = path31.join(claudeDir, "settings.json");
21966
+ const claudeJsonPath = path31.join(homeDir, ".claude.json");
21967
+ const exeOsDir = path31.join(homeDir, ".exe-os");
21488
21968
  let removed = 0;
21489
- if (existsSync20(settingsPath)) {
21490
- const settings = JSON.parse(readFileSync15(settingsPath, "utf8"));
21969
+ const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
21970
+ if (existsSync21(settingsPath)) {
21971
+ const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
21491
21972
  if (settings.hooks) {
21492
21973
  for (const key of Object.keys(settings.hooks)) {
21493
21974
  const groups = settings.hooks[key];
@@ -21507,37 +21988,176 @@ async function runClaudeUninstall() {
21507
21988
  delete settings.hooks[key];
21508
21989
  }
21509
21990
  }
21510
- const { writeFileSync: writeFileSync8 } = await import("fs");
21511
- writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
21512
- console.log("Removed exe-os hooks from settings.json");
21991
+ let permCount = 0;
21992
+ if (Array.isArray(settings.permissions?.allow)) {
21993
+ const before = settings.permissions.allow.length;
21994
+ settings.permissions.allow = settings.permissions.allow.filter(
21995
+ (p) => !p.startsWith("mcp__exe-mem__") && !p.startsWith("mcp__exe-os__")
21996
+ );
21997
+ permCount = before - settings.permissions.allow.length;
21998
+ }
21999
+ if (!dryRun) {
22000
+ writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
22001
+ }
22002
+ log("\u2713 Removed exe-os hooks from settings.json");
22003
+ if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
21513
22004
  removed++;
21514
22005
  }
21515
22006
  }
21516
- if (existsSync20(claudeJsonPath)) {
21517
- const claudeJson = JSON.parse(readFileSync15(claudeJsonPath, "utf8"));
22007
+ if (existsSync21(claudeJsonPath)) {
22008
+ const claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
21518
22009
  if (claudeJson.mcpServers) {
21519
22010
  let removedMcp = false;
21520
22011
  for (const key of ["exe-mem", "exe-os"]) {
21521
22012
  if (claudeJson.mcpServers[key]) {
21522
- delete claudeJson.mcpServers[key];
22013
+ if (!dryRun) delete claudeJson.mcpServers[key];
21523
22014
  removedMcp = true;
21524
22015
  }
21525
22016
  }
21526
22017
  if (removedMcp) {
21527
- const { writeFileSync: writeFileSync8 } = await import("fs");
21528
- writeFileSync8(
21529
- claudeJsonPath,
21530
- JSON.stringify(claudeJson, null, 2) + "\n"
21531
- );
21532
- console.log("Removed exe-os MCP server from claude.json");
22018
+ if (!dryRun) {
22019
+ writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
22020
+ }
22021
+ log("\u2713 Removed exe-os MCP server from claude.json");
21533
22022
  removed++;
21534
22023
  }
21535
22024
  }
21536
22025
  }
22026
+ const skillsDir = path31.join(claudeDir, "skills");
22027
+ if (existsSync21(skillsDir)) {
22028
+ let skillCount = 0;
22029
+ try {
22030
+ const entries = readdirSync4(skillsDir);
22031
+ for (const entry of entries) {
22032
+ if (entry.startsWith("exe")) {
22033
+ const fullPath = path31.join(skillsDir, entry);
22034
+ if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
22035
+ skillCount++;
22036
+ }
22037
+ }
22038
+ } catch {
22039
+ }
22040
+ if (skillCount > 0) {
22041
+ log(`\u2713 Removed ${skillCount} skill directories`);
22042
+ removed++;
22043
+ }
22044
+ }
22045
+ const claudeMdPath = path31.join(claudeDir, "CLAUDE.md");
22046
+ if (existsSync21(claudeMdPath)) {
22047
+ const content = readFileSync17(claudeMdPath, "utf8");
22048
+ const startMarker = "<!-- exe-os:orchestration-start -->";
22049
+ const endMarker = "<!-- exe-os:orchestration-end -->";
22050
+ const startIdx = content.indexOf(startMarker);
22051
+ const endIdx = content.indexOf(endMarker);
22052
+ if (startIdx !== -1 && endIdx !== -1) {
22053
+ const cleaned = (content.slice(0, startIdx) + content.slice(endIdx + endMarker.length)).replace(/\n{3,}/g, "\n\n").trim() + "\n";
22054
+ if (!dryRun) writeFileSync9(claudeMdPath, cleaned);
22055
+ log("\u2713 Removed orchestration block from CLAUDE.md");
22056
+ removed++;
22057
+ }
22058
+ }
22059
+ const agentsDir = path31.join(claudeDir, "agents");
22060
+ if (existsSync21(agentsDir)) {
22061
+ let agentCount = 0;
22062
+ try {
22063
+ const entries = readdirSync4(agentsDir).filter((f) => f.endsWith(".md"));
22064
+ let knownNames = /* @__PURE__ */ new Set();
22065
+ const rosterPath = path31.join(exeOsDir, "exe-employees.json");
22066
+ if (existsSync21(rosterPath)) {
22067
+ try {
22068
+ const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
22069
+ knownNames = new Set(roster.map((e) => e.name));
22070
+ } catch {
22071
+ }
22072
+ }
22073
+ for (const entry of entries) {
22074
+ const name = entry.replace(/\.md$/, "");
22075
+ if (knownNames.has(name)) {
22076
+ if (!dryRun) rmSync(path31.join(agentsDir, entry), { force: true });
22077
+ agentCount++;
22078
+ }
22079
+ }
22080
+ } catch {
22081
+ }
22082
+ if (agentCount > 0) {
22083
+ log(`\u2713 Removed ${agentCount} agent identity files`);
22084
+ removed++;
22085
+ }
22086
+ }
22087
+ const projectsDir = path31.join(claudeDir, "projects");
22088
+ if (existsSync21(projectsDir)) {
22089
+ let projectCount = 0;
22090
+ try {
22091
+ const projects = readdirSync4(projectsDir);
22092
+ for (const proj of projects) {
22093
+ const projSettings = path31.join(projectsDir, proj, "settings.json");
22094
+ if (!existsSync21(projSettings)) continue;
22095
+ try {
22096
+ const pSettings = JSON.parse(readFileSync17(projSettings, "utf8"));
22097
+ let changed = false;
22098
+ if (Array.isArray(pSettings.permissions?.allow)) {
22099
+ const before = pSettings.permissions.allow.length;
22100
+ pSettings.permissions.allow = pSettings.permissions.allow.filter(
22101
+ (p) => !p.startsWith("mcp__exe-mem__") && !p.startsWith("mcp__exe-os__")
22102
+ );
22103
+ if (pSettings.permissions.allow.length < before) changed = true;
22104
+ }
22105
+ if (changed && !dryRun) {
22106
+ writeFileSync9(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
22107
+ }
22108
+ if (changed) projectCount++;
22109
+ } catch {
22110
+ }
22111
+ }
22112
+ } catch {
22113
+ }
22114
+ if (projectCount > 0) {
22115
+ log(`\u2713 Cleaned exe-os entries from ${projectCount} project settings`);
22116
+ removed++;
22117
+ }
22118
+ }
22119
+ try {
22120
+ const { execSync: execSync13 } = await import("child_process");
22121
+ const exeBinPath = execSync13("which exe", { encoding: "utf-8" }).trim();
22122
+ const binDir = path31.dirname(exeBinPath);
22123
+ let symlinkCount = 0;
22124
+ const rosterPath = path31.join(exeOsDir, "exe-employees.json");
22125
+ if (existsSync21(rosterPath)) {
22126
+ const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
22127
+ for (const emp of roster) {
22128
+ if (emp.name === "exe") continue;
22129
+ for (const suffix of ["", "-opencode"]) {
22130
+ const linkPath = path31.join(binDir, `${emp.name}${suffix}`);
22131
+ if (existsSync21(linkPath)) {
22132
+ if (!dryRun) rmSync(linkPath, { force: true });
22133
+ symlinkCount++;
22134
+ }
22135
+ }
22136
+ }
22137
+ }
22138
+ if (symlinkCount > 0) {
22139
+ log(`\u2713 Removed ${symlinkCount} employee symlinks`);
22140
+ removed++;
22141
+ }
22142
+ } catch {
22143
+ }
22144
+ if (purge && existsSync21(exeOsDir)) {
22145
+ if (!dryRun) {
22146
+ process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
22147
+ process.stdout.write(" Removing ~/.exe-os...\n");
22148
+ rmSync(exeOsDir, { recursive: true, force: true });
22149
+ }
22150
+ log("\u2713 Purged ~/.exe-os data directory");
22151
+ removed++;
22152
+ }
21537
22153
  if (removed === 0) {
21538
22154
  console.log("Nothing to remove \u2014 exe-os was not installed.");
21539
22155
  } else {
21540
- console.log("Done. Run \x1B[36mexe-os claude install\x1B[0m to reinstall.");
22156
+ if (dryRun) {
22157
+ console.log("\nDry run complete. Re-run without --dry-run to apply.");
22158
+ } else {
22159
+ console.log("\nDone. Run \x1B[36mexe-os claude install\x1B[0m to reinstall.");
22160
+ }
21541
22161
  }
21542
22162
  }
21543
22163
  async function checkForUpdateOnBoot() {
@@ -21546,7 +22166,7 @@ async function checkForUpdateOnBoot() {
21546
22166
  const config = await loadConfig2();
21547
22167
  if (!config.autoUpdate.checkOnBoot) return;
21548
22168
  const { checkForUpdate: checkForUpdate2 } = await init_update().then(() => update_exports);
21549
- const packageRoot = path30.resolve(
22169
+ const packageRoot = path31.resolve(
21550
22170
  new URL("../..", import.meta.url).pathname
21551
22171
  );
21552
22172
  const result = checkForUpdate2(packageRoot);
@@ -21602,7 +22222,7 @@ async function runActivate(key) {
21602
22222
  const idTemplate = getIdentityTemplate(identityKey);
21603
22223
  if (idTemplate) {
21604
22224
  const idPath = identityPath2(name);
21605
- const dir = path30.dirname(idPath);
22225
+ const dir = path31.dirname(idPath);
21606
22226
  if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
21607
22227
  fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
21608
22228
  }