@askexenow/exe-os 0.8.40 → 0.8.42

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 (78) hide show
  1. package/dist/bin/backfill-conversations.js +805 -642
  2. package/dist/bin/backfill-responses.js +804 -641
  3. package/dist/bin/backfill-vectors.js +791 -634
  4. package/dist/bin/cleanup-stale-review-tasks.js +788 -631
  5. package/dist/bin/cli.js +1376 -659
  6. package/dist/bin/exe-agent.js +20 -1
  7. package/dist/bin/exe-assign.js +1503 -1343
  8. package/dist/bin/exe-boot.js +2549 -1784
  9. package/dist/bin/exe-call.js +39 -1
  10. package/dist/bin/exe-cloud.js +12 -2
  11. package/dist/bin/exe-dispatch.js +39 -2
  12. package/dist/bin/exe-doctor.js +791 -634
  13. package/dist/bin/exe-export-behaviors.js +792 -637
  14. package/dist/bin/exe-forget.js +145 -0
  15. package/dist/bin/exe-gateway.js +2501 -1846
  16. package/dist/bin/exe-heartbeat.js +147 -1
  17. package/dist/bin/exe-kill.js +795 -640
  18. package/dist/bin/exe-launch-agent.js +2168 -2008
  19. package/dist/bin/exe-link.js +44 -12
  20. package/dist/bin/exe-new-employee.js +6 -2
  21. package/dist/bin/exe-pending-messages.js +146 -1
  22. package/dist/bin/exe-pending-notifications.js +788 -631
  23. package/dist/bin/exe-pending-reviews.js +176 -1
  24. package/dist/bin/exe-rename.js +23 -0
  25. package/dist/bin/exe-review.js +490 -327
  26. package/dist/bin/exe-search.js +157 -4
  27. package/dist/bin/exe-session-cleanup.js +2487 -403
  28. package/dist/bin/exe-settings.js +2 -1
  29. package/dist/bin/exe-status.js +474 -317
  30. package/dist/bin/exe-team.js +474 -317
  31. package/dist/bin/git-sweep.js +2691 -151
  32. package/dist/bin/graph-backfill.js +794 -637
  33. package/dist/bin/graph-export.js +798 -641
  34. package/dist/bin/scan-tasks.js +2951 -44
  35. package/dist/bin/setup.js +50 -26
  36. package/dist/bin/shard-migrate.js +792 -637
  37. package/dist/bin/wiki-sync.js +794 -637
  38. package/dist/gateway/index.js +2542 -1887
  39. package/dist/hooks/bug-report-worker.js +2118 -576
  40. package/dist/hooks/commit-complete.js +2690 -150
  41. package/dist/hooks/error-recall.js +157 -4
  42. package/dist/hooks/ingest-worker.js +1455 -803
  43. package/dist/hooks/instructions-loaded.js +151 -0
  44. package/dist/hooks/notification.js +153 -2
  45. package/dist/hooks/post-compact.js +164 -0
  46. package/dist/hooks/pre-compact.js +3073 -101
  47. package/dist/hooks/pre-tool-use.js +151 -0
  48. package/dist/hooks/prompt-ingest-worker.js +1670 -1509
  49. package/dist/hooks/prompt-submit.js +2650 -1074
  50. package/dist/hooks/response-ingest-worker.js +154 -6
  51. package/dist/hooks/session-end.js +153 -2
  52. package/dist/hooks/session-start.js +157 -4
  53. package/dist/hooks/stop.js +151 -0
  54. package/dist/hooks/subagent-stop.js +155 -2
  55. package/dist/hooks/summary-worker.js +190 -21
  56. package/dist/index.js +326 -102
  57. package/dist/lib/cloud-sync.js +31 -10
  58. package/dist/lib/config.js +2 -0
  59. package/dist/lib/consolidation.js +69 -2
  60. package/dist/lib/database.js +19 -0
  61. package/dist/lib/device-registry.js +19 -0
  62. package/dist/lib/embedder.js +3 -1
  63. package/dist/lib/employee-templates.js +20 -1
  64. package/dist/lib/exe-daemon.js +285 -18
  65. package/dist/lib/hybrid-search.js +157 -4
  66. package/dist/lib/messaging.js +39 -2
  67. package/dist/lib/schedules.js +792 -637
  68. package/dist/lib/store.js +796 -636
  69. package/dist/lib/tasks.js +1485 -918
  70. package/dist/lib/tmux-routing.js +194 -10
  71. package/dist/mcp/server.js +1643 -924
  72. package/dist/mcp/tools/create-task.js +2283 -829
  73. package/dist/mcp/tools/list-tasks.js +2788 -159
  74. package/dist/mcp/tools/send-message.js +39 -2
  75. package/dist/mcp/tools/update-task.js +79 -0
  76. package/dist/runtime/index.js +280 -68
  77. package/dist/tui/App.js +1485 -645
  78. package/package.json +3 -2
