@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
@@ -268,75 +268,19 @@ var init_provider_table = __esm({
268
268
  }
269
269
  });
270
270
 
271
- // src/lib/intercom-queue.ts
272
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
273
- import path2 from "path";
274
- import os2 from "os";
275
- function ensureDir() {
276
- const dir = path2.dirname(QUEUE_PATH);
277
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
278
- }
279
- function readQueue() {
280
- try {
281
- if (!existsSync2(QUEUE_PATH)) return [];
282
- return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
283
- } catch {
284
- return [];
285
- }
286
- }
287
- function writeQueue(queue) {
288
- ensureDir();
289
- const tmp = `${QUEUE_PATH}.tmp`;
290
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
291
- renameSync(tmp, QUEUE_PATH);
292
- }
293
- function queueIntercom(targetSession, reason) {
294
- const queue = readQueue();
295
- const existing = queue.find((q) => q.targetSession === targetSession);
296
- if (existing) {
297
- existing.attempts++;
298
- existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
299
- existing.reason = reason;
300
- } else {
301
- queue.push({
302
- targetSession,
303
- queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
304
- attempts: 0,
305
- reason
306
- });
307
- }
308
- writeQueue(queue);
309
- }
310
- var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
311
- var init_intercom_queue = __esm({
312
- "src/lib/intercom-queue.ts"() {
313
- "use strict";
314
- QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
315
- TTL_MS = 60 * 60 * 1e3;
316
- INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
317
- }
318
- });
319
-
320
- // src/lib/db-retry.ts
321
- var init_db_retry = __esm({
322
- "src/lib/db-retry.ts"() {
323
- "use strict";
324
- }
325
- });
326
-
327
271
  // src/lib/config.ts
