@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
@@ -6,12 +6,12 @@ import crypto from "crypto";
6
6
 
7
7
  // src/lib/database.ts
8
8
  import { createClient } from "@libsql/client";
9
- var _client = null;
9
+ var _resilientClient = null;
10
10
  function getClient() {
11
- if (!_client) {
11
+ if (!_resilientClient) {
12
12
  throw new Error("Database client not initialized. Call initDatabase() first.");
13
13
  }
14
- return _client;
14
+ return _resilientClient;
15
15
  }
16
16
 
17
17
  // src/lib/reminders.ts
@@ -6,12 +6,12 @@ import crypto from "crypto";
6
6
 
7
7
  // src/lib/database.ts
8
8
  import { createClient } from "@libsql/client";
9
- var _client = null;
9
+ var _resilientClient = null;
10
10
  function getClient() {
11
- if (!_client) {
11
+ if (!_resilientClient) {
12
12
  throw new Error("Database client not initialized. Call initDatabase() first.");
13
13
  }
14
- return _client;
14
+ return _resilientClient;
15
15
  }
16
16
 
17
17
  // src/lib/reminders.ts
@@ -19,19 +19,27 @@ var __copyProps = (to, from, except, desc) => {
19
19
  };
20
20
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
21
 
22
+ // src/lib/db-retry.ts
23
+ var init_db_retry = __esm({
24
+ "src/lib/db-retry.ts"() {
25
+ "use strict";
26
+ }
27
+ });
28
+
22
29
  // src/lib/database.ts
23
30
  import { createClient } from "@libsql/client";
24
31
  function getClient() {
25
- if (!_client) {
32
+ if (!_resilientClient) {
26
33
  throw new Error("Database client not initialized. Call initDatabase() first.");
27
34
  }
28
- return _client;
35
+ return _resilientClient;
29
36
  }
30
- var _client;
37
+ var _resilientClient;
31
38
  var init_database = __esm({
32
39
  "src/lib/database.ts"() {
33
40
  "use strict";
34
- _client = null;
41
+ init_db_retry();
42
+ _resilientClient = null;
35
43
  }
36
44
  });
37
45
 
@@ -508,21 +516,139 @@ var init_tasks_crud = __esm({
508
516
  });
509
517
 
510
518
  // src/lib/employees.ts
519
+ var employees_exports = {};
520
+ __export(employees_exports, {
521
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
522
+ addEmployee: () => addEmployee,
523
+ getEmployee: () => getEmployee,
524
+ getEmployeeByRole: () => getEmployeeByRole,
525
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
526
+ hasRole: () => hasRole,
527
+ isMultiInstance: () => isMultiInstance,
528
+ loadEmployees: () => loadEmployees,
529
+ loadEmployeesSync: () => loadEmployeesSync,
530
+ registerBinSymlinks: () => registerBinSymlinks,
531
+ saveEmployees: () => saveEmployees,
532
+ validateEmployeeName: () => validateEmployeeName
533
+ });
511
534
  import { readFile as readFile2, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
512
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
535
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
513
536
  import { execSync as execSync2 } from "child_process";
514
537
  import path4 from "path";
515
- var EMPLOYEES_PATH;
538
+ function validateEmployeeName(name) {
539
+ if (!name) {
540
+ return { valid: false, error: "Name is required" };
541
+ }
542
+ if (name.length > 32) {
543
+ return { valid: false, error: "Name must be 32 characters or fewer" };
544
+ }
545
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
546
+ return {
547
+ valid: false,
548
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
549
+ };
550
+ }
551
+ return { valid: true };
552
+ }
553
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
554
+ if (!existsSync4(employeesPath)) {
555
+ return [];
556
+ }
557
+ const raw = await readFile2(employeesPath, "utf-8");
558
+ try {
559
+ return JSON.parse(raw);
560
+ } catch {
561
+ return [];
562
+ }
563
+ }
564
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
565
+ await mkdir3(path4.dirname(employeesPath), { recursive: true });
566
+ await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
567
+ }
568
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
569
+ if (!existsSync4(employeesPath)) return [];
570
+ try {
571
+ return JSON.parse(readFileSync4(employeesPath, "utf-8"));
572
+ } catch {
573
+ return [];
574
+ }
575
+ }
576
+ function getEmployee(employees, name) {
577
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
578
+ }
579
+ function getEmployeeByRole(employees, role) {
580
+ const lower = role.toLowerCase();
581
+ return employees.find((e) => e.role.toLowerCase() === lower);
582
+ }
583
+ function getEmployeeNamesByRole(employees, role) {
584
+ const lower = role.toLowerCase();
585
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
586
+ }
587
+ function hasRole(agentName, role) {
588
+ const employees = loadEmployeesSync();
589
+ const emp = getEmployee(employees, agentName);
590
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
591
+ }
592
+ function isMultiInstance(agentName, employees) {
593
+ const roster = employees ?? loadEmployeesSync();
594
+ const emp = getEmployee(roster, agentName);
595
+ if (!emp) return false;
596
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
597
+ }
598
+ function addEmployee(employees, employee) {
599
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
600
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
601
+ throw new Error(`Employee '${normalized.name}' already exists`);
602
+ }
603
+ return [...employees, normalized];
604
+ }
605
+ function registerBinSymlinks(name) {
606
+ const created = [];
607
+ const skipped = [];
608
+ const errors = [];
609
+ let exeBinPath;
610
+ try {
611
+ exeBinPath = execSync2("which exe", { encoding: "utf-8" }).trim();
612
+ } catch {
613
+ errors.push("Could not find 'exe' in PATH");
614
+ return { created, skipped, errors };
615
+ }
616
+ const binDir = path4.dirname(exeBinPath);
617
+ let target;
618
+ try {
619
+ target = readlinkSync(exeBinPath);
620
+ } catch {
621
+ errors.push("Could not read 'exe' symlink");
622
+ return { created, skipped, errors };
623
+ }
624
+ for (const suffix of ["", "-opencode"]) {
625
+ const linkName = `${name}${suffix}`;
626
+ const linkPath = path4.join(binDir, linkName);
627
+ if (existsSync4(linkPath)) {
628
+ skipped.push(linkName);
629
+ continue;
630
+ }
631
+ try {
632
+ symlinkSync(target, linkPath);
633
+ created.push(linkName);
634
+ } catch (err) {
635
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
636
+ }
637
+ }
638
+ return { created, skipped, errors };
639
+ }
640
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
516
641
  var init_employees = __esm({
517
642
  "src/lib/employees.ts"() {
518
643
  "use strict";
519
644
  init_config();
520
645
  EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
646
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
521
647
  }
522
648
  });