@@ -749,6 +749,61 @@ var init_session_kill_telemetry = __esm({
749
749
  }
750
750
  });
751
751
 
752
+ // src/lib/state-bus.ts
753
+ var StateBus, orgBus;
754
+ var init_state_bus = __esm({
755
+ "src/lib/state-bus.ts"() {
756
+ "use strict";
757
+ StateBus = class {
758
+ handlers = /* @__PURE__ */ new Map();
759
+ globalHandlers = /* @__PURE__ */ new Set();
760
+ /** Emit an event to all subscribers */
761
+ emit(event) {
762
+ const typeHandlers = this.handlers.get(event.type);
763
+ if (typeHandlers) {
764
+ for (const handler of typeHandlers) {
765
+ try {
766
+ handler(event);
767
+ } catch {
768
+ }
769
+ }
770
+ }
771
+ for (const handler of this.globalHandlers) {
772
+ try {
773
+ handler(event);
774
+ } catch {
775
+ }
776
+ }
777
+ }
778
+ /** Subscribe to a specific event type */
779
+ on(type, handler) {
780
+ if (!this.handlers.has(type)) {
781
+ this.handlers.set(type, /* @__PURE__ */ new Set());
782
+ }
783
+ this.handlers.get(type).add(handler);
784
+ }
785
+ /** Subscribe to ALL events */
786
+ onAny(handler) {
787
+ this.globalHandlers.add(handler);
788
+ }
789
+ /** Unsubscribe from a specific event type */
790
+ off(type, handler) {
791
+ this.handlers.get(type)?.delete(handler);
792
+ }
793
+ /** Unsubscribe from ALL events */
794
+ offAny(handler) {
795
+ this.globalHandlers.delete(handler);
796
+ }
797
+ /** Remove all listeners */
798
+ clear() {
799
+ this.handlers.clear();
800
+ this.globalHandlers.clear();
801
+ }
802
+ };
803
+ orgBus = new StateBus();
804
+ }
805
+ });
806
+
752
807
  // src/lib/tasks-crud.ts
753
808
  import crypto3 from "crypto";
754
809
  import path8 from "path";
@@ -892,9 +947,15 @@ async function createTaskCore(input) {
892
947
  }
893
948
  }
894
949
  const complexity = input.complexity ?? "standard";
