@askexenow/exe-os 0.8.85 → 0.8.87

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 (57) hide show
  1. package/dist/bin/cleanup-stale-review-tasks.js +57 -19
  2. package/dist/bin/cli.js +510 -340
  3. package/dist/bin/exe-agent-config.js +242 -0
  4. package/dist/bin/exe-agent.js +3 -3
  5. package/dist/bin/exe-boot.js +344 -346
  6. package/dist/bin/exe-dispatch.js +375 -250
  7. package/dist/bin/exe-forget.js +5 -1
  8. package/dist/bin/exe-gateway.js +260 -135
  9. package/dist/bin/exe-healthcheck.js +133 -1
  10. package/dist/bin/exe-heartbeat.js +72 -31
  11. package/dist/bin/exe-link.js +25 -2
  12. package/dist/bin/exe-new-employee.js +22 -0
  13. package/dist/bin/exe-pending-messages.js +55 -17
  14. package/dist/bin/exe-pending-reviews.js +57 -19
  15. package/dist/bin/exe-search.js +6 -2
  16. package/dist/bin/exe-session-cleanup.js +260 -135
  17. package/dist/bin/exe-start-codex.js +2598 -0
  18. package/dist/bin/exe-start.sh +15 -3
  19. package/dist/bin/exe-status.js +57 -19
  20. package/dist/bin/git-sweep.js +391 -266
  21. package/dist/bin/install.js +22 -0
  22. package/dist/bin/scan-tasks.js +394 -269
  23. package/dist/bin/setup.js +50 -5
  24. package/dist/gateway/index.js +257 -132
  25. package/dist/hooks/bug-report-worker.js +242 -117
  26. package/dist/hooks/commit-complete.js +389 -264
  27. package/dist/hooks/error-recall.js +6 -2
  28. package/dist/hooks/ingest-worker.js +314 -193
  29. package/dist/hooks/post-compact.js +84 -46
  30. package/dist/hooks/pre-compact.js +272 -147
  31. package/dist/hooks/pre-tool-use.js +104 -66
  32. package/dist/hooks/prompt-submit.js +126 -66
  33. package/dist/hooks/session-end.js +277 -152
  34. package/dist/hooks/session-start.js +70 -28
  35. package/dist/hooks/stop.js +90 -52
  36. package/dist/hooks/subagent-stop.js +84 -46
  37. package/dist/hooks/summary-worker.js +175 -114
  38. package/dist/index.js +296 -171
  39. package/dist/lib/agent-config.js +167 -0
  40. package/dist/lib/cloud-sync.js +25 -2
  41. package/dist/lib/exe-daemon.js +338 -213
  42. package/dist/lib/hybrid-search.js +7 -2
  43. package/dist/lib/messaging.js +95 -39
  44. package/dist/lib/runtime-table.js +16 -0
  45. package/dist/lib/session-wrappers.js +22 -0
  46. package/dist/lib/tasks.js +242 -117
  47. package/dist/lib/tmux-routing.js +314 -189
  48. package/dist/mcp/server.js +573 -274
  49. package/dist/mcp/tools/create-task.js +260 -135
  50. package/dist/mcp/tools/list-tasks.js +68 -30
  51. package/dist/mcp/tools/send-message.js +100 -44
  52. package/dist/mcp/tools/update-task.js +123 -67
  53. package/dist/runtime/index.js +276 -151
  54. package/dist/tui/App.js +479 -354
  55. package/package.json +1 -1
  56. package/src/commands/exe/agent-config.md +27 -0
  57. package/src/commands/exe/cc-doctor.md +10 -0
@@ -269,123 +269,19 @@ var init_provider_table = __esm({
269
269
  }
270
270
  });
271
271
 
