@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
@@ -109,6 +109,61 @@ var init_memory = __esm({
109
109
  }
110
110
  });
111
111
 
112
+ // src/lib/db-retry.ts
113
+ function isBusyError(err) {
114
+ if (err instanceof Error) {
115
+ const msg = err.message.toLowerCase();
116
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
117
+ }
118
+ return false;
119
+ }
120
+ function delay(ms) {
121
+ return new Promise((resolve) => setTimeout(resolve, ms));
122
+ }
123
+ async function retryOnBusy(fn, label) {
124
+ let lastError;
125
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
126
+ try {
127
+ return await fn();
128
+ } catch (err) {
129
+ lastError = err;
130
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
131
+ throw err;
132
+ }
133
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
134
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
135
+ process.stderr.write(
136
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
137
+ `
138
+ );
139
+ await delay(backoff + jitter);
140
+ }
141
+ }
142
+ throw lastError;
143
+ }
144
+ function wrapWithRetry(client) {
145
+ return new Proxy(client, {
146
+ get(target, prop, receiver) {
147
+ if (prop === "execute") {
148
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
149
+ }
150
+ if (prop === "batch") {
151
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
152
+ }
153
+ return Reflect.get(target, prop, receiver);
154
+ }
155
+ });
156
+ }
157
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
158
+ var init_db_retry = __esm({
159
+ "src/lib/db-retry.ts"() {
160
+ "use strict";
161
+ MAX_RETRIES = 3;
162
+ BASE_DELAY_MS = 200;
163
+ MAX_JITTER_MS = 300;
164
+ }
165
+ });
166
+
112
167
  // src/lib/database.ts
113
168
  var database_exports = {};
114
169
  __export(database_exports, {
@@ -116,6 +171,7 @@ __export(database_exports, {
116
171
  disposeTurso: () => disposeTurso,
117
172
  ensureSchema: () => ensureSchema,
118
173
  getClient: () => getClient,
174
+ getRawClient: () => getRawClient,
119
175
  initDatabase: () => initDatabase,
120
176
  initTurso: () => initTurso,
121
177
  isInitialized: () => isInitialized
@@ -125,6 +181,7 @@ async function initDatabase(config) {
125
181
  if (_client) {
126
182
  _client.close();
127
183
  _client = null;
184
+ _resilientClient = null;
128
185
  }
129
186
  const opts = {
130
187
  url: `file:${config.dbPath}`
@@ -133,20 +190,27 @@ async function initDatabase(config) {
133
190
  opts.encryptionKey = config.encryptionKey;
134
191
  }
135
192
  _client = createClient(opts);
193
+ _resilientClient = wrapWithRetry(_client);
136
194
  }
137
195
  function isInitialized() {
138
196
  return _client !== null;
139
197
  }
140
198
  function getClient() {
199
+ if (!_resilientClient) {
200
+ throw new Error("Database client not initialized. Call initDatabase() first.");
201
+ }
202
+ return _resilientClient;
203
+ }
204
+ function getRawClient() {
141
205
  if (!_client) {
142
206
  throw new Error("Database client not initialized. Call initDatabase() first.");
143
207
  }
144
208
  return _client;
145
209
  }
146
210
  async function ensureSchema() {
147
- const client = getClient();
211
+ const client = getRawClient();
148
212
  await client.execute("PRAGMA journal_mode = WAL");
149
- await client.execute("PRAGMA busy_timeout = 5000");
213
+ await client.execute("PRAGMA busy_timeout = 30000");
150
214
  try {
151
215
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
152
216
  } catch {
@@ -939,13 +1003,16 @@ async function disposeDatabase() {
939
1003
  if (_client) {
940
1004
  _client.close();
941
1005
  _client = null;
1006
+ _resilientClient = null;
942
1007
  }
943
1008
  }
944
- var _client, initTurso, disposeTurso;
1009
+ var _client, _resilientClient, initTurso, disposeTurso;
945
1010
  var init_database = __esm({
946
1011
  "src/lib/database.ts"() {
947
1012
  "use strict";
1013
+ init_db_retry();
948
1014
  _client = null;
1015
+ _resilientClient = null;
949
1016
  initTurso = initDatabase;
950
1017
  disposeTurso = disposeDatabase;
951
1018
  }
@@ -1252,7 +1319,7 @@ function listShards() {
1252
1319
  }
1253
1320
  async function ensureShardSchema(client) {
1254
1321
  await client.execute("PRAGMA journal_mode = WAL");
1255
- await client.execute("PRAGMA busy_timeout = 5000");
1322
+ await client.execute("PRAGMA busy_timeout = 30000");
1256
1323
  try {
1257
1324
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1258
1325
  } catch {
@@ -1490,20 +1557,38 @@ var init_notifications = __esm({
1490
1557
 
1491
1558
  // src/lib/employees.ts
1492
1559
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1493
- import { existsSync as existsSync6, symlinkSync, readlinkSync } from "fs";
1560
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
1494
1561
  import { execSync as execSync3 } from "child_process";
1495
1562
  import path7 from "path";
1496
- var EMPLOYEES_PATH;
1563
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1564
+ if (!existsSync6(employeesPath)) return [];
1565
+ try {
1566
+ return JSON.parse(readFileSync4(employeesPath, "utf-8"));
1567
+ } catch {
1568
+ return [];
1569
+ }
1570
+ }
1571
+ function getEmployee(employees, name) {
1572
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
1573
+ }
1574
+ function isMultiInstance(agentName, employees) {
1575
+ const roster = employees ?? loadEmployeesSync();
1576
+ const emp = getEmployee(roster, agentName);
1577
+ if (!emp) return false;
1578
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
1579
+ }
1580
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
1497
1581
  var init_employees = __esm({
1498
1582
  "src/lib/employees.ts"() {
1499
1583
  "use strict";
1500
1584
  init_config();
1501
1585
  EMPLOYEES_PATH = path7.join(EXE_AI_DIR, "exe-employees.json");
1586
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1502
1587
  }
1503
1588
  });
1504
1589
 
1505
1590
  // src/lib/license.ts
1506
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
1591
+ import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
1507
1592
  import { randomUUID } from "crypto";
1508
1593
  import path8 from "path";
1509
1594
  import { jwtVerify, importSPKI } from "jose";
@@ -1511,14 +1596,14 @@ function loadDeviceId() {
1511
1596
  const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
1512
1597
  try {
1513
1598
  if (existsSync7(deviceJsonPath)) {
1514
- const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
1599
+ const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
1515
1600
  if (data.deviceId) return data.deviceId;
1516
1601
  }
1517
1602
  } catch {
1518
1603
  }
1519
1604
  try {
1520
1605
  if (existsSync7(DEVICE_ID_PATH)) {
1521
- const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
1606
+ const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
1522
1607
  if (id2) return id2;
1523
1608
  }
1524
1609
  } catch {
@@ -1531,7 +1616,7 @@ function loadDeviceId() {
1531
1616
  function loadLicense() {
1532
1617
  try {
1533
1618
  if (!existsSync7(LICENSE_PATH)) return null;
1534
- return readFileSync4(LICENSE_PATH, "utf8").trim();
1619
+ return readFileSync5(LICENSE_PATH, "utf8").trim();
1535
1620
  } catch {
1536
1621
  return null;
1537
1622
  }
@@ -1561,7 +1646,7 @@ async function verifyLicenseJwt(token) {
1561
1646
  async function getCachedLicense() {
1562
1647
  try {
1563
1648
  if (!existsSync7(CACHE_PATH)) return null;
1564
- const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
1649
+ const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
1565
1650
  if (!raw.token || typeof raw.token !== "string") return null;
1566
1651
  return await verifyLicenseJwt(raw.token);
1567
1652
  } catch {
@@ -1657,12 +1742,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1657
1742
  });
1658
1743
 
1659
1744
  // src/lib/plan-limits.ts
1660
- import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
1745
+ import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
1661
1746
  import path9 from "path";
1662
1747
  function getLicenseSync() {
1663
1748
  try {
1664
1749
  if (!existsSync8(CACHE_PATH2)) return freeLicense();
1665
- const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
1750
+ const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
1666
1751
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1667
1752
  const parts = raw.token.split(".");
1668
1753
  if (parts.length !== 3) return freeLicense();
@@ -1720,7 +1805,7 @@ function assertEmployeeLimitSync(rosterPath) {
1720
1805
  let count = 0;
1721
1806
  try {
1722
1807
  if (existsSync8(filePath)) {
1723
- const raw = readFileSync5(filePath, "utf8");
1808
+ const raw = readFileSync6(filePath, "utf8");
1724
1809
  const employees = JSON.parse(raw);
1725
1810
  count = Array.isArray(employees) ? employees.length : 0;
1726
1811
  }
@@ -1757,7 +1842,7 @@ var init_plan_limits = __esm({
1757
1842
  import net from "net";
1758
1843
  import { spawn } from "child_process";
1759
1844
  import { randomUUID as randomUUID2 } from "crypto";
1760
- import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync as statSync2 } from "fs";
1845
+ import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync as statSync2 } from "fs";
1761
1846
  import path10 from "path";
1762
1847
  import { fileURLToPath } from "url";
1763
1848
  function handleData(chunk) {
@@ -1782,7 +1867,7 @@ function handleData(chunk) {
1782
1867
  function cleanupStaleFiles() {
1783
1868
  if (existsSync9(PID_PATH)) {
1784
1869
  try {
1785
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1870
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1786
1871
  if (pid > 0) {
1787
1872
  try {
1788
1873
  process.kill(pid, 0);
@@ -1930,11 +2015,11 @@ async function connectEmbedDaemon() {
1930
2015
  }
1931
2016
  }
1932
2017
  const start = Date.now();
1933
- let delay = 100;
2018
+ let delay2 = 100;
1934
2019
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1935
- await new Promise((r) => setTimeout(r, delay));
2020
+ await new Promise((r) => setTimeout(r, delay2));
1936
2021
  if (await connectToSocket()) return true;
1937
- delay = Math.min(delay * 2, 3e3);
2022
+ delay2 = Math.min(delay2 * 2, 3e3);
1938
2023
  }
1939
2024
  return false;
1940
2025
  }
@@ -1990,7 +2075,7 @@ function killAndRespawnDaemon() {
1990
2075
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1991
2076
  if (existsSync9(PID_PATH)) {
1992
2077
  try {
1993
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
2078
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1994
2079
  if (pid > 0) {
1995
2080
  try {
1996
2081
  process.kill(pid, "SIGKILL");
@@ -2026,11 +2111,11 @@ async function embedViaClient(text, priority = "high") {
2026
2111
  `);
2027
2112
  killAndRespawnDaemon();
2028
2113
  const start = Date.now();
2029
- let delay = 200;
2114
+ let delay2 = 200;
2030
2115
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2031
- await new Promise((r) => setTimeout(r, delay));
2116
+ await new Promise((r) => setTimeout(r, delay2));
2032
2117
  if (await connectToSocket()) break;
2033
- delay = Math.min(delay * 2, 3e3);
2118
+ delay2 = Math.min(delay2 * 2, 3e3);
2034
2119
  }
