@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
@@ -317,19 +317,27 @@ var init_intercom_queue = __esm({
317
317
  }
318
318
  });
319
319
 
320
+ // src/lib/db-retry.ts
321
+ var init_db_retry = __esm({
322
+ "src/lib/db-retry.ts"() {
323
+ "use strict";
324
+ }
325
+ });
326
+
320
327
  // src/lib/database.ts
321
328
  import { createClient } from "@libsql/client";
322
329
  function getClient() {
323
- if (!_client) {
330
+ if (!_resilientClient) {
324
331
  throw new Error("Database client not initialized. Call initDatabase() first.");
325
332
  }
326
- return _client;
333
+ return _resilientClient;
327
334
  }
328
- var _client;
335
+ var _resilientClient;
329
336
  var init_database = __esm({
330
337
  "src/lib/database.ts"() {
331
338
  "use strict";
332
- _client = null;
339
+ init_db_retry();
340
+ _resilientClient = null;
333
341
  }
334
342
  });
335
343
 
@@ -524,20 +532,38 @@ var init_config = __esm({
524
532
 
525
533
  // src/lib/employees.ts
526
534
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
527
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
535
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
528
536
  import { execSync as execSync3 } from "child_process";
529
537
  import path4 from "path";
530
- var EMPLOYEES_PATH;
538
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
539
+ if (!existsSync4(employeesPath)) return [];
540
+ try {
541
+ return JSON.parse(readFileSync4(employeesPath, "utf-8"));
542
+ } catch {
543
+ return [];
544
+ }
545
+ }
546
+ function getEmployee(employees, name) {
547
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
548
+ }
549
+ function isMultiInstance(agentName, employees) {
550
+ const roster = employees ?? loadEmployeesSync();
551
+ const emp = getEmployee(roster, agentName);
552
+ if (!emp) return false;
553
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
554
+ }
555
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
531
556
  var init_employees = __esm({
532
557
  "src/lib/employees.ts"() {
533
558
  "use strict";
534
559
  init_config();
535
560
  EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
561
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
536
562
  }
537
563
  });
538
564
 
539
565
  // src/lib/license.ts
540
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
566
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
541
567
  import { randomUUID } from "crypto";
542
568
  import path5 from "path";
543
569
  import { jwtVerify, importSPKI } from "jose";
@@ -560,12 +586,12 @@ var init_license = __esm({
560
586
  });
561
587
 
562
588
  // src/lib/plan-limits.ts
563
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
589
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
564
590
  import path6 from "path";
565
591
  function getLicenseSync() {
566
592
  try {
567
593
  if (!existsSync6(CACHE_PATH2)) return freeLicense();
568
- const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
594
+ const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
569
595
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
570
596
  const parts = raw.token.split(".");
571
597
  if (parts.length !== 3) return freeLicense();
@@ -604,7 +630,7 @@ function assertEmployeeLimitSync(rosterPath) {
604
630
  let count = 0;
605
631
  try {
606
632
  if (existsSync6(filePath)) {
607
- const raw = readFileSync5(filePath, "utf8");
633
+ const raw = readFileSync6(filePath, "utf8");
608
634
  const employees = JSON.parse(raw);
609
635
  count = Array.isArray(employees) ? employees.length : 0;
610
636
  }
@@ -642,7 +668,7 @@ import crypto from "crypto";
642
668
  import path7 from "path";
643
669
  import os4 from "os";
644
670
  import {
645
- readFileSync as readFileSync6,
671
+ readFileSync as readFileSync7,
646
672
  readdirSync,
647
673
  unlinkSync,
648
674
  existsSync as existsSync7,
@@ -728,7 +754,7 @@ import crypto3 from "crypto";
728
754
  import path8 from "path";
729
755
  import { execSync as execSync4 } from "child_process";
730
756
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
731
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
757
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
732
758
  async function writeCheckpoint(input) {
733
759
  const client = getClient();
734
760
  const row = await resolveTask(client, input.taskId);
@@ -1105,7 +1131,7 @@ async function ensureGitignoreExe(baseDir) {
1105
1131
  const gitignorePath = path8.join(baseDir, ".gitignore");
1106
1132
  try {
1107
1133
  if (existsSync8(gitignorePath)) {
1108
- const content = readFileSync7(gitignorePath, "utf-8");
1134
+ const content = readFileSync8(gitignorePath, "utf-8");
1109
1135
  if (/^\/?exe\/?$/m.test(content)) return;
1110
1136
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
1111
1137
  } else {
@@ -1481,7 +1507,7 @@ async function dispatchTaskToEmployee(input) {
1481
1507
  } else {
1482
1508
  const projectDir = input.projectDir ?? process.cwd();
1483
1509
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
1484
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
1510
+ autoInstance: isMultiInstance(input.assignedTo)
1485
1511
  });
1486
1512
  if (result.status === "failed") {
1487
1513
  process.stderr.write(
@@ -1516,6 +1542,7 @@ var init_tasks_notify = __esm({
1516
1542
  init_session_key();
1517
1543
  init_notifications();
1518
1544
  init_transport();
1545
+ init_employees();
1519
1546
  }
1520
1547
  });
1521
1548
 
@@ -2268,10 +2295,46 @@ var init_capacity_monitor = __esm({
2268
2295
 
2269
2296
  // src/lib/tmux-routing.ts
2270
2297
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2271
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
2298
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
2272
2299
  import path13 from "path";
2273
2300
  import os5 from "os";
2274
2301
  import { fileURLToPath } from "url";
2302
+ import { unlinkSync as unlinkSync4 } from "fs";
2303
+ function spawnLockPath(sessionName) {
2304
+ return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2305
+ }
2306
+ function isProcessAlive(pid) {
2307
+ try {
2308
+ process.kill(pid, 0);
2309
+ return true;
2310
+ } catch {
2311
+ return false;
2312
+ }
2313
+ }
2314
+ function acquireSpawnLock(sessionName) {
2315
+ if (!existsSync10(SPAWN_LOCK_DIR)) {
2316
+ mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
2317
+ }
2318
+ const lockFile = spawnLockPath(sessionName);
2319
+ if (existsSync10(lockFile)) {
2320
+ try {
2321
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
2322
+ const age = Date.now() - lock.timestamp;
2323
+ if (isProcessAlive(lock.pid) && age < 6e4) {
2324
+ return false;
2325
+ }
2326
+ } catch {
2327
+ }
2328
+ }
2329
+ writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2330
+ return true;
2331
+ }
2332
+ function releaseSpawnLock(sessionName) {
2333
+ try {
2334
+ unlinkSync4(spawnLockPath(sessionName));
2335
+ } catch {
2336
+ }
2337
+ }
2275
2338
  function resolveBehaviorsExporterScript() {
2276
2339
  try {
2277
2340
  const thisFile = fileURLToPath(import.meta.url);
@@ -2335,7 +2398,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
2335
2398
  }
2336
2399
  function getParentExe(sessionKey) {
2337
2400
  try {
2338
- const data = JSON.parse(readFileSync8(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2401
+ const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2339
2402
  return data.parentExe || null;
2340
2403
  } catch {
2341
2404
  return null;
@@ -2343,7 +2406,7 @@ function getParentExe(sessionKey) {
2343
2406
  }
2344
2407
  function getDispatchedBy(sessionKey) {
2345
2408
  try {
2346
- const data = JSON.parse(readFileSync8(
2409
+ const data = JSON.parse(readFileSync9(
2347
2410
  path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2348
2411
  "utf8"
2349
2412
  ));
@@ -2370,10 +2433,10 @@ function isEmployeeAlive(sessionName) {
2370
2433
  }
2371
2434
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
2372
2435
  const base = employeeSessionName(employeeName, exeSession);
2373
- if (!isAlive(base)) return 0;
2436
+ if (!isAlive(base) && acquireSpawnLock(base)) return 0;
2374
2437
  for (let i = 2; i <= maxInstances; i++) {
2375
2438
  const candidate = employeeSessionName(employeeName, exeSession, i);
2376
- if (!isAlive(candidate)) return i;
2439
+ if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
2377
2440
  }
2378
2441
  return null;
2379
2442
  }
@@ -2406,7 +2469,7 @@ async function verifyPaneAtCapacity(sessionName) {
2406
2469
  function readDebounceState() {
2407
2470
  try {
2408
2471
  if (!existsSync10(DEBOUNCE_FILE)) return {};
2409
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
2472
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
2410
2473
  } catch {
2411
2474
  return {};
2412
2475
  }
@@ -2606,7 +2669,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2606
2669
  const claudeJsonPath = path13.join(os5.homedir(), ".claude.json");
2607
2670
  let claudeJson = {};
2608
2671
  try {
2609
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
2672
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
2610
2673
  } catch {
2611
2674
  }
2612
2675
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -2624,7 +2687,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2624
2687
  const settingsPath = path13.join(projSettingsDir, "settings.json");
2625
2688
  let settings = {};
2626
2689
  try {
2627
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
2690
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
2628
2691
  } catch {
2629
2692
  }
2630
2693
  const perms = settings.permissions ?? {};
@@ -2737,6 +2800,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2737
2800
  command: spawnCommand
2738
2801
  });
2739
2802
  if (spawnResult.error) {
2803
+ releaseSpawnLock(sessionName);
2740
2804
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
2741
2805
  }
2742
2806
  transport.pipeLog(sessionName, logFile);
@@ -2774,6 +2838,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2774
2838
  }
2775
2839
  }
2776
2840
  if (!booted) {
2841
+ releaseSpawnLock(sessionName);
2777
2842
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
2778
2843
  }
2779
2844
  if (!useExeAgent) {
@@ -2790,9 +2855,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2790
2855
  pid: 0,
2791
2856
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
2792
2857
  });
2858
+ releaseSpawnLock(sessionName);
2793
2859
  return { sessionName };
2794
2860
  }
2795
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
2861
+ 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;
2796
2862
  var init_tmux_routing = __esm({
2797
2863
  "src/lib/tmux-routing.ts"() {
2798
2864
  init_session_registry();
@@ -2803,6 +2869,7 @@ var init_tmux_routing = __esm({
2803
2869
  init_provider_table();
2804
2870
  init_intercom_queue();
2805
2871
  init_plan_limits();
2872
+ SPAWN_LOCK_DIR = path13.join(os5.homedir(), ".exe-os", "spawn-locks");
2806
2873
  SESSION_CACHE = path13.join(os5.homedir(), ".exe-os", "session-cache");
2807
2874
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
2808
2875
  VERIFY_PANE_LINES = 200;
@@ -2815,6 +2882,7 @@ var init_tmux_routing = __esm({
2815
2882
  });
2816
2883
  init_tmux_routing();
2817
2884
  export {
2885
+ acquireSpawnLock,
2818
2886
  employeeSessionName,
2819
2887
  ensureEmployee,
2820
2888
  extractRootExe,
@@ -2829,6 +2897,7 @@ export {
2829
2897
  notifyParentExe,
2830
2898
  parseParentExe,
2831
2899
  registerParentExe,
2900
+ releaseSpawnLock,
2832
2901
  resolveExeSession,
2833
2902
  sendIntercom,
2834
2903
  spawnEmployee,