950
+ let sessionScope = null;
951
+ try {
952
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
953
+ sessionScope = resolveExeSession2();
954
+ } catch {
955
+ }
895
956
  await client.execute({
896
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, created_at, updated_at)
897
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
957
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
958
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
898
959
  args: [
899
960
  id,
900
961
  input.title,
@@ -913,6 +974,7 @@ async function createTaskCore(input) {
913
974
  input.budgetFallbackModel ?? null,
914
975
  0,
915
976
  null,
977
+ sessionScope,
916
978
  now,
917
979
  now
918
980
  ]
@@ -957,9 +1019,18 @@ async function listTasks(input) {
957
1019
  conditions.push("priority = ?");
958
1020
  args.push(input.priority);
959
1021
  }
1022
+ try {
1023
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
1024
+ const session = resolveExeSession2();
1025
+ if (session) {
1026
+ conditions.push("(session_scope IS NULL OR session_scope = ?)");
1027
+ args.push(session);
1028
+ }
1029
+ } catch {
1030
+ }
960
1031
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
961
1032
  const result = await client.execute({
962
- sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC`,
1033
+ sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
963
1034
  args
964
1035
  });
965
1036
  return result.rows.map((r) => ({
@@ -1180,6 +1251,34 @@ async function listPendingReviews(limit) {
1180
1251
  });
1181
1252
  return result.rows;
1182
1253
  }
1254
+ async function cleanupOrphanedReviews() {
1255
+ const client = getClient();
1256
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1257
+ const r1 = await client.execute({
1258
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1259
+ WHERE status = 'needs_review'
1260
+ AND assigned_by = 'system'
1261
+ AND title LIKE 'Review:%'
1262
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
1263
+ args: [now]
1264
+ });
1265
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
1266
+ const r2 = await client.execute({
1267
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1268
+ WHERE status = 'needs_review'
1269
+ AND result IS NOT NULL
1270
+ AND updated_at < ?`,
1271
+ args: [now, staleThreshold]
1272
+ });
1273
+ const total = r1.rowsAffected + r2.rowsAffected;
1274
+ if (total > 0) {
1275
+ process.stderr.write(
1276
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
1277
+ `
1278
+ );
1279
+ }
1280
+ return total;
1281
+ }
1183
1282
  function getReviewChecklist(role, agent, taskSlug) {
1184
1283
  const roleLower = role.toLowerCase();
1185
1284
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -1293,6 +1392,7 @@ var init_tasks_review = __esm({
1293
1392
  init_tasks_crud();
1294
1393
  init_tmux_routing();
1295
1394
  init_session_key();
1395
+ init_state_bus();
1296
1396
  }
1297
1397
  });
1298
1398
 
@@ -1457,13 +1557,12 @@ function assertSessionScope(actionType, targetProject) {
1457
1557
  };
1458
1558
  }
1459
1559
  process.stderr.write(
1460
- `[session-scope] Cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
1560
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
1461
1561
  `
1462
1562
  );
1463
1563
  return {
1464
- allowed: true,
1465
- // v1: warn-only, don't block
1466
- reason: "cross_session_granted",
1564
+ allowed: false,
1565
+ reason: "cross_session_denied",
1467
1566
  currentProject,
1468
1567
  targetProject,
1469
1568
  targetSession: findSessionForProject(targetProject)?.windowName
@@ -1489,8 +1588,9 @@ async function dispatchTaskToEmployee(input) {
1489
1588
  try {
1490
1589
  const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
1491
1590
  const check = assertSessionScope2("dispatch_task", input.projectName);
1492
- if (check.reason === "cross_session_granted") {
1591
+ if (check.reason === "cross_session_denied") {
1493
1592
  crossProject = true;
1593
+ return { dispatched: "skipped", crossProject: true };
1494
1594
  }
1495
1595
  } catch {
1496
1596
  }
@@ -1859,6 +1959,7 @@ var init_skill_learning = __esm({
1859
1959
  // src/lib/tasks.ts
1860
1960
  var tasks_exports = {};
1861
1961
  __export(tasks_exports, {
1962
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
1862
1963
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
1863
1964
  countPendingReviews: () => countPendingReviews,
1864
1965
  createTask: () => createTask,
@@ -1924,6 +2025,21 @@ async function updateTask(input) {
1924
2025
  });
1925
2026
  } catch {
1926
2027
  }
2028
+ try {
2029
+ const client = getClient();
2030
+ const cascaded = await client.execute({
2031
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
2032
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
2033
+ args: [now, taskId]
2034
+ });
2035
+ if (cascaded.rowsAffected > 0) {
2036
+ process.stderr.write(
2037
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
2038
+ `
2039
+ );
2040
+ }
2041
+ } catch {
2042
+ }
1927
2043
  }
1928
2044
  const isTerminal = input.status === "done" || input.status === "needs_review";
