@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,6 +277,61 @@ 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
  var database_exports = {};
282
337
  __export(database_exports, {
@@ -284,6 +339,7 @@ __export(database_exports, {
284
339
  disposeTurso: () => disposeTurso,
285
340
  ensureSchema: () => ensureSchema,
286
341
  getClient: () => getClient,
342
+ getRawClient: () => getRawClient,
287
343
  initDatabase: () => initDatabase,
288
344
  initTurso: () => initTurso,
289
345
  isInitialized: () => isInitialized
@@ -293,6 +349,7 @@ async function initDatabase(config) {
293
349
  if (_client) {
294
350
  _client.close();
295
351
  _client = null;
352
+ _resilientClient = null;
296
353
  }
297
354
  const opts = {
298
355
  url: `file:${config.dbPath}`
@@ -301,20 +358,27 @@ async function initDatabase(config) {
301
358
  opts.encryptionKey = config.encryptionKey;
302
359
  }
303
360
  _client = createClient(opts);
361
+ _resilientClient = wrapWithRetry(_client);
304
362
  }
305
363
  function isInitialized() {
306
364
  return _client !== null;
307
365
  }
308
366
  function getClient() {
367
+ if (!_resilientClient) {
368
+ throw new Error("Database client not initialized. Call initDatabase() first.");
369
+ }
370
+ return _resilientClient;
371
+ }
372
+ function getRawClient() {
309
373
  if (!_client) {
310
374
  throw new Error("Database client not initialized. Call initDatabase() first.");
311
375
  }
312
376
  return _client;
313
377
  }
314
378
  async function ensureSchema() {
315
- const client = getClient();
379
+ const client = getRawClient();
316
380
  await client.execute("PRAGMA journal_mode = WAL");
317
- await client.execute("PRAGMA busy_timeout = 5000");
381
+ await client.execute("PRAGMA busy_timeout = 30000");
318
382
  try {
319
383
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
320
384
  } catch {
@@ -1107,13 +1171,16 @@ async function disposeDatabase() {
1107
1171
  if (_client) {
1108
1172
  _client.close();
1109
1173
  _client = null;
1174
+ _resilientClient = null;
1110
1175
  }
1111
1176
  }
1112
- var _client, initTurso, disposeTurso;
1177
+ var _client, _resilientClient, initTurso, disposeTurso;
1113
1178
  var init_database = __esm({
1114
1179
  "src/lib/database.ts"() {
1115
1180
  "use strict";
1181
+ init_db_retry();
1116
1182
  _client = null;
1183
+ _resilientClient = null;
1117
1184
  initTurso = initDatabase;
1118
1185
  disposeTurso = disposeDatabase;
1119
1186
  }
@@ -1323,7 +1390,7 @@ function listShards() {
1323
1390
  }
1324
1391
  async function ensureShardSchema(client) {
1325
1392
  await client.execute("PRAGMA journal_mode = WAL");
1326
- await client.execute("PRAGMA busy_timeout = 5000");
1393
+ await client.execute("PRAGMA busy_timeout = 30000");
1327
1394
  try {
1328
1395
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1329
1396
  } catch {
@@ -1584,7 +1651,8 @@ async function writeMemory(record) {
1584
1651
  has_error: record.has_error ? 1 : 0,
1585
1652
  raw_text: record.raw_text,
1586
1653
  vector: record.vector,
1587
- version: _nextVersion++,
1654
+ version: 0,
1655
+ // Placeholder — assigned atomically at flush time
1588
1656
  task_id: record.task_id ?? null,
1589
1657
  importance: record.importance ?? 5,
1590
1658
  status: record.status ?? "active",
@@ -1618,6 +1686,13 @@ async function flushBatch() {
1618
1686
  _flushing = true;
1619
1687
  try {
1620
1688
  const batch = _pendingRecords.slice(0);
1689
+ const client = getClient();
1690
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1691
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1692
+ for (const row of batch) {
1693
+ row.version = baseVersion++;
1694
+ }
1695
+ _nextVersion = baseVersion;
1621
1696
  const buildStmt = (row) => {
1622
1697
  const hasVector = row.vector !== null;
1623
1698
  const taskId = row.task_id ?? null;
@@ -2296,8 +2371,8 @@ function drainQueue(isSessionBusy2, sendKeys) {
2296
2371
  } catch {
2297
2372
  }
2298
2373
  item.attempts++;
2299
- if (item.attempts >= MAX_RETRIES) {
2300
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
2374
+ if (item.attempts >= MAX_RETRIES2) {
2375
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
2301
2376
  failed++;
2302
2377
  continue;
2303
2378
  }
@@ -2330,12 +2405,12 @@ function logQueue(msg) {
2330
2405
  } catch {
2331
2406
  }
2332
2407
  }
2333
- var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
2408
+ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
2334
2409
  var init_intercom_queue = __esm({
2335
2410
  "src/lib/intercom-queue.ts"() {
2336
2411
  "use strict";
2337
2412
  QUEUE_PATH = path5.join(os3.homedir(), ".exe-os", "intercom-queue.json");
2338
- MAX_RETRIES = 5;
2413
+ MAX_RETRIES2 = 5;
2339
2414
  TTL_MS = 60 * 60 * 1e3;
2340
2415
  INTERCOM_LOG = path5.join(os3.homedir(), ".exe-os", "intercom.log");
2341
2416
  }
@@ -2343,7 +2418,7 @@ var init_intercom_queue = __esm({
2343
2418
 
2344
2419
  // src/lib/employees.ts
2345
2420
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2346
- import { existsSync as existsSync6, symlinkSync, readlinkSync } from "fs";
2421
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
2347
2422
  import { execSync as execSync4 } from "child_process";
2348
2423
  import path6 from "path";
2349
2424
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
@@ -2357,20 +2432,35 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
2357
2432
  return [];
2358
2433
  }
2359
2434
  }
2435
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
2436
+ if (!existsSync6(employeesPath)) return [];
2437
+ try {
2438
+ return JSON.parse(readFileSync4(employeesPath, "utf-8"));
2439
+ } catch {
2440
+ return [];
2441
+ }
2442
+ }
2360
2443
  function getEmployee(employees, name) {
2361
2444
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
2362
2445
  }
2363
- var EMPLOYEES_PATH;
2446
+ function isMultiInstance(agentName, employees) {
2447
+ const roster = employees ?? loadEmployeesSync();
2448
+ const emp = getEmployee(roster, agentName);
2449
+ if (!emp) return false;
2450
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
2451
+ }
2452
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
2364
2453
  var init_employees = __esm({
2365
2454
  "src/lib/employees.ts"() {
2366
2455
  "use strict";
2367
2456
  init_config();
2368
2457
  EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
2458
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
2369
2459
  }
2370
2460
  });
2371
2461
 
2372
2462
  // src/lib/license.ts
2373
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2463
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2374
2464
  import { randomUUID } from "crypto";
2375
2465
  import path7 from "path";
2376
2466
  import { jwtVerify, importSPKI } from "jose";
@@ -2393,12 +2483,12 @@ var init_license = __esm({
2393
2483
  });
2394
2484
 
2395
2485
  // src/lib/plan-limits.ts
2396
- import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
2486
+ import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
2397
2487
  import path8 from "path";
2398
2488
  function getLicenseSync() {
2399
2489
  try {
2400
2490
  if (!existsSync8(CACHE_PATH2)) return freeLicense();
2401
- const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
2491
+ const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
2402
2492
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2403
2493
  const parts = raw.token.split(".");
2404
2494
  if (parts.length !== 3) return freeLicense();
@@ -2437,7 +2527,7 @@ function assertEmployeeLimitSync(rosterPath) {
2437
2527
  let count = 0;
2438
2528
  try {
2439
2529
  if (existsSync8(filePath)) {
2440
- const raw = readFileSync5(filePath, "utf8");
2530
+ const raw = readFileSync6(filePath, "utf8");
2441
2531
  const employees = JSON.parse(raw);
2442
2532
  count = Array.isArray(employees) ? employees.length : 0;
2443
2533
  }
@@ -2475,7 +2565,7 @@ import crypto2 from "crypto";
2475
2565
  import path9 from "path";
2476
2566
  import os4 from "os";
2477
2567
  import {
2478
- readFileSync as readFileSync6,
2568
+ readFileSync as readFileSync7,
2479
2569
  readdirSync,
2480
2570
  unlinkSync,
2481
2571
  existsSync as existsSync9,
@@ -2631,7 +2721,7 @@ import crypto4 from "crypto";
2631
2721
  import path10 from "path";
2632
2722
  import { execSync as execSync5 } from "child_process";
2633
2723
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
2634
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
2724
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
2635
2725
  async function writeCheckpoint(input) {
2636
2726
  const client = getClient();
2637
2727
  const row = await resolveTask(client, input.taskId);
@@ -3008,7 +3098,7 @@ async function ensureGitignoreExe(baseDir) {
3008
3098
  const gitignorePath = path10.join(baseDir, ".gitignore");
3009
3099
  try {
3010
3100
  if (existsSync10(gitignorePath)) {
3011
- const content = readFileSync7(gitignorePath, "utf-8");
3101
+ const content = readFileSync8(gitignorePath, "utf-8");
3012
3102
  if (/^\/?exe\/?$/m.test(content)) return;
3013
3103
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3014
3104
  } else {
@@ -3483,7 +3573,7 @@ async function dispatchTaskToEmployee(input) {
3483
3573
  } else {
3484
3574
  const projectDir = input.projectDir ?? process.cwd();
3485
3575
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
3486
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
3576
+ autoInstance: isMultiInstance(input.assignedTo)
3487
3577
  });
3488
3578
  if (result.status === "failed") {
3489
3579
  process.stderr.write(
@@ -3518,6 +3608,7 @@ var init_tasks_notify = __esm({
3518
3608
  init_session_key();
3519
3609
  init_notifications();
3520
3610
  init_transport();
3611
+ init_employees();
3521
3612
  }
3522
3613
  });
3523
3614
 
@@ -4271,6 +4362,7 @@ var init_capacity_monitor = __esm({
4271
4362
  // src/lib/tmux-routing.ts
4272
4363
  var tmux_routing_exports = {};
4273
4364
  __export(tmux_routing_exports, {
4365
+ acquireSpawnLock: () => acquireSpawnLock,
4274
4366
  employeeSessionName: () => employeeSessionName,
4275
4367
  ensureEmployee: () => ensureEmployee,
4276
4368
  extractRootExe: () => extractRootExe,
@@ -4285,16 +4377,53 @@ __export(tmux_routing_exports, {
4285
4377
  notifyParentExe: () => notifyParentExe,
4286
4378
  parseParentExe: () => parseParentExe,
4287
4379
  registerParentExe: () => registerParentExe,
4380
+ releaseSpawnLock: () => releaseSpawnLock,
4288
4381
  resolveExeSession: () => resolveExeSession,
4289
4382
  sendIntercom: () => sendIntercom,
4290
4383
  spawnEmployee: () => spawnEmployee,
4291
4384
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4292
4385
  });
4293
4386
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4294
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4387
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4295
4388
  import path15 from "path";
4296
4389
  import os5 from "os";
4297
4390
  import { fileURLToPath } from "url";
4391
+ import { unlinkSync as unlinkSync4 } from "fs";
4392
+ function spawnLockPath(sessionName) {
4393
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4394
+ }
4395
+ function isProcessAlive(pid) {
4396
+ try {
4397
+ process.kill(pid, 0);
4398
+ return true;
4399
+ } catch {
4400
+ return false;
4401
+ }
4402
+ }
4403
+ function acquireSpawnLock(sessionName) {
4404
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4405
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4406
+ }
4407
+ const lockFile = spawnLockPath(sessionName);
4408
+ if (existsSync12(lockFile)) {
4409
+ try {
4410
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
4411
+ const age = Date.now() - lock.timestamp;
4412
+ if (isProcessAlive(lock.pid) && age < 6e4) {
4413
+ return false;
4414
+ }
4415
+ } catch {
4416
+ }
4417
+ }
4418
+ writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4419
+ return true;
4420
+ }
4421
+ function releaseSpawnLock(sessionName) {
4422
+ try {
4423
+ unlinkSync4(spawnLockPath(sessionName));
4424
+ } catch {
4425
+ }
4426
+ }
4298
4427
  function resolveBehaviorsExporterScript() {
4299
4428
  try {
4300
4429
  const thisFile = fileURLToPath(import.meta.url);
@@ -4358,7 +4487,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4358
4487
  }
4359
4488
  function getParentExe(sessionKey) {
4360
4489
  try {
4361
- const data = JSON.parse(readFileSync8(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4490
+ const data = JSON.parse(readFileSync9(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4362
4491
  return data.parentExe || null;
4363
4492
  } catch {
4364
4493
  return null;
@@ -4366,7 +4495,7 @@ function getParentExe(sessionKey) {
4366
4495
  }
4367
4496
  function getDispatchedBy(sessionKey) {
4368
4497
  try {
4369
- const data = JSON.parse(readFileSync8(
4498
+ const data = JSON.parse(readFileSync9(
4370
4499
  path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4371
4500
  "utf8"
4372
4501
  ));
@@ -4393,10 +4522,10 @@ function isEmployeeAlive(sessionName) {
4393
4522
  }
4394
4523
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
4395
4524
  const base = employeeSessionName(employeeName, exeSession);
4396
- if (!isAlive(base)) return 0;
4525
+ if (!isAlive(base) && acquireSpawnLock(base)) return 0;
4397
4526
  for (let i = 2; i <= maxInstances; i++) {
4398
4527
  const candidate = employeeSessionName(employeeName, exeSession, i);
4399
- if (!isAlive(candidate)) return i;
4528
+ if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
4400
4529
  }
4401
4530
  return null;
4402
4531
  }
@@ -4429,7 +4558,7 @@ async function verifyPaneAtCapacity(sessionName) {
4429
4558
  function readDebounceState() {
4430
4559
  try {
4431
4560
  if (!existsSync12(DEBOUNCE_FILE)) return {};
4432
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
4561
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
4433
4562
  } catch {
4434
4563
  return {};
4435
4564
  }
@@ -4629,7 +4758,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4629
4758
  const claudeJsonPath = path15.join(os5.homedir(), ".claude.json");
4630
4759
  let claudeJson = {};
4631
4760
  try {
4632
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
4761
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
4633
4762
  } catch {
4634
4763
  }
4635
4764
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4647,7 +4776,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4647
4776
  const settingsPath = path15.join(projSettingsDir, "settings.json");
4648
4777
  let settings = {};
4649
4778
  try {
4650
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
4779
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
4651
4780
  } catch {
4652
4781
  }
4653
4782
  const perms = settings.permissions ?? {};
@@ -4760,6 +4889,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4760
4889
  command: spawnCommand
4761
4890
  });
4762
4891
  if (spawnResult.error) {
4892
+ releaseSpawnLock(sessionName);
4763
4893
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4764
4894
  }
4765
4895
  transport.pipeLog(sessionName, logFile);
@@ -4797,6 +4927,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4797
4927
  }
4798
4928
  }
4799
4929
  if (!booted) {
4930
+ releaseSpawnLock(sessionName);
4800
4931
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4801
4932
  }
4802
4933
  if (!useExeAgent) {
@@ -4813,9 +4944,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4813
4944
  pid: 0,
4814
4945
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4815
4946
  });
4947
+ releaseSpawnLock(sessionName);
4816
4948
  return { sessionName };
4817
4949
  }
4818
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
4950
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
4819
4951
  var init_tmux_routing = __esm({
4820
4952
  "src/lib/tmux-routing.ts"() {
4821
4953
  "use strict";
@@ -4827,6 +4959,7 @@ var init_tmux_routing = __esm({
4827
4959
  init_provider_table();
4828
4960
  init_intercom_queue();
4829
4961
  init_plan_limits();
4962
+ SPAWN_LOCK_DIR = path15.join(os5.homedir(), ".exe-os", "spawn-locks");
4830
4963
  SESSION_CACHE = path15.join(os5.homedir(), ".exe-os", "session-cache");
4831
4964
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4832
4965
  VERIFY_PANE_LINES = 200;
@@ -5319,7 +5452,7 @@ __export(agent_signals_exports, {
5319
5452
  hasOpenTasks: () => hasOpenTasks,
5320
5453
  hasUnreadInbox: () => hasUnreadInbox
5321
5454
  });
5322
- import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
5455
+ import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
5323
5456
  import os6 from "os";
5324
5457
  import path16 from "path";
5325
5458
  async function hasOpenTasks(client, agentId) {
@@ -5364,7 +5497,7 @@ async function hasUnreadInbox(client, agentId) {
5364
5497
  function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path16.join(os6.homedir(), ".exe-os", "intercom.log")) {
5365
5498
  if (!existsSync13(intercomLog)) return false;
5366
5499
  try {
5367
- const raw = readFileSync9(intercomLog, "utf8");
5500
+ const raw = readFileSync10(intercomLog, "utf8");
5368
5501
  const lines = raw.split("\n");
5369
5502
  for (let i = lines.length - 1; i >= 0; i--) {
5370
5503
  const line = lines[i];
@@ -5796,11 +5929,11 @@ ${synthesisText}`;
5796
5929
  }
5797
5930
  const insertSql = vector ? `INSERT INTO memories
5798
5931
  (id, agent_id, agent_role, session_id, timestamp,
5799
- tool_name, project_name, has_error, raw_text, vector, version, consolidated)
5800
- VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1)` : `INSERT INTO memories
5932
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
5933
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1, 9)` : `INSERT INTO memories
5801
5934
  (id, agent_id, agent_role, session_id, timestamp,
5802
- tool_name, project_name, has_error, raw_text, vector, version, consolidated)
5803
- VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1)`;
5935
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
5936
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1, 9)`;
5804
5937
  const insertArgs = vector ? [consolidatedId, cluster.agentId, now, cluster.projectName, rawText, vectorToBlob(vector)] : [consolidatedId, cluster.agentId, now, cluster.projectName, rawText];
5805
5938
  await client.execute({ sql: insertSql, args: insertArgs });
5806
5939
  const sourceIds = cluster.memories.map((m) => m.id);
@@ -6047,7 +6180,7 @@ var init_consolidation = __esm({
6047
6180
  import net from "net";
6048
6181
  import { spawn } from "child_process";
6049
6182
  import { randomUUID as randomUUID3 } from "crypto";
6050
- import { existsSync as existsSync14, unlinkSync as unlinkSync4, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
6183
+ import { existsSync as existsSync14, unlinkSync as unlinkSync5, readFileSync as readFileSync11, openSync, closeSync, statSync } from "fs";
6051
6184
  import path17 from "path";
6052
6185
  import { fileURLToPath as fileURLToPath2 } from "url";
6053
6186
  function handleData(chunk) {
@@ -6072,7 +6205,7 @@ function handleData(chunk) {
6072
6205
  function cleanupStaleFiles() {
6073
6206
  if (existsSync14(PID_PATH)) {
6074
6207
  try {
6075
- const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
6208
+ const pid = parseInt(readFileSync11(PID_PATH, "utf8").trim(), 10);
6076
6209
  if (pid > 0) {
6077
6210
  try {
6078
6211
  process.kill(pid, 0);
@@ -6083,11 +6216,11 @@ function cleanupStaleFiles() {
6083
6216
  } catch {
6084
6217
  }
6085
6218
  try {
6086
- unlinkSync4(PID_PATH);
6219
+ unlinkSync5(PID_PATH);
6087
6220
  } catch {
6088
6221
  }
6089
6222
  try {
6090
- unlinkSync4(SOCKET_PATH);
6223
+ unlinkSync5(SOCKET_PATH);
6091
6224
  } catch {
6092
6225
  }
6093
6226
  }
@@ -6139,7 +6272,7 @@ function spawnDaemon() {
6139
6272
  }
6140
6273
  }
6141
6274
  }
6142
- function acquireSpawnLock() {
6275
+ function acquireSpawnLock2() {
6143
6276
  try {
6144
6277
  const fd = openSync(SPAWN_LOCK_PATH, "wx");
6145
6278
  closeSync(fd);
@@ -6149,7 +6282,7 @@ function acquireSpawnLock() {
6149
6282
  const stat = statSync(SPAWN_LOCK_PATH);
6150
6283
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
6151
6284
  try {
6152
- unlinkSync4(SPAWN_LOCK_PATH);
6285
+ unlinkSync5(SPAWN_LOCK_PATH);
6153
6286
  } catch {
6154
6287
  }
6155
6288
  try {
@@ -6164,9 +6297,9 @@ function acquireSpawnLock() {
6164
6297
  return false;
6165
6298
  }
6166
6299
  }
6167
- function releaseSpawnLock() {
6300
+ function releaseSpawnLock2() {
6168
6301
  try {
6169
- unlinkSync4(SPAWN_LOCK_PATH);
6302
+ unlinkSync5(SPAWN_LOCK_PATH);
6170
6303
  } catch {
6171
6304
  }
6172
6305
  }
@@ -6211,20 +6344,20 @@ function connectToSocket() {
6211
6344
  async function connectEmbedDaemon() {
6212
6345
  if (_socket && _connected) return true;
6213
6346
  if (await connectToSocket()) return true;
6214
- if (acquireSpawnLock()) {
6347
+ if (acquireSpawnLock2()) {
6215
6348
  try {
6216
6349
  cleanupStaleFiles();
6217
6350
  spawnDaemon();
6218
6351
  } finally {
6219
- releaseSpawnLock();
6352
+ releaseSpawnLock2();
6220
6353
  }
6221
6354
  }
6222
6355
  const start = Date.now();
6223
- let delay = 100;
6356
+ let delay2 = 100;
6224
6357
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
6225
- await new Promise((r) => setTimeout(r, delay));
6358
+ await new Promise((r) => setTimeout(r, delay2));
6226
6359
  if (await connectToSocket()) return true;
6227
- delay = Math.min(delay * 2, 3e3);
6360
+ delay2 = Math.min(delay2 * 2, 3e3);
6228
6361
  }
6229
6362
  return false;
6230
6363
  }
@@ -6280,7 +6413,7 @@ function killAndRespawnDaemon() {
6280
6413
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
6281
6414
  if (existsSync14(PID_PATH)) {
6282
6415
  try {
6283
- const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
6416
+ const pid = parseInt(readFileSync11(PID_PATH, "utf8").trim(), 10);
6284
6417
  if (pid > 0) {
6285
6418
  try {
6286
6419
  process.kill(pid, "SIGKILL");
@@ -6297,11 +6430,11 @@ function killAndRespawnDaemon() {
6297
6430
  _connected = false;
6298
6431
  _buffer = "";
6299
6432
  try {
6300
- unlinkSync4(PID_PATH);
6433
+ unlinkSync5(PID_PATH);
6301
6434
  } catch {
6302
6435
  }
6303
6436
  try {
6304
- unlinkSync4(SOCKET_PATH);
6437
+ unlinkSync5(SOCKET_PATH);
6305
6438
  } catch {
6306
6439
  }
6307
6440
  spawnDaemon();
@@ -6316,11 +6449,11 @@ async function embedViaClient(text, priority = "high") {
6316
6449
  `);
6317
6450
  killAndRespawnDaemon();
6318
6451
  const start = Date.now();
6319
- let delay = 200;
6452
+ let delay2 = 200;
6320
6453
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
6321
- await new Promise((r) => setTimeout(r, delay));
6454
+ await new Promise((r) => setTimeout(r, delay2));
6322
6455
  if (await connectToSocket()) break;
6323
- delay = Math.min(delay * 2, 3e3);
6456
+ delay2 = Math.min(delay2 * 2, 3e3);
6324
6457
  }
6325
6458
  if (!_connected) return null;
6326
6459
  }
@@ -6332,11 +6465,11 @@ async function embedViaClient(text, priority = "high") {
6332
6465
  `);
6333
6466
  killAndRespawnDaemon();
6334
6467
  const start = Date.now();
6335
- let delay = 200;
6468
+ let delay2 = 200;
6336
6469
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
6337
- await new Promise((r) => setTimeout(r, delay));
6470
+ await new Promise((r) => setTimeout(r, delay2));
6338
6471
  if (await connectToSocket()) break;
6339
- delay = Math.min(delay * 2, 3e3);
6472
+ delay2 = Math.min(delay2 * 2, 3e3);
6340
6473
  }
6341
6474
  if (!_connected) return null;
6342
6475
  const retry = await sendRequest([text], priority);
@@ -7232,11 +7365,11 @@ __export(update_check_exports, {
7232
7365
  getRemoteVersion: () => getRemoteVersion
7233
7366
  });
7234
7367
  import { execSync as execSync11 } from "child_process";
7235
- import { readFileSync as readFileSync11 } from "fs";
7368
+ import { readFileSync as readFileSync12 } from "fs";
7236
7369
  import path18 from "path";
7237
7370
  function getLocalVersion(packageRoot) {
7238
7371
  const pkgPath = path18.join(packageRoot, "package.json");
7239
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
7372
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7240
7373
  return pkg.version;
7241
7374
  }
7242
7375
  function getRemoteVersion() {
@@ -7308,12 +7441,12 @@ __export(device_registry_exports, {
7308
7441
  });
7309
7442
  import crypto9 from "crypto";
7310
7443
  import os7 from "os";
7311
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, existsSync as existsSync15 } from "fs";
7444
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, existsSync as existsSync15 } from "fs";
7312
7445
  import path19 from "path";
7313
7446
  function getDeviceInfo() {
7314
7447
  if (existsSync15(DEVICE_JSON_PATH)) {
7315
7448
  try {
7316
- const raw = readFileSync12(DEVICE_JSON_PATH, "utf8");
7449
+ const raw = readFileSync13(DEVICE_JSON_PATH, "utf8");
7317
7450
  const data = JSON.parse(raw);
7318
7451
  if (data.deviceId && data.friendlyName && data.hostname) {
7319
7452
  return data;
@@ -7691,7 +7824,7 @@ async function deliverLocalMessage(messageId) {
7691
7824
  return true;
7692
7825
  } catch {
7693
7826
  const newRetryCount = msg.retryCount + 1;
7694
- if (newRetryCount >= MAX_RETRIES2) {
7827
+ if (newRetryCount >= MAX_RETRIES3) {
7695
7828
  await markFailed(messageId, "session unavailable after 10 retries");
7696
7829
  } else {
7697
7830
  await client.execute({
@@ -7780,7 +7913,7 @@ async function retryPendingMessages() {
7780
7913
  sql: `SELECT * FROM messages
7781
7914
  WHERE status = 'pending' AND retry_count < ?
7782
7915
  ORDER BY id`,
7783
- args: [MAX_RETRIES2]
7916
+ args: [MAX_RETRIES3]
7784
7917
  });
7785
7918
  let delivered = 0;
7786
7919
  for (const row of result.rows) {
@@ -7793,13 +7926,13 @@ async function retryPendingMessages() {
7793
7926
  }
7794
7927
  return delivered;
7795
7928
  }
7796
- var MAX_RETRIES2, _wsClientSend;
7929
+ var MAX_RETRIES3, _wsClientSend;
7797
7930
  var init_messaging = __esm({
7798
7931
  "src/lib/messaging.ts"() {
7799
7932
  "use strict";
7800
7933
  init_database();
7801
7934
  init_tmux_routing();
7802
- MAX_RETRIES2 = 10;
7935
+ MAX_RETRIES3 = 10;
7803
7936
  _wsClientSend = null;
7804
7937
  }
7805
7938
  });
@@ -7808,7 +7941,7 @@ var init_messaging = __esm({
7808
7941
  init_config();
7809
7942
  init_memory();
7810
7943
  import net2 from "net";
7811
- import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync5, mkdirSync as mkdirSync8, existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
7944
+ import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync6, mkdirSync as mkdirSync8, existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
7812
7945
  import path20 from "path";
7813
7946
  import { getLlama } from "node-llama-cpp";
7814
7947
  var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path20.join(EXE_AI_DIR, "exed.sock");
@@ -7913,11 +8046,11 @@ async function shutdown() {
7913
8046
  }
7914
8047
  _llama = null;
7915
8048
  try {
7916
- unlinkSync5(SOCKET_PATH2);
8049
+ unlinkSync6(SOCKET_PATH2);
7917
8050
  } catch {
7918
8051
  }
7919
8052
  try {
7920
- unlinkSync5(PID_PATH2);
8053
+ unlinkSync6(PID_PATH2);
7921
8054
  } catch {
7922
8055
  }
7923
8056
  process.stderr.write("[exed] Shutdown complete.\n");
@@ -7955,18 +8088,18 @@ function startServer() {
7955
8088
  const oldPath = path20.join(path20.dirname(SOCKET_PATH2), oldFile);
7956
8089
  try {
7957
8090
  if (oldFile.endsWith(".pid")) {
7958
- const pid = parseInt(readFileSync13(oldPath, "utf8").trim(), 10);
8091
+ const pid = parseInt(readFileSync14(oldPath, "utf8").trim(), 10);
7959
8092
  if (pid > 0) try {
7960
8093
  process.kill(pid, "SIGKILL");
7961
8094
  } catch {
7962
8095
  }
7963
8096
  }
7964
- unlinkSync5(oldPath);
8097
+ unlinkSync6(oldPath);
7965
8098
  } catch {
7966
8099
  }
7967
8100
  }
7968
8101
  try {
7969
- unlinkSync5(SOCKET_PATH2);
8102
+ unlinkSync6(SOCKET_PATH2);
7970
8103
  } catch {
7971
8104
  }
7972
8105
  const server = net2.createServer((socket) => {
@@ -8379,7 +8512,7 @@ process.on("SIGTERM", () => void shutdown());
8379
8512
  function checkExistingDaemon() {
8380
8513
  try {
8381
8514
  if (!existsSync16(PID_PATH2)) return false;
8382
- const pid = parseInt(readFileSync13(PID_PATH2, "utf8").trim(), 10);
8515
+ const pid = parseInt(readFileSync14(PID_PATH2, "utf8").trim(), 10);
8383
8516
  if (!pid || isNaN(pid)) return false;
8384
8517
  process.kill(pid, 0);
8385
8518
  process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
@@ -8392,7 +8525,7 @@ function checkExistingDaemon() {
8392
8525
  return true;
8393
8526
  }
8394
8527
  try {
8395
- unlinkSync5(PID_PATH2);
8528
+ unlinkSync6(PID_PATH2);
8396
8529
  } catch {
8397
8530
  }
8398
8531
  return false;
@@ -8555,11 +8688,11 @@ try {
8555
8688
  process.stderr.write(`[exed] FATAL: ${err instanceof Error ? err.message : String(err)}
8556
8689
  `);
8557
8690
  try {
8558
- unlinkSync5(SOCKET_PATH2);
8691
+ unlinkSync6(SOCKET_PATH2);
8559
8692
  } catch {
8560
8693
  }
8561
8694
  try {
8562
- unlinkSync5(PID_PATH2);
8695
+ unlinkSync6(PID_PATH2);
8563
8696
  } catch {
8564
8697
  }
8565
8698
  process.exit(1);