272
- // src/lib/intercom-queue.ts
273
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
274
- import path2 from "path";
275
- import os2 from "os";
276
- function ensureDir() {
277
- const dir = path2.dirname(QUEUE_PATH);
278
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
279
- }
280
- function readQueue() {
281
- try {
282
- if (!existsSync2(QUEUE_PATH)) return [];
283
- return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
284
- } catch {
285
- return [];
286
- }
287
- }
288
- function writeQueue(queue) {
289
- ensureDir();
290
- const tmp = `${QUEUE_PATH}.tmp`;
291
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
292
- renameSync(tmp, QUEUE_PATH);
293
- }
294
- function queueIntercom(targetSession, reason) {
295
- const queue = readQueue();
296
- const existing = queue.find((q) => q.targetSession === targetSession);
297
- if (existing) {
298
- existing.attempts++;
299
- existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
300
- existing.reason = reason;
301
- } else {
302
- queue.push({
303
- targetSession,
304
- queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
305
- attempts: 0,
306
- reason
307
- });
308
- }
309
- writeQueue(queue);
310
- }
311
- var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
312
- var init_intercom_queue = __esm({
313
- "src/lib/intercom-queue.ts"() {
314
- "use strict";
315
- QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
316
- TTL_MS = 60 * 60 * 1e3;
317
- INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
318
- }
319
- });
320
-
321
- // src/lib/db-retry.ts
322
- function isBusyError(err) {
323
- if (err instanceof Error) {
324
- const msg = err.message.toLowerCase();
325
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
326
- }
327
- return false;
328
- }
329
- function delay(ms) {
330
- return new Promise((resolve) => setTimeout(resolve, ms));
331
- }
332
- async function retryOnBusy(fn, label) {
333
- let lastError;
334
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
335
- try {
336
- return await fn();
337
- } catch (err) {
338
- lastError = err;
339
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
340
- throw err;
341
- }
342
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
343
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
344
- process.stderr.write(
345
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
346
- `
347
- );
348
- await delay(backoff + jitter);
349
- }
350
- }
351
- throw lastError;
352
- }
353
- function wrapWithRetry(client) {
354
- return new Proxy(client, {
355
- get(target, prop, receiver) {
356
- if (prop === "execute") {
357
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
358
- }
359
- if (prop === "batch") {
360
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
361
- }
362
- return Reflect.get(target, prop, receiver);
363
- }
364
- });
365
- }
366
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
367
- var init_db_retry = __esm({
368
- "src/lib/db-retry.ts"() {
369
- "use strict";
370
- MAX_RETRIES = 3;
371
- BASE_DELAY_MS = 200;
372
- MAX_JITTER_MS = 300;
373
- }
374
- });
375
-
376
272
  // src/lib/config.ts
377
273
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
378
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
379
- import path3 from "path";
380
- import os3 from "os";
274
+ import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
275
+ import path2 from "path";
276
+ import os2 from "os";
381
277
  function resolveDataDir() {
382
278
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
383
279
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
384
- const newDir = path3.join(os3.homedir(), ".exe-os");
385
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
386
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
280
+ const newDir = path2.join(os2.homedir(), ".exe-os");
281
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
282
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
387
283
  try {
388
- renameSync2(legacyDir, newDir);
284
+ renameSync(legacyDir, newDir);
389
285
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
390
286
  `);
391
287
  } catch {
@@ -447,9 +343,9 @@ function normalizeAutoUpdate(raw) {
447
343
  async function loadConfig() {
448
344
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
449
345
  await mkdir(dir, { recursive: true });
450
- const configPath = path3.join(dir, "config.json");
451
- if (!existsSync3(configPath)) {
452
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
346
+ const configPath = path2.join(dir, "config.json");
347
+ if (!existsSync2(configPath)) {
348
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
453
349
  }
454
350
  const raw = await readFile(configPath, "utf-8");
455
351
  try {
@@ -467,13 +363,13 @@ async function loadConfig() {
467
363
  normalizeScalingRoadmap(migratedCfg);
468
364
  normalizeSessionLifecycle(migratedCfg);
469
365
  normalizeAutoUpdate(migratedCfg);
470
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
366
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
471
367
  if (config.dbPath.startsWith("~")) {
472
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
368
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
473
369
  }
474
370
  return config;
475
371
  } catch {
476
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
372
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
477
373
  }
478
374
  }
479
375
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -481,10 +377,10 @@ var init_config = __esm({
481
377
  "src/lib/config.ts"() {
482
378
  "use strict";
483
379
  EXE_AI_DIR = resolveDataDir();
484
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
485
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
486
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
487
- LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
380
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
381
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
382
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
383
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
488
384
  CURRENT_CONFIG_VERSION = 1;
489
385
  DEFAULT_CONFIG = {
490
386
  config_version: CURRENT_CONFIG_VERSION,
@@ -556,11 +452,166 @@ var init_config = __esm({
556
452
  }
557
453
  });
558
454
 
455
+ // src/lib/runtime-table.ts
456
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
457
+ var init_runtime_table = __esm({
458
+ "src/lib/runtime-table.ts"() {
459
+ "use strict";
460
+ RUNTIME_TABLE = {
461
+ codex: {
462
+ binary: "codex",
463
+ launchMode: "exec",
464
+ autoApproveFlag: "--full-auto",
465
+ inlineFlag: "--no-alt-screen",
466
+ apiKeyEnv: "OPENAI_API_KEY",
467
+ defaultModel: "gpt-5.4"
468
+ }
469
+ };
470
+ DEFAULT_RUNTIME = "claude";
471
+ }
472
+ });
473
+
474
+ // src/lib/agent-config.ts
475
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
476
+ import path3 from "path";
477
+ function loadAgentConfig() {
478
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
479
+ try {
480
+ return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
481
+ } catch {
482
+ return {};
483
+ }
484
+ }
485
+ function getAgentRuntime(agentId) {
486
+ const config = loadAgentConfig();
487
+ const entry = config[agentId];
488
+ if (entry) return entry;
489
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
490
+ }
491
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
492
+ var init_agent_config = __esm({
493
+ "src/lib/agent-config.ts"() {
494
+ "use strict";
495
+ init_config();
496
+ init_runtime_table();
497
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
498
+ DEFAULT_MODELS = {
499
+ claude: "claude-opus-4",
500
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
501
+ opencode: "minimax-m2.7"
502
+ };
503
+ }
504
+ });
505
+
506
+ // src/lib/intercom-queue.ts
507
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
508
+ import path4 from "path";
509
+ import os3 from "os";
510
+ function ensureDir() {
511
+ const dir = path4.dirname(QUEUE_PATH);
512
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
513
+ }
514
+ function readQueue() {
515
+ try {
516
+ if (!existsSync4(QUEUE_PATH)) return [];
517
+ return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ function writeQueue(queue) {
523
+ ensureDir();
524
+ const tmp = `${QUEUE_PATH}.tmp`;
525
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
526
+ renameSync2(tmp, QUEUE_PATH);
527
+ }
528
+ function queueIntercom(targetSession, reason) {
529
+ const queue = readQueue();
530
+ const existing = queue.find((q) => q.targetSession === targetSession);
531
+ if (existing) {
532
+ existing.attempts++;
533
+ existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
534
+ existing.reason = reason;
535
+ } else {
536
+ queue.push({
537
+ targetSession,
538
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
539
+ attempts: 0,
540
+ reason
541
+ });
542
+ }
543
+ writeQueue(queue);
544
+ }
545
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
546
+ var init_intercom_queue = __esm({
547
+ "src/lib/intercom-queue.ts"() {
548
+ "use strict";
549
+ QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
550
+ TTL_MS = 60 * 60 * 1e3;
551
+ INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
552
+ }
553
+ });
554
+
555
+ // src/lib/db-retry.ts
556
+ function isBusyError(err) {
557
+ if (err instanceof Error) {
558
+ const msg = err.message.toLowerCase();
559
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
560
+ }
561
+ return false;
562
+ }
563
+ function delay(ms) {
564
+ return new Promise((resolve) => setTimeout(resolve, ms));
565
+ }
566
+ async function retryOnBusy(fn, label) {
567
+ let lastError;
568
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
569
+ try {
570
+ return await fn();
571
+ } catch (err) {
572
+ lastError = err;
573
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
574
+ throw err;
575
+ }
576
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
577
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
578
+ process.stderr.write(
579
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
580
+ `
581
+ );
582
+ await delay(backoff + jitter);
583
+ }
584
+ }
585
+ throw lastError;
586
+ }
587
+ function wrapWithRetry(client) {
588
+ return new Proxy(client, {
589
+ get(target, prop, receiver) {
590
+ if (prop === "execute") {
591
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
592
+ }
593
+ if (prop === "batch") {
594
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
595
+ }
596
+ return Reflect.get(target, prop, receiver);
597
+ }
598
+ });
599
+ }
600
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
601
+ var init_db_retry = __esm({
602
+ "src/lib/db-retry.ts"() {
603
+ "use strict";
604
+ MAX_RETRIES = 3;
605
+ BASE_DELAY_MS = 200;
606
+ MAX_JITTER_MS = 300;
607
+ }
608
+ });
609
+
559
610
  // src/lib/employees.ts
560
611
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
561
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
612
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
562
613
  import { execSync as execSync3 } from "child_process";
563
- import path4 from "path";
614
+ import path5 from "path";
564
615
  import os4 from "os";
565
616
  function normalizeRole(role) {
566
617
  return (role ?? "").trim().toLowerCase();
@@ -579,9 +630,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
579
630
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
580
631
  }
581
632
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
582
- if (!existsSync4(employeesPath)) return [];
633
+ if (!existsSync5(employeesPath)) return [];
583
634
  try {
584
- return JSON.parse(readFileSync4(employeesPath, "utf-8"));
635
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
585
636
  } catch {
586
637
  return [];
587
638
  }
@@ -600,7 +651,7 @@ var init_employees = __esm({
600
651
  "src/lib/employees.ts"() {
601
652
  "use strict";
602
653
  init_config();
603
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
654
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
604
655
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
605
656
  COORDINATOR_ROLE = "COO";
606
657
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
@@ -1581,18 +1632,18 @@ var init_database = __esm({
1581
1632
  });
1582
1633
 
1583
1634
  // src/lib/license.ts
1584
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1635
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
1585
1636
  import { randomUUID } from "crypto";
1586
- import path5 from "path";
1637
+ import path6 from "path";
1587
1638
  import { jwtVerify, importSPKI } from "jose";
1588
1639
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1589
1640
  var init_license = __esm({
1590
1641
  "src/lib/license.ts"() {
1591
1642
  "use strict";
1592
1643
  init_config();
1593
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
1594
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
1595
- DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
1644
+ LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
1645
+ CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
1646
+ DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
1596
1647
  PLAN_LIMITS = {
1597
1648
  free: { devices: 1, employees: 1, memories: 5e3 },
1598
1649
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1604,12 +1655,12 @@ var init_license = __esm({
1604
1655
  });
1605
1656
 
1606
1657
  // src/lib/plan-limits.ts
1607
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1608
- import path6 from "path";
1658
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
1659
+ import path7 from "path";
1609
1660
  function getLicenseSync() {
1610
1661
  try {
1611
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
1612
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
1662
+ if (!existsSync7(CACHE_PATH2)) return freeLicense();
1663
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
1613
1664
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1614
1665
  const parts = raw.token.split(".");
1615
1666
  if (parts.length !== 3) return freeLicense();
@@ -1647,8 +1698,8 @@ function assertEmployeeLimitSync(rosterPath) {
1647
1698
  const filePath = rosterPath ?? EMPLOYEES_PATH;
1648
1699
  let count = 0;
1649
1700
  try {
1650
- if (existsSync6(filePath)) {
1651
- const raw = readFileSync6(filePath, "utf8");
1701
+ if (existsSync7(filePath)) {
1702
+ const raw = readFileSync7(filePath, "utf8");
1652
1703
  const employees = JSON.parse(raw);
1653
1704
  count = Array.isArray(employees) ? employees.length : 0;
1654
1705
  }
@@ -1677,19 +1728,19 @@ var init_plan_limits = __esm({
1677
1728
  this.name = "PlanLimitError";
1678
1729
  }
1679
1730
  };
1680
- CACHE_PATH2 = path6.join(EXE_AI_DIR, "license-cache.json");
1731
+ CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
1681
1732
  }
1682
1733
  });
1683
1734
 
1684
1735
  // src/lib/notifications.ts
1685
1736
  import crypto from "crypto";
1686
- import path7 from "path";
1737
+ import path8 from "path";
1687
1738
  import os5 from "os";
1688
1739
  import {
1689
- readFileSync as readFileSync7,
1740
+ readFileSync as readFileSync8,
1690
1741
  readdirSync,
1691
1742
  unlinkSync as unlinkSync2,
1692
- existsSync as existsSync7,
1743
+ existsSync as existsSync8,
1693
1744
  rmdirSync
1694
1745
  } from "fs";
1695
1746
  async function writeNotification(notification) {
@@ -1848,11 +1899,11 @@ var init_state_bus = __esm({
1848
1899
 
1849
1900
  // src/lib/tasks-crud.ts
1850
1901
  import crypto3 from "crypto";
1851
- import path8 from "path";
1902
+ import path9 from "path";
1852
1903
  import os6 from "os";
1853
1904
  import { execSync as execSync4 } from "child_process";
1854
1905
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1855
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
1906
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
1856
1907
  async function writeCheckpoint(input) {
1857
1908
  const client = getClient();
1858
1909
  const row = await resolveTask(client, input.taskId);
@@ -2027,8 +2078,8 @@ ${laneWarning}` : laneWarning;
2027
2078
  }
2028
2079
  if (input.baseDir) {
2029
2080
  try {
2030
- await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
2031
- await mkdir3(path8.join(input.baseDir, "exe", "research"), { recursive: true });
2081
+ await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
2082
+ await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2032
2083
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2033
2084
  await ensureGitignoreExe(input.baseDir);
2034
2085
  } catch {
@@ -2064,10 +2115,10 @@ ${laneWarning}` : laneWarning;
2064
2115
  });
2065
2116
  if (input.baseDir) {
2066
2117
  try {
2067
- const EXE_OS_DIR = path8.join(os6.homedir(), ".exe-os");
2068
- const mdPath = path8.join(EXE_OS_DIR, taskFile);
2069
- const mdDir = path8.dirname(mdPath);
2070
- if (!existsSync8(mdDir)) await mkdir3(mdDir, { recursive: true });
2118
+ const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
2119
+ const mdPath = path9.join(EXE_OS_DIR, taskFile);
2120
+ const mdDir = path9.dirname(mdPath);
2121
+ if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2071
2122
  const reviewer = input.reviewer ?? input.assignedBy;
2072
2123
  const mdContent = `# ${input.title}
2073
2124
 
@@ -2092,7 +2143,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2092
2143
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2093
2144
  `;
2094
2145
  await writeFile3(mdPath, mdContent, "utf-8");
2095
- } catch {
2146
+ } catch (err) {
2147
+ process.stderr.write(
2148
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2149
+ `
2150
+ );
2096
2151
  }
2097
2152
  }
2098
2153
  return {
@@ -2352,9 +2407,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2352
2407
  return { taskFile, assignedTo, assignedBy, taskSlug };
2353
2408
  }
2354
2409
  async function ensureArchitectureDoc(baseDir, projectName) {
2355
- const archPath = path8.join(baseDir, "exe", "ARCHITECTURE.md");
2410
+ const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
2356
2411
  try {
2357
- if (existsSync8(archPath)) return;
2412
+ if (existsSync9(archPath)) return;
2358
2413
  const template = [
2359
2414
  `# ${projectName} \u2014 System Architecture`,
2360
2415
  "",
@@ -2387,10 +2442,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2387
2442
  }
2388
2443
  }
2389
2444
  async function ensureGitignoreExe(baseDir) {
2390
- const gitignorePath = path8.join(baseDir, ".gitignore");
2445
+ const gitignorePath = path9.join(baseDir, ".gitignore");
2391
2446
  try {
2392
- if (existsSync8(gitignorePath)) {
2393
- const content = readFileSync8(gitignorePath, "utf-8");
2447
+ if (existsSync9(gitignorePath)) {
2448
+ const content = readFileSync9(gitignorePath, "utf-8");
2394
2449
  if (/^\/?exe\/?$/m.test(content)) return;
2395
2450
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2396
2451
  } else {
@@ -2421,8 +2476,8 @@ var init_tasks_crud = __esm({
2421
2476
  });
2422
2477
 
2423
2478
  // src/lib/tasks-review.ts
2424
- import path9 from "path";
2425
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
2479
+ import path10 from "path";
2480
+ import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
2426
2481
  async function countPendingReviews(sessionScope) {
2427
2482
  const client = getClient();
2428
2483
  if (sessionScope) {
@@ -2603,11 +2658,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2603
2658
  );
2604
2659
  }
2605
2660
  try {
2606
- const cacheDir = path9.join(EXE_AI_DIR, "session-cache");
2607
- if (existsSync9(cacheDir)) {
2661
+ const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
2662
+ if (existsSync10(cacheDir)) {
2608
2663
  for (const f of readdirSync2(cacheDir)) {
2609
2664
  if (f.startsWith("review-notified-")) {
2610
- unlinkSync3(path9.join(cacheDir, f));
2665
+ unlinkSync3(path10.join(cacheDir, f));
2611
2666
  }
2612
2667
  }
2613
2668
  }
@@ -2628,7 +2683,7 @@ var init_tasks_review = __esm({
2628
2683
  });
2629
2684
 
2630
2685
  // src/lib/tasks-chain.ts
2631
- import path10 from "path";
2686
+ import path11 from "path";
2632
2687
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2633
2688
  async function cascadeUnblock(taskId, baseDir, now) {
2634
2689
  const client = getClient();
@@ -2645,7 +2700,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
2645
2700
  });
2646
2701
  for (const ur of unblockedRows.rows) {
2647
2702
  try {
2648
- const ubFile = path10.join(baseDir, String(ur.task_file));
2703
+ const ubFile = path11.join(baseDir, String(ur.task_file));
2649
2704
  let ubContent = await readFile3(ubFile, "utf-8");
2650
2705
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
2651
2706
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -2714,7 +2769,7 @@ var init_tasks_chain = __esm({
2714
2769
 
2715
2770
  // src/lib/project-name.ts
2716
2771
  import { execSync as execSync5 } from "child_process";
2717
- import path11 from "path";
2772
+ import path12 from "path";
2718
2773
  function getProjectName(cwd) {
2719
2774
  const dir = cwd ?? process.cwd();
2720
2775
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -2727,7 +2782,7 @@ function getProjectName(cwd) {
2727
2782
  timeout: 2e3,
2728
2783
  stdio: ["pipe", "pipe", "pipe"]
2729
2784
  }).trim();
2730
- repoRoot = path11.dirname(gitCommonDir);
2785
+ repoRoot = path12.dirname(gitCommonDir);
2731
2786
  } catch {
2732
2787
  repoRoot = execSync5("git rev-parse --show-toplevel", {
2733
2788
  cwd: dir,
@@ -2736,11 +2791,11 @@ function getProjectName(cwd) {
2736
2791
  stdio: ["pipe", "pipe", "pipe"]
2737
2792
  }).trim();
2738
2793
  }
2739
- _cached2 = path11.basename(repoRoot);
2794
+ _cached2 = path12.basename(repoRoot);
2740
2795
  _cachedCwd = dir;
2741
2796
  return _cached2;
2742
2797
  } catch {
2743
- _cached2 = path11.basename(dir);
2798
+ _cached2 = path12.basename(dir);
2744
2799
  _cachedCwd = dir;
2745
2800
  return _cached2;
2746
2801
  }
@@ -3213,8 +3268,8 @@ __export(tasks_exports, {
3213
3268
  updateTaskStatus: () => updateTaskStatus,
3214
3269
  writeCheckpoint: () => writeCheckpoint
3215
3270
  });
3216
- import path12 from "path";
3217
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
3271
+ import path13 from "path";
3272
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
3218
3273
  async function createTask(input) {
3219
3274
  const result2 = await createTaskCore(input);
3220
3275
  if (!input.skipDispatch && result2.status !== "blocked" && !process.env.VITEST) {
@@ -3233,11 +3288,11 @@ async function updateTask(input) {
3233
3288
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3234
3289
  try {
3235
3290
  const agent = String(row.assigned_to);
3236
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3237
- const cachePath = path12.join(cacheDir, `current-task-${agent}.json`);
3291
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3292
+ const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3238
3293
  if (input.status === "in_progress") {
3239
- mkdirSync4(cacheDir, { recursive: true });
3240
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3294
+ mkdirSync5(cacheDir, { recursive: true });
3295
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3241
3296
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3242
3297
  try {
3243
3298
  unlinkSync4(cachePath);
@@ -3704,13 +3759,13 @@ __export(tmux_routing_exports, {
3704
3759
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3705
3760
  });
3706
3761
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
3707
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3708
- import path13 from "path";
3762
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
3763
+ import path14 from "path";
3709
3764
  import os7 from "os";
3710
3765
  import { fileURLToPath } from "url";
3711
3766
  import { unlinkSync as unlinkSync5 } from "fs";
3712
3767
  function spawnLockPath(sessionName) {
3713
- return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3768
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3714
3769
  }
3715
3770
  function isProcessAlive(pid) {
3716
3771
  try {
@@ -3721,13 +3776,13 @@ function isProcessAlive(pid) {
3721
3776
  }
3722
3777
  }
3723
3778
  function acquireSpawnLock(sessionName) {
3724
- if (!existsSync10(SPAWN_LOCK_DIR)) {
3725
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
3779
+ if (!existsSync11(SPAWN_LOCK_DIR)) {
3780
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
3726
3781
  }
3727
3782
  const lockFile = spawnLockPath(sessionName);
3728
- if (existsSync10(lockFile)) {
3783
+ if (existsSync11(lockFile)) {
3729
3784
  try {
3730
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
3785
+ const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
3731
3786
  const age = Date.now() - lock.timestamp;
3732
3787
  if (isProcessAlive(lock.pid) && age < 6e4) {
3733
3788
  return false;
@@ -3735,7 +3790,7 @@ function acquireSpawnLock(sessionName) {
3735
3790
  } catch {
3736
3791
  }
3737
3792
  }
3738
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3793
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3739
3794
  return true;
3740
3795
  }
3741
3796
  function releaseSpawnLock(sessionName) {
@@ -3747,13 +3802,13 @@ function releaseSpawnLock(sessionName) {
3747
3802
  function resolveBehaviorsExporterScript() {
3748
3803
  try {
3749
3804
  const thisFile = fileURLToPath(import.meta.url);
3750
- const scriptPath = path13.join(
3751
- path13.dirname(thisFile),
3805
+ const scriptPath = path14.join(
3806
+ path14.dirname(thisFile),
3752
3807
  "..",
3753
3808
  "bin",
3754
3809
  "exe-export-behaviors.js"
3755
3810
  );
3756
- return existsSync10(scriptPath) ? scriptPath : null;
3811
+ return existsSync11(scriptPath) ? scriptPath : null;
3757
3812
  } catch {
3758
3813
  return null;
3759
3814
  }
@@ -3819,12 +3874,12 @@ function extractRootExe(name) {
3819
3874
  return parts.length > 0 ? parts[parts.length - 1] : null;
3820
3875
  }
3821
3876
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3822
- if (!existsSync10(SESSION_CACHE)) {
3823
- mkdirSync5(SESSION_CACHE, { recursive: true });
3877
+ if (!existsSync11(SESSION_CACHE)) {
3878
+ mkdirSync6(SESSION_CACHE, { recursive: true });
3824
3879
  }
3825
3880
  const rootExe = extractRootExe(parentExe) ?? parentExe;
3826
- const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3827
- writeFileSync6(filePath, JSON.stringify({
3881
+ const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3882
+ writeFileSync7(filePath, JSON.stringify({
3828
3883
  parentExe: rootExe,
3829
3884
  dispatchedBy: dispatchedBy || rootExe,
3830
3885
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -3832,7 +3887,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3832
3887
  }
3833
3888
  function getParentExe(sessionKey) {
3834
3889
  try {
3835
- const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3890
+ const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3836
3891
  return data.parentExe || null;
3837
3892
  } catch {
3838
3893
  return null;
@@ -3840,8 +3895,8 @@ function getParentExe(sessionKey) {
3840
3895
  }
3841
3896
  function getDispatchedBy(sessionKey) {
3842
3897
  try {
3843
- const data = JSON.parse(readFileSync9(
3844
- path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3898
+ const data = JSON.parse(readFileSync10(
3899
+ path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3845
3900
  "utf8"
3846
3901
  ));
3847
3902
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -3902,32 +3957,50 @@ async function verifyPaneAtCapacity(sessionName) {
3902
3957
  }
3903
3958
  function readDebounceState() {
3904
3959
  try {
3905
- if (!existsSync10(DEBOUNCE_FILE)) return {};
3906
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
3960
+ if (!existsSync11(DEBOUNCE_FILE)) return {};
3961
+ const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
3962
+ const state = {};
3963
+ for (const [key, val] of Object.entries(raw)) {
3964
+ if (typeof val === "number") {
3965
+ state[key] = { lastSent: val, pending: 0 };
3966
+ } else if (val && typeof val === "object" && "lastSent" in val) {
3967
+ state[key] = val;
3968
+ }
3969
+ }
3970
+ return state;
3907
3971
  } catch {
3908
3972
  return {};
3909
3973
  }
3910
3974
  }
3911
3975
  function writeDebounceState(state) {
3912
3976
  try {
3913
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
3914
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
3977
+ if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
3978
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
3915
3979
  } catch {
3916
3980
  }
3917
3981
  }
3918
3982
  function isDebounced(targetSession) {
3919
3983
  const state = readDebounceState();
3920
- const lastSent = state[targetSession] ?? 0;
3921
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
3984
+ const entry = state[targetSession];
3985
+ const lastSent = entry?.lastSent ?? 0;
3986
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
3987
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
3988
+ state[targetSession].pending++;
3989
+ writeDebounceState(state);
3990
+ return true;
3991
+ }
3992
+ return false;
3922
3993
  }
3923
3994
  function recordDebounce(targetSession) {
3924
3995
  const state = readDebounceState();
3925
- state[targetSession] = Date.now();
3996
+ const batched = state[targetSession]?.pending ?? 0;
3997
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
3926
3998
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
3927
3999
  for (const key of Object.keys(state)) {
3928
- if ((state[key] ?? 0) < cutoff) delete state[key];
4000
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
3929
4001
  }
3930
4002
  writeDebounceState(state);
4003
+ return batched;
3931
4004
  }
3932
4005
  function logIntercom(msg) {
3933
4006
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -3972,7 +4045,7 @@ function sendIntercom(targetSession) {
3972
4045
  return "skipped_exe";
3973
4046
  }
3974
4047
  if (isDebounced(targetSession)) {
3975
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4048
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
3976
4049
  return "debounced";
3977
4050
  }
3978
4051
  try {
@@ -3984,14 +4057,14 @@ function sendIntercom(targetSession) {
3984
4057
  const sessionState = getSessionState(targetSession);
3985
4058
  if (sessionState === "no_claude") {
3986
4059
  queueIntercom(targetSession, "claude not running in session");
3987
- recordDebounce(targetSession);
3988
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4060
+ const batched2 = recordDebounce(targetSession);
4061
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
3989
4062
  return "queued";
3990
4063
  }
3991
4064
  if (sessionState === "thinking" || sessionState === "tool") {
3992
4065
  queueIntercom(targetSession, "session busy at send time");
3993
- recordDebounce(targetSession);
3994
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4066
+ const batched2 = recordDebounce(targetSession);
4067
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
3995
4068
  return "queued";
3996
4069
  }
3997
4070
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -3999,8 +4072,8 @@ function sendIntercom(targetSession) {
3999
4072
  transport.sendKeys(targetSession, "q");
4000
4073
  }
4001
4074
  transport.sendKeys(targetSession, "/exe-intercom");
4002
- recordDebounce(targetSession);
4003
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4075
+ const batched = recordDebounce(targetSession);
4076
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4004
4077
  return "delivered";
4005
4078
  } catch {
4006
4079
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4102,26 +4175,26 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4102
4175
  const transport = getTransport();
4103
4176
  const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
4104
4177
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
4105
- const logDir = path13.join(os7.homedir(), ".exe-os", "session-logs");
4106
- const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4107
- if (!existsSync10(logDir)) {
4108
- mkdirSync5(logDir, { recursive: true });
4178
+ const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4179
+ const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4180
+ if (!existsSync11(logDir)) {
4181
+ mkdirSync6(logDir, { recursive: true });
4109
4182
  }
4110
4183
  transport.kill(sessionName);
4111
4184
  let cleanupSuffix = "";
4112
4185
  try {
4113
4186
  const thisFile = fileURLToPath(import.meta.url);
4114
- const cleanupScript = path13.join(path13.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4115
- if (existsSync10(cleanupScript)) {
4187
+ const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4188
+ if (existsSync11(cleanupScript)) {
4116
4189
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
4117
4190
  }
4118
4191
  } catch {
4119
4192
  }
4120
4193
  try {
4121
- const claudeJsonPath = path13.join(os7.homedir(), ".claude.json");
4194
+ const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
4122
4195
  let claudeJson = {};
4123
4196
  try {
4124
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
4197
+ claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
4125
4198
  } catch {
4126
4199
  }
4127
4200
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4129,17 +4202,17 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4129
4202
  const trustDir = opts?.cwd ?? projectDir2;
4130
4203
  if (!projects[trustDir]) projects[trustDir] = {};
4131
4204
  projects[trustDir].hasTrustDialogAccepted = true;
4132
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4205
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4133
4206
  } catch {
4134
4207
  }
4135
4208
  try {
4136
- const settingsDir = path13.join(os7.homedir(), ".claude", "projects");
4209
+ const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
4137
4210
  const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
4138
- const projSettingsDir = path13.join(settingsDir, normalizedKey);
4139
- const settingsPath = path13.join(projSettingsDir, "settings.json");
4211
+ const projSettingsDir = path14.join(settingsDir, normalizedKey);
4212
+ const settingsPath = path14.join(projSettingsDir, "settings.json");
4140
4213
  let settings = {};
4141
4214
  try {
4142
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
4215
+ settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
4143
4216
  } catch {
4144
4217
  }
4145
4218
  const perms = settings.permissions ?? {};
@@ -4167,20 +4240,23 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4167
4240
  if (changed) {
4168
4241
  perms.allow = allow;
4169
4242
  settings.permissions = perms;
4170
- mkdirSync5(projSettingsDir, { recursive: true });
4171
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4243
+ mkdirSync6(projSettingsDir, { recursive: true });
4244
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4172
4245
  }
4173
4246
  } catch {
4174
4247
  }
4175
4248
  const spawnCwd = opts?.cwd ?? projectDir2;
4176
4249
  const useExeAgent = !!(opts?.model && opts?.provider);
4177
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4250
+ const agentRtConfig = getAgentRuntime(employeeName2);
4251
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4252
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4253
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4178
4254
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4179
4255
  let identityFlag = "";
4180
4256
  let behaviorsFlag = "";
4181
4257
  let legacyFallbackWarned = false;
4182
4258
  if (!useExeAgent && !useBinSymlink) {
4183
- const identityPath = path13.join(
4259
+ const identityPath = path14.join(
4184
4260
  os7.homedir(),
4185
4261
  ".exe-os",
4186
4262
  "identity",
@@ -4190,13 +4266,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4190
4266
  const hasAgentFlag = claudeSupportsAgentFlag();
4191
4267
  if (hasAgentFlag) {
4192
4268
  identityFlag = ` --agent ${employeeName2}`;
4193
- } else if (existsSync10(identityPath)) {
4269
+ } else if (existsSync11(identityPath)) {
4194
4270
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4195
4271
  legacyFallbackWarned = true;
4196
4272
  }
4197
4273
  const behaviorsFile = exportBehaviorsSync(
4198
4274
  employeeName2,
4199
- path13.basename(spawnCwd),
4275
+ path14.basename(spawnCwd),
4200
4276
  sessionName
4201
4277
  );
4202
4278
  if (behaviorsFile) {
@@ -4211,16 +4287,16 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4211
4287
  }
4212
4288
  let sessionContextFlag = "";
4213
4289
  try {
4214
- const ctxDir = path13.join(os7.homedir(), ".exe-os", "session-cache");
4215
- mkdirSync5(ctxDir, { recursive: true });
4216
- const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
4290
+ const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4291
+ mkdirSync6(ctxDir, { recursive: true });
4292
+ const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4217
4293
  const ctxContent = [
4218
4294
  `## Session Context`,
4219
4295
  `You are running in tmux session: ${sessionName}.`,
4220
4296
  `Your parent coordinator session is ${exeSession2}.`,
4221
4297
  `Your employees (if any) use the -${exeSession2} suffix.`
4222
4298
  ].join("\n");
4223
- writeFileSync6(ctxFile, ctxContent);
4299
+ writeFileSync7(ctxFile, ctxContent);
4224
4300
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4225
4301
  } catch {
4226
4302
  }
@@ -4234,9 +4310,48 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4234
4310
  }
4235
4311
  }
4236
4312
  }
4313
+ if (useCodex) {
4314
+ const codexCfg = RUNTIME_TABLE.codex;
4315
+ if (codexCfg?.apiKeyEnv) {
4316
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4317
+ if (keyVal) {
4318
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4319
+ }
4320
+ }
4321
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4322
+ }
4323
+ if (useOpencode) {
4324
+ const ocCfg = PROVIDER_TABLE.opencode;
4325
+ if (ocCfg?.apiKeyEnv) {
4326
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4327
+ if (keyVal) {
4328
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4329
+ }
4330
+ }
4331
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4332
+ }
4333
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4334
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4335
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4336
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4337
+ }
4338
+ }
4237
4339
  let spawnCommand;
4238
4340
  if (useExeAgent) {
4239
4341
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName2} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4342
+ } else if (useCodex) {
4343
+ process.stderr.write(
4344
+ `[tmux-routing] agent-config: ${employeeName2} \u2192 codex (${agentRtConfig.model})
4345
+ `
4346
+ );
4347
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName2}${cleanupSuffix}`;
4348
+ } else if (useOpencode) {
4349
+ const binName = `${employeeName2}-opencode`;
4350
+ process.stderr.write(
4351
+ `[tmux-routing] agent-config: ${employeeName2} \u2192 opencode (${agentRtConfig.model})
4352
+ `
4353
+ );
4354
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4240
4355
  } else if (useBinSymlink) {
4241
4356
  const binName = `${employeeName2}-${ccProvider}`;
4242
4357
  process.stderr.write(
@@ -4258,11 +4373,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4258
4373
  transport.pipeLog(sessionName, logFile);
4259
4374
  try {
4260
4375
  const mySession = getMySession();
4261
- const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4262
- writeFileSync6(dispatchInfo, JSON.stringify({
4376
+ const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4377
+ writeFileSync7(dispatchInfo, JSON.stringify({
4263
4378
  dispatchedBy: mySession,
4264
4379
  rootExe: exeSession2,
4265
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4380
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4381
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4382
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4266
4383
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4267
4384
  }));
4268
4385
  } catch {
@@ -4280,6 +4397,11 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4280
4397
  booted = true;
4281
4398
  break;
4282
4399
  }
4400
+ } else if (useCodex) {
4401
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4402
+ booted = true;
4403
+ break;
4404
+ }
4283
4405
  } else {
4284
4406
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4285
4407
  booted = true;
@@ -4291,9 +4413,10 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4291
4413
  }
4292
4414
  if (!booted) {
4293
4415
  releaseSpawnLock(sessionName);
4294
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4416
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
4417
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4295
4418
  }
4296
- if (!useExeAgent) {
4419
+ if (!useExeAgent && !useCodex) {
4297
4420
  try {
4298
4421
  transport.sendKeys(sessionName, `/exe-call ${employeeName2}`);
4299
4422
  } catch {
@@ -4320,17 +4443,19 @@ var init_tmux_routing = __esm({
4320
4443
  init_cc_agent_support();
4321
4444
  init_mcp_prefix();
4322
4445
  init_provider_table();
4446
+ init_agent_config();
4447
+ init_runtime_table();
4323
4448
  init_intercom_queue();
4324
4449
  init_plan_limits();
4325
4450
  init_employees();
4326
- SPAWN_LOCK_DIR = path13.join(os7.homedir(), ".exe-os", "spawn-locks");
4327
- SESSION_CACHE = path13.join(os7.homedir(), ".exe-os", "session-cache");
4451
+ SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4452
+ SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4328
4453
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4329
4454
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4330
4455
  VERIFY_PANE_LINES = 200;
4331
4456
  INTERCOM_DEBOUNCE_MS = 3e4;
4332
- INTERCOM_LOG2 = path13.join(os7.homedir(), ".exe-os", "intercom.log");
4333
- DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
4457
+ INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4458
+ DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4334
4459
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4335
4460
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4336
4461
  }
@@ -4349,13 +4474,13 @@ __export(shard_manager_exports, {
4349
4474
  listShards: () => listShards,
4350
4475
  shardExists: () => shardExists
4351
4476
  });
4352
- import path15 from "path";
4353
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
4477
+ import path16 from "path";
4478
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
4354
4479
  import { createClient as createClient2 } from "@libsql/client";
4355
4480
  function initShardManager(encryptionKey) {
4356
4481
  _encryptionKey = encryptionKey;
4357
- if (!existsSync12(SHARDS_DIR)) {
4358
- mkdirSync6(SHARDS_DIR, { recursive: true });
4482
+ if (!existsSync13(SHARDS_DIR)) {
4483
+ mkdirSync7(SHARDS_DIR, { recursive: true });
4359
4484
  }
4360
4485
  _shardingEnabled = true;
4361
4486
  }
@@ -4375,7 +4500,7 @@ function getShardClient(projectName) {
4375
4500
  }
4376
4501
  const cached = _shards.get(safeName);
4377
4502
  if (cached) return cached;
4378
- const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
4503
+ const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
4379
4504
  const client = createClient2({
4380
4505
  url: `file:${dbPath}`,
4381
4506
  encryptionKey: _encryptionKey
@@ -4385,10 +4510,10 @@ function getShardClient(projectName) {
4385
4510
  }
4386
4511
  function shardExists(projectName) {
4387
4512
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4388
- return existsSync12(path15.join(SHARDS_DIR, `${safeName}.db`));
4513
+ return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
4389
4514
  }
4390
4515
  function listShards() {
4391
- if (!existsSync12(SHARDS_DIR)) return [];
4516
+ if (!existsSync13(SHARDS_DIR)) return [];
4392
4517
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4393
4518
  }
4394
4519
  async function ensureShardSchema(client) {
@@ -4574,7 +4699,7 @@ var init_shard_manager = __esm({
4574
4699
  "src/lib/shard-manager.ts"() {
4575
4700
  "use strict";
4576
4701
  init_config();
4577
- SHARDS_DIR = path15.join(EXE_AI_DIR, "shards");
4702
+ SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
4578
4703
  _shards = /* @__PURE__ */ new Map();
4579
4704
  _encryptionKey = null;
4580
4705
  _shardingEnabled = false;
@@ -4772,16 +4897,16 @@ init_database();
4772
4897
 
4773
4898
  // src/lib/keychain.ts
4774
4899
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4775
- import { existsSync as existsSync11 } from "fs";
4776
- import path14 from "path";
4900
+ import { existsSync as existsSync12 } from "fs";
4901
+ import path15 from "path";
4777
4902
  import os8 from "os";
4778
4903
  var SERVICE = "exe-mem";
4779
4904
  var ACCOUNT = "master-key";
4780
4905
  function getKeyDir() {
4781
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os8.homedir(), ".exe-os");
4906
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
4782
4907
  }
4783
4908
  function getKeyPath() {
4784
- return path14.join(getKeyDir(), "master.key");
4909
+ return path15.join(getKeyDir(), "master.key");
4785
4910
  }
4786
4911
  async function tryKeytar() {
4787
4912
  try {
@@ -4802,7 +4927,7 @@ async function getMasterKey() {
4802
4927
  }
4803
4928
  }
4804
4929
  const keyPath = getKeyPath();
4805
- if (!existsSync11(keyPath)) {
4930
+ if (!existsSync12(keyPath)) {
4806
4931
  process.stderr.write(
4807
4932
  `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4808
4933
  `