523
649
 
524
650
  // src/lib/session-registry.ts
525
- import { readFileSync as readFileSync4, writeFileSync, mkdirSync, existsSync as existsSync5 } from "fs";
651
+ import { readFileSync as readFileSync5, writeFileSync, mkdirSync, existsSync as existsSync5 } from "fs";
526
652
  import path5 from "path";
527
653
  import os3 from "os";
528
654
  function registerSession(entry) {
@@ -541,7 +667,7 @@ function registerSession(entry) {
541
667
  }
542
668
  function listSessions() {
543
669
  try {
544
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
670
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
545
671
  return JSON.parse(raw);
546
672
  } catch {
547
673
  return [];
@@ -771,7 +897,7 @@ var init_provider_table = __esm({
771
897
  });
772
898
 
773
899
  // src/lib/intercom-queue.ts
774
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
900
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
775
901
  import path6 from "path";
776
902
  import os4 from "os";
777
903
  function ensureDir() {
@@ -781,7 +907,7 @@ function ensureDir() {
781
907
  function readQueue() {
782
908
  try {
783
909
  if (!existsSync6(QUEUE_PATH)) return [];
784
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
910
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
785
911
  } catch {
786
912
  return [];
787
913
  }
@@ -820,7 +946,7 @@ var init_intercom_queue = __esm({
820
946
  });
821
947
 
822
948
  // src/lib/license.ts
823
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
949
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
824
950
  import { randomUUID } from "crypto";
825
951
  import path7 from "path";
826
952
  import { jwtVerify, importSPKI } from "jose";
@@ -843,12 +969,12 @@ var init_license = __esm({
843
969
  });
844
970
 
845
971
  // src/lib/plan-limits.ts
846
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
972
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
847
973
  import path8 from "path";
848
974
  function getLicenseSync() {
849
975
  try {
850
976
  if (!existsSync8(CACHE_PATH2)) return freeLicense();
851
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
977
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
852
978
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
853
979
  const parts = raw.token.split(".");
854
980
  if (parts.length !== 3) return freeLicense();
@@ -887,7 +1013,7 @@ function assertEmployeeLimitSync(rosterPath) {
887
1013
  let count = 0;
888
1014
  try {
889
1015
  if (existsSync8(filePath)) {
890
- const raw = readFileSync7(filePath, "utf8");
1016
+ const raw = readFileSync8(filePath, "utf8");
891
1017
  const employees = JSON.parse(raw);
892
1018
  count = Array.isArray(employees) ? employees.length : 0;
893
1019
  }
@@ -922,10 +1048,46 @@ var init_plan_limits = __esm({
922
1048
 
923
1049
  // src/lib/tmux-routing.ts
924
1050
  import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
925
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9, appendFileSync } from "fs";
1051
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9, appendFileSync } from "fs";
926
1052
  import path9 from "path";
927
1053
  import os5 from "os";
928
1054
  import { fileURLToPath } from "url";
1055
+ import { unlinkSync as unlinkSync2 } from "fs";
1056
+ function spawnLockPath(sessionName) {
1057
+ return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
1058
+ }
1059
+ function isProcessAlive(pid) {
1060
+ try {
1061
+ process.kill(pid, 0);
1062
+ return true;
1063
+ } catch {
1064
+ return false;
1065
+ }
1066
+ }
1067
+ function acquireSpawnLock(sessionName) {
1068
+ if (!existsSync9(SPAWN_LOCK_DIR)) {
1069
+ mkdirSync4(SPAWN_LOCK_DIR, { recursive: true });
1070
+ }
1071
+ const lockFile = spawnLockPath(sessionName);
1072
+ if (existsSync9(lockFile)) {
1073
+ try {
1074
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
1075
+ const age = Date.now() - lock.timestamp;
1076
+ if (isProcessAlive(lock.pid) && age < 6e4) {
1077
+ return false;
1078
+ }
1079
+ } catch {
1080
+ }
1081
+ }
1082
+ writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
1083
+ return true;
1084
+ }
1085
+ function releaseSpawnLock(sessionName) {
1086
+ try {
1087
+ unlinkSync2(spawnLockPath(sessionName));
1088
+ } catch {
1089
+ }
1090
+ }
929
1091
  function resolveBehaviorsExporterScript() {
930
1092
  try {
931
1093
  const thisFile = fileURLToPath(import.meta.url);
@@ -971,7 +1133,7 @@ function extractRootExe(name) {
971
1133
  }
972
1134
  function getParentExe(sessionKey) {
973
1135
  try {
974
- const data = JSON.parse(readFileSync8(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
1136
+ const data = JSON.parse(readFileSync9(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
975
1137
  return data.parentExe || null;
976
1138
  } catch {
977
1139
  return null;
@@ -995,17 +1157,17 @@ function isEmployeeAlive(sessionName) {
995
1157
  }
996
1158
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
997
1159
  const base = employeeSessionName(employeeName, exeSession);
998
- if (!isAlive(base)) return 0;
1160
+ if (!isAlive(base) && acquireSpawnLock(base)) return 0;
999
1161
  for (let i = 2; i <= maxInstances; i++) {
1000
1162
  const candidate = employeeSessionName(employeeName, exeSession, i);
1001
- if (!isAlive(candidate)) return i;
1163
+ if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
1002
1164
  }
1003
1165
  return null;
1004
1166
  }
1005
1167
  function readDebounceState() {
1006
1168
  try {
1007
1169
  if (!existsSync9(DEBOUNCE_FILE)) return {};
1008
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
1170
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
1009
1171
  } catch {
1010
1172
  return {};
1011
1173
  }
@@ -1179,7 +1341,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1179
1341
  const claudeJsonPath = path9.join(os5.homedir(), ".claude.json");
1180
1342
  let claudeJson = {};
1181
1343
  try {
1182
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
1344
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
1183
1345
  } catch {
1184
1346
  }
1185
1347
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -1197,7 +1359,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1197
1359
  const settingsPath = path9.join(projSettingsDir, "settings.json");
1198
1360
  let settings = {};
1199
1361
  try {
1200
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
1362
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
1201
1363
  } catch {
1202
1364
  }
1203
1365
  const perms = settings.permissions ?? {};
@@ -1310,6 +1472,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1310
1472
  command: spawnCommand
1311
1473
  });
1312
1474
  if (spawnResult.error) {
1475
+ releaseSpawnLock(sessionName);
1313
1476
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
1314
1477
  }
1315
1478
  transport.pipeLog(sessionName, logFile);
@@ -1347,6 +1510,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1347
1510
  }
1348
1511
  }
1349
1512
  if (!booted) {
1513
+ releaseSpawnLock(sessionName);
1350
1514
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
1351
1515
  }
1352
1516
  if (!useExeAgent) {
@@ -1363,9 +1527,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1363
1527
  pid: 0,
1364
1528
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
1365
1529
  });
1530
+ releaseSpawnLock(sessionName);
1366
1531
  return { sessionName };
1367
1532
  }
1368
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
1533
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
1369
1534
  var init_tmux_routing = __esm({
1370
1535
  "src/lib/tmux-routing.ts"() {
1371
1536
  "use strict";
@@ -1377,6 +1542,7 @@ var init_tmux_routing = __esm({
1377
1542
  init_provider_table();
1378
1543
  init_intercom_queue();
1379
1544
  init_plan_limits();
1545
+ SPAWN_LOCK_DIR = path9.join(os5.homedir(), ".exe-os", "spawn-locks");
1380
1546
  SESSION_CACHE = path9.join(os5.homedir(), ".exe-os", "session-cache");
1381
1547
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
1382
1548
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -1389,7 +1555,7 @@ var init_tmux_routing = __esm({
1389
1555
 
1390
1556
  // src/lib/tasks-review.ts
1391
1557
  import path10 from "path";
1392
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
1558
+ import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
1393
1559
  var init_tasks_review = __esm({
1394
1560
  "src/lib/tasks-review.ts"() {
1395
1561
  "use strict";
@@ -1544,7 +1710,7 @@ async function dispatchTaskToEmployee(input) {
1544
1710
  } else {
1545
1711
  const projectDir = input.projectDir ?? process.cwd();
1546
1712
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
1547
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
1713
+ autoInstance: isMultiInstance(input.assignedTo)
1548
1714
  });
1549
1715
  if (result.status === "failed") {
1550
1716
  process.stderr.write(
@@ -1566,12 +1732,13 @@ var init_tasks_notify = __esm({
1566
1732
  init_session_key();
1567
1733
  init_notifications();
1568
1734
  init_transport();
1735
+ init_employees();
1569
1736
  }
1570
1737
  });
1571
1738
 
1572
1739
  // src/lib/tasks.ts
1573
1740
  import path13 from "path";
1574
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3 } from "fs";
1741
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
1575
1742
  async function createTask(input) {
1576
1743
  const result = await createTaskCore(input);
1577
1744
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -1607,7 +1774,7 @@ import { z } from "zod";
1607
1774
 
1608
1775
  // src/adapters/claude/active-agent.ts
1609
1776
  init_config();
1610
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
1777
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5, readdirSync as readdirSync3 } from "fs";
1611
1778
  import { execSync as execSync7 } from "child_process";
1612
1779
  import path14 from "path";
1613
1780
 
@@ -1623,14 +1790,14 @@ function getMarkerPath() {
1623
1790
  function getActiveAgent() {
1624
1791
  try {
1625
1792
  const markerPath = getMarkerPath();
1626
- const raw = readFileSync9(markerPath, "utf8");
1793
+ const raw = readFileSync10(markerPath, "utf8");
1627
1794
  const data = JSON.parse(raw);
1628
1795
  if (data.agentId) {
1629
1796
  if (data.startedAt) {
1630
1797
  const age = Date.now() - new Date(data.startedAt).getTime();
1631
1798
  if (age > STALE_MS) {
1632
1799
  try {
1633
- unlinkSync4(markerPath);
1800
+ unlinkSync5(markerPath);
1634
1801
  } catch {
1635
1802
  }
1636
1803
  } else {
@@ -1653,7 +1820,7 @@ function getActiveAgent() {
1653
1820
  "tmux display-message -p '#{session_name}' 2>/dev/null",
1654
1821
  { encoding: "utf8", timeout: 2e3 }
1655
1822
  ).trim();
1656
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
1823
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
1657
1824
  if (empMatch && empMatch[1] !== "exe") {
1658
1825
  return { agentId: empMatch[1], agentRole: "employee" };
1659
1826
  }
@@ -1717,8 +1884,8 @@ function registerCreateTask(server) {
1717
1884
  try {
1718
1885
  const exeSession = resolveExeSession();
1719
1886
  if (exeSession) {
1720
- const MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["tom", "sasha"]);
1721
- const useAutoInstance = MULTI_INSTANCE_ROLES.has(assigned_to);
1887
+ const { isMultiInstance: isMultiInstance2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
1888
+ const useAutoInstance = isMultiInstance2(assigned_to);
1722
1889
  const { loadConfigSync: loadConfigSync2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1723
1890
  const cfg = loadConfigSync2();
1724
1891
  const result = ensureEmployee(assigned_to, exeSession, process.cwd(), {
@@ -6,12 +6,12 @@ import crypto from "crypto";
6
6
 
7
7
  // src/lib/database.ts
8
8
  import { createClient } from "@libsql/client";
9
- var _client = null;
9
+ var _resilientClient = null;
10
10
  function getClient() {
11
- if (!_client) {
11
+ if (!_resilientClient) {
12
12
  throw new Error("Database client not initialized. Call initDatabase() first.");
13
13
  }
14
- return _client;
14
+ return _resilientClient;
15
15
  }
16
16
 
17
17
  // src/lib/behaviors.ts
@@ -188,7 +188,7 @@ function getActiveAgent() {
188
188
  "tmux display-message -p '#{session_name}' 2>/dev/null",
189
189
  { encoding: "utf8", timeout: 2e3 }
190
190
  ).trim();
191
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
191
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
192
192
  if (empMatch && empMatch[1] !== "exe") {
193
193
  return { agentId: empMatch[1], agentRole: "employee" };
194
194
  }
@@ -6,12 +6,12 @@ import crypto from "crypto";
6
6
 
7
7
  // src/lib/database.ts
8
8
  import { createClient } from "@libsql/client";
9
- var _client = null;
9
+ var _resilientClient = null;
10
10
  function getClient() {
11
- if (!_client) {
11
+ if (!_resilientClient) {
12
12
  throw new Error("Database client not initialized. Call initDatabase() first.");
13
13
  }
14
- return _client;
14
+ return _resilientClient;
15
15
  }
16
16
 
17
17
  // src/lib/reminders.ts
@@ -3,19 +3,27 @@ var __esm = (fn, res) => function __init() {
3
3
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
4
  };
5
5
 
6
+ // src/lib/db-retry.ts
7
+ var init_db_retry = __esm({
8
+ "src/lib/db-retry.ts"() {
9
+ "use strict";
10
+ }
11
+ });
12
+
6
13
  // src/lib/database.ts
7
14
  import { createClient } from "@libsql/client";
8
15
  function getClient() {
9
- if (!_client) {
16
+ if (!_resilientClient) {
10
17
  throw new Error("Database client not initialized. Call initDatabase() first.");
11
18
  }
12
- return _client;
19
+ return _resilientClient;
13
20
  }
14
- var _client;
21
+ var _resilientClient;
15
22
  var init_database = __esm({
16
23
  "src/lib/database.ts"() {
17
24
  "use strict";
18
- _client = null;
25
+ init_db_retry();
26
+ _resilientClient = null;
19
27
  }
20
28
  });
21
29
 
@@ -194,7 +202,7 @@ var init_tasks_crud = __esm({
194
202
 
195
203
  // src/lib/employees.ts
196
204
  import { readFile as readFile2, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
197
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
205
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
198
206
  import { execSync as execSync2 } from "child_process";
199
207
  import path4 from "path";
200
208
  var EMPLOYEES_PATH;
@@ -262,7 +270,7 @@ var init_provider_table = __esm({
262
270
  });
263
271
 
264
272
  // src/lib/intercom-queue.ts
265
- import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync2, existsSync as existsSync5, mkdirSync } from "fs";
273
+ import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, existsSync as existsSync5, mkdirSync } from "fs";
266
274
  import path6 from "path";
267
275
  import os4 from "os";
268
276
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
@@ -276,7 +284,7 @@ var init_intercom_queue = __esm({
276
284
  });
277
285
 
278
286
  // src/lib/license.ts
279
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
287
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
280
288
  import { randomUUID } from "crypto";
281
289
  import path7 from "path";
282
290
  import { jwtVerify, importSPKI } from "jose";
@@ -292,7 +300,7 @@ var init_license = __esm({
292
300
  });
293
301
 
294
302
  // src/lib/plan-limits.ts
295
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
303
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
296
304
  import path8 from "path";
297
305
  var CACHE_PATH2;
298
306
  var init_plan_limits = __esm({
@@ -310,7 +318,7 @@ var init_plan_limits = __esm({
310
318
  import path9 from "path";
311
319
  import os5 from "os";
312
320
  import { fileURLToPath } from "url";
313
- var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
321
+ var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
314
322
  var init_tmux_routing = __esm({
315
323
  "src/lib/tmux-routing.ts"() {
316
324
  "use strict";
@@ -322,6 +330,7 @@ var init_tmux_routing = __esm({
322
330
  init_provider_table();
323
331
  init_intercom_queue();
324
332
  init_plan_limits();
333
+ SPAWN_LOCK_DIR = path9.join(os5.homedir(), ".exe-os", "spawn-locks");
325
334
  SESSION_CACHE = path9.join(os5.homedir(), ".exe-os", "session-cache");
326
335
  INTERCOM_LOG2 = path9.join(os5.homedir(), ".exe-os", "intercom.log");
327
336
  DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
@@ -364,6 +373,7 @@ var init_tasks_notify = __esm({
364
373
  init_session_key();
365
374
  init_notifications();
366
375
  init_transport();
376
+ init_employees();
367
377
  }
368
378
  });
369
379