@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
@@ -30,6 +30,7 @@ var config_exports = {};
30
30
  __export(config_exports, {
31
31
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
32
32
  CONFIG_PATH: () => CONFIG_PATH,
33
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
33
34
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
34
35
  DB_PATH: () => DB_PATH,
35
36
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -185,7 +186,7 @@ async function loadConfigFrom(configPath) {
185
186
  return { ...DEFAULT_CONFIG };
186
187
  }
187
188
  }
188
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
189
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
189
190
  var init_config = __esm({
190
191
  "src/lib/config.ts"() {
191
192
  "use strict";
@@ -193,6 +194,7 @@ var init_config = __esm({
193
194
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
194
195
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
195
196
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
197
+ COO_AGENT_NAME = "exe";
196
198
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
197
199
  CURRENT_CONFIG_VERSION = 1;
198
200
  DEFAULT_CONFIG = {
@@ -587,6 +589,13 @@ async function ensureSchema() {
587
589
  });
588
590
  } catch {
589
591
  }
592
+ try {
593
+ await client.execute({
594
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
595
+ args: []
596
+ });
597
+ } catch {
598
+ }
590
599
  try {
591
600
  await client.execute({
592
601
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -1033,6 +1042,18 @@ async function ensureSchema() {
1033
1042
  CREATE INDEX IF NOT EXISTS idx_session_kills_agent
1034
1043
  ON session_kills(agent_id);
1035
1044
  `);
1045
+ await client.execute(`
1046
+ CREATE TABLE IF NOT EXISTS global_procedures (
1047
+ id TEXT PRIMARY KEY,
1048
+ title TEXT NOT NULL,
1049
+ content TEXT NOT NULL,
1050
+ priority TEXT NOT NULL DEFAULT 'p0',
1051
+ domain TEXT,
1052
+ active INTEGER NOT NULL DEFAULT 1,
1053
+ created_at TEXT NOT NULL,
1054
+ updated_at TEXT NOT NULL
1055
+ )
1056
+ `);
1036
1057
  await client.executeMultiple(`
1037
1058
  CREATE TABLE IF NOT EXISTS conversations (
1038
1059
  id TEXT PRIMARY KEY,
@@ -1337,6 +1358,61 @@ var init_keychain = __esm({
1337
1358
  }
1338
1359
  });
1339
1360
 
1361
+ // src/lib/state-bus.ts
1362
+ var StateBus, orgBus;
1363
+ var init_state_bus = __esm({
1364
+ "src/lib/state-bus.ts"() {
1365
+ "use strict";
1366
+ StateBus = class {
1367
+ handlers = /* @__PURE__ */ new Map();
1368
+ globalHandlers = /* @__PURE__ */ new Set();
1369
+ /** Emit an event to all subscribers */
1370
+ emit(event) {
1371
+ const typeHandlers = this.handlers.get(event.type);
1372
+ if (typeHandlers) {
1373
+ for (const handler of typeHandlers) {
1374
+ try {
1375
+ handler(event);
1376
+ } catch {
1377
+ }
1378
+ }
1379
+ }
1380
+ for (const handler of this.globalHandlers) {
1381
+ try {
1382
+ handler(event);
1383
+ } catch {
1384
+ }
1385
+ }
1386
+ }
1387
+ /** Subscribe to a specific event type */
1388
+ on(type, handler) {
1389
+ if (!this.handlers.has(type)) {
1390
+ this.handlers.set(type, /* @__PURE__ */ new Set());
1391
+ }
1392
+ this.handlers.get(type).add(handler);
1393
+ }
1394
+ /** Subscribe to ALL events */
1395
+ onAny(handler) {
1396
+ this.globalHandlers.add(handler);
1397
+ }
1398
+ /** Unsubscribe from a specific event type */
1399
+ off(type, handler) {
1400
+ this.handlers.get(type)?.delete(handler);
1401
+ }
1402
+ /** Unsubscribe from ALL events */
1403
+ offAny(handler) {
1404
+ this.globalHandlers.delete(handler);
1405
+ }
1406
+ /** Remove all listeners */
1407
+ clear() {
1408
+ this.handlers.clear();
1409
+ this.globalHandlers.clear();
1410
+ }
1411
+ };
1412
+ orgBus = new StateBus();
1413
+ }
1414
+ });
1415
+
1340
1416
  // src/lib/shard-manager.ts
1341
1417
  var shard_manager_exports = {};
1342
1418
  __export(shard_manager_exports, {
@@ -1578,6 +1654,71 @@ var init_shard_manager = __esm({
1578
1654
  }
1579
1655
  });
1580
1656
 
1657
+ // src/lib/global-procedures.ts
1658
+ var global_procedures_exports = {};
1659
+ __export(global_procedures_exports, {
1660
+ deactivateGlobalProcedure: () => deactivateGlobalProcedure,
1661
+ getGlobalProceduresBlock: () => getGlobalProceduresBlock,
1662
+ loadGlobalProcedures: () => loadGlobalProcedures,
1663
+ storeGlobalProcedure: () => storeGlobalProcedure
1664
+ });
1665
+ import { randomUUID } from "crypto";
1666
+ async function loadGlobalProcedures() {
1667
+ const client = getClient();
1668
+ const result = await client.execute({
1669
+ sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
1670
+ args: []
1671
+ });
1672
+ const procedures = result.rows;
1673
+ if (procedures.length > 0) {
1674
+ _cache = procedures.map((p) => `### ${p.title}
1675
+ ${p.content}`).join("\n\n");
1676
+ } else {
1677
+ _cache = "";
1678
+ }
1679
+ _cacheLoaded = true;
1680
+ return procedures;
1681
+ }
1682
+ function getGlobalProceduresBlock() {
1683
+ if (!_cacheLoaded) return "";
1684
+ if (!_cache) return "";
1685
+ return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
1686
+
1687
+ ${_cache}
1688
+ `;
1689
+ }
1690
+ async function storeGlobalProcedure(input) {
1691
+ const id = randomUUID();
1692
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1693
+ const client = getClient();
1694
+ await client.execute({
1695
+ sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
1696
+ VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
1697
+ args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
1698
+ });
1699
+ await loadGlobalProcedures();
1700
+ return id;
1701
+ }
1702
+ async function deactivateGlobalProcedure(id) {
1703
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1704
+ const client = getClient();
1705
+ const result = await client.execute({
1706
+ sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
1707
+ args: [now, id]
1708
+ });
1709
+ await loadGlobalProcedures();
1710
+ return result.rowsAffected > 0;
1711
+ }
1712
+ var _cache, _cacheLoaded;
1713
+ var init_global_procedures = __esm({
1714
+ "src/lib/global-procedures.ts"() {
1715
+ "use strict";
1716
+ init_database();
1717
+ _cache = "";
1718
+ _cacheLoaded = false;
1719
+ }
1720
+ });
1721
+
1581
1722
  // src/lib/store.ts
1582
1723
  var store_exports = {};
1583
1724
  __export(store_exports, {
@@ -1657,6 +1798,11 @@ async function initStore(options) {
1657
1798
  "version-query"
1658
1799
  );
1659
1800
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1801
+ try {
1802
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
1803
+ await loadGlobalProcedures2();
1804
+ } catch {
1805
+ }
1660
1806
  }
1661
1807
  function classifyTier(record) {
1662
1808
  if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
@@ -1698,6 +1844,12 @@ async function writeMemory(record) {
1698
1844
  supersedes_id: record.supersedes_id ?? null
1699
1845
  };
1700
1846
  _pendingRecords.push(dbRow);
1847
+ orgBus.emit({
1848
+ type: "memory_stored",
1849
+ agentId: record.agent_id,
1850
+ project: record.project_name,
1851
+ timestamp: record.timestamp
1852
+ });
1701
1853
  const MAX_PENDING = 1e3;
1702
1854
  if (_pendingRecords.length > MAX_PENDING) {
1703
1855
  const dropped = _pendingRecords.length - MAX_PENDING;
@@ -2043,6 +2195,7 @@ var init_store = __esm({
2043
2195
  init_database();
2044
2196
  init_keychain();
2045
2197
  init_config();
2198
+ init_state_bus();
2046
2199
  INIT_MAX_RETRIES = 3;
2047
2200
  INIT_RETRY_DELAY_MS = 1e3;
2048
2201
  _pendingRecords = [];
@@ -2498,7 +2651,7 @@ var init_employees = __esm({
2498
2651
 
2499
2652
  // src/lib/license.ts
2500
2653
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2501
- import { randomUUID } from "crypto";
2654
+ import { randomUUID as randomUUID2 } from "crypto";
2502
2655
  import path7 from "path";
2503
2656
  import { jwtVerify, importSPKI } from "jose";
2504
2657
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
@@ -2896,9 +3049,15 @@ async function createTaskCore(input) {
2896
3049
  }
2897
3050
  }
2898
3051
  const complexity = input.complexity ?? "standard";
3052
+ let sessionScope = null;
3053
+ try {
3054
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3055
+ sessionScope = resolveExeSession2();
3056
+ } catch {
3057
+ }
2899
3058
  await client.execute({
2900
- 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)
2901
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3059
+ 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)
3060
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2902
3061
  args: [
2903
3062
  id,
2904
3063
  input.title,
@@ -2917,6 +3076,7 @@ async function createTaskCore(input) {
2917
3076
  input.budgetFallbackModel ?? null,
2918
3077
  0,
2919
3078
  null,
3079
+ sessionScope,
2920
3080
  now,
2921
3081
  now
2922
3082
  ]
@@ -2961,9 +3121,18 @@ async function listTasks(input) {
2961
3121
  conditions.push("priority = ?");
2962
3122
  args.push(input.priority);
2963
3123
  }
3124
+ try {
3125
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3126
+ const session = resolveExeSession2();
3127
+ if (session) {
3128
+ conditions.push("(session_scope IS NULL OR session_scope = ?)");
3129
+ args.push(session);
3130
+ }
3131
+ } catch {
3132
+ }
2964
3133
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2965
3134
  const result = await client.execute({
2966
- 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`,
3135
+ 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`,
2967
3136
  args
2968
3137
  });
2969
3138
  return result.rows.map((r) => ({
@@ -3157,6 +3326,7 @@ var init_tasks_crud = __esm({
3157
3326
  // src/lib/tasks-review.ts
3158
3327
  var tasks_review_exports = {};
3159
3328
  __export(tasks_review_exports, {
3329
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
3160
3330
  cleanupReviewFile: () => cleanupReviewFile,
3161
3331
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
3162
3332
  countPendingReviews: () => countPendingReviews,
@@ -3193,6 +3363,34 @@ async function listPendingReviews(limit) {
3193
3363
  });
3194
3364
  return result.rows;
3195
3365
  }
3366
+ async function cleanupOrphanedReviews() {
3367
+ const client = getClient();
3368
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3369
+ const r1 = await client.execute({
3370
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
3371
+ WHERE status = 'needs_review'
3372
+ AND assigned_by = 'system'
3373
+ AND title LIKE 'Review:%'
3374
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3375
+ args: [now]
3376
+ });
3377
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
3378
+ const r2 = await client.execute({
3379
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
3380
+ WHERE status = 'needs_review'
3381
+ AND result IS NOT NULL
3382
+ AND updated_at < ?`,
3383
+ args: [now, staleThreshold]
3384
+ });
3385
+ const total = r1.rowsAffected + r2.rowsAffected;
3386
+ if (total > 0) {
3387
+ process.stderr.write(
3388
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
3389
+ `
3390
+ );
3391
+ }
3392
+ return total;
3393
+ }
3196
3394
  function getReviewChecklist(role, agent, taskSlug) {
3197
3395
  const roleLower = role.toLowerCase();
3198
3396
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -3300,6 +3498,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
3300
3498
  skipDispatch: true
3301
3499
  });
3302
3500
  const reviewId = reviewTask.id;
3501
+ orgBus.emit({
3502
+ type: "review_created",
3503
+ reviewId,
3504
+ employee: agent,
3505
+ reviewer,
3506
+ timestamp: now
3507
+ });
3303
3508
  await writeNotification({
3304
3509
  agentId: agent,
3305
3510
  agentRole: String(row.assigned_to),
@@ -3396,6 +3601,7 @@ var init_tasks_review = __esm({
3396
3601
  init_tasks_crud();
3397
3602
  init_tmux_routing();
3398
3603
  init_session_key();
3604
+ init_state_bus();
3399
3605
  }
3400
3606
  });
3401
3607
 
@@ -3560,13 +3766,12 @@ function assertSessionScope(actionType, targetProject) {
3560
3766
  };
3561
3767
  }
3562
3768
  process.stderr.write(
3563
- `[session-scope] Cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
3769
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
3564
3770
  `
3565
3771
  );
3566
3772
  return {
3567
- allowed: true,
3568
- // v1: warn-only, don't block
3569
- reason: "cross_session_granted",
3773
+ allowed: false,
3774
+ reason: "cross_session_denied",
3570
3775
  currentProject,
3571
3776
  targetProject,
3572
3777
  targetSession: findSessionForProject(targetProject)?.windowName
@@ -3592,8 +3797,9 @@ async function dispatchTaskToEmployee(input) {
3592
3797
  try {
3593
3798
  const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
3594
3799
  const check = assertSessionScope2("dispatch_task", input.projectName);
3595
- if (check.reason === "cross_session_granted") {
3800
+ if (check.reason === "cross_session_denied") {
3596
3801
  crossProject = true;
3802
+ return { dispatched: "skipped", crossProject: true };
3597
3803
  }
3598
3804
  } catch {
3599
3805
  }
@@ -3962,6 +4168,7 @@ var init_skill_learning = __esm({
3962
4168
  // src/lib/tasks.ts
3963
4169
  var tasks_exports = {};
3964
4170
  __export(tasks_exports, {
4171
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
3965
4172
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
3966
4173
  countPendingReviews: () => countPendingReviews,
3967
4174
  createTask: () => createTask,
@@ -4027,6 +4234,21 @@ async function updateTask(input) {
4027
4234
  });
4028
4235
  } catch {
4029
4236
  }
4237
+ try {
4238
+ const client = getClient();
4239
+ const cascaded = await client.execute({
4240
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
4241
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
4242
+ args: [now, taskId]
4243
+ });
4244
+ if (cascaded.rowsAffected > 0) {
4245
+ process.stderr.write(
4246
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
4247
+ `
4248
+ );
4249
+ }
4250
+ } catch {
4251
+ }
4030
4252
  }
4031
4253
  const isTerminal = input.status === "done" || input.status === "needs_review";
4032
4254
  if (isTerminal) {
@@ -4040,6 +4262,13 @@ async function updateTask(input) {
4040
4262
  await cascadeUnblock(taskId, input.baseDir, now);
4041
4263
  } catch {
4042
4264
  }
4265
+ orgBus.emit({
4266
+ type: "task_completed",
4267
+ taskId,
4268
+ employee: String(row.assigned_to),
4269
+ result: input.result ?? "",
4270
+ timestamp: now
4271
+ });
4043
4272
  if (row.parent_task_id) {
4044
4273
  try {
4045
4274
  await checkSubtaskCompletion(String(row.parent_task_id), String(row.project_name));
@@ -4107,6 +4336,7 @@ var init_tasks = __esm({
4107
4336
  init_database();
4108
4337
  init_config();
4109
4338
  init_notifications();
4339
+ init_state_bus();
4110
4340
  init_tasks_crud();
4111
4341
  init_tasks_review();
4112
4342
  init_tasks_crud();
@@ -4497,8 +4727,28 @@ function getMySession() {
4497
4727
  return getTransport().getMySession();
4498
4728
  }
4499
4729
  function employeeSessionName(employee, exeSession, instance) {
4730
+ if (!/^exe\d+$/.test(exeSession)) {
4731
+ const root = extractRootExe(exeSession);
4732
+ if (root) {
4733
+ process.stderr.write(
4734
+ `[tmux-routing] WARN: exeSession="${exeSession}" is not a root exe session, using "${root}" instead
4735
+ `
4736
+ );
4737
+ exeSession = root;
4738
+ } else {
4739
+ throw new Error(
4740
+ `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1"), not an agent session`
4741
+ );
4742
+ }
4743
+ }
4500
4744
  const suffix = instance != null && instance > 0 ? String(instance) : "";
4501
- return `${employee}${suffix}-${exeSession}`;
4745
+ const name = `${employee}${suffix}-${exeSession}`;
4746
+ if (!VALID_SESSION_NAME.test(name)) {
4747
+ throw new Error(
4748
+ `Invalid session name "${name}" \u2014 must match {agent}-exe{N} or {agent}{instance}-exe{N}`
4749
+ );
4750
+ }
4751
+ return name;
4502
4752
  }
4503
4753
  function parseParentExe(sessionName, agentId) {
4504
4754
  const escaped = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -4738,6 +4988,22 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4738
4988
  error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
4739
4989
  };
4740
4990
  }
4991
+ if (!/^exe\d+$/.test(exeSession)) {
4992
+ const root = extractRootExe(exeSession);
4993
+ if (root) {
4994
+ process.stderr.write(
4995
+ `[ensureEmployee] WARN: caller passed exeSession="${exeSession}" (not a root exe). Auto-correcting to "${root}".
4996
+ `
4997
+ );
4998
+ exeSession = root;
4999
+ } else {
5000
+ return {
5001
+ status: "failed",
5002
+ sessionName: "",
5003
+ error: `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1")`
5004
+ };
5005
+ }
5006
+ }
4741
5007
  let effectiveInstance = opts?.instance;
4742
5008
  if (effectiveInstance === void 0 && opts?.autoInstance) {
4743
5009
  const free = findFreeInstance(
@@ -4984,7 +5250,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4984
5250
  releaseSpawnLock(sessionName);
4985
5251
  return { sessionName };
4986
5252
  }
4987
- 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;
5253
+ 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;
4988
5254
  var init_tmux_routing = __esm({
4989
5255
  "src/lib/tmux-routing.ts"() {
4990
5256
  "use strict";
@@ -4999,6 +5265,7 @@ var init_tmux_routing = __esm({
4999
5265
  SPAWN_LOCK_DIR = path15.join(os6.homedir(), ".exe-os", "spawn-locks");
5000
5266
  SESSION_CACHE = path15.join(os6.homedir(), ".exe-os", "session-cache");
5001
5267
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5268
+ VALID_SESSION_NAME = /^[a-z]+-exe\d+$|^[a-z]+\d+-exe\d+$/;
5002
5269
  VERIFY_PANE_LINES = 200;
5003
5270
  INTERCOM_DEBOUNCE_MS = 3e4;
5004
5271
  INTERCOM_LOG2 = path15.join(os6.homedir(), ".exe-os", "intercom.log");
@@ -5856,7 +6123,7 @@ __export(consolidation_exports, {
5856
6123
  selectUnconsolidated: () => selectUnconsolidated,
5857
6124
  storeConsolidation: () => storeConsolidation
5858
6125
  });
5859
- import { randomUUID as randomUUID2 } from "crypto";
6126
+ import { randomUUID as randomUUID3 } from "crypto";
5860
6127
  async function selectUnconsolidated(client, limit = 200) {
5861
6128
  const result = await client.execute({
5862
6129
  sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
@@ -5952,7 +6219,7 @@ async function consolidateCluster(cluster, model) {
5952
6219
  return textBlock?.text ?? "";
5953
6220
  }
5954
6221
  async function storeConsolidation(client, cluster, synthesisText, embedFn) {
5955
- const consolidatedId = randomUUID2();
6222
+ const consolidatedId = randomUUID3();
5956
6223
  const now = (/* @__PURE__ */ new Date()).toISOString();
5957
6224
  const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
5958
6225
 
@@ -5977,7 +6244,7 @@ ${synthesisText}`;
5977
6244
  const linkStmts = sourceIds.map((sourceId) => ({
5978
6245
  sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
5979
6246
  VALUES (?, ?, ?, ?)`,
5980
- args: [randomUUID2(), consolidatedId, sourceId, now]
6247
+ args: [randomUUID3(), consolidatedId, sourceId, now]
5981
6248
  }));
5982
6249
  const placeholders = sourceIds.map(() => "?").join(",");
5983
6250
  const markStmt = {
@@ -6216,7 +6483,7 @@ var init_consolidation = __esm({
6216
6483
  // src/lib/exe-daemon-client.ts
6217
6484
  import net from "net";
6218
6485
  import { spawn } from "child_process";
6219
- import { randomUUID as randomUUID3 } from "crypto";
6486
+ import { randomUUID as randomUUID4 } from "crypto";
6220
6487
  import { existsSync as existsSync14, unlinkSync as unlinkSync5, readFileSync as readFileSync11, openSync, closeSync, statSync } from "fs";
6221
6488
  import path17 from "path";
6222
6489
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -6408,7 +6675,7 @@ function sendRequest(texts, priority) {
6408
6675
  resolve({ error: "Not connected" });
6409
6676
  return;
6410
6677
  }
6411
- const id = randomUUID3();
6678
+ const id = randomUUID4();
6412
6679
  const timer = setTimeout(() => {
6413
6680
  _pending.delete(id);
6414
6681
  resolve({ error: "Request timeout" });
@@ -6426,7 +6693,7 @@ function sendRequest(texts, priority) {
6426
6693
  async function pingDaemon() {
6427
6694
  if (!_socket || !_connected) return null;
6428
6695
  return new Promise((resolve) => {
6429
- const id = randomUUID3();
6696
+ const id = randomUUID4();
6430
6697
  const timer = setTimeout(() => {
6431
6698
  _pending.delete(id);
6432
6699
  resolve(null);