@askexenow/exe-os 0.8.41 → 0.8.43

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 (76) 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 +1345 -660
  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 +2518 -1798
  9. package/dist/bin/exe-call.js +39 -1
  10. package/dist/bin/exe-cloud.js +15 -1
  11. package/dist/bin/exe-dispatch.js +39 -2
  12. package/dist/bin/exe-doctor.js +790 -633
  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 +2500 -1877
  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 +28 -2
  20. package/dist/bin/exe-new-employee.js +25 -3
  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 +147 -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 +154 -3
  27. package/dist/bin/exe-session-cleanup.js +2466 -413
  28. package/dist/bin/exe-status.js +474 -317
  29. package/dist/bin/exe-team.js +474 -317
  30. package/dist/bin/git-sweep.js +2690 -150
  31. package/dist/bin/graph-backfill.js +794 -637
  32. package/dist/bin/graph-export.js +798 -641
  33. package/dist/bin/scan-tasks.js +2951 -44
  34. package/dist/bin/setup.js +62 -26
  35. package/dist/bin/shard-migrate.js +792 -637
  36. package/dist/bin/wiki-sync.js +794 -637
  37. package/dist/gateway/index.js +2504 -1895
  38. package/dist/hooks/bug-report-worker.js +2118 -576
  39. package/dist/hooks/commit-complete.js +2689 -149
  40. package/dist/hooks/error-recall.js +154 -3
  41. package/dist/hooks/ingest-worker.js +1439 -815
  42. package/dist/hooks/instructions-loaded.js +151 -0
  43. package/dist/hooks/notification.js +153 -2
  44. package/dist/hooks/post-compact.js +164 -0
  45. package/dist/hooks/pre-compact.js +3073 -101
  46. package/dist/hooks/pre-tool-use.js +151 -0
  47. package/dist/hooks/prompt-ingest-worker.js +1714 -1537
  48. package/dist/hooks/prompt-submit.js +2658 -1113
  49. package/dist/hooks/response-ingest-worker.js +170 -6
  50. package/dist/hooks/session-end.js +153 -2
  51. package/dist/hooks/session-start.js +154 -3
  52. package/dist/hooks/stop.js +151 -0
  53. package/dist/hooks/subagent-stop.js +151 -0
  54. package/dist/hooks/summary-worker.js +179 -7
  55. package/dist/index.js +278 -100
  56. package/dist/lib/cloud-sync.js +28 -2
  57. package/dist/lib/consolidation.js +69 -2
  58. package/dist/lib/database.js +19 -0
  59. package/dist/lib/device-registry.js +19 -0
  60. package/dist/lib/employee-templates.js +20 -1
  61. package/dist/lib/exe-daemon.js +236 -16
  62. package/dist/lib/hybrid-search.js +154 -3
  63. package/dist/lib/license.js +15 -1
  64. package/dist/lib/messaging.js +39 -2
  65. package/dist/lib/schedules.js +792 -637
  66. package/dist/lib/store.js +796 -636
  67. package/dist/lib/tasks.js +1614 -1091
  68. package/dist/lib/tmux-routing.js +149 -9
  69. package/dist/mcp/server.js +1825 -1138
  70. package/dist/mcp/tools/create-task.js +2280 -828
  71. package/dist/mcp/tools/list-tasks.js +2788 -159
  72. package/dist/mcp/tools/send-message.js +39 -2
  73. package/dist/mcp/tools/update-task.js +64 -0
  74. package/dist/runtime/index.js +235 -67
  75. package/dist/tui/App.js +1452 -644
  76. package/package.json +3 -2
@@ -297,6 +297,10 @@ function loadLicense() {
297
297
  return null;
298
298
  }
299
299
  }
