@askexenow/exe-os 0.8.83 → 0.8.86

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 (103) hide show
  1. package/dist/bin/backfill-conversations.js +746 -595
  2. package/dist/bin/backfill-responses.js +745 -594
  3. package/dist/bin/backfill-vectors.js +312 -226
  4. package/dist/bin/cleanup-stale-review-tasks.js +154 -21
  5. package/dist/bin/cli.js +14678 -12676
  6. package/dist/bin/exe-agent-config.js +242 -0
  7. package/dist/bin/exe-agent.js +100 -91
  8. package/dist/bin/exe-assign.js +1003 -854
  9. package/dist/bin/exe-boot.js +1420 -485
  10. package/dist/bin/exe-call.js +10 -0
  11. package/dist/bin/exe-cloud.js +29 -6
  12. package/dist/bin/exe-dispatch.js +572 -271
  13. package/dist/bin/exe-doctor.js +403 -6
  14. package/dist/bin/exe-export-behaviors.js +175 -72
  15. package/dist/bin/exe-forget.js +102 -3
  16. package/dist/bin/exe-gateway.js +796 -292
  17. package/dist/bin/exe-healthcheck.js +134 -1
  18. package/dist/bin/exe-heartbeat.js +172 -36
  19. package/dist/bin/exe-kill.js +175 -72
  20. package/dist/bin/exe-launch-agent.js +189 -76
  21. package/dist/bin/exe-link.js +927 -82
  22. package/dist/bin/exe-new-employee.js +60 -8
  23. package/dist/bin/exe-pending-messages.js +151 -19
  24. package/dist/bin/exe-pending-notifications.js +97 -2
  25. package/dist/bin/exe-pending-reviews.js +155 -22
  26. package/dist/bin/exe-rename.js +564 -23
  27. package/dist/bin/exe-review.js +231 -73
  28. package/dist/bin/exe-search.js +995 -228
  29. package/dist/bin/exe-session-cleanup.js +4930 -1664
  30. package/dist/bin/exe-settings.js +20 -5
  31. package/dist/bin/exe-start-codex.js +2598 -0
  32. package/dist/bin/exe-start.sh +15 -3
  33. package/dist/bin/exe-status.js +154 -21
  34. package/dist/bin/exe-team.js +97 -2
  35. package/dist/bin/git-sweep.js +1180 -363
  36. package/dist/bin/graph-backfill.js +175 -72
  37. package/dist/bin/graph-export.js +175 -72
  38. package/dist/bin/install.js +60 -7
  39. package/dist/bin/list-providers.js +1 -0
  40. package/dist/bin/scan-tasks.js +1185 -367
  41. package/dist/bin/setup.js +914 -270
  42. package/dist/bin/shard-migrate.js +175 -72
  43. package/dist/bin/update.js +1 -0
  44. package/dist/bin/wiki-sync.js +175 -72
  45. package/dist/gateway/index.js +792 -285
  46. package/dist/hooks/bug-report-worker.js +445 -135
  47. package/dist/hooks/commit-complete.js +1178 -361
  48. package/dist/hooks/error-recall.js +994 -228
  49. package/dist/hooks/ingest-worker.js +1799 -1234
  50. package/dist/hooks/ingest.js +3 -0
  51. package/dist/hooks/instructions-loaded.js +707 -97
  52. package/dist/hooks/notification.js +699 -89
  53. package/dist/hooks/post-compact.js +757 -109
  54. package/dist/hooks/pre-compact.js +1061 -244
  55. package/dist/hooks/pre-tool-use.js +787 -130
  56. package/dist/hooks/prompt-ingest-worker.js +242 -101
  57. package/dist/hooks/prompt-submit.js +1121 -299
  58. package/dist/hooks/response-ingest-worker.js +242 -101
  59. package/dist/hooks/session-end.js +4063 -397
  60. package/dist/hooks/session-start.js +1071 -254
  61. package/dist/hooks/stop.js +768 -120
  62. package/dist/hooks/subagent-stop.js +757 -109
  63. package/dist/hooks/summary-worker.js +1706 -1011
  64. package/dist/index.js +1821 -1098
  65. package/dist/lib/agent-config.js +167 -0
  66. package/dist/lib/cloud-sync.js +932 -88
  67. package/dist/lib/consolidation.js +2 -1
  68. package/dist/lib/database.js +642 -87
  69. package/dist/lib/db-daemon-client.js +503 -0
  70. package/dist/lib/device-registry.js +547 -7
  71. package/dist/lib/embedder.js +14 -28
  72. package/dist/lib/employee-templates.js +84 -74
  73. package/dist/lib/employees.js +9 -0
  74. package/dist/lib/exe-daemon-client.js +16 -29
  75. package/dist/lib/exe-daemon.js +2733 -1575
  76. package/dist/lib/hybrid-search.js +995 -228
  77. package/dist/lib/identity.js +87 -67
  78. package/dist/lib/keychain.js +9 -1
  79. package/dist/lib/messaging.js +103 -40
  80. package/dist/lib/reminders.js +91 -74
  81. package/dist/lib/runtime-table.js +16 -0
  82. package/dist/lib/schedules.js +96 -2
  83. package/dist/lib/session-wrappers.js +22 -0
  84. package/dist/lib/skill-learning.js +103 -85
  85. package/dist/lib/store.js +234 -73
  86. package/dist/lib/tasks.js +348 -134
  87. package/dist/lib/tmux-routing.js +422 -208
  88. package/dist/lib/token-spend.js +273 -0
  89. package/dist/lib/ws-client.js +11 -0
  90. package/dist/mcp/server.js +5742 -696
  91. package/dist/mcp/tools/complete-reminder.js +94 -77
  92. package/dist/mcp/tools/create-reminder.js +94 -77
  93. package/dist/mcp/tools/create-task.js +375 -152
  94. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  95. package/dist/mcp/tools/list-reminders.js +94 -77
  96. package/dist/mcp/tools/list-tasks.js +99 -31
  97. package/dist/mcp/tools/send-message.js +108 -45
  98. package/dist/mcp/tools/update-task.js +162 -77
  99. package/dist/runtime/index.js +1075 -258
  100. package/dist/tui/App.js +1333 -506
  101. package/package.json +6 -1
  102. package/src/commands/exe/agent-config.md +27 -0
  103. 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"]);