328
272
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
329
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
330
- import path3 from "path";
331
- import os3 from "os";
273
+ import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
274
+ import path2 from "path";
275
+ import os2 from "os";
332
276
  function resolveDataDir() {
333
277
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
334
278
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
335
- const newDir = path3.join(os3.homedir(), ".exe-os");
336
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
337
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
279
+ const newDir = path2.join(os2.homedir(), ".exe-os");
280
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
281
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
338
282
  try {
339
- renameSync2(legacyDir, newDir);
283
+ renameSync(legacyDir, newDir);
340
284
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
341
285
  `);
342
286
  } catch {
@@ -398,9 +342,9 @@ function normalizeAutoUpdate(raw) {
398
342
  async function loadConfig() {
399
343
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
400
344
  await mkdir(dir, { recursive: true });
401
- const configPath = path3.join(dir, "config.json");
402
- if (!existsSync3(configPath)) {
403
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
345
+ const configPath = path2.join(dir, "config.json");
346
+ if (!existsSync2(configPath)) {
347
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
404
348
  }
405
349
  const raw = await readFile(configPath, "utf-8");
406
350
  try {
@@ -418,13 +362,13 @@ async function loadConfig() {
418
362
  normalizeScalingRoadmap(migratedCfg);
419
363
  normalizeSessionLifecycle(migratedCfg);
420
364
  normalizeAutoUpdate(migratedCfg);
421
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
365
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
422
366
  if (config.dbPath.startsWith("~")) {
423
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
367
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
424
368
  }
425
369
  return config;
426
370
  } catch {
427
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
371
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
428
372
  }
429
373
  }
430
374
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -432,10 +376,10 @@ var init_config = __esm({
432
376
  "src/lib/config.ts"() {
433
377
  "use strict";
434
378
  EXE_AI_DIR = resolveDataDir();
435
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
436
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
437
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
438
- LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
379
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
380
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
381
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
382
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
439
383
  CURRENT_CONFIG_VERSION = 1;
440
384
  DEFAULT_CONFIG = {
441
385
  config_version: CURRENT_CONFIG_VERSION,
@@ -507,11 +451,118 @@ var init_config = __esm({
507
451
  }
508
452
  });
509
453
 
454
+ // src/lib/runtime-table.ts
455
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
456
+ var init_runtime_table = __esm({
457
+ "src/lib/runtime-table.ts"() {
458
+ "use strict";
459
+ RUNTIME_TABLE = {
460
+ codex: {
461
+ binary: "codex",
462
+ launchMode: "exec",
463
+ autoApproveFlag: "--full-auto",
464
+ inlineFlag: "--no-alt-screen",
465
+ apiKeyEnv: "OPENAI_API_KEY",
466
+ defaultModel: "gpt-5.4"
467
+ }
468
+ };
469
+ DEFAULT_RUNTIME = "claude";
470
+ }
471
+ });
472
+
473
+ // src/lib/agent-config.ts
474
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
475
+ import path3 from "path";
476
+ function loadAgentConfig() {
477
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
478
+ try {
479
+ return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
480
+ } catch {
481
+ return {};
482
+ }
483
+ }
484
+ function getAgentRuntime(agentId) {
485
+ const config = loadAgentConfig();
486
+ const entry = config[agentId];
487
+ if (entry) return entry;
488
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
489
+ }
490
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
491
+ var init_agent_config = __esm({
492
+ "src/lib/agent-config.ts"() {
493
+ "use strict";
494
+ init_config();
495
+ init_runtime_table();
496
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
497
+ DEFAULT_MODELS = {
498
+ claude: "claude-opus-4",
499
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
500
+ opencode: "minimax-m2.7"
501
+ };
502
+ }
503
+ });
504
+
505
+ // src/lib/intercom-queue.ts
506
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
507
+ import path4 from "path";
508
+ import os3 from "os";
509
+ function ensureDir() {
510
+ const dir = path4.dirname(QUEUE_PATH);
511
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
512
+ }
513
+ function readQueue() {
514
+ try {
515
+ if (!existsSync4(QUEUE_PATH)) return [];
516
+ return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
517
+ } catch {
518
+ return [];
519
+ }
520
+ }
521
+ function writeQueue(queue) {
522
+ ensureDir();
523
+ const tmp = `${QUEUE_PATH}.tmp`;
524
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
525
+ renameSync2(tmp, QUEUE_PATH);
526
+ }
527
+ function queueIntercom(targetSession, reason) {
528
+ const queue = readQueue();
529
+ const existing = queue.find((q) => q.targetSession === targetSession);
530
+ if (existing) {
531
+ existing.attempts++;
532
+ existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
533
+ existing.reason = reason;
534
+ } else {
535
+ queue.push({
536
+ targetSession,
537
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
538
+ attempts: 0,
539
+ reason
540
+ });
541
+ }
542
+ writeQueue(queue);
543
+ }
544
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
545
+ var init_intercom_queue = __esm({
546
+ "src/lib/intercom-queue.ts"() {
547
+ "use strict";
548
+ QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
549
+ TTL_MS = 60 * 60 * 1e3;
550
+ INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
551
+ }
552
+ });
553
+
554
+ // src/lib/db-retry.ts
555
+ var init_db_retry = __esm({
556
+ "src/lib/db-retry.ts"() {
557
+ "use strict";
558
+ }
559
+ });
560
+
510
561
  // src/lib/employees.ts
511
562
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
512
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
563
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
513
564
  import { execSync as execSync3 } from "child_process";
514
- import path4 from "path";
565
+ import path5 from "path";
515
566
  import os4 from "os";
516
567
  function normalizeRole(role) {
517
568
  return (role ?? "").trim().toLowerCase();
@@ -530,9 +581,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
530
581
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
531
582
  }
532
583
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
533
- if (!existsSync4(employeesPath)) return [];
584
+ if (!existsSync5(employeesPath)) return [];
534
585
  try {
535
- return JSON.parse(readFileSync4(employeesPath, "utf-8"));
586
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
536
587
  } catch {
537
588
  return [];
538
589
  }
@@ -551,7 +602,7 @@ var init_employees = __esm({
551
602
  "src/lib/employees.ts"() {
552
603
  "use strict";
553
604
  init_config();
554
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
605
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
555
606
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
556
607
  COORDINATOR_ROLE = "COO";
557
608
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
@@ -584,18 +635,18 @@ var init_database = __esm({
584
635
  });
585
636
 
586
637
  // src/lib/license.ts
587
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
638
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
588
639
  import { randomUUID } from "crypto";
589
- import path5 from "path";
640
+ import path6 from "path";
590
641
  import { jwtVerify, importSPKI } from "jose";
591
642
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
592
643
  var init_license = __esm({
593
644
  "src/lib/license.ts"() {
594
645
  "use strict";
595
646
  init_config();
596
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
597
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
598
- DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
647
+ LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
648
+ CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
649
+ DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
599
650
  PLAN_LIMITS = {
600
651
  free: { devices: 1, employees: 1, memories: 5e3 },
601
652
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -607,12 +658,12 @@ var init_license = __esm({
607
658
  });
608
659
 
609
660
  // src/lib/plan-limits.ts
610
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
611
- import path6 from "path";
661
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
662
+ import path7 from "path";
612
663
  function getLicenseSync() {
613
664
  try {
614
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
615
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
665
+ if (!existsSync7(CACHE_PATH2)) return freeLicense();
666
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
616
667
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
617
668
  const parts = raw.token.split(".");
618
669
  if (parts.length !== 3) return freeLicense();
@@ -650,8 +701,8 @@ function assertEmployeeLimitSync(rosterPath) {
650
701
  const filePath = rosterPath ?? EMPLOYEES_PATH;
651
702
  let count = 0;
652
703
  try {
653
- if (existsSync6(filePath)) {
654
- const raw = readFileSync6(filePath, "utf8");
704
+ if (existsSync7(filePath)) {
705
+ const raw = readFileSync7(filePath, "utf8");
655
706
  const employees = JSON.parse(raw);
656
707
  count = Array.isArray(employees) ? employees.length : 0;
657
708
  }
@@ -680,19 +731,19 @@ var init_plan_limits = __esm({
680
731
  this.name = "PlanLimitError";
681
732
  }
682
733
  };
683
- CACHE_PATH2 = path6.join(EXE_AI_DIR, "license-cache.json");
734
+ CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
684
735
  }
685
736
  });
686
737
 
687
738
  // src/lib/notifications.ts
688
739
  import crypto from "crypto";
689
- import path7 from "path";
740
+ import path8 from "path";
690
741
  import os5 from "os";
691
742
  import {
692
- readFileSync as readFileSync7,
743
+ readFileSync as readFileSync8,
693
744
  readdirSync,
694
745
  unlinkSync as unlinkSync2,
695
- existsSync as existsSync7,
746
+ existsSync as existsSync8,
696
747
  rmdirSync
697
748
  } from "fs";
698
749
  async function writeNotification(notification) {
@@ -851,11 +902,11 @@ var init_state_bus = __esm({
851
902
 
852
903
  // src/lib/tasks-crud.ts
853
904
  import crypto3 from "crypto";
854
- import path8 from "path";
905
+ import path9 from "path";
855
906
  import os6 from "os";
856
907
  import { execSync as execSync4 } from "child_process";
857
908
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
858
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
909
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
859
910
  async function writeCheckpoint(input) {
860
911
  const client = getClient();
861
912
  const row = await resolveTask(client, input.taskId);
@@ -1030,8 +1081,8 @@ ${laneWarning}` : laneWarning;
1030
1081
  }
1031
1082
  if (input.baseDir) {
1032
1083
  try {
1033
- await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
1034
- await mkdir3(path8.join(input.baseDir, "exe", "research"), { recursive: true });
1084
+ await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
1085
+ await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
1035
1086
  await ensureArchitectureDoc(input.baseDir, input.projectName);
1036
1087
  await ensureGitignoreExe(input.baseDir);
1037
1088
  } catch {
@@ -1067,10 +1118,10 @@ ${laneWarning}` : laneWarning;
1067
1118
  });
1068
1119
  if (input.baseDir) {
1069
1120
  try {
1070
- const EXE_OS_DIR = path8.join(os6.homedir(), ".exe-os");
1071
- const mdPath = path8.join(EXE_OS_DIR, taskFile);
1072
- const mdDir = path8.dirname(mdPath);
1073
- if (!existsSync8(mdDir)) await mkdir3(mdDir, { recursive: true });
1121
+ const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
1122
+ const mdPath = path9.join(EXE_OS_DIR, taskFile);
1123
+ const mdDir = path9.dirname(mdPath);
1124
+ if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
1074
1125
  const reviewer = input.reviewer ?? input.assignedBy;
1075
1126
  const mdContent = `# ${input.title}
1076
1127
 
@@ -1095,7 +1146,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
1095
1146
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
1096
1147
  `;
1097
1148
  await writeFile3(mdPath, mdContent, "utf-8");
1098
- } catch {
1149
+ } catch (err) {
1150
+ process.stderr.write(
1151
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
1152
+ `
1153
+ );
1099
1154
  }
1100
1155
  }
