@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
@@ -281,12 +281,68 @@ var init_crm_bridge = __esm({
281
281
  }
282
282
  });
283
283
 
284
+ // src/lib/db-retry.ts
285
+ function isBusyError(err) {
286
+ if (err instanceof Error) {
287
+ const msg = err.message.toLowerCase();
288
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
289
+ }
290
+ return false;
291
+ }
292
+ function delay(ms) {
293
+ return new Promise((resolve) => setTimeout(resolve, ms));
294
+ }
295
+ async function retryOnBusy(fn, label) {
296
+ let lastError;
297
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
298
+ try {
299
+ return await fn();
300
+ } catch (err) {
301
+ lastError = err;
302
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
303
+ throw err;
304
+ }
305
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
306
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
307
+ process.stderr.write(
308
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
309
+ `
310
+ );
311
+ await delay(backoff + jitter);
312
+ }
313
+ }
314
+ throw lastError;
315
+ }
316
+ function wrapWithRetry(client) {
317
+ return new Proxy(client, {
318
+ get(target, prop, receiver) {
319
+ if (prop === "execute") {
320
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
321
+ }
322
+ if (prop === "batch") {
323
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
324
+ }
325
+ return Reflect.get(target, prop, receiver);
326
+ }
327
+ });
328
+ }
329
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
330
+ var init_db_retry = __esm({
331
+ "src/lib/db-retry.ts"() {
332
+ "use strict";
333
+ MAX_RETRIES = 3;
334
+ BASE_DELAY_MS = 200;
335
+ MAX_JITTER_MS = 300;
336
+ }
337
+ });
338
+
284
339
  // src/lib/database.ts
285
340
  import { createClient } from "@libsql/client";
286
341
  async function initDatabase(config2) {
287
342
  if (_client) {
288
343
  _client.close();
289
344
  _client = null;
345
+ _resilientClient = null;
290
346
  }
291
347
  const opts = {
292
348
  url: `file:${config2.dbPath}`
@@ -295,17 +351,24 @@ async function initDatabase(config2) {
295
351
  opts.encryptionKey = config2.encryptionKey;
296
352
  }
297
353
  _client = createClient(opts);
354
+ _resilientClient = wrapWithRetry(_client);
298
355
  }
299
356
  function getClient() {
357
+ if (!_resilientClient) {
358
+ throw new Error("Database client not initialized. Call initDatabase() first.");
359
+ }
360
+ return _resilientClient;
361
+ }
362
+ function getRawClient() {
300
363
  if (!_client) {
301
364
  throw new Error("Database client not initialized. Call initDatabase() first.");
302
365
  }
303
366
  return _client;
304
367
  }
305
368
  async function ensureSchema() {
306
- const client = getClient();
369
+ const client = getRawClient();
307
370
  await client.execute("PRAGMA journal_mode = WAL");
308
- await client.execute("PRAGMA busy_timeout = 5000");
371
+ await client.execute("PRAGMA busy_timeout = 30000");
309
372
  try {
310
373
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
311
374
  } catch {
@@ -1098,13 +1161,16 @@ async function disposeDatabase() {
1098
1161
  if (_client) {
1099
1162
  _client.close();
1100
1163
  _client = null;
1164
+ _resilientClient = null;
1101
1165
  }
1102
1166
  }
1103
- var _client, initTurso, disposeTurso;
1167
+ var _client, _resilientClient, initTurso, disposeTurso;
1104
1168
  var init_database = __esm({
1105
1169
  "src/lib/database.ts"() {
1106
1170
  "use strict";
1171
+ init_db_retry();
1107
1172
  _client = null;
1173
+ _resilientClient = null;
1108
1174
  initTurso = initDatabase;
1109
1175
  disposeTurso = disposeDatabase;
1110
1176
  }
@@ -1539,11 +1605,11 @@ async function connectEmbedDaemon() {
1539
1605
  }
1540
1606
  }
1541
1607
  const start = Date.now();
1542
- let delay = 100;
1608
+ let delay2 = 100;
1543
1609
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1544
- await new Promise((r) => setTimeout(r, delay));
1610
+ await new Promise((r) => setTimeout(r, delay2));
1545
1611
  if (await connectToSocket()) return true;
1546
- delay = Math.min(delay * 2, 3e3);
1612
+ delay2 = Math.min(delay2 * 2, 3e3);
1547
1613
  }
1548
1614
  return false;
1549
1615
  }
@@ -1635,11 +1701,11 @@ async function embedViaClient(text, priority = "high") {
1635
1701
  `);
1636
1702
  killAndRespawnDaemon();
1637
1703
  const start = Date.now();
1638
- let delay = 200;
1704
+ let delay2 = 200;
1639
1705
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1640
- await new Promise((r) => setTimeout(r, delay));
1706
+ await new Promise((r) => setTimeout(r, delay2));
1641
1707
  if (await connectToSocket()) break;
1642
- delay = Math.min(delay * 2, 3e3);
1708
+ delay2 = Math.min(delay2 * 2, 3e3);
1643
1709
  }
