@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
@@ -277,12 +277,68 @@ var init_memory = __esm({
277
277
  }
278
278
  });
279
279
 
280
+ // src/lib/db-retry.ts
281
+ function isBusyError(err) {
282
+ if (err instanceof Error) {
283
+ const msg = err.message.toLowerCase();
284
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
285
+ }
286
+ return false;
287
+ }
288
+ function delay(ms) {
289
+ return new Promise((resolve) => setTimeout(resolve, ms));
290
+ }
291
+ async function retryOnBusy(fn, label) {
292
+ let lastError;
293
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
294
+ try {
295
+ return await fn();
296
+ } catch (err) {
297
+ lastError = err;
298
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
299
+ throw err;
300
+ }
301
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
302
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
303
+ process.stderr.write(
304
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
305
+ `
306
+ );
307
+ await delay(backoff + jitter);
308
+ }
309
+ }
310
+ throw lastError;
311
+ }
312
+ function wrapWithRetry(client) {
313
+ return new Proxy(client, {
314
+ get(target, prop, receiver) {
315
+ if (prop === "execute") {
316
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
317
+ }
318
+ if (prop === "batch") {
319
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
320
+ }
321
+ return Reflect.get(target, prop, receiver);
322
+ }
323
+ });
324
+ }
325
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
326
+ var init_db_retry = __esm({
327
+ "src/lib/db-retry.ts"() {
328
+ "use strict";
329
+ MAX_RETRIES = 3;
330
+ BASE_DELAY_MS = 200;
331
+ MAX_JITTER_MS = 300;
332
+ }
333
+ });
334
+
280
335
  // src/lib/database.ts
281
336
  import { createClient } from "@libsql/client";
282
337
  async function initDatabase(config) {
283
338
  if (_client) {
284
339
  _client.close();
285
340
  _client = null;
341
+ _resilientClient = null;
286
342
  }
287
343
  const opts = {
288
344
  url: `file:${config.dbPath}`
@@ -291,17 +347,24 @@ async function initDatabase(config) {
291
347
  opts.encryptionKey = config.encryptionKey;
292
348
  }
293
349
  _client = createClient(opts);
350
+ _resilientClient = wrapWithRetry(_client);
294
351
  }
295
352
  function getClient() {
353
+ if (!_resilientClient) {
354
+ throw new Error("Database client not initialized. Call initDatabase() first.");
355
+ }
356
+ return _resilientClient;
357
+ }
358
+ function getRawClient() {
296
359
  if (!_client) {
297
360
  throw new Error("Database client not initialized. Call initDatabase() first.");
298
361
  }
299
362
  return _client;
300
363
  }
301
364
  async function ensureSchema() {
302
- const client = getClient();
365
+ const client = getRawClient();
303
366
  await client.execute("PRAGMA journal_mode = WAL");
304
- await client.execute("PRAGMA busy_timeout = 5000");
367
+ await client.execute("PRAGMA busy_timeout = 30000");
305
368
  try {
306
369
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
307
370
  } catch {
@@ -1094,13 +1157,16 @@ async function disposeDatabase() {
1094
1157
  if (_client) {
1095
1158
  _client.close();
1096
1159
  _client = null;
1160
+ _resilientClient = null;
1097
1161
  }
1098
1162
  }
1099
- var _client, initTurso, disposeTurso;
1163
+ var _client, _resilientClient, initTurso, disposeTurso;
1100
1164
  var init_database = __esm({
1101
1165
  "src/lib/database.ts"() {
1102
1166
  "use strict";
1167
+ init_db_retry();
1103
1168
  _client = null;
1169
+ _resilientClient = null;
1104
1170
  initTurso = initDatabase;
1105
1171
  disposeTurso = disposeDatabase;
1106
1172
  }
@@ -1213,7 +1279,7 @@ function listShards() {
1213
1279
  }
1214
1280
  async function ensureShardSchema(client) {
1215
1281
  await client.execute("PRAGMA journal_mode = WAL");
1216
- await client.execute("PRAGMA busy_timeout = 5000");
1282
+ await client.execute("PRAGMA busy_timeout = 30000");
1217
1283
  try {
1218
1284
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1219
1285
  } catch {
@@ -1474,7 +1540,8 @@ async function writeMemory(record) {
1474
1540
  has_error: record.has_error ? 1 : 0,
1475
1541
  raw_text: record.raw_text,
1476
1542
  vector: record.vector,
1477
- version: _nextVersion++,
1543
+ version: 0,
1544
+ // Placeholder — assigned atomically at flush time
1478
1545
  task_id: record.task_id ?? null,
1479
1546
  importance: record.importance ?? 5,
1480
1547
  status: record.status ?? "active",
@@ -1508,6 +1575,13 @@ async function flushBatch() {
1508
1575
  _flushing = true;
1509
1576
  try {
1510
1577
  const batch = _pendingRecords.slice(0);
1578
+ const client = getClient();
1579
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1580
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1581
+ for (const row of batch) {
1582
+ row.version = baseVersion++;
1583
+ }
1584
+ _nextVersion = baseVersion;
1511
1585
  const buildStmt = (row) => {
1512
1586
  const hasVector = row.vector !== null;
1513
1587
  const taskId = row.task_id ?? null;
@@ -2098,11 +2172,11 @@ async function connectEmbedDaemon() {
2098
2172
  }
2099
2173
  }
2100
2174
  const start = Date.now();
2101
- let delay = 100;
2175
+ let delay2 = 100;
2102
2176
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2103
- await new Promise((r) => setTimeout(r, delay));
2177
+ await new Promise((r) => setTimeout(r, delay2));
2104
2178
  if (await connectToSocket()) return true;
2105
- delay = Math.min(delay * 2, 3e3);
2179
+ delay2 = Math.min(delay2 * 2, 3e3);
2106
2180
  }
2107
2181
  return false;
2108
2182
  }
@@ -2194,11 +2268,11 @@ async function embedViaClient(text, priority = "high") {
2194
2268
  `);
2195
2269
  killAndRespawnDaemon();
2196
2270
  const start = Date.now();
2197
- let delay = 200;
2271
+ let delay2 = 200;
2198
2272
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2199
- await new Promise((r) => setTimeout(r, delay));
2273
+ await new Promise((r) => setTimeout(r, delay2));
2200
2274
  if (await connectToSocket()) break;
2201
- delay = Math.min(delay * 2, 3e3);
2275
+ delay2 = Math.min(delay2 * 2, 3e3);
2202
2276
  }