@@ -564,31 +615,38 @@ function getClient() {
564
615
  if (!_resilientClient) {
565
616
  throw new Error("Database client not initialized. Call initDatabase() first.");
566
617
  }
618
+ if (process.env.EXE_IS_DAEMON === "1") {
619
+ return _resilientClient;
620
+ }
621
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
622
+ return _daemonClient;
623
+ }
567
624
  return _resilientClient;
568
625
  }
569
- var _resilientClient;
626
+ var _resilientClient, _daemonClient;
570
627
  var init_database = __esm({
571
628
  "src/lib/database.ts"() {
572
629
  "use strict";
573
630
  init_db_retry();
574
631
  init_employees();
575
632
  _resilientClient = null;
633
+ _daemonClient = null;
576
634
  }
577
635
  });
578
636
 
579
637
  // src/lib/license.ts
580
- 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";
581
639
  import { randomUUID } from "crypto";
582
- import path5 from "path";
640
+ import path6 from "path";
583
641
  import { jwtVerify, importSPKI } from "jose";
584
642
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
585
643
  var init_license = __esm({
586
644
  "src/lib/license.ts"() {
587
645
  "use strict";
588
646
  init_config();
589
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
590
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
591
- 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");
592
650
  PLAN_LIMITS = {
593
651
  free: { devices: 1, employees: 1, memories: 5e3 },
594
652
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -600,12 +658,12 @@ var init_license = __esm({
600
658
  });
601
659
 
602
660
  // src/lib/plan-limits.ts
603
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
604
- import path6 from "path";
661
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
662
+ import path7 from "path";
605
663
  function getLicenseSync() {
606
664
  try {
607
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
608
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
665
+ if (!existsSync7(CACHE_PATH2)) return freeLicense();
666
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
609
667
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
610
668
  const parts = raw.token.split(".");
611
669
  if (parts.length !== 3) return freeLicense();
@@ -643,8 +701,8 @@ function assertEmployeeLimitSync(rosterPath) {
643
701
  const filePath = rosterPath ?? EMPLOYEES_PATH;
644
702
  let count = 0;
645
703
  try {
646
- if (existsSync6(filePath)) {
647
- const raw = readFileSync6(filePath, "utf8");
704
+ if (existsSync7(filePath)) {
705
+ const raw = readFileSync7(filePath, "utf8");
648
706
  const employees = JSON.parse(raw);
649
707
  count = Array.isArray(employees) ? employees.length : 0;
650
708
  }
@@ -673,19 +731,19 @@ var init_plan_limits = __esm({
673
731
  this.name = "PlanLimitError";
674
732
  }
675
733
  };
676
- CACHE_PATH2 = path6.join(EXE_AI_DIR, "license-cache.json");
734
+ CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
677
735
  }
678
736
  });
679
737
 
680
738
  // src/lib/notifications.ts
681
739
  import crypto from "crypto";
682
- import path7 from "path";
740
+ import path8 from "path";
683
741
  import os5 from "os";
684
742
  import {
685
- readFileSync as readFileSync7,
743
+ readFileSync as readFileSync8,
686
744
  readdirSync,
687
745
  unlinkSync as unlinkSync2,
688
- existsSync as existsSync7,
746
+ existsSync as existsSync8,
689
747
  rmdirSync
690
748
  } from "fs";
691
749
  async function writeNotification(notification) {
@@ -844,10 +902,11 @@ var init_state_bus = __esm({
844
902
 
845
903
  // src/lib/tasks-crud.ts
846
904
  import crypto3 from "crypto";
847
- import path8 from "path";
905
+ import path9 from "path";
906
+ import os6 from "os";
848
907
  import { execSync as execSync4 } from "child_process";
849
908
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
850
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
909
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
851
910
  async function writeCheckpoint(input) {
852
911
  const client = getClient();
853
912
  const row = await resolveTask(client, input.taskId);
@@ -888,6 +947,35 @@ function extractParentFromContext(contextBody) {
888
947
  function slugify(title) {
889
948
  return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
890
949
  }
950
+ function buildKeywordIndex() {
951
+ const idx = /* @__PURE__ */ new Map();
952
+ for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
953
+ for (const kw of keywords) {
954
+ const existing = idx.get(kw) ?? [];
955
+ existing.push(role);
956
+ idx.set(kw, existing);
957
+ }
958
+ }
959
+ return idx;
960
+ }
961
+ function checkLaneAffinity(title, context, assigneeName) {
962
+ const employees = loadEmployeesSync();
963
+ const employee = employees.find((e) => e.name === assigneeName);
964
+ if (!employee) return void 0;
965
+ const assigneeRole = employee.role;
966
+ const text = `${title} ${context}`.toLowerCase();
967
+ const matchedRoles = /* @__PURE__ */ new Set();
968
+ for (const [keyword, roles] of KEYWORD_INDEX) {
969
+ if (text.includes(keyword)) {
970
+ for (const role of roles) matchedRoles.add(role);
971
+ }
972
+ }
973
+ if (matchedRoles.size === 0) return void 0;
974
+ if (matchedRoles.has(assigneeRole)) return void 0;
975
+ if (assigneeRole === "COO") return void 0;
976
+ const expectedRoles = Array.from(matchedRoles).join(" or ");
977
+ return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
978
+ }
891
979
  async function resolveTask(client, identifier, scopeSession) {
892
980
  const scope = sessionScopeFilter(scopeSession);
893
981
  let result = await client.execute({
@@ -937,7 +1025,14 @@ async function createTaskCore(input) {
937
1025
  const id = crypto3.randomUUID();
938
1026
  const now = (/* @__PURE__ */ new Date()).toISOString();
939
1027
  const slug = slugify(input.title);
940
- const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
1028
+ let earlySessionScope = null;
1029
+ try {
1030
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
1031
+ earlySessionScope = resolveExeSession2();
1032
+ } catch {
1033
+ }
1034
+ const scope = earlySessionScope ?? "default";
1035
+ const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
941
1036
  let blockedById = null;
942
1037
  const initialStatus = input.blockedBy ? "blocked" : "open";
943
1038
  if (input.blockedBy) {
@@ -977,22 +1072,24 @@ async function createTaskCore(input) {
977
1072
  if (dupCheck.rows.length > 0) {
978
1073
  warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
979
1074
  }
1075
+ if (!process.env.DISABLE_LANE_AFFINITY) {
1076
+ const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
1077
+ if (laneWarning) {
1078
+ warning = warning ? `${warning}
1079
+ ${laneWarning}` : laneWarning;
1080
+ }
1081
+ }
980
1082
  if (input.baseDir) {
981
1083
  try {
982
- await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
983
- 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 });
984
1086
  await ensureArchitectureDoc(input.baseDir, input.projectName);
985
1087
  await ensureGitignoreExe(input.baseDir);
986
1088
  } catch {
987
1089
  }
988
1090
  }
989
1091
  const complexity = input.complexity ?? "standard";
990
- let sessionScope = null;
991
- try {
992
- const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
993
- sessionScope = resolveExeSession2();
994
- } catch {
995
- }
1092
+ const sessionScope = earlySessionScope;
996
1093
  await client.execute({
997
1094
  sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
998
1095
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -1019,6 +1116,43 @@ async function createTaskCore(input) {
1019
1116
  now
1020
1117
  ]
1021
1118
  });
1119
+ if (input.baseDir) {
1120
+ try {
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 });
1125
+ const reviewer = input.reviewer ?? input.assignedBy;
1126
+ const mdContent = `# ${input.title}
1127
+
1128
+ **ID:** ${id}
1129
+ **Status:** ${initialStatus}
1130
+ **Priority:** ${input.priority}
1131
+ **Assigned by:** ${input.assignedBy}
1132
+ **Assigned to:** ${input.assignedTo}
1133
+ **Project:** ${input.projectName}
1134
+ **Created:** ${now.split("T")[0]}${parentTaskId ? `
1135
+ **Parent task:** ${parentTaskId}` : ""}
1136
+ **Reviewer:** ${reviewer}
1137
+
1138
+ ## Context
1139
+
1140
+ ${input.context}
1141
+
1142
+ ## MANDATORY: When done
1143
+
1144
+ You MUST call update_task with status "done" and a result summary when finished.
1145
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
1146
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
1147
+ `;
1148
+ await writeFile3(mdPath, mdContent, "utf-8");
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
+ );
1154
+ }
1155
+ }
1022
1156
  return {
1023
1157
  id,
1024
1158
  title: input.title,
@@ -1211,7 +1345,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1211
1345
  return { row, taskFile, now, taskId };
1212
1346
  }
1213
1347
  }
1214
- if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
1348
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
1215
1349
  process.stderr.write(
1216
1350
  `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
1217
1351
  `
@@ -1276,9 +1410,9 @@ async function deleteTaskCore(taskId, _baseDir) {
1276
1410
  return { taskFile, assignedTo, assignedBy, taskSlug };
1277
1411
  }
1278
1412
  async function ensureArchitectureDoc(baseDir, projectName) {
1279
- const archPath = path8.join(baseDir, "exe", "ARCHITECTURE.md");
1413
+ const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
1280
1414
  try {
1281
- if (existsSync8(archPath)) return;
1415
+ if (existsSync9(archPath)) return;
1282
1416
  const template = [
1283
1417
  `# ${projectName} \u2014 System Architecture`,
1284
1418
  "",
@@ -1311,10 +1445,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
1311
1445
  }
1312
1446
  }
1313
1447
  async function ensureGitignoreExe(baseDir) {
1314
- const gitignorePath = path8.join(baseDir, ".gitignore");
1448
+ const gitignorePath = path9.join(baseDir, ".gitignore");
1315
1449
  try {
1316
- if (existsSync8(gitignorePath)) {
1317
- const content = readFileSync8(gitignorePath, "utf-8");
1450
+ if (existsSync9(gitignorePath)) {
1451
+ const content = readFileSync9(gitignorePath, "utf-8");
1318
1452
  if (/^\/?exe\/?$/m.test(content)) return;
1319
1453
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
1320
1454
  } else {
@@ -1323,20 +1457,30 @@ async function ensureGitignoreExe(baseDir) {
1323
1457
  } catch {
1324
1458
  }
1325
1459
  }
1326
- var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
1460
+ var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
1327
1461
  var init_tasks_crud = __esm({
1328
1462
  "src/lib/tasks-crud.ts"() {
1329
1463
  "use strict";
1330
1464
  init_database();
1331
1465
  init_task_scope();
1466
+ init_employees();
1467
+ LANE_KEYWORDS = {
1468
+ CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
1469
+ CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
1470
+ "Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
1471
+ "Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
1472
+ "Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
1473
+ "AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
1474
+ };
1475
+ KEYWORD_INDEX = buildKeywordIndex();
1332
1476
  DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
1333
1477
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
1334
1478
  }
1335
1479
  });
1336
1480
 
1337
1481
  // src/lib/tasks-review.ts
1338
- import path9 from "path";
1339
- 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";
1340
1484
  async function countPendingReviews(sessionScope) {
1341
1485
  const client = getClient();
1342
1486
  if (sessionScope) {
@@ -1358,7 +1502,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
1358
1502
  const result2 = await client.execute({
1359
1503
  sql: `SELECT COUNT(*) as cnt FROM tasks
1360
1504
  WHERE status = 'needs_review' AND updated_at > ?
1361
- AND (session_scope = ? OR session_scope IS NULL)`,
1505
+ AND session_scope = ?`,
1362
1506
  args: [sinceIso, sessionScope]
1363
1507
  });
1364
1508
  return Number(result2.rows[0]?.cnt) || 0;
@@ -1376,7 +1520,7 @@ async function listPendingReviews(limit, sessionScope) {
1376
1520
  const result2 = await client.execute({
1377
1521
  sql: `SELECT title, assigned_to, project_name FROM tasks
1378
1522
  WHERE status = 'needs_review'
1379
- AND (session_scope = ? OR session_scope IS NULL)
1523
+ AND session_scope = ?
1380
1524
  ORDER BY priority ASC, created_at DESC LIMIT ?`,
1381
1525
  args: [sessionScope, limit]
1382
1526
  });
@@ -1497,14 +1641,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
1497
1641
  if (parts.length >= 3 && parts[0] === "review") {
1498
1642
  const agent = parts[1];
1499
1643
  const slug = parts.slice(2).join("-");
1500
- const originalTaskFile = `exe/${agent}/${slug}.md`;
1644
+ const legacyTaskFile = `exe/${agent}/${slug}.md`;
1501
1645
  const result = await client.execute({
1502
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
1503
- args: [now, originalTaskFile]
1646
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
1647
+ args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
1504
1648
  });
1505
1649
  if (result.rowsAffected > 0) {
1506
1650
  process.stderr.write(
1507
- `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
1651
+ `[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
1508
1652
  `
1509
1653
  );
1510
1654
  }
@@ -1517,11 +1661,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
1517
1661
  );
1518
1662
  }
1519
1663
  try {
1520
- const cacheDir = path9.join(EXE_AI_DIR, "session-cache");
1521
- if (existsSync9(cacheDir)) {
1664
+ const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
1665
+ if (existsSync10(cacheDir)) {
1522
1666
  for (const f of readdirSync2(cacheDir)) {
1523
1667
  if (f.startsWith("review-notified-")) {
1524
- unlinkSync3(path9.join(cacheDir, f));
1668
+ unlinkSync3(path10.join(cacheDir, f));
1525
1669
  }
1526
1670
  }
1527
1671
  }
@@ -1542,7 +1686,7 @@ var init_tasks_review = __esm({
1542
1686
  });
1543
1687
 
1544
1688
  // src/lib/tasks-chain.ts
1545
- import path10 from "path";
1689
+ import path11 from "path";
1546
1690
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
1547
1691
  async function cascadeUnblock(taskId, baseDir, now) {
1548
1692
  const client = getClient();
@@ -1559,7 +1703,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
1559
1703
  });
1560
1704
  for (const ur of unblockedRows.rows) {
1561
1705
  try {
1562
- const ubFile = path10.join(baseDir, String(ur.task_file));
1706
+ const ubFile = path11.join(baseDir, String(ur.task_file));
1563
1707
  let ubContent = await readFile3(ubFile, "utf-8");
1564
1708
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
1565
1709
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -1628,7 +1772,7 @@ var init_tasks_chain = __esm({
1628
1772
 
1629
1773
  // src/lib/project-name.ts
1630
1774
  import { execSync as execSync5 } from "child_process";
1631
- import path11 from "path";
1775
+ import path12 from "path";
1632
1776
  function getProjectName(cwd) {
1633
1777
  const dir = cwd ?? process.cwd();
1634
1778
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -1641,7 +1785,7 @@ function getProjectName(cwd) {
1641
1785
  timeout: 2e3,
1642
1786
  stdio: ["pipe", "pipe", "pipe"]
1643
1787
  }).trim();
1644
- repoRoot = path11.dirname(gitCommonDir);
1788
+ repoRoot = path12.dirname(gitCommonDir);
1645
1789
  } catch {
1646
1790
  repoRoot = execSync5("git rev-parse --show-toplevel", {
1647
1791
  cwd: dir,
@@ -1650,11 +1794,11 @@ function getProjectName(cwd) {
1650
1794
  stdio: ["pipe", "pipe", "pipe"]
1651
1795
  }).trim();
1652
1796
  }
1653
- _cached2 = path11.basename(repoRoot);
1797
+ _cached2 = path12.basename(repoRoot);
1654
1798
  _cachedCwd = dir;
1655
1799
  return _cached2;
1656
1800
  } catch {
1657
- _cached2 = path11.basename(dir);
1801
+ _cached2 = path12.basename(dir);
1658
1802
  _cachedCwd = dir;
1659
1803
  return _cached2;
1660
1804
  }
@@ -1686,7 +1830,7 @@ function findSessionForProject(projectName) {
1686
1830
  const sessions = listSessions();
1687
1831
  for (const s of sessions) {
1688
1832
  const proj = s.projectDir.split("/").filter(Boolean).pop();
1689
- if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
1833
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
1690
1834
  }
1691
1835
  return null;
1692
1836
  }
@@ -1732,7 +1876,7 @@ var init_session_scope = __esm({
1732
1876
 
1733
1877
  // src/lib/tasks-notify.ts
1734
1878
  async function dispatchTaskToEmployee(input) {
1735
- if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
1879
+ if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
1736
1880
  let crossProject = false;
1737
1881
  if (input.projectName) {
1738
1882
  try {
@@ -2127,8 +2271,8 @@ __export(tasks_exports, {
2127
2271
  updateTaskStatus: () => updateTaskStatus,
2128
2272
  writeCheckpoint: () => writeCheckpoint
2129
2273
  });
2130
- import path12 from "path";
2131
- 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";
2132
2276
  async function createTask(input) {
2133
2277
  const result = await createTaskCore(input);
2134
2278
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -2147,11 +2291,11 @@ async function updateTask(input) {
2147
2291
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
2148
2292
  try {
2149
2293
  const agent = String(row.assigned_to);
2150
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
2151
- 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`);
2152
2296
  if (input.status === "in_progress") {
2153
- mkdirSync4(cacheDir, { recursive: true });
2154
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
2297
+ mkdirSync5(cacheDir, { recursive: true });
2298
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
2155
2299
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
2156
2300
  try {
2157
2301
  unlinkSync4(cachePath);
@@ -2211,7 +2355,7 @@ async function updateTask(input) {
2211
2355
  }
2212
2356
  const isTerminal = input.status === "done" || input.status === "needs_review";
2213
2357
  if (isTerminal) {
2214
- const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
2358
+ const isCoordinator = isCoordinatorName(String(row.assigned_to));
2215
2359
  if (!isCoordinator) {
2216
2360
  notifyTaskDone();
2217
2361
  }
@@ -2236,7 +2380,7 @@ async function updateTask(input) {
2236
2380
  }
2237
2381
  }
2238
2382
  }
2239
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
2383
+ if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
2240
2384
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
2241
2385
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
2242
2386
  taskId,
@@ -2252,7 +2396,7 @@ async function updateTask(input) {
2252
2396
  });
2253
2397
  }
2254
2398
  let nextTask;
2255
- if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
2399
+ if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
2256
2400
  try {
2257
2401
  nextTask = await findNextTask(String(row.assigned_to));
2258
2402
  } catch {
@@ -2618,13 +2762,13 @@ __export(tmux_routing_exports, {
2618
2762
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
2619
2763
  });
2620
2764
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2621
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
2622
- import path13 from "path";
2623
- import os6 from "os";
2765
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
2766
+ import path14 from "path";
2767
+ import os7 from "os";
2624
2768
  import { fileURLToPath } from "url";
2625
2769
  import { unlinkSync as unlinkSync5 } from "fs";
2626
2770
  function spawnLockPath(sessionName) {
2627
- return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2771
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2628
2772
  }
2629
2773
  function isProcessAlive(pid) {
2630
2774
  try {
@@ -2635,13 +2779,13 @@ function isProcessAlive(pid) {
2635
2779
  }
2636
2780
  }
2637
2781
  function acquireSpawnLock(sessionName) {
2638
- if (!existsSync10(SPAWN_LOCK_DIR)) {
2639
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
2782
+ if (!existsSync11(SPAWN_LOCK_DIR)) {
2783
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
2640
2784
  }
2641
2785
  const lockFile = spawnLockPath(sessionName);
2642
- if (existsSync10(lockFile)) {
2786
+ if (existsSync11(lockFile)) {
2643
2787
  try {
2644
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
2788
+ const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
2645
2789
  const age = Date.now() - lock.timestamp;
2646
2790
  if (isProcessAlive(lock.pid) && age < 6e4) {
2647
2791
  return false;
@@ -2649,7 +2793,7 @@ function acquireSpawnLock(sessionName) {
2649
2793
  } catch {
2650
2794
  }
2651
2795
  }
2652
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2796
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2653
2797
  return true;
2654
2798
  }
2655
2799
  function releaseSpawnLock(sessionName) {
@@ -2661,13 +2805,13 @@ function releaseSpawnLock(sessionName) {
2661
2805
  function resolveBehaviorsExporterScript() {
2662
2806
  try {
2663
2807
  const thisFile = fileURLToPath(import.meta.url);
2664
- const scriptPath = path13.join(
2665
- path13.dirname(thisFile),
2808
+ const scriptPath = path14.join(
2809
+ path14.dirname(thisFile),
2666
2810
  "..",
2667
2811
  "bin",
2668
2812
  "exe-export-behaviors.js"
2669
2813
  );
2670
- return existsSync10(scriptPath) ? scriptPath : null;
2814
+ return existsSync11(scriptPath) ? scriptPath : null;
2671
2815
  } catch {
2672
2816
  return null;
2673
2817
  }
@@ -2733,12 +2877,12 @@ function extractRootExe(name) {
2733
2877
  return parts.length > 0 ? parts[parts.length - 1] : null;
2734
2878
  }
2735
2879
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
2736
- if (!existsSync10(SESSION_CACHE)) {
2737
- mkdirSync5(SESSION_CACHE, { recursive: true });
2880
+ if (!existsSync11(SESSION_CACHE)) {
2881
+ mkdirSync6(SESSION_CACHE, { recursive: true });
2738
2882
  }
2739
2883
  const rootExe = extractRootExe(parentExe) ?? parentExe;
2740
- const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
2741
- writeFileSync6(filePath, JSON.stringify({
2884
+ const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
2885
+ writeFileSync7(filePath, JSON.stringify({
2742
2886
  parentExe: rootExe,
2743
2887
  dispatchedBy: dispatchedBy || rootExe,
2744
2888
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2746,7 +2890,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
2746
2890
  }
2747
2891
  function getParentExe(sessionKey) {
2748
2892
  try {
2749
- 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"));
2750
2894
  return data.parentExe || null;
2751
2895
  } catch {
2752
2896
  return null;
@@ -2754,8 +2898,8 @@ function getParentExe(sessionKey) {
2754
2898
  }
2755
2899
  function getDispatchedBy(sessionKey) {
2756
2900
  try {
2757
- const data = JSON.parse(readFileSync9(
2758
- path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2901
+ const data = JSON.parse(readFileSync10(
2902
+ path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2759
2903
  "utf8"
2760
2904
  ));
2761
2905
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -2816,32 +2960,50 @@ async function verifyPaneAtCapacity(sessionName) {
2816
2960
  }
2817
2961
  function readDebounceState() {
2818
2962
  try {
2819
- if (!existsSync10(DEBOUNCE_FILE)) return {};
2820
- 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;
2821
2974
  } catch {
2822
2975
  return {};
2823
2976
  }
2824
2977
  }
2825
2978
  function writeDebounceState(state) {
2826
2979
  try {
2827
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
2828
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
2980
+ if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
2981
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
2829
2982
  } catch {
2830
2983
  }
2831
2984
  }
2832
2985
  function isDebounced(targetSession) {
2833
2986
  const state = readDebounceState();
2834
- const lastSent = state[targetSession] ?? 0;
2835
- 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;
2836
2996
  }
2837
2997
  function recordDebounce(targetSession) {
2838
2998
  const state = readDebounceState();
2839
- state[targetSession] = Date.now();
2999
+ const batched = state[targetSession]?.pending ?? 0;
3000
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
2840
3001
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
2841
3002
  for (const key of Object.keys(state)) {
2842
- if ((state[key] ?? 0) < cutoff) delete state[key];
3003
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
2843
3004
  }
2844
3005
  writeDebounceState(state);
3006
+ return batched;
2845
3007
  }
2846
3008
  function logIntercom(msg) {
2847
3009
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -2886,7 +3048,7 @@ function sendIntercom(targetSession) {
2886
3048
  return "skipped_exe";
2887
3049
  }
2888
3050
  if (isDebounced(targetSession)) {
2889
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
3051
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
2890
3052
  return "debounced";
2891
3053
  }
2892
3054
  try {
@@ -2898,14 +3060,14 @@ function sendIntercom(targetSession) {
2898
3060
  const sessionState = getSessionState(targetSession);
2899
3061
  if (sessionState === "no_claude") {
2900
3062
  queueIntercom(targetSession, "claude not running in session");
2901
- recordDebounce(targetSession);
2902
- 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]` : ""}`);
2903
3065
  return "queued";
2904
3066
  }
2905
3067
  if (sessionState === "thinking" || sessionState === "tool") {
2906
3068
  queueIntercom(targetSession, "session busy at send time");
2907
- recordDebounce(targetSession);
2908
- 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]` : ""}`);
2909
3071
  return "queued";
2910
3072
  }
2911
3073
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -2913,8 +3075,8 @@ function sendIntercom(targetSession) {
2913
3075
  transport.sendKeys(targetSession, "q");
2914
3076
  }
2915
3077
  transport.sendKeys(targetSession, "/exe-intercom");
2916
- recordDebounce(targetSession);
2917
- 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)`);
2918
3080
  return "delivered";
2919
3081
  } catch {
2920
3082
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -2944,7 +3106,7 @@ function notifyParentExe(sessionKey) {
2944
3106
  return true;
2945
3107
  }
2946
3108
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
2947
- if (employeeName === "exe" || isCoordinatorName(employeeName)) {
3109
+ if (isCoordinatorName(employeeName)) {
2948
3110
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
2949
3111
  }
2950
3112
  try {
@@ -3016,26 +3178,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3016
3178
  const transport = getTransport();
3017
3179
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3018
3180
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3019
- const logDir = path13.join(os6.homedir(), ".exe-os", "session-logs");
3020
- const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3021
- if (!existsSync10(logDir)) {
3022
- 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 });
3023
3185
  }
3024
3186
  transport.kill(sessionName);
3025
3187
  let cleanupSuffix = "";
3026
3188
  try {
3027
3189
  const thisFile = fileURLToPath(import.meta.url);
3028
- const cleanupScript = path13.join(path13.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3029
- if (existsSync10(cleanupScript)) {
3190
+ const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3191
+ if (existsSync11(cleanupScript)) {
3030
3192
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
3031
3193
  }
3032
3194
  } catch {
3033
3195
  }
3034
3196
  try {
3035
- const claudeJsonPath = path13.join(os6.homedir(), ".claude.json");
3197
+ const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
3036
3198
  let claudeJson = {};
3037
3199
  try {
3038
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
3200
+ claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
3039
3201
  } catch {
3040
3202
  }
3041
3203
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3043,17 +3205,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3043
3205
  const trustDir = opts?.cwd ?? projectDir;
3044
3206
  if (!projects[trustDir]) projects[trustDir] = {};
3045
3207
  projects[trustDir].hasTrustDialogAccepted = true;
3046
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
3208
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
3047
3209
  } catch {
3048
3210
  }
3049
3211
  try {
3050
- const settingsDir = path13.join(os6.homedir(), ".claude", "projects");
3212
+ const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
3051
3213
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3052
- const projSettingsDir = path13.join(settingsDir, normalizedKey);
3053
- const settingsPath = path13.join(projSettingsDir, "settings.json");
3214
+ const projSettingsDir = path14.join(settingsDir, normalizedKey);
3215
+ const settingsPath = path14.join(projSettingsDir, "settings.json");
3054
3216
  let settings = {};
3055
3217
  try {
3056
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
3218
+ settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
3057
3219
  } catch {
3058
3220
  }
3059
3221
  const perms = settings.permissions ?? {};
@@ -3081,21 +3243,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3081
3243
  if (changed) {
3082
3244
  perms.allow = allow;
3083
3245
  settings.permissions = perms;
3084
- mkdirSync5(projSettingsDir, { recursive: true });
3085
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3246
+ mkdirSync6(projSettingsDir, { recursive: true });
3247
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3086
3248
  }
3087
3249
  } catch {
3088
3250
  }
3089
3251
  const spawnCwd = opts?.cwd ?? projectDir;
3090
3252
  const useExeAgent = !!(opts?.model && opts?.provider);
3091
- 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();
3092
3257
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
3093
3258
  let identityFlag = "";
3094
3259
  let behaviorsFlag = "";
3095
3260
  let legacyFallbackWarned = false;
3096
3261
  if (!useExeAgent && !useBinSymlink) {
3097
- const identityPath = path13.join(
3098
- os6.homedir(),
3262
+ const identityPath = path14.join(
3263
+ os7.homedir(),
3099
3264
  ".exe-os",
3100
3265
  "identity",
3101
3266
  `${employeeName}.md`
@@ -3104,13 +3269,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3104
3269
  const hasAgentFlag = claudeSupportsAgentFlag();
3105
3270
  if (hasAgentFlag) {
3106
3271
  identityFlag = ` --agent ${employeeName}`;
3107
- } else if (existsSync10(identityPath)) {
3272
+ } else if (existsSync11(identityPath)) {
3108
3273
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
3109
3274
  legacyFallbackWarned = true;
3110
3275
  }
3111
3276
  const behaviorsFile = exportBehaviorsSync(
3112
3277
  employeeName,
3113
- path13.basename(spawnCwd),
3278
+ path14.basename(spawnCwd),
3114
3279
  sessionName
3115
3280
  );
3116
3281
  if (behaviorsFile) {
@@ -3125,16 +3290,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3125
3290
  }
3126
3291
  let sessionContextFlag = "";
3127
3292
  try {
3128
- const ctxDir = path13.join(os6.homedir(), ".exe-os", "session-cache");
3129
- mkdirSync5(ctxDir, { recursive: true });
3130
- 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`);
3131
3296
  const ctxContent = [
3132
3297
  `## Session Context`,
3133
3298
  `You are running in tmux session: ${sessionName}.`,
3134
3299
  `Your parent coordinator session is ${exeSession}.`,
3135
3300
  `Your employees (if any) use the -${exeSession} suffix.`
3136
3301
  ].join("\n");
3137
- writeFileSync6(ctxFile, ctxContent);
3302
+ writeFileSync7(ctxFile, ctxContent);
3138
3303
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
3139
3304
  } catch {
3140
3305
  }
@@ -3148,9 +3313,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3148
3313
  }
3149
3314
  }
3150
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
+ }
3151
3342
  let spawnCommand;
3152
3343
  if (useExeAgent) {
3153
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}`;
3154
3358
  } else if (useBinSymlink) {
3155
3359
  const binName = `${employeeName}-${ccProvider}`;
3156
3360
  process.stderr.write(
@@ -3172,11 +3376,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3172
3376
  transport.pipeLog(sessionName, logFile);
3173
3377
  try {
3174
3378
  const mySession = getMySession();
3175
- const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3176
- writeFileSync6(dispatchInfo, JSON.stringify({
3379
+ const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3380
+ writeFileSync7(dispatchInfo, JSON.stringify({
3177
3381
  dispatchedBy: mySession,
3178
3382
  rootExe: exeSession,
3179
- 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,
3180
3386
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3181
3387
  }));
3182
3388
  } catch {
@@ -3194,6 +3400,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3194
3400
  booted = true;
3195
3401
  break;
3196
3402
  }
3403
+ } else if (useCodex) {
3404
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
3405
+ booted = true;
3406
+ break;
3407
+ }
3197
3408
  } else {
3198
3409
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
3199
3410
  booted = true;
@@ -3205,9 +3416,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3205
3416
  }
3206
3417
  if (!booted) {
3207
3418
  releaseSpawnLock(sessionName);
3208
- 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` };
3209
3421
  }
3210
- if (!useExeAgent) {
3422
+ if (!useExeAgent && !useCodex) {
3211
3423
  try {
3212
3424
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
3213
3425
  } catch {
@@ -3233,17 +3445,19 @@ var init_tmux_routing = __esm({
3233
3445
  init_cc_agent_support();
3234
3446
  init_mcp_prefix();
3235
3447
  init_provider_table();
3448
+ init_agent_config();
3449
+ init_runtime_table();
3236
3450
  init_intercom_queue();
3237
3451
  init_plan_limits();
3238
3452
  init_employees();
3239
- SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
3240
- SESSION_CACHE = path13.join(os6.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");
3241
3455
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3242
3456
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
3243
3457
  VERIFY_PANE_LINES = 200;
3244
3458
  INTERCOM_DEBOUNCE_MS = 3e4;
3245
- INTERCOM_LOG2 = path13.join(os6.homedir(), ".exe-os", "intercom.log");
3246
- 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");
3247
3461
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3248
3462
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
3249
3463
  }