1101
1156
  return {
@@ -1355,9 +1410,9 @@ async function deleteTaskCore(taskId, _baseDir) {
1355
1410
  return { taskFile, assignedTo, assignedBy, taskSlug };
1356
1411
  }
1357
1412
  async function ensureArchitectureDoc(baseDir, projectName) {
1358
- const archPath = path8.join(baseDir, "exe", "ARCHITECTURE.md");
1413
+ const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
1359
1414
  try {
1360
- if (existsSync8(archPath)) return;
1415
+ if (existsSync9(archPath)) return;
1361
1416
  const template = [
1362
1417
  `# ${projectName} \u2014 System Architecture`,
1363
1418
  "",
@@ -1390,10 +1445,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
1390
1445
  }
1391
1446
  }
1392
1447
  async function ensureGitignoreExe(baseDir) {
1393
- const gitignorePath = path8.join(baseDir, ".gitignore");
1448
+ const gitignorePath = path9.join(baseDir, ".gitignore");
1394
1449
  try {
1395
- if (existsSync8(gitignorePath)) {
1396
- const content = readFileSync8(gitignorePath, "utf-8");
1450
+ if (existsSync9(gitignorePath)) {
1451
+ const content = readFileSync9(gitignorePath, "utf-8");
1397
1452
  if (/^\/?exe\/?$/m.test(content)) return;
1398
1453
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
1399
1454
  } else {
@@ -1424,8 +1479,8 @@ var init_tasks_crud = __esm({
1424
1479
  });
1425
1480
 
1426
1481
  // src/lib/tasks-review.ts
1427
- import path9 from "path";
1428
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
1482
+ import path10 from "path";
1483
+ import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
1429
1484
  async function countPendingReviews(sessionScope) {
1430
1485
  const client = getClient();
1431
1486
  if (sessionScope) {
@@ -1606,11 +1661,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
1606
1661
  );
1607
1662
  }
1608
1663
  try {
1609
- const cacheDir = path9.join(EXE_AI_DIR, "session-cache");
1610
- if (existsSync9(cacheDir)) {
1664
+ const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
1665
+ if (existsSync10(cacheDir)) {
1611
1666
  for (const f of readdirSync2(cacheDir)) {
1612
1667
  if (f.startsWith("review-notified-")) {
1613
- unlinkSync3(path9.join(cacheDir, f));
1668
+ unlinkSync3(path10.join(cacheDir, f));
1614
1669
  }
1615
1670
  }
1616
1671
  }
@@ -1631,7 +1686,7 @@ var init_tasks_review = __esm({
1631
1686
  });
1632
1687
 
1633
1688
  // src/lib/tasks-chain.ts
1634
- import path10 from "path";
1689
+ import path11 from "path";
1635
1690
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
1636
1691
  async function cascadeUnblock(taskId, baseDir, now) {
1637
1692
  const client = getClient();
@@ -1648,7 +1703,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
1648
1703
  });
1649
1704
  for (const ur of unblockedRows.rows) {
1650
1705
  try {
1651
- const ubFile = path10.join(baseDir, String(ur.task_file));
1706
+ const ubFile = path11.join(baseDir, String(ur.task_file));
1652
1707
  let ubContent = await readFile3(ubFile, "utf-8");
1653
1708
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
1654
1709
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -1717,7 +1772,7 @@ var init_tasks_chain = __esm({
1717
1772
 
1718
1773
  // src/lib/project-name.ts
1719
1774
  import { execSync as execSync5 } from "child_process";
1720
- import path11 from "path";
1775
+ import path12 from "path";
1721
1776
  function getProjectName(cwd) {
1722
1777
  const dir = cwd ?? process.cwd();
1723
1778
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -1730,7 +1785,7 @@ function getProjectName(cwd) {
1730
1785
  timeout: 2e3,
1731
1786
  stdio: ["pipe", "pipe", "pipe"]
1732
1787
  }).trim();
1733
- repoRoot = path11.dirname(gitCommonDir);
1788
+ repoRoot = path12.dirname(gitCommonDir);
1734
1789
  } catch {
1735
1790
  repoRoot = execSync5("git rev-parse --show-toplevel", {
1736
1791
  cwd: dir,
@@ -1739,11 +1794,11 @@ function getProjectName(cwd) {
1739
1794
  stdio: ["pipe", "pipe", "pipe"]
1740
1795
  }).trim();
1741
1796
  }
1742
- _cached2 = path11.basename(repoRoot);
1797
+ _cached2 = path12.basename(repoRoot);
1743
1798
  _cachedCwd = dir;
1744
1799
  return _cached2;
1745
1800
  } catch {
1746
- _cached2 = path11.basename(dir);
1801
+ _cached2 = path12.basename(dir);
1747
1802
  _cachedCwd = dir;
1748
1803
  return _cached2;
1749
1804
  }
@@ -2216,8 +2271,8 @@ __export(tasks_exports, {
2216
2271
  updateTaskStatus: () => updateTaskStatus,
2217
2272
  writeCheckpoint: () => writeCheckpoint
2218
2273
  });
2219
- import path12 from "path";
2220
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
2274
+ import path13 from "path";
2275
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
2221
2276
  async function createTask(input) {
2222
2277
  const result = await createTaskCore(input);
2223
2278
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -2236,11 +2291,11 @@ async function updateTask(input) {
2236
2291
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
2237
2292
  try {
2238
2293
  const agent = String(row.assigned_to);
2239
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
2240
- const cachePath = path12.join(cacheDir, `current-task-${agent}.json`);
2294
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
2295
+ const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
2241
2296
  if (input.status === "in_progress") {
2242
- mkdirSync4(cacheDir, { recursive: true });
2243
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
2297
+ mkdirSync5(cacheDir, { recursive: true });
2298
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
2244
2299
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
2245
2300
  try {
2246
2301
  unlinkSync4(cachePath);
@@ -2707,13 +2762,13 @@ __export(tmux_routing_exports, {
2707
2762
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
2708
2763
  });
2709
2764
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2710
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
2711
- import path13 from "path";
2765
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
2766
+ import path14 from "path";
2712
2767
  import os7 from "os";
2713
2768
  import { fileURLToPath } from "url";
2714
2769
  import { unlinkSync as unlinkSync5 } from "fs";
2715
2770
  function spawnLockPath(sessionName) {
2716
- return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2771
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2717
2772
  }
2718
2773
  function isProcessAlive(pid) {
2719
2774
  try {
@@ -2724,13 +2779,13 @@ function isProcessAlive(pid) {
2724
2779
  }
2725
2780
  }
2726
2781
  function acquireSpawnLock(sessionName) {
2727
- if (!existsSync10(SPAWN_LOCK_DIR)) {
2728
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
2782
+ if (!existsSync11(SPAWN_LOCK_DIR)) {
2783
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
2729
2784
  }
2730
2785
  const lockFile = spawnLockPath(sessionName);
2731
- if (existsSync10(lockFile)) {
2786
+ if (existsSync11(lockFile)) {
2732
2787
  try {
2733
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
2788
+ const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
2734
2789
  const age = Date.now() - lock.timestamp;
2735
2790
  if (isProcessAlive(lock.pid) && age < 6e4) {
2736
2791
  return false;
@@ -2738,7 +2793,7 @@ function acquireSpawnLock(sessionName) {
2738
2793
  } catch {
2739
2794
  }
2740
2795
  }
2741
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2796
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2742
2797
  return true;
2743
2798
  }
2744
2799
  function releaseSpawnLock(sessionName) {
@@ -2750,13 +2805,13 @@ function releaseSpawnLock(sessionName) {
2750
2805
  function resolveBehaviorsExporterScript() {
2751
2806
  try {
2752
2807
  const thisFile = fileURLToPath(import.meta.url);
2753
- const scriptPath = path13.join(
2754
- path13.dirname(thisFile),
2808
+ const scriptPath = path14.join(
2809
+ path14.dirname(thisFile),
2755
2810
  "..",
2756
2811
  "bin",
2757
2812
  "exe-export-behaviors.js"
2758
2813
  );
2759
- return existsSync10(scriptPath) ? scriptPath : null;
2814
+ return existsSync11(scriptPath) ? scriptPath : null;
2760
2815
  } catch {
2761
2816
  return null;
2762
2817
  }
@@ -2822,12 +2877,12 @@ function extractRootExe(name) {
2822
2877
  return parts.length > 0 ? parts[parts.length - 1] : null;
2823
2878
  }
2824
2879
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
2825
- if (!existsSync10(SESSION_CACHE)) {
2826
- mkdirSync5(SESSION_CACHE, { recursive: true });
2880
+ if (!existsSync11(SESSION_CACHE)) {
2881
+ mkdirSync6(SESSION_CACHE, { recursive: true });
2827
2882
  }
2828
2883
  const rootExe = extractRootExe(parentExe) ?? parentExe;
2829
- const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
2830
- writeFileSync6(filePath, JSON.stringify({
2884
+ const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
2885
+ writeFileSync7(filePath, JSON.stringify({
2831
2886
  parentExe: rootExe,
2832
2887
  dispatchedBy: dispatchedBy || rootExe,
2833
2888
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2835,7 +2890,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
2835
2890
  }
2836
2891
  function getParentExe(sessionKey) {
2837
2892
  try {
2838
- const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2893
+ const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2839
2894
  return data.parentExe || null;
2840
2895
  } catch {
2841
2896
  return null;
@@ -2843,8 +2898,8 @@ function getParentExe(sessionKey) {
2843
2898
  }
2844
2899
  function getDispatchedBy(sessionKey) {
2845
2900
  try {
2846
- const data = JSON.parse(readFileSync9(
2847
- path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2901
+ const data = JSON.parse(readFileSync10(
2902
+ path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2848
2903
  "utf8"
2849
2904
  ));
2850
2905
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -2905,32 +2960,50 @@ async function verifyPaneAtCapacity(sessionName) {
2905
2960
  }
2906
2961
  function readDebounceState() {
2907
2962
  try {
2908
- if (!existsSync10(DEBOUNCE_FILE)) return {};
2909
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
2963
+ if (!existsSync11(DEBOUNCE_FILE)) return {};
2964
+ const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
2965
+ const state = {};
2966
+ for (const [key, val] of Object.entries(raw)) {
2967
+ if (typeof val === "number") {
2968
+ state[key] = { lastSent: val, pending: 0 };
2969
+ } else if (val && typeof val === "object" && "lastSent" in val) {
2970
+ state[key] = val;
2971
+ }
2972
+ }
2973
+ return state;
2910
2974
  } catch {
2911
2975
  return {};
2912
2976
  }
2913
2977
  }
2914
2978
  function writeDebounceState(state) {
2915
2979
  try {
2916
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
2917
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
2980
+ if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
2981
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
2918
2982
  } catch {
2919
2983
  }
2920
2984
  }
2921
2985
  function isDebounced(targetSession) {
2922
2986
  const state = readDebounceState();
2923
- const lastSent = state[targetSession] ?? 0;
2924
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
2987
+ const entry = state[targetSession];
2988
+ const lastSent = entry?.lastSent ?? 0;
2989
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
2990
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
2991
+ state[targetSession].pending++;
2992
+ writeDebounceState(state);
2993
+ return true;
2994
+ }
2995
+ return false;
2925
2996
  }
2926
2997
  function recordDebounce(targetSession) {
2927
2998
  const state = readDebounceState();
2928
- state[targetSession] = Date.now();
2999
+ const batched = state[targetSession]?.pending ?? 0;
3000
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
2929
3001
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
2930
3002
  for (const key of Object.keys(state)) {
2931
- if ((state[key] ?? 0) < cutoff) delete state[key];
3003
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
2932
3004
  }
2933
3005
  writeDebounceState(state);
3006
+ return batched;
2934
3007
  }
2935
3008
  function logIntercom(msg) {
2936
3009
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -2975,7 +3048,7 @@ function sendIntercom(targetSession) {
2975
3048
  return "skipped_exe";
2976
3049
  }
2977
3050
  if (isDebounced(targetSession)) {
2978
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
3051
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
2979
3052
  return "debounced";
2980
3053
  }
2981
3054
  try {
@@ -2987,14 +3060,14 @@ function sendIntercom(targetSession) {
2987
3060
  const sessionState = getSessionState(targetSession);
2988
3061
  if (sessionState === "no_claude") {
2989
3062
  queueIntercom(targetSession, "claude not running in session");
2990
- recordDebounce(targetSession);
2991
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
3063
+ const batched2 = recordDebounce(targetSession);
3064
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
2992
3065
  return "queued";
2993
3066
  }
2994
3067
  if (sessionState === "thinking" || sessionState === "tool") {
2995
3068
  queueIntercom(targetSession, "session busy at send time");
2996
- recordDebounce(targetSession);
2997
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
3069
+ const batched2 = recordDebounce(targetSession);
3070
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
2998
3071
  return "queued";
2999
3072
  }
3000
3073
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -3002,8 +3075,8 @@ function sendIntercom(targetSession) {
3002
3075
  transport.sendKeys(targetSession, "q");
3003
3076
  }
3004
3077
  transport.sendKeys(targetSession, "/exe-intercom");
3005
- recordDebounce(targetSession);
3006
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
3078
+ const batched = recordDebounce(targetSession);
3079
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
3007
3080
  return "delivered";
3008
3081
  } catch {
3009
3082
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -3105,26 +3178,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3105
3178
  const transport = getTransport();
3106
3179
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3107
3180
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3108
- const logDir = path13.join(os7.homedir(), ".exe-os", "session-logs");
3109
- const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3110
- if (!existsSync10(logDir)) {
3111
- mkdirSync5(logDir, { recursive: true });
3181
+ const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
3182
+ const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3183
+ if (!existsSync11(logDir)) {
3184
+ mkdirSync6(logDir, { recursive: true });
3112
3185
  }
3113
3186
  transport.kill(sessionName);
3114
3187
  let cleanupSuffix = "";
3115
3188
  try {
3116
3189
  const thisFile = fileURLToPath(import.meta.url);
3117
- const cleanupScript = path13.join(path13.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3118
- if (existsSync10(cleanupScript)) {
3190
+ const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3191
+ if (existsSync11(cleanupScript)) {
3119
3192
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
3120
3193
  }
3121
3194
  } catch {
3122
3195
  }
3123
3196
  try {
3124
- const claudeJsonPath = path13.join(os7.homedir(), ".claude.json");
3197
+ const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
3125
3198
  let claudeJson = {};
3126
3199
  try {
3127
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
3200
+ claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
3128
3201
  } catch {
3129
3202
  }
3130
3203
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3132,17 +3205,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3132
3205
  const trustDir = opts?.cwd ?? projectDir;
3133
3206
  if (!projects[trustDir]) projects[trustDir] = {};
3134
3207
  projects[trustDir].hasTrustDialogAccepted = true;
3135
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
3208
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
3136
3209
  } catch {
3137
3210
  }
3138
3211
  try {
3139
- const settingsDir = path13.join(os7.homedir(), ".claude", "projects");
3212
+ const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
3140
3213
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3141
- const projSettingsDir = path13.join(settingsDir, normalizedKey);
3142
- const settingsPath = path13.join(projSettingsDir, "settings.json");
3214
+ const projSettingsDir = path14.join(settingsDir, normalizedKey);
3215
+ const settingsPath = path14.join(projSettingsDir, "settings.json");
3143
3216
  let settings = {};
3144
3217
  try {
3145
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
3218
+ settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
3146
3219
  } catch {
3147
3220
  }
3148
3221
  const perms = settings.permissions ?? {};
@@ -3170,20 +3243,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3170
3243
  if (changed) {
3171
3244
  perms.allow = allow;
3172
3245
  settings.permissions = perms;
3173
- mkdirSync5(projSettingsDir, { recursive: true });
3174
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3246
+ mkdirSync6(projSettingsDir, { recursive: true });
3247
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3175
3248
  }
3176
3249
  } catch {
3177
3250
  }
3178
3251
  const spawnCwd = opts?.cwd ?? projectDir;
3179
3252
  const useExeAgent = !!(opts?.model && opts?.provider);
3180
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
3253
+ const agentRtConfig = getAgentRuntime(employeeName);
3254
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
3255
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
3256
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
3181
3257
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
3182
3258
  let identityFlag = "";
3183
3259
  let behaviorsFlag = "";
3184
3260
  let legacyFallbackWarned = false;
3185
3261
  if (!useExeAgent && !useBinSymlink) {
3186
- const identityPath = path13.join(
3262
+ const identityPath = path14.join(
3187
3263
  os7.homedir(),
3188
3264
  ".exe-os",
3189
3265
  "identity",
@@ -3193,13 +3269,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3193
3269
  const hasAgentFlag = claudeSupportsAgentFlag();
3194
3270
  if (hasAgentFlag) {
3195
3271
  identityFlag = ` --agent ${employeeName}`;
3196
- } else if (existsSync10(identityPath)) {
3272
+ } else if (existsSync11(identityPath)) {
3197
3273
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
3198
3274
  legacyFallbackWarned = true;
3199
3275
  }
3200
3276
  const behaviorsFile = exportBehaviorsSync(
3201
3277
  employeeName,
3202
- path13.basename(spawnCwd),
3278
+ path14.basename(spawnCwd),
3203
3279
  sessionName
3204
3280
  );
3205
3281
  if (behaviorsFile) {
@@ -3214,16 +3290,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3214
3290
  }
3215
3291
  let sessionContextFlag = "";
3216
3292
  try {
3217
- const ctxDir = path13.join(os7.homedir(), ".exe-os", "session-cache");
3218
- mkdirSync5(ctxDir, { recursive: true });
3219
- const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
3293
+ const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
3294
+ mkdirSync6(ctxDir, { recursive: true });
3295
+ const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
3220
3296
  const ctxContent = [
3221
3297
  `## Session Context`,
3222
3298
  `You are running in tmux session: ${sessionName}.`,
3223
3299
  `Your parent coordinator session is ${exeSession}.`,
3224
3300
  `Your employees (if any) use the -${exeSession} suffix.`
3225
3301
  ].join("\n");
3226
- writeFileSync6(ctxFile, ctxContent);
3302
+ writeFileSync7(ctxFile, ctxContent);
3227
3303
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
3228
3304
  } catch {
3229
3305
  }
@@ -3237,9 +3313,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3237
3313
  }
3238
3314
  }
3239
3315
  }
3316
+ if (useCodex) {
3317
+ const codexCfg = RUNTIME_TABLE.codex;
3318
+ if (codexCfg?.apiKeyEnv) {
3319
+ const keyVal = process.env[codexCfg.apiKeyEnv];
3320
+ if (keyVal) {
3321
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
3322
+ }
3323
+ }
3324
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
3325
+ }
3326
+ if (useOpencode) {
3327
+ const ocCfg = PROVIDER_TABLE.opencode;
3328
+ if (ocCfg?.apiKeyEnv) {
3329
+ const keyVal = process.env[ocCfg.apiKeyEnv];
3330
+ if (keyVal) {
3331
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
3332
+ }
3333
+ }
3334
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
3335
+ }
3336
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
3337
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
3338
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
3339
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
3340
+ }
3341
+ }
3240
3342
  let spawnCommand;
3241
3343
  if (useExeAgent) {
3242
3344
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
3345
+ } else if (useCodex) {
3346
+ process.stderr.write(
3347
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
3348
+ `
3349
+ );
3350
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
3351
+ } else if (useOpencode) {
3352
+ const binName = `${employeeName}-opencode`;
3353
+ process.stderr.write(
3354
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
3355
+ `
3356
+ );
3357
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
3243
3358
  } else if (useBinSymlink) {
3244
3359
  const binName = `${employeeName}-${ccProvider}`;
3245
3360
  process.stderr.write(
@@ -3261,11 +3376,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3261
3376
  transport.pipeLog(sessionName, logFile);
3262
3377
  try {
3263
3378
  const mySession = getMySession();
3264
- const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3265
- writeFileSync6(dispatchInfo, JSON.stringify({
3379
+ const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3380
+ writeFileSync7(dispatchInfo, JSON.stringify({
3266
3381
  dispatchedBy: mySession,
3267
3382
  rootExe: exeSession,
3268
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
3383
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
3384
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
3385
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
3269
3386
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3270
3387
  }));
3271
3388
  } catch {
@@ -3283,6 +3400,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3283
3400
  booted = true;
3284
3401
  break;
3285
3402
  }
3403
+ } else if (useCodex) {
3404
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
3405
+ booted = true;
3406
+ break;
3407
+ }
3286
3408
  } else {
3287
3409
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
3288
3410
  booted = true;
@@ -3294,9 +3416,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3294
3416
  }
3295
3417
  if (!booted) {
3296
3418
  releaseSpawnLock(sessionName);
3297
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3419
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
3420
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
3298
3421
  }
3299
- if (!useExeAgent) {
3422
+ if (!useExeAgent && !useCodex) {
3300
3423
  try {
3301
3424
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
3302
3425
  } catch {
@@ -3322,17 +3445,19 @@ var init_tmux_routing = __esm({
3322
3445
  init_cc_agent_support();
3323
3446
  init_mcp_prefix();
3324
3447
  init_provider_table();
3448
+ init_agent_config();
3449
+ init_runtime_table();
3325
3450
  init_intercom_queue();
3326
3451
  init_plan_limits();
3327
3452
  init_employees();
3328
- SPAWN_LOCK_DIR = path13.join(os7.homedir(), ".exe-os", "spawn-locks");
3329
- SESSION_CACHE = path13.join(os7.homedir(), ".exe-os", "session-cache");
3453
+ SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
3454
+ SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
3330
3455
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3331
3456
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
3332
3457
  VERIFY_PANE_LINES = 200;
3333
3458
  INTERCOM_DEBOUNCE_MS = 3e4;
3334
- INTERCOM_LOG2 = path13.join(os7.homedir(), ".exe-os", "intercom.log");
3335
- DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
3459
+ INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
3460
+ DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
3336
3461
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3337
3462
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
3338
3463
  }