2203
2277
  if (!_connected) return null;
2204
2278
  }
@@ -2210,11 +2284,11 @@ async function embedViaClient(text, priority = "high") {
2210
2284
  `);
2211
2285
  killAndRespawnDaemon();
2212
2286
  const start = Date.now();
2213
- let delay = 200;
2287
+ let delay2 = 200;
2214
2288
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2215
- await new Promise((r) => setTimeout(r, delay));
2289
+ await new Promise((r) => setTimeout(r, delay2));
2216
2290
  if (await connectToSocket()) break;
2217
- delay = Math.min(delay * 2, 3e3);
2291
+ delay2 = Math.min(delay2 * 2, 3e3);
2218
2292
  }
2219
2293
  if (!_connected) return null;
2220
2294
  const retry = await sendRequest([text], priority);
@@ -3035,8 +3109,8 @@ function drainQueue(isSessionBusy, sendKeys) {
3035
3109
  } catch {
3036
3110
  }
3037
3111
  item.attempts++;
3038
- if (item.attempts >= MAX_RETRIES) {
3039
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
3112
+ if (item.attempts >= MAX_RETRIES2) {
3113
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
3040
3114
  failed++;
3041
3115
  continue;
3042
3116
  }
@@ -3069,12 +3143,12 @@ function logQueue(msg) {
3069
3143
  } catch {
3070
3144
  }
3071
3145
  }
3072
- var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
3146
+ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
3073
3147
  var init_intercom_queue = __esm({
3074
3148
  "src/lib/intercom-queue.ts"() {
3075
3149
  "use strict";
3076
3150
  QUEUE_PATH = path10.join(os3.homedir(), ".exe-os", "intercom-queue.json");
3077
- MAX_RETRIES = 5;
3151
+ MAX_RETRIES2 = 5;
3078
3152
  TTL_MS = 60 * 60 * 1e3;
3079
3153
  INTERCOM_LOG = path10.join(os3.homedir(), ".exe-os", "intercom.log");
3080
3154
  }
@@ -3082,7 +3156,7 @@ var init_intercom_queue = __esm({
3082
3156
 
3083
3157
  // src/lib/employees.ts
3084
3158
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
3085
- import { existsSync as existsSync9, symlinkSync, readlinkSync } from "fs";
3159
+ import { existsSync as existsSync9, symlinkSync, readlinkSync, readFileSync as readFileSync7 } from "fs";
3086
3160
  import { execSync as execSync6 } from "child_process";
3087
3161
  import path11 from "path";
3088
3162
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
@@ -3109,7 +3183,7 @@ var init_employees = __esm({
3109
3183
  });
3110
3184
 
3111
3185
  // src/lib/license.ts
3112
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
3186
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
3113
3187
  import { randomUUID as randomUUID2 } from "crypto";
3114
3188
  import path12 from "path";
3115
3189
  import { jwtVerify, importSPKI } from "jose";
@@ -3132,12 +3206,12 @@ var init_license = __esm({
3132
3206
  });
3133
3207
 
3134
3208
  // src/lib/plan-limits.ts
3135
- import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
3209
+ import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
3136
3210
  import path13 from "path";
3137
3211
  function getLicenseSync() {
3138
3212
  try {
3139
3213
  if (!existsSync11(CACHE_PATH2)) return freeLicense();
3140
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3214
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3141
3215
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3142
3216
  const parts = raw.token.split(".");
3143
3217
  if (parts.length !== 3) return freeLicense();
@@ -3176,7 +3250,7 @@ function assertEmployeeLimitSync(rosterPath) {
3176
3250
  let count = 0;
3177
3251
  try {
3178
3252
  if (existsSync11(filePath)) {
3179
- const raw = readFileSync8(filePath, "utf8");
3253
+ const raw = readFileSync9(filePath, "utf8");
3180
3254
  const employees = JSON.parse(raw);
3181
3255
  count = Array.isArray(employees) ? employees.length : 0;
3182
3256
  }
@@ -3211,10 +3285,46 @@ var init_plan_limits = __esm({
3211
3285
 
3212
3286
  // src/lib/tmux-routing.ts
3213
3287
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
3214
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
3288
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
3215
3289
  import path14 from "path";
3216
3290
  import os4 from "os";
3217
3291
  import { fileURLToPath as fileURLToPath2 } from "url";
3292
+ import { unlinkSync as unlinkSync3 } from "fs";
3293
+ function spawnLockPath(sessionName) {
3294
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3295
+ }
3296
+ function isProcessAlive(pid) {
3297
+ try {
3298
+ process.kill(pid, 0);
3299
+ return true;
3300
+ } catch {
3301
+ return false;
3302
+ }
3303
+ }
3304
+ function acquireSpawnLock2(sessionName) {
3305
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
3306
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
3307
+ }
3308
+ const lockFile = spawnLockPath(sessionName);
3309
+ if (existsSync12(lockFile)) {
3310
+ try {
3311
+ const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
3312
+ const age = Date.now() - lock.timestamp;
3313
+ if (isProcessAlive(lock.pid) && age < 6e4) {
3314
+ return false;
3315
+ }
3316
+ } catch {
3317
+ }
3318
+ }
3319
+ writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3320
+ return true;
3321
+ }
3322
+ function releaseSpawnLock2(sessionName) {
3323
+ try {
3324
+ unlinkSync3(spawnLockPath(sessionName));
3325
+ } catch {
3326
+ }
3327
+ }
3218
3328
  function resolveBehaviorsExporterScript() {
3219
3329
  try {
3220
3330
  const thisFile = fileURLToPath2(import.meta.url);
@@ -3260,7 +3370,7 @@ function extractRootExe(name) {
3260
3370
  }
3261
3371
  function getParentExe(sessionKey) {
3262
3372
  try {
3263
- const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3373
+ const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3264
3374
  return data.parentExe || null;
3265
3375
  } catch {
3266
3376
  return null;
@@ -3284,17 +3394,17 @@ function isEmployeeAlive(sessionName) {
3284
3394
  }
3285
3395
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
3286
3396
  const base = employeeSessionName(employeeName, exeSession);
3287
- if (!isAlive(base)) return 0;
3397
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
3288
3398
  for (let i = 2; i <= maxInstances; i++) {
3289
3399
  const candidate = employeeSessionName(employeeName, exeSession, i);
3290
- if (!isAlive(candidate)) return i;
3400
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
3291
3401
  }
3292
3402
  return null;
3293
3403
  }
3294
3404
  function readDebounceState() {
3295
3405
  try {
3296
3406
  if (!existsSync12(DEBOUNCE_FILE)) return {};
3297
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
3407
+ return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
3298
3408
  } catch {
3299
3409
  return {};
3300
3410
  }
@@ -3468,7 +3578,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3468
3578
  const claudeJsonPath = path14.join(os4.homedir(), ".claude.json");
3469
3579
  let claudeJson = {};
3470
3580
  try {
3471
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
3581
+ claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
3472
3582
  } catch {
3473
3583
  }
3474
3584
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3486,7 +3596,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3486
3596
  const settingsPath = path14.join(projSettingsDir, "settings.json");
3487
3597
  let settings = {};
3488
3598
  try {
3489
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
3599
+ settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
3490
3600
  } catch {
3491
3601
  }
3492
3602
  const perms = settings.permissions ?? {};
@@ -3599,6 +3709,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3599
3709
  command: spawnCommand
3600
3710
  });
3601
3711
  if (spawnResult.error) {
3712
+ releaseSpawnLock2(sessionName);
3602
3713
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
3603
3714
  }
3604
3715
  transport.pipeLog(sessionName, logFile);
@@ -3636,6 +3747,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3636
3747
  }
3637
3748
  }
3638
3749
  if (!booted) {
3750
+ releaseSpawnLock2(sessionName);
3639
3751
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3640
3752
  }
3641
3753
  if (!useExeAgent) {
@@ -3652,9 +3764,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3652
3764
  pid: 0,
3653
3765
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3654
3766
  });
3767
+ releaseSpawnLock2(sessionName);
3655
3768
  return { sessionName };
3656
3769
  }
3657
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3770
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3658
3771
  var init_tmux_routing = __esm({
3659
3772
  "src/lib/tmux-routing.ts"() {
3660
3773
  "use strict";
@@ -3666,6 +3779,7 @@ var init_tmux_routing = __esm({
3666
3779
  init_provider_table();
3667
3780
  init_intercom_queue();
3668
3781
  init_plan_limits();
3782
+ SPAWN_LOCK_DIR = path14.join(os4.homedir(), ".exe-os", "spawn-locks");
3669
3783
  SESSION_CACHE = path14.join(os4.homedir(), ".exe-os", "session-cache");
3670
3784
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3671
3785
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -3813,7 +3927,7 @@ async function deliverLocalMessage(messageId) {
3813
3927
  return true;
3814
3928
  } catch {
3815
3929
  const newRetryCount = msg.retryCount + 1;
3816
- if (newRetryCount >= MAX_RETRIES2) {
3930
+ if (newRetryCount >= MAX_RETRIES3) {
3817
3931
  await markFailed(messageId, "session unavailable after 10 retries");
3818
3932
  } else {
3819
3933
  await client.execute({
@@ -3902,7 +4016,7 @@ async function retryPendingMessages() {
3902
4016
  sql: `SELECT * FROM messages
3903
4017
  WHERE status = 'pending' AND retry_count < ?
3904
4018
  ORDER BY id`,
3905
- args: [MAX_RETRIES2]
4019
+ args: [MAX_RETRIES3]
3906
4020
  });
3907
4021
  let delivered = 0;
3908
4022
  for (const row of result.rows) {
@@ -3915,13 +4029,13 @@ async function retryPendingMessages() {
3915
4029
  }
3916
4030
  return delivered;
3917
4031
  }
3918
- var MAX_RETRIES2, _wsClientSend;
4032
+ var MAX_RETRIES3, _wsClientSend;
3919
4033
  var init_messaging = __esm({
3920
4034
  "src/lib/messaging.ts"() {
3921
4035
  "use strict";
3922
4036
  init_database();
3923
4037
  init_tmux_routing();
3924
- MAX_RETRIES2 = 10;
4038
+ MAX_RETRIES3 = 10;
3925
4039
  _wsClientSend = null;
3926
4040
  }
3927
4041
  });
@@ -3931,9 +4045,9 @@ import crypto4 from "crypto";
3931
4045
  import path15 from "path";
3932
4046
  import os5 from "os";
3933
4047
  import {
3934
- readFileSync as readFileSync10,
4048
+ readFileSync as readFileSync11,
3935
4049
  readdirSync as readdirSync3,
3936
- unlinkSync as unlinkSync3,
4050
+ unlinkSync as unlinkSync4,
3937
4051
  existsSync as existsSync13,
3938
4052
  rmdirSync
3939
4053
  } from "fs";
@@ -3973,7 +4087,7 @@ import crypto5 from "crypto";
3973
4087
  import path16 from "path";
3974
4088
  import { execSync as execSync8 } from "child_process";
3975
4089
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3976
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
4090
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
3977
4091
  function extractParentFromContext(contextBody) {
3978
4092
  if (!contextBody) return null;
3979
4093
  const match = contextBody.match(
@@ -4163,7 +4277,7 @@ async function ensureGitignoreExe(baseDir) {
4163
4277
  const gitignorePath = path16.join(baseDir, ".gitignore");
4164
4278
  try {
4165
4279
  if (existsSync14(gitignorePath)) {
4166
- const content = readFileSync11(gitignorePath, "utf-8");
4280
+ const content = readFileSync12(gitignorePath, "utf-8");
4167
4281
  if (/^\/?exe\/?$/m.test(content)) return;
4168
4282
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4169
4283
  } else {
@@ -4190,7 +4304,7 @@ __export(tasks_review_exports, {
4190
4304
  listPendingReviews: () => listPendingReviews
4191
4305
  });
4192
4306
  import path17 from "path";
4193
- import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
4307
+ import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as unlinkSync5 } from "fs";
4194
4308
  async function countPendingReviews() {
4195
4309
  const client = getClient();
4196
4310
  const result = await client.execute({
@@ -4404,7 +4518,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4404
4518
  if (existsSync15(cacheDir)) {
4405
4519
  for (const f of readdirSync4(cacheDir)) {
4406
4520
  if (f.startsWith("review-notified-")) {
4407
- unlinkSync4(path17.join(cacheDir, f));
4521
+ unlinkSync5(path17.join(cacheDir, f));
4408
4522
  }
4409
4523
  }
4410
4524
  }
@@ -4429,7 +4543,7 @@ init_config();
4429
4543
  init_config();
4430
4544
  init_store();
4431
4545
  import { spawn as spawn2 } from "child_process";
4432
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, existsSync as existsSync16, openSync as openSync2, closeSync as closeSync2 } from "fs";
4546
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, existsSync as existsSync16, openSync as openSync2, closeSync as closeSync2 } from "fs";
4433
4547
  import path18 from "path";
4434
4548
  import { fileURLToPath as fileURLToPath3 } from "url";
4435
4549
 
@@ -4838,7 +4952,7 @@ function getActiveAgent() {
4838
4952
  "tmux display-message -p '#{session_name}' 2>/dev/null",
4839
4953
  { encoding: "utf8", timeout: 2e3 }
4840
4954
  ).trim();
4841
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
4955
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
4842
4956
  if (empMatch && empMatch[1] !== "exe") {
4843
4957
  return { agentId: empMatch[1], agentRole: "employee" };
4844
4958
  }
@@ -4872,7 +4986,7 @@ function openWorkerLog() {
4872
4986
  var CACHE_DIR2 = path18.join(EXE_AI_DIR, "session-cache");
4873
4987
  function loadInjectedIds(sessionId) {
4874
4988
  try {
4875
- const raw = readFileSync12(path18.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
4989
+ const raw = readFileSync13(path18.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
4876
4990
  return new Set(JSON.parse(raw));
4877
4991
  } catch {
4878
4992
  return /* @__PURE__ */ new Set();
@@ -4967,7 +5081,7 @@ ${fresh.map(
4967
5081
  const lastCheckPath = path18.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
4968
5082
  let lastCheckedAt = "";
4969
5083
  try {
4970
- lastCheckedAt = readFileSync12(lastCheckPath, "utf8").trim();
5084
+ lastCheckedAt = readFileSync13(lastCheckPath, "utf8").trim();
4971
5085
  } catch {
4972
5086
  }
4973
5087
  const totalCount = await countPendingReviews2();