300
+ function saveLicense(apiKey) {
301
+ mkdirSync(EXE_AI_DIR, { recursive: true });
302
+ writeFileSync(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
303
+ }
300
304
  async function verifyLicenseJwt(token) {
301
305
  try {
302
306
  const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
@@ -387,7 +391,21 @@ function getCacheAgeMs() {
387
391
  }
388
392
  }
389
393
  async function checkLicense() {
390
- const key = loadLicense();
394
+ let key = loadLicense();
395
+ if (!key) {
396
+ try {
397
+ const configPath = path3.join(EXE_AI_DIR, "config.json");
398
+ if (existsSync3(configPath)) {
399
+ const raw = JSON.parse(readFileSync3(configPath, "utf8"));
400
+ const cloud = raw.cloud;
401
+ if (cloud?.apiKey) {
402
+ key = cloud.apiKey;
403
+ saveLicense(key);
404
+ }
405
+ }
406
+ } catch {
407
+ }
408
+ }
391
409
  if (!key) return FREE_LICENSE;
392
410
  const cached = await getCachedLicense();
393
411
  if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
@@ -477,7 +495,7 @@ async function fetchWithRetry(url, init) {
477
495
  try {
478
496
  const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
479
497
  const resp = await fetch(url, { ...init, signal });
480
- if (resp.status >= 500 && attempt < MAX_RETRIES) {
498
+ if (resp && resp.status >= 500 && attempt < MAX_RETRIES) {
481
499
  await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
482
500
  continue;
483
501
  }
@@ -521,6 +539,10 @@ async function cloudPush(records, maxVersion, config) {
521
539
  },
522
540
  body: JSON.stringify({ version: maxVersion, blob })
523
541
  });
542
+ if (resp == null) {
543
+ logError("[cloud-sync] PUSH FAILED: no response from server");
544
+ return false;
545
+ }
524
546
  if (resp.status === 409) {
525
547
  logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
526
548
  return false;
@@ -543,6 +565,10 @@ async function cloudPull(sinceVersion, config) {
543
565
  },
544
566
  body: JSON.stringify({ since_version: sinceVersion })
545
567
  });
568
+ if (response == null) {
569
+ logError("[cloud-sync] PULL FAILED: no response from server");
570
+ return { records: [], maxVersion: sinceVersion };
571
+ }
546
572
  if (!response.ok) return { records: [], maxVersion: sinceVersion };
547
573
  const data = await response.json();
548
574
  const allRecords = [];
@@ -3,6 +3,22 @@ var __esm = (fn, res) => function __init() {
3
3
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
4
  };
5
5
 
6
+ // src/lib/db-retry.ts
7
+ var init_db_retry = __esm({
8
+ "src/lib/db-retry.ts"() {
9
+ "use strict";
10
+ }
11
+ });
12
+
13
+ // src/lib/database.ts
14
+ import { createClient } from "@libsql/client";
15
+ var init_database = __esm({
16
+ "src/lib/database.ts"() {
17
+ "use strict";
18
+ init_db_retry();
19
+ }
20
+ });
21
+
6
22
  // src/lib/config.ts
7
23
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
8
24
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
@@ -103,8 +119,8 @@ var init_config = __esm({
103
119
  // src/lib/consolidation.ts
104
120
  import { randomUUID } from "crypto";
105
121
 
106
- // src/lib/database.ts
107
- import { createClient } from "@libsql/client";
122
+ // src/lib/store.ts
123
+ init_database();
108
124
 
109
125
  // src/lib/keychain.ts
110
126
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
@@ -115,6 +131,57 @@ import crypto from "crypto";
115
131
 
116
132
  // src/lib/store.ts
117
133
  init_config();
134
+
135
+ // src/lib/state-bus.ts
136
+ var StateBus = class {
137
+ handlers = /* @__PURE__ */ new Map();
138
+ globalHandlers = /* @__PURE__ */ new Set();
139
+ /** Emit an event to all subscribers */
140
+ emit(event) {
141
+ const typeHandlers = this.handlers.get(event.type);
142
+ if (typeHandlers) {
143
+ for (const handler of typeHandlers) {
144
+ try {
145
+ handler(event);
146
+ } catch {
147
+ }
148
+ }
149
+ }
150
+ for (const handler of this.globalHandlers) {
151
+ try {
152
+ handler(event);
153
+ } catch {
154
+ }
155
+ }
156
+ }
157
+ /** Subscribe to a specific event type */
158
+ on(type, handler) {
159
+ if (!this.handlers.has(type)) {
160
+ this.handlers.set(type, /* @__PURE__ */ new Set());
161
+ }
162
+ this.handlers.get(type).add(handler);
163
+ }
164
+ /** Subscribe to ALL events */
165
+ onAny(handler) {
166
+ this.globalHandlers.add(handler);
167
+ }
168
+ /** Unsubscribe from a specific event type */
169
+ off(type, handler) {
170
+ this.handlers.get(type)?.delete(handler);
171
+ }
172
+ /** Unsubscribe from ALL events */
173
+ offAny(handler) {
174
+ this.globalHandlers.delete(handler);
175
+ }
176
+ /** Remove all listeners */
177
+ clear() {
178
+ this.handlers.clear();
179
+ this.globalHandlers.clear();
180
+ }
181
+ };
182
+ var orgBus = new StateBus();
183
+
184
+ // src/lib/store.ts
118
185
  function vectorToBlob(vector) {
119
186
  const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
120
187
  return JSON.stringify(Array.from(f32));
@@ -293,6 +293,13 @@ async function ensureSchema() {
293
293
  });
294
294
  } catch {
295
295
  }
296
+ try {
297
+ await client.execute({
298
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
299
+ args: []
300
+ });
301
+ } catch {
302
+ }
296
303
  try {
297
304
  await client.execute({
298
305
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -739,6 +746,18 @@ async function ensureSchema() {
739
746
  CREATE INDEX IF NOT EXISTS idx_session_kills_agent
740
747
  ON session_kills(agent_id);
741
748
  `);
749
+ await client.execute(`
750
+ CREATE TABLE IF NOT EXISTS global_procedures (
751
+ id TEXT PRIMARY KEY,
752
+ title TEXT NOT NULL,
753
+ content TEXT NOT NULL,
754
+ priority TEXT NOT NULL DEFAULT 'p0',
755
+ domain TEXT,
756
+ active INTEGER NOT NULL DEFAULT 1,
757
+ created_at TEXT NOT NULL,
758
+ updated_at TEXT NOT NULL
759
+ )
760
+ `);
742
761
  await client.executeMultiple(`
743
762
  CREATE TABLE IF NOT EXISTS conversations (
744
763
  id TEXT PRIMARY KEY,
@@ -315,6 +315,13 @@ async function ensureSchema() {
315
315
  });
316
316
  } catch {
317
317
  }
318
+ try {
319
+ await client.execute({
320
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
321
+ args: []
322
+ });
323
+ } catch {
324
+ }
318
325
  try {
319
326
  await client.execute({
320
327
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -761,6 +768,18 @@ async function ensureSchema() {
761
768
  CREATE INDEX IF NOT EXISTS idx_session_kills_agent
762
769
  ON session_kills(agent_id);
763
770
  `);
771
+ await client.execute(`
772
+ CREATE TABLE IF NOT EXISTS global_procedures (
773
+ id TEXT PRIMARY KEY,
774
+ title TEXT NOT NULL,
775
+ content TEXT NOT NULL,
776
+ priority TEXT NOT NULL DEFAULT 'p0',
777
+ domain TEXT,
778
+ active INTEGER NOT NULL DEFAULT 1,
779
+ created_at TEXT NOT NULL,
780
+ updated_at TEXT NOT NULL
781
+ )
782
+ `);
764
783
  await client.executeMultiple(`
765
784
  CREATE TABLE IF NOT EXISTS conversations (
766
785
  id TEXT PRIMARY KEY,
@@ -1,3 +1,21 @@
1
+ // src/lib/global-procedures.ts
2
+ import { randomUUID } from "crypto";
3
+
4
+ // src/lib/database.ts
5
+ import { createClient } from "@libsql/client";
6
+
7
+ // src/lib/global-procedures.ts
8
+ var _cache = "";
9
+ var _cacheLoaded = false;
10
+ function getGlobalProceduresBlock() {
11
+ if (!_cacheLoaded) return "";
12
+ if (!_cache) return "";
13
+ return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
14
+
15
+ ${_cache}
16
+ `;
17
+ }
18
+
1
19
  // src/lib/employee-templates.ts
2
20
  var BASE_OPERATING_PROCEDURES = `
3
21
  EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
@@ -148,7 +166,8 @@ var PROCEDURES_MARKER = "EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES";
148
166
  function getSessionPrompt(storedPrompt) {
149
167
  const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
150
168
  const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
151
- return `${rolePrompt}
169
+ const globalBlock = getGlobalProceduresBlock();
170
+ return `${globalBlock}${rolePrompt}
152
171
  ${BASE_OPERATING_PROCEDURES}`;
153
172
  }
154
173
  var TEMPLATES = {
@@ -589,6 +589,13 @@ async function ensureSchema() {
589
589
  });
590
590
  } catch {
591
591
  }
592
+ try {
593
+ await client.execute({
594
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
595
+ args: []
596
+ });
597
+ } catch {
598
+ }
592
599
  try {
593
600
  await client.execute({
594
601
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -1035,6 +1042,18 @@ async function ensureSchema() {
1035
1042
  CREATE INDEX IF NOT EXISTS idx_session_kills_agent
1036
1043
  ON session_kills(agent_id);
1037
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
+ `);
1038
1057
  await client.executeMultiple(`
1039
1058
  CREATE TABLE IF NOT EXISTS conversations (
1040
1059
  id TEXT PRIMARY KEY,
@@ -1339,6 +1358,61 @@ var init_keychain = __esm({
1339
1358
  }
1340
1359
  });
1341
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
+
1342
1416
  // src/lib/shard-manager.ts
1343
1417
  var shard_manager_exports = {};
1344
1418
  __export(shard_manager_exports, {
@@ -1580,6 +1654,71 @@ var init_shard_manager = __esm({
1580
1654
  }
1581
1655
  });
1582
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
+
1583
1722
  // src/lib/store.ts
1584
1723
  var store_exports = {};
1585
1724
  __export(store_exports, {
@@ -1659,6 +1798,11 @@ async function initStore(options) {
1659
1798
  "version-query"
1660
1799
  );
1661
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
+ }
1662
1806
  }
1663
1807
  function classifyTier(record) {
1664
1808
  if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
@@ -1700,6 +1844,12 @@ async function writeMemory(record) {
1700
1844
  supersedes_id: record.supersedes_id ?? null
1701
1845
  };
1702
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
+ });
1703
1853
  const MAX_PENDING = 1e3;
1704
1854
  if (_pendingRecords.length > MAX_PENDING) {
1705
1855
  const dropped = _pendingRecords.length - MAX_PENDING;
@@ -2045,6 +2195,7 @@ var init_store = __esm({
2045
2195
  init_database();
2046
2196
  init_keychain();
2047
2197
  init_config();
2198
+ init_state_bus();
2048
2199
  INIT_MAX_RETRIES = 3;
2049
2200
  INIT_RETRY_DELAY_MS = 1e3;
2050
2201
  _pendingRecords = [];
@@ -2500,7 +2651,7 @@ var init_employees = __esm({
2500
2651
 
2501
2652
  // src/lib/license.ts
2502
2653
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2503
- import { randomUUID } from "crypto";
2654
+ import { randomUUID as randomUUID2 } from "crypto";
2504
2655
  import path7 from "path";
2505
2656
  import { jwtVerify, importSPKI } from "jose";
2506
2657
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
@@ -2898,9 +3049,15 @@ async function createTaskCore(input) {
2898
3049
  }
2899
3050
  }
2900
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
+ }
2901
3058
  await client.execute({
2902
- 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)
2903
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2904
3061
  args: [
2905
3062
  id,
2906
3063
  input.title,
@@ -2919,6 +3076,7 @@ async function createTaskCore(input) {
2919
3076
  input.budgetFallbackModel ?? null,
2920
3077
  0,
2921
3078
  null,
3079
+ sessionScope,
2922
3080
  now,
2923
3081
  now
2924
3082
  ]
@@ -2963,6 +3121,15 @@ async function listTasks(input) {
2963
3121
  conditions.push("priority = ?");
2964
3122
  args.push(input.priority);
2965
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
+ }
2966
3133
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2967
3134
  const result = await client.execute({
2968
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`,
@@ -3331,6 +3498,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
3331
3498
  skipDispatch: true
3332
3499
  });
3333
3500
  const reviewId = reviewTask.id;
3501
+ orgBus.emit({
3502
+ type: "review_created",
3503
+ reviewId,
3504
+ employee: agent,
3505
+ reviewer,
3506
+ timestamp: now
3507
+ });
3334
3508
  await writeNotification({
3335
3509
  agentId: agent,
3336
3510
  agentRole: String(row.assigned_to),
@@ -3427,6 +3601,7 @@ var init_tasks_review = __esm({
3427
3601
  init_tasks_crud();
3428
3602
  init_tmux_routing();
3429
3603
  init_session_key();
3604
+ init_state_bus();
3430
3605
  }
3431
3606
  });
3432
3607
 
@@ -3591,13 +3766,12 @@ function assertSessionScope(actionType, targetProject) {
3591
3766
  };
3592
3767
  }
3593
3768
  process.stderr.write(
3594
- `[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}"
3595
3770
  `
3596
3771
  );
3597
3772
  return {
3598
- allowed: true,
3599
- // v1: warn-only, don't block
3600
- reason: "cross_session_granted",
3773
+ allowed: false,
3774
+ reason: "cross_session_denied",
3601
3775
  currentProject,
3602
3776
  targetProject,
3603
3777
  targetSession: findSessionForProject(targetProject)?.windowName
@@ -3623,8 +3797,9 @@ async function dispatchTaskToEmployee(input) {
3623
3797
  try {
3624
3798
  const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
3625
3799
  const check = assertSessionScope2("dispatch_task", input.projectName);
3626
- if (check.reason === "cross_session_granted") {
3800
+ if (check.reason === "cross_session_denied") {
3627
3801
  crossProject = true;
3802
+ return { dispatched: "skipped", crossProject: true };
3628
3803
  }
3629
3804
  } catch {
3630
3805
  }
@@ -4087,6 +4262,13 @@ async function updateTask(input) {
4087
4262
  await cascadeUnblock(taskId, input.baseDir, now);
4088
4263
  } catch {
4089
4264
  }
4265
+ orgBus.emit({
4266
+ type: "task_completed",
4267
+ taskId,
4268
+ employee: String(row.assigned_to),
4269
+ result: input.result ?? "",
4270
+ timestamp: now
4271
+ });
4090
4272
  if (row.parent_task_id) {
4091
4273
  try {
4092
4274
  await checkSubtaskCompletion(String(row.parent_task_id), String(row.project_name));
@@ -4154,6 +4336,7 @@ var init_tasks = __esm({
4154
4336
  init_database();
4155
4337
  init_config();
4156
4338
  init_notifications();
4339
+ init_state_bus();
4157
4340
  init_tasks_crud();
4158
4341
  init_tasks_review();
4159
4342
  init_tasks_crud();
@@ -4544,8 +4727,28 @@ function getMySession() {
4544
4727
  return getTransport().getMySession();
4545
4728
  }
4546
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
+ }
4547
4744
  const suffix = instance != null && instance > 0 ? String(instance) : "";
4548
- 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;
4549
4752
  }
4550
4753
  function parseParentExe(sessionName, agentId) {
4551
4754
  const escaped = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -4785,6 +4988,22 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4785
4988
  error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
4786
4989
  };
4787
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
+ }
4788
5007
  let effectiveInstance = opts?.instance;
4789
5008
  if (effectiveInstance === void 0 && opts?.autoInstance) {
4790
5009
  const free = findFreeInstance(
@@ -5031,7 +5250,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5031
5250
  releaseSpawnLock(sessionName);
5032
5251
  return { sessionName };
5033
5252
  }
5034
- 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;
5035
5254
  var init_tmux_routing = __esm({
5036
5255
  "src/lib/tmux-routing.ts"() {
5037
5256
  "use strict";
@@ -5046,6 +5265,7 @@ var init_tmux_routing = __esm({
5046
5265
  SPAWN_LOCK_DIR = path15.join(os6.homedir(), ".exe-os", "spawn-locks");
5047
5266
  SESSION_CACHE = path15.join(os6.homedir(), ".exe-os", "session-cache");
5048
5267
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5268
+ VALID_SESSION_NAME = /^[a-z]+-exe\d+$|^[a-z]+\d+-exe\d+$/;
5049
5269
  VERIFY_PANE_LINES = 200;
5050
5270
  INTERCOM_DEBOUNCE_MS = 3e4;
5051
5271
  INTERCOM_LOG2 = path15.join(os6.homedir(), ".exe-os", "intercom.log");
@@ -5903,7 +6123,7 @@ __export(consolidation_exports, {
5903
6123
  selectUnconsolidated: () => selectUnconsolidated,
5904
6124
  storeConsolidation: () => storeConsolidation
5905
6125
  });
5906
- import { randomUUID as randomUUID2 } from "crypto";
6126
+ import { randomUUID as randomUUID3 } from "crypto";
5907
6127
  async function selectUnconsolidated(client, limit = 200) {
5908
6128
  const result = await client.execute({
5909
6129
  sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
@@ -5999,7 +6219,7 @@ async function consolidateCluster(cluster, model) {
5999
6219
  return textBlock?.text ?? "";
6000
6220
  }
6001
6221
  async function storeConsolidation(client, cluster, synthesisText, embedFn) {
6002
- const consolidatedId = randomUUID2();
6222
+ const consolidatedId = randomUUID3();
6003
6223
  const now = (/* @__PURE__ */ new Date()).toISOString();
6004
6224
  const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
6005
6225
 
@@ -6024,7 +6244,7 @@ ${synthesisText}`;
6024
6244
  const linkStmts = sourceIds.map((sourceId) => ({
6025
6245
  sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
6026
6246
  VALUES (?, ?, ?, ?)`,
6027
- args: [randomUUID2(), consolidatedId, sourceId, now]
6247
+ args: [randomUUID3(), consolidatedId, sourceId, now]
6028
6248
  }));
6029
6249
  const placeholders = sourceIds.map(() => "?").join(",");
6030
6250
  const markStmt = {
@@ -6263,7 +6483,7 @@ var init_consolidation = __esm({
6263
6483
  // src/lib/exe-daemon-client.ts
6264
6484
  import net from "net";
6265
6485
  import { spawn } from "child_process";
6266
- import { randomUUID as randomUUID3 } from "crypto";
6486
+ import { randomUUID as randomUUID4 } from "crypto";
6267
6487
  import { existsSync as existsSync14, unlinkSync as unlinkSync5, readFileSync as readFileSync11, openSync, closeSync, statSync } from "fs";
6268
6488
  import path17 from "path";
6269
6489
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -6455,7 +6675,7 @@ function sendRequest(texts, priority) {
6455
6675
  resolve({ error: "Not connected" });
6456
6676
  return;
6457
6677
  }
6458
- const id = randomUUID3();
6678
+ const id = randomUUID4();
6459
6679
  const timer = setTimeout(() => {
6460
6680
  _pending.delete(id);
6461
6681
  resolve({ error: "Request timeout" });
@@ -6473,7 +6693,7 @@ function sendRequest(texts, priority) {
6473
6693
  async function pingDaemon() {
6474
6694
  if (!_socket || !_connected) return null;
6475
6695
  return new Promise((resolve) => {
6476
- const id = randomUUID3();
6696
+ const id = randomUUID4();
6477
6697
  const timer = setTimeout(() => {
6478
6698
  _pending.delete(id);
6479
6699
  resolve(null);