1644
1710
  if (!_connected) return null;
1645
1711
  }
@@ -1651,11 +1717,11 @@ async function embedViaClient(text, priority = "high") {
1651
1717
  `);
1652
1718
  killAndRespawnDaemon();
1653
1719
  const start = Date.now();
1654
- let delay = 200;
1720
+ let delay2 = 200;
1655
1721
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1656
- await new Promise((r) => setTimeout(r, delay));
1722
+ await new Promise((r) => setTimeout(r, delay2));
1657
1723
  if (await connectToSocket()) break;
1658
- delay = Math.min(delay * 2, 3e3);
1724
+ delay2 = Math.min(delay2 * 2, 3e3);
1659
1725
  }
1660
1726
  if (!_connected) return null;
1661
1727
  const retry = await sendRequest([text], priority);
@@ -1873,7 +1939,7 @@ function listShards() {
1873
1939
  }
1874
1940
  async function ensureShardSchema(client) {
1875
1941
  await client.execute("PRAGMA journal_mode = WAL");
1876
- await client.execute("PRAGMA busy_timeout = 5000");
1942
+ await client.execute("PRAGMA busy_timeout = 30000");
1877
1943
  try {
1878
1944
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1879
1945
  } catch {
@@ -2134,7 +2200,8 @@ async function writeMemory(record) {
2134
2200
  has_error: record.has_error ? 1 : 0,
2135
2201
  raw_text: record.raw_text,
2136
2202
  vector: record.vector,
2137
- version: _nextVersion++,
2203
+ version: 0,
2204
+ // Placeholder — assigned atomically at flush time
2138
2205
  task_id: record.task_id ?? null,
2139
2206
  importance: record.importance ?? 5,
2140
2207
  status: record.status ?? "active",
@@ -2168,6 +2235,13 @@ async function flushBatch() {
2168
2235
  _flushing = true;
2169
2236
  try {
2170
2237
  const batch = _pendingRecords.slice(0);
2238
+ const client = getClient();
2239
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2240
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2241
+ for (const row of batch) {
2242
+ row.version = baseVersion++;
2243
+ }
2244
+ _nextVersion = baseVersion;
2171
2245
  const buildStmt = (row) => {
2172
2246
  const hasVector = row.vector !== null;
2173
2247
  const taskId = row.task_id ?? null;
@@ -4510,25 +4584,43 @@ var init_intercom_queue = __esm({
4510
4584
 
4511
4585
  // src/lib/employees.ts
4512
4586
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
4513
- import { existsSync as existsSync8, symlinkSync, readlinkSync } from "fs";
4587
+ import { existsSync as existsSync8, symlinkSync, readlinkSync, readFileSync as readFileSync7 } from "fs";
4514
4588
  import { execSync as execSync3 } from "child_process";
4515
4589
  import path9 from "path";
4516
- var EMPLOYEES_PATH;
4590
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
4591
+ if (!existsSync8(employeesPath)) return [];
4592
+ try {
4593
+ return JSON.parse(readFileSync7(employeesPath, "utf-8"));
4594
+ } catch {
4595
+ return [];
4596
+ }
4597
+ }
4598
+ function getEmployee(employees, name) {
4599
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
4600
+ }
4601
+ function isMultiInstance(agentName, employees) {
4602
+ const roster = employees ?? loadEmployeesSync();
4603
+ const emp = getEmployee(roster, agentName);
4604
+ if (!emp) return false;
4605
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
4606
+ }
4607
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
4517
4608
  var init_employees = __esm({
4518
4609
  "src/lib/employees.ts"() {
4519
4610
  "use strict";
4520
4611
  init_config();
4521
4612
  EMPLOYEES_PATH = path9.join(EXE_AI_DIR, "exe-employees.json");
4613
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
4522
4614
  }
4523
4615
  });
4524
4616
 
4525
4617
  // src/lib/plan-limits.ts
4526
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
4618
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4527
4619
  import path10 from "path";
4528
4620
  function getLicenseSync() {
4529
4621
  try {
4530
4622
  if (!existsSync9(CACHE_PATH2)) return freeLicense();
4531
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
4623
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
4532
4624
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
4533
4625
  const parts = raw.token.split(".");
4534
4626
  if (parts.length !== 3) return freeLicense();
@@ -4567,7 +4659,7 @@ function assertEmployeeLimitSync(rosterPath) {
4567
4659
  let count = 0;
4568
4660
  try {
4569
4661
  if (existsSync9(filePath)) {
4570
- const raw = readFileSync7(filePath, "utf8");
4662
+ const raw = readFileSync8(filePath, "utf8");
4571
4663
  const employees = JSON.parse(raw);
4572
4664
  count = Array.isArray(employees) ? employees.length : 0;
4573
4665
  }
@@ -4602,10 +4694,46 @@ var init_plan_limits = __esm({
4602
4694
 
4603
4695
  // src/lib/tmux-routing.ts
4604
4696
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
4605
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
4697
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
4606
4698
  import path11 from "path";
4607
4699
  import os4 from "os";
4608
4700
  import { fileURLToPath as fileURLToPath2 } from "url";
4701
+ import { unlinkSync as unlinkSync2 } from "fs";
4702
+ function spawnLockPath(sessionName) {
4703
+ return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4704
+ }
4705
+ function isProcessAlive(pid) {
4706
+ try {
4707
+ process.kill(pid, 0);
4708
+ return true;
4709
+ } catch {
4710
+ return false;
4711
+ }
4712
+ }
4713
+ function acquireSpawnLock2(sessionName) {
4714
+ if (!existsSync10(SPAWN_LOCK_DIR)) {
4715
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4716
+ }
4717
+ const lockFile = spawnLockPath(sessionName);
4718
+ if (existsSync10(lockFile)) {
4719
+ try {
4720
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
4721
+ const age = Date.now() - lock.timestamp;
4722
+ if (isProcessAlive(lock.pid) && age < 6e4) {
4723
+ return false;
4724
+ }
4725
+ } catch {
4726
+ }
4727
+ }
4728
+ writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4729
+ return true;
4730
+ }
4731
+ function releaseSpawnLock2(sessionName) {
4732
+ try {
4733
+ unlinkSync2(spawnLockPath(sessionName));
4734
+ } catch {
4735
+ }
4736
+ }
4609
4737
  function resolveBehaviorsExporterScript() {
4610
4738
  try {
4611
4739
  const thisFile = fileURLToPath2(import.meta.url);
@@ -4651,7 +4779,7 @@ function extractRootExe(name) {
4651
4779
  }
4652
4780
  function getParentExe(sessionKey) {
4653
4781
  try {
4654
- const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4782
+ const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4655
4783
  return data.parentExe || null;
4656
4784
  } catch {
4657
4785
  return null;
@@ -4659,7 +4787,7 @@ function getParentExe(sessionKey) {
4659
4787
  }
4660
4788
  function getDispatchedBy(sessionKey) {
4661
4789
  try {
4662
- const data = JSON.parse(readFileSync8(
4790
+ const data = JSON.parse(readFileSync9(
4663
4791
  path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4664
4792
  "utf8"
4665
4793
  ));
@@ -4686,17 +4814,17 @@ function isEmployeeAlive(sessionName) {
4686
4814
  }
4687
4815
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
4688
4816
  const base = employeeSessionName(employeeName, exeSession);
4689
- if (!isAlive(base)) return 0;
4817
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
4690
4818
  for (let i = 2; i <= maxInstances; i++) {
4691
4819
  const candidate = employeeSessionName(employeeName, exeSession, i);
4692
- if (!isAlive(candidate)) return i;
4820
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
4693
4821
  }
4694
4822
  return null;
4695
4823
  }
4696
4824
  function readDebounceState() {
4697
4825
  try {
4698
4826
  if (!existsSync10(DEBOUNCE_FILE)) return {};
4699
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
4827
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
4700
4828
  } catch {
4701
4829
  return {};
4702
4830
  }
@@ -4892,7 +5020,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4892
5020
  const claudeJsonPath = path11.join(os4.homedir(), ".claude.json");
4893
5021
  let claudeJson = {};
4894
5022
  try {
4895
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5023
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
4896
5024
  } catch {
4897
5025
  }
4898
5026
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4910,7 +5038,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4910
5038
  const settingsPath = path11.join(projSettingsDir, "settings.json");
4911
5039
  let settings = {};
4912
5040
  try {
4913
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
5041
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
4914
5042
  } catch {
4915
5043
  }
4916
5044
  const perms = settings.permissions ?? {};
@@ -5023,6 +5151,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5023
5151
  command: spawnCommand
5024
5152
  });
5025
5153
  if (spawnResult.error) {
5154
+ releaseSpawnLock2(sessionName);
5026
5155
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
5027
5156
  }
5028
5157
  transport.pipeLog(sessionName, logFile);
@@ -5060,6 +5189,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5060
5189
  }
5061
5190
  }
5062
5191
  if (!booted) {
5192
+ releaseSpawnLock2(sessionName);
5063
5193
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
5064
5194
  }
5065
5195
  if (!useExeAgent) {
@@ -5076,9 +5206,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5076
5206
  pid: 0,
5077
5207
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
5078
5208
  });
5209
+ releaseSpawnLock2(sessionName);
5079
5210
  return { sessionName };
5080
5211
  }
5081
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
5212
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
5082
5213
  var init_tmux_routing = __esm({
5083
5214
  "src/lib/tmux-routing.ts"() {
5084
5215
  "use strict";
@@ -5090,6 +5221,7 @@ var init_tmux_routing = __esm({
5090
5221
  init_provider_table();
5091
5222
  init_intercom_queue();
5092
5223
  init_plan_limits();
5224
+ SPAWN_LOCK_DIR = path11.join(os4.homedir(), ".exe-os", "spawn-locks");
5093
5225
  SESSION_CACHE = path11.join(os4.homedir(), ".exe-os", "session-cache");
5094
5226
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5095
5227
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -5237,7 +5369,7 @@ async function deliverLocalMessage(messageId) {
5237
5369
  return true;
5238
5370
  } catch {
5239
5371
  const newRetryCount = msg.retryCount + 1;
5240
- if (newRetryCount >= MAX_RETRIES) {
5372
+ if (newRetryCount >= MAX_RETRIES2) {
5241
5373
  await markFailed(messageId, "session unavailable after 10 retries");
5242
5374
  } else {
5243
5375
  await client.execute({
@@ -5326,7 +5458,7 @@ async function retryPendingMessages() {
5326
5458
  sql: `SELECT * FROM messages
5327
5459
  WHERE status = 'pending' AND retry_count < ?
5328
5460
  ORDER BY id`,
5329
- args: [MAX_RETRIES]
5461
+ args: [MAX_RETRIES2]
5330
5462
  });
5331
5463
  let delivered = 0;
5332
5464
  for (const row of result.rows) {
@@ -5339,13 +5471,13 @@ async function retryPendingMessages() {
5339
5471
  }
5340
5472
  return delivered;
5341
5473
  }
5342
- var MAX_RETRIES, _wsClientSend;
5474
+ var MAX_RETRIES2, _wsClientSend;
5343
5475
  var init_messaging = __esm({
5344
5476
  "src/lib/messaging.ts"() {
5345
5477
  "use strict";
5346
5478
  init_database();
5347
5479
  init_tmux_routing();
5348
- MAX_RETRIES = 10;
5480
+ MAX_RETRIES2 = 10;
5349
5481
  _wsClientSend = null;
5350
5482
  }
5351
5483
  });
@@ -5355,9 +5487,9 @@ import crypto4 from "crypto";
5355
5487
  import path12 from "path";
5356
5488
  import os5 from "os";
5357
5489
  import {
5358
- readFileSync as readFileSync9,
5490
+ readFileSync as readFileSync10,
5359
5491
  readdirSync,
5360
- unlinkSync as unlinkSync2,
5492
+ unlinkSync as unlinkSync3,
5361
5493
  existsSync as existsSync11,
5362
5494
  rmdirSync
5363
5495
  } from "fs";
@@ -5407,7 +5539,7 @@ import crypto5 from "crypto";
5407
5539
  import path13 from "path";
5408
5540
  import { execSync as execSync5 } from "child_process";
5409
5541
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5410
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
5542
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
5411
5543
  async function writeCheckpoint(input) {
5412
5544
  const client = getClient();
5413
5545
  const row = await resolveTask(client, input.taskId);
@@ -5784,7 +5916,7 @@ async function ensureGitignoreExe(baseDir) {
5784
5916
  const gitignorePath = path13.join(baseDir, ".gitignore");
5785
5917
  try {
5786
5918
  if (existsSync12(gitignorePath)) {
5787
- const content = readFileSync10(gitignorePath, "utf-8");
5919
+ const content = readFileSync11(gitignorePath, "utf-8");
5788
5920
  if (/^\/?exe\/?$/m.test(content)) return;
5789
5921
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
5790
5922
  } else {
@@ -5805,7 +5937,7 @@ var init_tasks_crud = __esm({
5805
5937
 
5806
5938
  // src/lib/tasks-review.ts
5807
5939
  import path14 from "path";
5808
- import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
5940
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
5809
5941
  async function countPendingReviews() {
5810
5942
  const client = getClient();
5811
5943
  const result = await client.execute({
@@ -5929,7 +6061,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5929
6061
  if (existsSync13(cacheDir)) {
5930
6062
  for (const f of readdirSync2(cacheDir)) {
5931
6063
  if (f.startsWith("review-notified-")) {
5932
- unlinkSync3(path14.join(cacheDir, f));
6064
+ unlinkSync4(path14.join(cacheDir, f));
5933
6065
  }
5934
6066
  }
5935
6067
  }
@@ -6160,7 +6292,7 @@ async function dispatchTaskToEmployee(input) {
6160
6292
  } else {
6161
6293
  const projectDir = input.projectDir ?? process.cwd();
6162
6294
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
6163
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
6295
+ autoInstance: isMultiInstance(input.assignedTo)
6164
6296
  });
6165
6297
  if (result.status === "failed") {
6166
6298
  process.stderr.write(
@@ -6195,6 +6327,7 @@ var init_tasks_notify = __esm({
6195
6327
  init_session_key();
6196
6328
  init_notifications();
6197
6329
  init_transport();
6330
+ init_employees();
6198
6331
  }
6199
6332
  });
6200
6333
 
@@ -6529,7 +6662,7 @@ __export(tasks_exports, {
6529
6662
  writeCheckpoint: () => writeCheckpoint
6530
6663
  });
6531
6664
  import path17 from "path";
6532
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "fs";
6665
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
6533
6666
  async function createTask(input) {
6534
6667
  const result = await createTaskCore(input);
6535
6668
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -6555,7 +6688,7 @@ async function updateTask(input) {
6555
6688
  writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
6556
6689
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
6557
6690
  try {
6558
- unlinkSync4(cachePath);
6691
+ unlinkSync5(cachePath);
6559
6692
  } catch {
6560
6693
  }
6561
6694
  }
@@ -6666,7 +6799,7 @@ var init_tasks = __esm({
6666
6799
  });
6667
6800
 
6668
6801
  // src/automation/trigger-engine.ts
6669
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
6802
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync6, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
6670
6803
  import { randomUUID as randomUUID7 } from "crypto";
6671
6804
  import path18 from "path";
6672
6805
  import os6 from "os";
@@ -6724,7 +6857,7 @@ function evaluateConditions(conditions, record) {
6724
6857
  function loadTriggers(project) {
6725
6858
  if (!existsSync14(TRIGGERS_PATH)) return [];
6726
6859
  try {
6727
- const raw = readFileSync11(TRIGGERS_PATH, "utf-8");
6860
+ const raw = readFileSync12(TRIGGERS_PATH, "utf-8");
6728
6861
  const all = JSON.parse(raw);
6729
6862
  if (!Array.isArray(all)) return [];
6730
6863
  if (project) {
@@ -7020,7 +7153,7 @@ var init_crm_webhook = __esm({
7020
7153
  });
7021
7154
 
7022
7155
  // src/bin/exe-gateway.ts
7023
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
7156
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
7024
7157
  import path19 from "path";
7025
7158
  import os7 from "os";
7026
7159
 
@@ -7857,7 +7990,7 @@ function loadConfig2() {
7857
7990
  return {};
7858
7991
  }
7859
7992
  try {
7860
- const raw = readFileSync12(CONFIG_PATH3, "utf-8");
7993
+ const raw = readFileSync13(CONFIG_PATH3, "utf-8");
7861
7994
  return JSON.parse(raw);
7862
7995
  } catch (err) {
7863
7996
  console.error(