2035
2120
  if (!_connected) return null;
2036
2121
  }
@@ -2042,11 +2127,11 @@ async function embedViaClient(text, priority = "high") {
2042
2127
  `);
2043
2128
  killAndRespawnDaemon();
2044
2129
  const start = Date.now();
2045
- let delay = 200;
2130
+ let delay2 = 200;
2046
2131
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2047
- await new Promise((r) => setTimeout(r, delay));
2132
+ await new Promise((r) => setTimeout(r, delay2));
2048
2133
  if (await connectToSocket()) break;
2049
- delay = Math.min(delay * 2, 3e3);
2134
+ delay2 = Math.min(delay2 * 2, 3e3);
2050
2135
  }
2051
2136
  if (!_connected) return null;
2052
2137
  const retry = await sendRequest([text], priority);
@@ -2162,7 +2247,7 @@ import crypto4 from "crypto";
2162
2247
  import path11 from "path";
2163
2248
  import { execSync as execSync4 } from "child_process";
2164
2249
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
2165
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
2250
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
2166
2251
  async function writeCheckpoint(input2) {
2167
2252
  const client = getClient();
2168
2253
  const row = await resolveTask(client, input2.taskId);
@@ -2539,7 +2624,7 @@ async function ensureGitignoreExe(baseDir) {
2539
2624
  const gitignorePath = path11.join(baseDir, ".gitignore");
2540
2625
  try {
2541
2626
  if (existsSync10(gitignorePath)) {
2542
- const content = readFileSync7(gitignorePath, "utf-8");
2627
+ const content = readFileSync8(gitignorePath, "utf-8");
2543
2628
  if (/^\/?exe\/?$/m.test(content)) return;
2544
2629
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2545
2630
  } else {
@@ -2559,7 +2644,7 @@ var init_tasks_crud = __esm({
2559
2644
  });
2560
2645
 
2561
2646
  // src/lib/session-registry.ts
2562
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync11 } from "fs";
2647
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync11 } from "fs";
2563
2648
  import path12 from "path";
2564
2649
  import os3 from "os";
2565
2650
  function registerSession(entry) {
@@ -2578,7 +2663,7 @@ function registerSession(entry) {
2578
2663
  }
2579
2664
  function listSessions() {
2580
2665
  try {
2581
- const raw = readFileSync8(REGISTRY_PATH, "utf8");
2666
+ const raw = readFileSync9(REGISTRY_PATH, "utf8");
2582
2667
  return JSON.parse(raw);
2583
2668
  } catch {
2584
2669
  return [];
@@ -2785,7 +2870,7 @@ var init_provider_table = __esm({
2785
2870
  });
2786
2871
 
2787
2872
  // src/lib/intercom-queue.ts
2788
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync12, mkdirSync as mkdirSync4 } from "fs";
2873
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync12, mkdirSync as mkdirSync4 } from "fs";
2789
2874
  import path13 from "path";
2790
2875
  import os4 from "os";
2791
2876
  function ensureDir() {
@@ -2795,7 +2880,7 @@ function ensureDir() {
2795
2880
  function readQueue() {
2796
2881
  try {
2797
2882
  if (!existsSync12(QUEUE_PATH)) return [];
2798
- return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
2883
+ return JSON.parse(readFileSync10(QUEUE_PATH, "utf8"));
2799
2884
  } catch {
2800
2885
  return [];
2801
2886
  }
@@ -2835,10 +2920,46 @@ var init_intercom_queue = __esm({
2835
2920
 
2836
2921
  // src/lib/tmux-routing.ts
2837
2922
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
2838
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync13, appendFileSync } from "fs";
2923
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync13, appendFileSync } from "fs";
2839
2924
  import path14 from "path";
2840
2925
  import os5 from "os";
2841
2926
  import { fileURLToPath as fileURLToPath2 } from "url";
2927
+ import { unlinkSync as unlinkSync3 } from "fs";
2928
+ function spawnLockPath(sessionName) {
2929
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2930
+ }
2931
+ function isProcessAlive(pid) {
2932
+ try {
2933
+ process.kill(pid, 0);
2934
+ return true;
2935
+ } catch {
2936
+ return false;
2937
+ }
2938
+ }
2939
+ function acquireSpawnLock2(sessionName) {
2940
+ if (!existsSync13(SPAWN_LOCK_DIR)) {
2941
+ mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
2942
+ }
2943
+ const lockFile = spawnLockPath(sessionName);
2944
+ if (existsSync13(lockFile)) {
2945
+ try {
2946
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
2947
+ const age = Date.now() - lock.timestamp;
2948
+ if (isProcessAlive(lock.pid) && age < 6e4) {
2949
+ return false;
2950
+ }
2951
+ } catch {
2952
+ }
2953
+ }
2954
+ writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2955
+ return true;
2956
+ }
2957
+ function releaseSpawnLock2(sessionName) {
2958
+ try {
2959
+ unlinkSync3(spawnLockPath(sessionName));
2960
+ } catch {
2961
+ }
2962
+ }
2842
2963
  function resolveBehaviorsExporterScript() {
2843
2964
  try {
2844
2965
  const thisFile = fileURLToPath2(import.meta.url);
@@ -2884,7 +3005,7 @@ function extractRootExe(name) {
2884
3005
  }
2885
3006
  function getParentExe(sessionKey) {
2886
3007
  try {
2887
- const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3008
+ const data = JSON.parse(readFileSync11(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2888
3009
  return data.parentExe || null;
2889
3010
  } catch {
2890
3011
  return null;
@@ -2892,7 +3013,7 @@ function getParentExe(sessionKey) {
2892
3013
  }
2893
3014
  function getDispatchedBy(sessionKey) {
2894
3015
  try {
2895
- const data = JSON.parse(readFileSync10(
3016
+ const data = JSON.parse(readFileSync11(
2896
3017
  path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2897
3018
  "utf8"
2898
3019
  ));
@@ -2919,17 +3040,17 @@ function isEmployeeAlive(sessionName) {
2919
3040
  }
2920
3041
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
2921
3042
  const base = employeeSessionName(employeeName, exeSession);
2922
- if (!isAlive(base)) return 0;
3043
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
2923
3044
  for (let i = 2; i <= maxInstances; i++) {
2924
3045
  const candidate = employeeSessionName(employeeName, exeSession, i);
2925
- if (!isAlive(candidate)) return i;
3046
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
2926
3047
  }
2927
3048
  return null;
2928
3049
  }
2929
3050
  function readDebounceState() {
2930
3051
  try {
2931
3052
  if (!existsSync13(DEBOUNCE_FILE)) return {};
2932
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
3053
+ return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
2933
3054
  } catch {
2934
3055
  return {};
2935
3056
  }
@@ -3125,7 +3246,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3125
3246
  const claudeJsonPath = path14.join(os5.homedir(), ".claude.json");
3126
3247
  let claudeJson = {};
3127
3248
  try {
3128
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
3249
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
3129
3250
  } catch {
3130
3251
  }
3131
3252
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3143,7 +3264,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3143
3264
  const settingsPath = path14.join(projSettingsDir, "settings.json");
3144
3265
  let settings = {};
3145
3266
  try {
3146
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
3267
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
3147
3268
  } catch {
3148
3269
  }
3149
3270
  const perms = settings.permissions ?? {};
@@ -3256,6 +3377,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3256
3377
  command: spawnCommand
3257
3378
  });
3258
3379
  if (spawnResult.error) {
3380
+ releaseSpawnLock2(sessionName);
3259
3381
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
3260
3382
  }
3261
3383
  transport.pipeLog(sessionName, logFile);
@@ -3293,6 +3415,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3293
3415
  }
3294
3416
  }
3295
3417
  if (!booted) {
3418
+ releaseSpawnLock2(sessionName);
3296
3419
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3297
3420
  }
3298
3421
  if (!useExeAgent) {
@@ -3309,9 +3432,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3309
3432
  pid: 0,
3310
3433
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3311
3434
  });
3435
+ releaseSpawnLock2(sessionName);
3312
3436
  return { sessionName };
3313
3437
  }
3314
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3438
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3315
3439
  var init_tmux_routing = __esm({
3316
3440
  "src/lib/tmux-routing.ts"() {
3317
3441
  "use strict";
@@ -3323,6 +3447,7 @@ var init_tmux_routing = __esm({
3323
3447
  init_provider_table();
3324
3448
  init_intercom_queue();
3325
3449
  init_plan_limits();
3450
+ SPAWN_LOCK_DIR = path14.join(os5.homedir(), ".exe-os", "spawn-locks");
3326
3451
  SESSION_CACHE = path14.join(os5.homedir(), ".exe-os", "session-cache");
3327
3452
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3328
3453
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -3335,7 +3460,7 @@ var init_tmux_routing = __esm({
3335
3460
 
3336
3461
  // src/lib/tasks-review.ts
3337
3462
  import path15 from "path";
3338
- import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
3463
+ import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
3339
3464
  async function countPendingReviews() {
3340
3465
  const client = getClient();
3341
3466
  const result = await client.execute({
@@ -3459,7 +3584,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3459
3584
  if (existsSync14(cacheDir)) {
3460
3585
  for (const f of readdirSync3(cacheDir)) {
3461
3586
  if (f.startsWith("review-notified-")) {
3462
- unlinkSync3(path15.join(cacheDir, f));
3587
+ unlinkSync4(path15.join(cacheDir, f));
3463
3588
  }
3464
3589
  }
3465
3590
  }
@@ -3648,7 +3773,7 @@ async function dispatchTaskToEmployee(input2) {
3648
3773
  } else {
3649
3774
  const projectDir = input2.projectDir ?? process.cwd();
3650
3775
  const result = ensureEmployee(input2.assignedTo, exeSession, projectDir, {
3651
- autoInstance: input2.assignedTo === "tom" || input2.assignedTo === "sasha"
3776
+ autoInstance: isMultiInstance(input2.assignedTo)
3652
3777
  });
3653
3778
  if (result.status === "failed") {
3654
3779
  process.stderr.write(
@@ -3683,6 +3808,7 @@ var init_tasks_notify = __esm({
3683
3808
  init_session_key();
3684
3809
  init_notifications();
3685
3810
  init_transport();
3811
+ init_employees();
3686
3812
  }
3687
3813
  });
3688
3814
 
@@ -4017,7 +4143,7 @@ __export(tasks_exports, {
4017
4143
  writeCheckpoint: () => writeCheckpoint
4018
4144
  });
4019
4145
  import path17 from "path";
4020
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4 } from "fs";
4146
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
4021
4147
  async function createTask(input2) {
4022
4148
  const result = await createTaskCore(input2);
4023
4149
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4043,7 +4169,7 @@ async function updateTask(input2) {
4043
4169
  writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4044
4170
  } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
4045
4171
  try {
4046
- unlinkSync4(cachePath);
4172
+ unlinkSync5(cachePath);
4047
4173
  } catch {
4048
4174
  }
4049
4175
  }
@@ -4378,7 +4504,8 @@ async function writeMemory(record) {
4378
4504
  has_error: record.has_error ? 1 : 0,
4379
4505
  raw_text: record.raw_text,
4380
4506
  vector: record.vector,
4381
- version: _nextVersion++,
4507
+ version: 0,
4508
+ // Placeholder — assigned atomically at flush time
4382
4509
  task_id: record.task_id ?? null,
4383
4510
  importance: record.importance ?? 5,
4384
4511
  status: record.status ?? "active",
@@ -4412,6 +4539,13 @@ async function flushBatch() {
4412
4539
  _flushing = true;
4413
4540
  try {
4414
4541
  const batch = _pendingRecords.slice(0);
4542
+ const client = getClient();
4543
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
4544
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4545
+ for (const row of batch) {
4546
+ row.version = baseVersion++;
4547
+ }
4548
+ _nextVersion = baseVersion;
4415
4549
  const buildStmt = (row) => {
4416
4550
  const hasVector = row.vector !== null;
4417
4551
  const taskId = row.task_id ?? null;
@@ -4571,8 +4705,8 @@ function findContainingChunk(filePath, snippet) {
4571
4705
  try {
4572
4706
  const ext = filePath.split(".").pop()?.toLowerCase();
4573
4707
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
4574
- const { readFileSync: readFileSync11 } = __require("fs");
4575
- const source = readFileSync11(filePath, "utf8");
4708
+ const { readFileSync: readFileSync12 } = __require("fs");
4709
+ const source = readFileSync12(filePath, "utf8");
4576
4710
  const lines = source.split("\n");
4577
4711
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
4578
4712
  let matchLine = -1;
@@ -4853,8 +4987,8 @@ process.stdin.on("end", async () => {
4853
4987
  fileContent = data.tool_input?.content ?? "";
4854
4988
  } else {
4855
4989
  try {
4856
- const { readFileSync: readFileSync11 } = await import("fs");
4857
- fileContent = readFileSync11(filePath, "utf8");
4990
+ const { readFileSync: readFileSync12 } = await import("fs");
4991
+ fileContent = readFileSync12(filePath, "utf8");
4858
4992
  } catch (err) {
4859
4993
  process.stderr.write(`[ingest-worker] file read failed (${filePath}): ${err instanceof Error ? err.message : String(err)}
4860
4994
  `);
@@ -247,7 +247,7 @@ function getActiveAgent() {
247
247
  "tmux display-message -p '#{session_name}' 2>/dev/null",
248
248
  { encoding: "utf8", timeout: 2e3 }
249
249
  ).trim();
250
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
250
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
251
251
  if (empMatch && empMatch[1] !== "exe") {
252
252
  return { agentId: empMatch[1], agentRole: "employee" };
253
253
  }