1929
2045
  if (isTerminal) {
@@ -1937,6 +2053,13 @@ async function updateTask(input) {
1937
2053
  await cascadeUnblock(taskId, input.baseDir, now);
1938
2054
  } catch {
1939
2055
  }
2056
+ orgBus.emit({
2057
+ type: "task_completed",
2058
+ taskId,
2059
+ employee: String(row.assigned_to),
2060
+ result: input.result ?? "",
2061
+ timestamp: now
2062
+ });
1940
2063
  if (row.parent_task_id) {
1941
2064
  try {
1942
2065
  await checkSubtaskCompletion(String(row.parent_task_id), String(row.project_name));
@@ -2004,6 +2127,7 @@ var init_tasks = __esm({
2004
2127
  init_database();
2005
2128
  init_config();
2006
2129
  init_notifications();
2130
+ init_state_bus();
2007
2131
  init_tasks_crud();
2008
2132
  init_tasks_review();
2009
2133
  init_tasks_crud();
@@ -2294,6 +2418,29 @@ var init_capacity_monitor = __esm({
2294
2418
  });
2295
2419
 
2296
2420
  // src/lib/tmux-routing.ts
2421
+ var tmux_routing_exports = {};
2422
+ __export(tmux_routing_exports, {
2423
+ acquireSpawnLock: () => acquireSpawnLock,
2424
+ employeeSessionName: () => employeeSessionName,
2425
+ ensureEmployee: () => ensureEmployee,
2426
+ extractRootExe: () => extractRootExe,
2427
+ findFreeInstance: () => findFreeInstance,
2428
+ getDispatchedBy: () => getDispatchedBy,
2429
+ getMySession: () => getMySession,
2430
+ getParentExe: () => getParentExe,
2431
+ getSessionState: () => getSessionState,
2432
+ isEmployeeAlive: () => isEmployeeAlive,
2433
+ isExeSession: () => isExeSession,
2434
+ isSessionBusy: () => isSessionBusy,
2435
+ notifyParentExe: () => notifyParentExe,
2436
+ parseParentExe: () => parseParentExe,
2437
+ registerParentExe: () => registerParentExe,
2438
+ releaseSpawnLock: () => releaseSpawnLock,
2439
+ resolveExeSession: () => resolveExeSession,
2440
+ sendIntercom: () => sendIntercom,
2441
+ spawnEmployee: () => spawnEmployee,
2442
+ verifyPaneAtCapacity: () => verifyPaneAtCapacity
2443
+ });
2297
2444
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2298
2445
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
2299
2446
  import path13 from "path";
@@ -2371,8 +2518,28 @@ function getMySession() {
2371
2518
  return getTransport().getMySession();
2372
2519
  }
2373
2520
  function employeeSessionName(employee, exeSession, instance) {
2521
+ if (!/^exe\d+$/.test(exeSession)) {
2522
+ const root = extractRootExe(exeSession);
2523
+ if (root) {
2524
+ process.stderr.write(
2525
+ `[tmux-routing] WARN: exeSession="${exeSession}" is not a root exe session, using "${root}" instead
2526
+ `
2527
+ );
2528
+ exeSession = root;
2529
+ } else {
2530
+ throw new Error(
2531
+ `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1"), not an agent session`
2532
+ );
2533
+ }
2534
+ }
2374
2535
  const suffix = instance != null && instance > 0 ? String(instance) : "";
2375
- return `${employee}${suffix}-${exeSession}`;
2536
+ const name = `${employee}${suffix}-${exeSession}`;
2537
+ if (!VALID_SESSION_NAME.test(name)) {
2538
+ throw new Error(
2539
+ `Invalid session name "${name}" \u2014 must match {agent}-exe{N} or {agent}{instance}-exe{N}`
2540
+ );
2541
+ }
2542
+ return name;
2376
2543
  }
2377
2544
  function parseParentExe(sessionName, agentId) {
2378
2545
  const escaped = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -2612,6 +2779,22 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
2612
2779
  error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
2613
2780
  };
2614
2781
  }
2782
+ if (!/^exe\d+$/.test(exeSession)) {
2783
+ const root = extractRootExe(exeSession);
2784
+ if (root) {
2785
+ process.stderr.write(
2786
+ `[ensureEmployee] WARN: caller passed exeSession="${exeSession}" (not a root exe). Auto-correcting to "${root}".
2787
+ `
2788
+ );
2789
+ exeSession = root;
2790
+ } else {
2791
+ return {
2792
+ status: "failed",
2793
+ sessionName: "",
2794
+ error: `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1")`
2795
+ };
2796
+ }
2797
+ }
2615
2798
  let effectiveInstance = opts?.instance;
2616
2799
  if (effectiveInstance === void 0 && opts?.autoInstance) {
2617
2800
  const free = findFreeInstance(
@@ -2858,7 +3041,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2858
3041
  releaseSpawnLock(sessionName);
2859
3042
  return { sessionName };
2860
3043
  }
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;
3044
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
2862
3045
  var init_tmux_routing = __esm({
2863
3046
  "src/lib/tmux-routing.ts"() {
2864
3047
  init_session_registry();
@@ -2872,6 +3055,7 @@ var init_tmux_routing = __esm({
2872
3055
  SPAWN_LOCK_DIR = path13.join(os5.homedir(), ".exe-os", "spawn-locks");
2873
3056
  SESSION_CACHE = path13.join(os5.homedir(), ".exe-os", "session-cache");
2874
3057
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3058
+ VALID_SESSION_NAME = /^[a-z]+-exe\d+$|^[a-z]+\d+-exe\d+$/;
2875
3059
  VERIFY_PANE_LINES = 200;
2876
3060
  INTERCOM_DEBOUNCE_MS = 3e4;
2877
3061
  INTERCOM_LOG2 = path13.join(os5.homedir(), ".exe-os", "intercom.log");