@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,123 +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
- function isBusyError(err) {
322
- if (err instanceof Error) {
323
- const msg = err.message.toLowerCase();
324
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
325
- }
326
- return false;
327
- }
328
- function delay(ms) {
329
- return new Promise((resolve) => setTimeout(resolve, ms));
330
- }
331
- async function retryOnBusy(fn, label) {
332
- let lastError;
333
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
334
- try {
335
- return await fn();
336
- } catch (err) {
337
- lastError = err;
338
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
339
- throw err;
340
- }
341
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
342
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
343
- process.stderr.write(
344
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
345
- `
346
- );
347
- await delay(backoff + jitter);
348
- }
349
- }
350
- throw lastError;
351
- }
352
- function wrapWithRetry(client) {
353
- return new Proxy(client, {
354
- get(target, prop, receiver) {
355
- if (prop === "execute") {
356
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
357
- }
358
- if (prop === "batch") {
359
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
360
- }
361
- return Reflect.get(target, prop, receiver);
362
- }
363
- });
364
- }
365
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
366
- var init_db_retry = __esm({
367
- "src/lib/db-retry.ts"() {
368
- "use strict";
369
- MAX_RETRIES = 3;
370
- BASE_DELAY_MS = 200;
371
- MAX_JITTER_MS = 300;
372
- }
373
- });
374
-
375
271
  // src/lib/config.ts
376
272
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
377
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
378
- import path3 from "path";
379
- 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";
380
276
  function resolveDataDir() {
381
277
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
382
278
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
383
- const newDir = path3.join(os3.homedir(), ".exe-os");
384
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
385
- 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)) {
386
282
  try {
387
- renameSync2(legacyDir, newDir);
283
+ renameSync(legacyDir, newDir);
388
284
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
389
285
  `);
390
286
  } catch {
@@ -446,9 +342,9 @@ function normalizeAutoUpdate(raw) {
446
342
  async function loadConfig() {
447
343
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
448
344
  await mkdir(dir, { recursive: true });
449
- const configPath = path3.join(dir, "config.json");
450
- if (!existsSync3(configPath)) {
451
- 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") };
452
348
  }
453
349
  const raw = await readFile(configPath, "utf-8");
454
350
  try {
@@ -466,13 +362,13 @@ async function loadConfig() {
466
362
  normalizeScalingRoadmap(migratedCfg);
467
363
  normalizeSessionLifecycle(migratedCfg);
468
364
  normalizeAutoUpdate(migratedCfg);
469
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
365
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
470
366
  if (config.dbPath.startsWith("~")) {
471
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
367
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
472
368
  }
473
369
  return config;
474
370
  } catch {
475
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
371
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
476
372
  }
477
373
  }
478
374
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -480,10 +376,10 @@ var init_config = __esm({
480
376
  "src/lib/config.ts"() {
481
377
  "use strict";
482
378
  EXE_AI_DIR = resolveDataDir();
483
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
484
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
485
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
486
- 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");
487
383
  CURRENT_CONFIG_VERSION = 1;
488
384
  DEFAULT_CONFIG = {
489
385
  config_version: CURRENT_CONFIG_VERSION,
@@ -555,11 +451,166 @@ var init_config = __esm({
555
451
  }
556
452
  });
557
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
+ function isBusyError(err) {
556
+ if (err instanceof Error) {
557
+ const msg = err.message.toLowerCase();
558
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
559
+ }
560
+ return false;
561
+ }
562
+ function delay(ms) {
563
+ return new Promise((resolve) => setTimeout(resolve, ms));
564
+ }
565
+ async function retryOnBusy(fn, label) {
566
+ let lastError;
567
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
568
+ try {
569
+ return await fn();
570
+ } catch (err) {
571
+ lastError = err;
572
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
573
+ throw err;
574
+ }
575
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
576
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
577
+ process.stderr.write(
578
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
579
+ `
580
+ );
581
+ await delay(backoff + jitter);
582
+ }
583
+ }
584
+ throw lastError;
585
+ }
586
+ function wrapWithRetry(client) {
587
+ return new Proxy(client, {
588
+ get(target, prop, receiver) {
589
+ if (prop === "execute") {
590
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
591
+ }
592
+ if (prop === "batch") {
593
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
594
+ }
595
+ return Reflect.get(target, prop, receiver);
596
+ }
597
+ });
598
+ }
599
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
600
+ var init_db_retry = __esm({
601
+ "src/lib/db-retry.ts"() {
602
+ "use strict";
603
+ MAX_RETRIES = 3;
604
+ BASE_DELAY_MS = 200;
605
+ MAX_JITTER_MS = 300;
606
+ }
607
+ });
608
+
558
609
  // src/lib/employees.ts
559
610
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
560
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
611
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
561
612
  import { execSync as execSync3 } from "child_process";
562
- import path4 from "path";
613
+ import path5 from "path";
563
614
  import os4 from "os";
564
615
  function normalizeRole(role) {
565
616
  return (role ?? "").trim().toLowerCase();
@@ -578,9 +629,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
578
629
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
579
630
  }
580
631
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
581
- if (!existsSync4(employeesPath)) return [];
632
+ if (!existsSync5(employeesPath)) return [];
582
633
  try {
583
- return JSON.parse(readFileSync4(employeesPath, "utf-8"));
634
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
584
635
  } catch {
585
636
  return [];
586
637
  }
@@ -599,7 +650,7 @@ var init_employees = __esm({
599
650
  "src/lib/employees.ts"() {
600
651
  "use strict";
601
652
  init_config();
602
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
653
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
603
654
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
604
655
  COORDINATOR_ROLE = "COO";
605
656
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
@@ -610,8 +661,8 @@ var init_employees = __esm({
610
661
  import net from "net";
611
662
  import { spawn } from "child_process";
612
663
  import { randomUUID } from "crypto";
613
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
614
- import path5 from "path";
664
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
665
+ import path6 from "path";
615
666
  import { fileURLToPath } from "url";
616
667
  function handleData(chunk) {
617
668
  _buffer += chunk.toString();
@@ -639,9 +690,9 @@ function handleData(chunk) {
639
690
  }
640
691
  }
641
692
  function cleanupStaleFiles() {
642
- if (existsSync5(PID_PATH)) {
693
+ if (existsSync6(PID_PATH)) {
643
694
  try {
644
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
695
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
645
696
  if (pid > 0) {
646
697
  try {
647
698
  process.kill(pid, 0);
@@ -662,11 +713,11 @@ function cleanupStaleFiles() {
662
713
  }
663
714
  }
664
715
  function findPackageRoot() {
665
- let dir = path5.dirname(fileURLToPath(import.meta.url));
666
- const { root } = path5.parse(dir);
716
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
717
+ const { root } = path6.parse(dir);
667
718
  while (dir !== root) {
668
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
669
- dir = path5.dirname(dir);
719
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
720
+ dir = path6.dirname(dir);
670
721
  }
671
722
  return null;
672
723
  }
@@ -676,8 +727,8 @@ function spawnDaemon() {
676
727
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
677
728
  return;
678
729
  }
679
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
680
- if (!existsSync5(daemonPath)) {
730
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
731
+ if (!existsSync6(daemonPath)) {
681
732
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
682
733
  `);
683
734
  return;
@@ -685,7 +736,7 @@ function spawnDaemon() {
685
736
  const resolvedPath = daemonPath;
686
737
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
687
738
  `);
688
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
739
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
689
740
  let stderrFd = "ignore";
690
741
  try {
691
742
  stderrFd = openSync(logPath, "a");
@@ -830,9 +881,9 @@ var init_exe_daemon_client = __esm({
830
881
  "src/lib/exe-daemon-client.ts"() {
831
882
  "use strict";
832
883
  init_config();
833
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
834
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
835
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
884
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
885
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
886
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
836
887
  SPAWN_LOCK_STALE_MS = 3e4;
837
888
  CONNECT_TIMEOUT_MS = 15e3;
838
889
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2057,18 +2108,18 @@ var init_database = __esm({
2057
2108
  });
2058
2109
 
2059
2110
  // src/lib/license.ts
2060
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2111
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2061
2112
  import { randomUUID as randomUUID2 } from "crypto";
2062
- import path6 from "path";
2113
+ import path7 from "path";
2063
2114
  import { jwtVerify, importSPKI } from "jose";
2064
2115
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2065
2116
  var init_license = __esm({
2066
2117
  "src/lib/license.ts"() {
2067
2118
  "use strict";
2068
2119
  init_config();
2069
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
2070
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
2071
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2120
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2121
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2122
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2072
2123
  PLAN_LIMITS = {
2073
2124
  free: { devices: 1, employees: 1, memories: 5e3 },
2074
2125
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2080,12 +2131,12 @@ var init_license = __esm({
2080
2131
  });
2081
2132
 
2082
2133
  // src/lib/plan-limits.ts
2083
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2084
- import path7 from "path";
2134
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2135
+ import path8 from "path";
2085
2136
  function getLicenseSync() {
2086
2137
  try {
2087
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2088
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2138
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2139
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2089
2140
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2090
2141
  const parts = raw.token.split(".");
2091
2142
  if (parts.length !== 3) return freeLicense();
@@ -2123,8 +2174,8 @@ function assertEmployeeLimitSync(rosterPath) {
2123
2174
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2124
2175
  let count = 0;
2125
2176
  try {
2126
- if (existsSync7(filePath)) {
2127
- const raw = readFileSync7(filePath, "utf8");
2177
+ if (existsSync8(filePath)) {
2178
+ const raw = readFileSync8(filePath, "utf8");
2128
2179
  const employees = JSON.parse(raw);
2129
2180
  count = Array.isArray(employees) ? employees.length : 0;
2130
2181
  }
@@ -2153,19 +2204,19 @@ var init_plan_limits = __esm({
2153
2204
  this.name = "PlanLimitError";
2154
2205
  }
2155
2206
  };
2156
- CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
2207
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2157
2208
  }
2158
2209
  });
2159
2210
 
2160
2211
  // src/lib/notifications.ts
2161
2212
  import crypto from "crypto";
2162
- import path8 from "path";
2213
+ import path9 from "path";
2163
2214
  import os5 from "os";
2164
2215
  import {
2165
- readFileSync as readFileSync8,
2216
+ readFileSync as readFileSync9,
2166
2217
  readdirSync,
2167
2218
  unlinkSync as unlinkSync3,
2168
- existsSync as existsSync8,
2219
+ existsSync as existsSync9,
2169
2220
  rmdirSync
2170
2221
  } from "fs";
2171
2222
  async function writeNotification(notification) {
@@ -2316,11 +2367,11 @@ __export(tasks_crud_exports, {
2316
2367
  writeCheckpoint: () => writeCheckpoint
2317
2368
  });
2318
2369
  import crypto3 from "crypto";
2319
- import path9 from "path";
2370
+ import path10 from "path";
2320
2371
  import os6 from "os";
2321
2372
  import { execSync as execSync4 } from "child_process";
2322
2373
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2323
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2374
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2324
2375
  async function writeCheckpoint(input) {
2325
2376
  const client = getClient();
2326
2377
  const row = await resolveTask(client, input.taskId);
@@ -2495,8 +2546,8 @@ ${laneWarning}` : laneWarning;
2495
2546
  }
2496
2547
  if (input.baseDir) {
2497
2548
  try {
2498
- await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
2499
- await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2549
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2550
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2500
2551
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2501
2552
  await ensureGitignoreExe(input.baseDir);
2502
2553
  } catch {
@@ -2532,10 +2583,10 @@ ${laneWarning}` : laneWarning;
2532
2583
  });
2533
2584
  if (input.baseDir) {
2534
2585
  try {
2535
- const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
2536
- const mdPath = path9.join(EXE_OS_DIR, taskFile);
2537
- const mdDir = path9.dirname(mdPath);
2538
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2586
+ const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
2587
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
2588
+ const mdDir = path10.dirname(mdPath);
2589
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2539
2590
  const reviewer = input.reviewer ?? input.assignedBy;
2540
2591
  const mdContent = `# ${input.title}
2541
2592
 
@@ -2560,7 +2611,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2560
2611
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2561
2612
  `;
2562
2613
  await writeFile3(mdPath, mdContent, "utf-8");
2563
- } catch {
2614
+ } catch (err) {
2615
+ process.stderr.write(
2616
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2617
+ `
2618
+ );
2564
2619
  }
2565
2620
  }
2566
2621
  return {
@@ -2820,9 +2875,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2820
2875
  return { taskFile, assignedTo, assignedBy, taskSlug };
2821
2876
  }
2822
2877
  async function ensureArchitectureDoc(baseDir, projectName) {
2823
- const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
2878
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2824
2879
  try {
2825
- if (existsSync9(archPath)) return;
2880
+ if (existsSync10(archPath)) return;
2826
2881
  const template = [
2827
2882
  `# ${projectName} \u2014 System Architecture`,
2828
2883
  "",
@@ -2855,10 +2910,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2855
2910
  }
2856
2911
  }
2857
2912
  async function ensureGitignoreExe(baseDir) {
2858
- const gitignorePath = path9.join(baseDir, ".gitignore");
2913
+ const gitignorePath = path10.join(baseDir, ".gitignore");
2859
2914
  try {
2860
- if (existsSync9(gitignorePath)) {
2861
- const content = readFileSync9(gitignorePath, "utf-8");
2915
+ if (existsSync10(gitignorePath)) {
2916
+ const content = readFileSync10(gitignorePath, "utf-8");
2862
2917
  if (/^\/?exe\/?$/m.test(content)) return;
2863
2918
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2864
2919
  } else {
@@ -2889,8 +2944,8 @@ var init_tasks_crud = __esm({
2889
2944
  });
2890
2945
 
2891
2946
  // src/lib/tasks-review.ts
2892
- import path10 from "path";
2893
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2947
+ import path11 from "path";
2948
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2894
2949
  async function countPendingReviews(sessionScope) {
2895
2950
  const client = getClient();
2896
2951
  if (sessionScope) {
@@ -3071,11 +3126,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3071
3126
  );
3072
3127
  }
3073
3128
  try {
3074
- const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
3075
- if (existsSync10(cacheDir)) {
3129
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3130
+ if (existsSync11(cacheDir)) {
3076
3131
  for (const f of readdirSync2(cacheDir)) {
3077
3132
  if (f.startsWith("review-notified-")) {
3078
- unlinkSync4(path10.join(cacheDir, f));
3133
+ unlinkSync4(path11.join(cacheDir, f));
3079
3134
  }
3080
3135
  }
3081
3136
  }
@@ -3096,7 +3151,7 @@ var init_tasks_review = __esm({
3096
3151
  });
3097
3152
 
3098
3153
  // src/lib/tasks-chain.ts
3099
- import path11 from "path";
3154
+ import path12 from "path";
3100
3155
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3101
3156
  async function cascadeUnblock(taskId, baseDir, now) {
3102
3157
  const client = getClient();
@@ -3113,7 +3168,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3113
3168
  });
3114
3169
  for (const ur of unblockedRows.rows) {
3115
3170
  try {
3116
- const ubFile = path11.join(baseDir, String(ur.task_file));
3171
+ const ubFile = path12.join(baseDir, String(ur.task_file));
3117
3172
  let ubContent = await readFile3(ubFile, "utf-8");
3118
3173
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3119
3174
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3182,7 +3237,7 @@ var init_tasks_chain = __esm({
3182
3237
 
3183
3238
  // src/lib/project-name.ts
3184
3239
  import { execSync as execSync5 } from "child_process";
3185
- import path12 from "path";
3240
+ import path13 from "path";
3186
3241
  function getProjectName(cwd) {
3187
3242
  const dir = cwd ?? process.cwd();
3188
3243
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3195,7 +3250,7 @@ function getProjectName(cwd) {
3195
3250
  timeout: 2e3,
3196
3251
  stdio: ["pipe", "pipe", "pipe"]
3197
3252
  }).trim();
3198
- repoRoot = path12.dirname(gitCommonDir);
3253
+ repoRoot = path13.dirname(gitCommonDir);
3199
3254
  } catch {
3200
3255
  repoRoot = execSync5("git rev-parse --show-toplevel", {
3201
3256
  cwd: dir,
@@ -3204,11 +3259,11 @@ function getProjectName(cwd) {
3204
3259
  stdio: ["pipe", "pipe", "pipe"]
3205
3260
  }).trim();
3206
3261
  }
3207
- _cached2 = path12.basename(repoRoot);
3262
+ _cached2 = path13.basename(repoRoot);
3208
3263
  _cachedCwd = dir;
3209
3264
  return _cached2;
3210
3265
  } catch {
3211
- _cached2 = path12.basename(dir);
3266
+ _cached2 = path13.basename(dir);
3212
3267
  _cachedCwd = dir;
3213
3268
  return _cached2;
3214
3269
  }
@@ -3681,8 +3736,8 @@ __export(tasks_exports, {
3681
3736
  updateTaskStatus: () => updateTaskStatus,
3682
3737
  writeCheckpoint: () => writeCheckpoint
3683
3738
  });
3684
- import path13 from "path";
3685
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
3739
+ import path14 from "path";
3740
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3686
3741
  async function createTask(input) {
3687
3742
  const result = await createTaskCore(input);
3688
3743
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3701,11 +3756,11 @@ async function updateTask(input) {
3701
3756
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3702
3757
  try {
3703
3758
  const agent = String(row.assigned_to);
3704
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3705
- const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3759
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3760
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3706
3761
  if (input.status === "in_progress") {
3707
- mkdirSync4(cacheDir, { recursive: true });
3708
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3762
+ mkdirSync5(cacheDir, { recursive: true });
3763
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3709
3764
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3710
3765
  try {
3711
3766
  unlinkSync5(cachePath);
@@ -4172,13 +4227,13 @@ __export(tmux_routing_exports, {
4172
4227
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4173
4228
  });
4174
4229
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
4175
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4176
- import path14 from "path";
4230
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4231
+ import path15 from "path";
4177
4232
  import os7 from "os";
4178
4233
  import { fileURLToPath as fileURLToPath2 } from "url";
4179
4234
  import { unlinkSync as unlinkSync6 } from "fs";
4180
4235
  function spawnLockPath(sessionName) {
4181
- return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4236
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4182
4237
  }
4183
4238
  function isProcessAlive(pid) {
4184
4239
  try {
@@ -4189,13 +4244,13 @@ function isProcessAlive(pid) {
4189
4244
  }
4190
4245
  }
4191
4246
  function acquireSpawnLock2(sessionName) {
4192
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4193
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4247
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4248
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4194
4249
  }
4195
4250
  const lockFile = spawnLockPath(sessionName);
4196
- if (existsSync11(lockFile)) {
4251
+ if (existsSync12(lockFile)) {
4197
4252
  try {
4198
- const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4253
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
4199
4254
  const age = Date.now() - lock.timestamp;
4200
4255
  if (isProcessAlive(lock.pid) && age < 6e4) {
4201
4256
  return false;
@@ -4203,7 +4258,7 @@ function acquireSpawnLock2(sessionName) {
4203
4258
  } catch {
4204
4259
  }
4205
4260
  }
4206
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4261
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4207
4262
  return true;
4208
4263
  }
4209
4264
  function releaseSpawnLock2(sessionName) {
@@ -4215,13 +4270,13 @@ function releaseSpawnLock2(sessionName) {
4215
4270
  function resolveBehaviorsExporterScript() {
4216
4271
  try {
4217
4272
  const thisFile = fileURLToPath2(import.meta.url);
4218
- const scriptPath = path14.join(
4219
- path14.dirname(thisFile),
4273
+ const scriptPath = path15.join(
4274
+ path15.dirname(thisFile),
4220
4275
  "..",
4221
4276
  "bin",
4222
4277
  "exe-export-behaviors.js"
4223
4278
  );
4224
- return existsSync11(scriptPath) ? scriptPath : null;
4279
+ return existsSync12(scriptPath) ? scriptPath : null;
4225
4280
  } catch {
4226
4281
  return null;
4227
4282
  }
@@ -4287,12 +4342,12 @@ function extractRootExe(name) {
4287
4342
  return parts.length > 0 ? parts[parts.length - 1] : null;
4288
4343
  }
4289
4344
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4290
- if (!existsSync11(SESSION_CACHE)) {
4291
- mkdirSync5(SESSION_CACHE, { recursive: true });
4345
+ if (!existsSync12(SESSION_CACHE)) {
4346
+ mkdirSync6(SESSION_CACHE, { recursive: true });
4292
4347
  }
4293
4348
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4294
- const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4295
- writeFileSync6(filePath, JSON.stringify({
4349
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4350
+ writeFileSync7(filePath, JSON.stringify({
4296
4351
  parentExe: rootExe,
4297
4352
  dispatchedBy: dispatchedBy || rootExe,
4298
4353
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4300,7 +4355,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4300
4355
  }
4301
4356
  function getParentExe(sessionKey) {
4302
4357
  try {
4303
- const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4358
+ const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4304
4359
  return data.parentExe || null;
4305
4360
  } catch {
4306
4361
  return null;
@@ -4308,8 +4363,8 @@ function getParentExe(sessionKey) {
4308
4363
  }
4309
4364
  function getDispatchedBy(sessionKey) {
4310
4365
  try {
4311
- const data = JSON.parse(readFileSync10(
4312
- path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4366
+ const data = JSON.parse(readFileSync11(
4367
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4313
4368
  "utf8"
4314
4369
  ));
4315
4370
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4370,32 +4425,50 @@ async function verifyPaneAtCapacity(sessionName) {
4370
4425
  }
4371
4426
  function readDebounceState() {
4372
4427
  try {
4373
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4374
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4428
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4429
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4430
+ const state = {};
4431
+ for (const [key, val] of Object.entries(raw)) {
4432
+ if (typeof val === "number") {
4433
+ state[key] = { lastSent: val, pending: 0 };
4434
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4435
+ state[key] = val;
4436
+ }
4437
+ }
4438
+ return state;
4375
4439
  } catch {
4376
4440
  return {};
4377
4441
  }
4378
4442
  }
4379
4443
  function writeDebounceState(state) {
4380
4444
  try {
4381
- if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4382
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4445
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4446
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4383
4447
  } catch {
4384
4448
  }
4385
4449
  }
4386
4450
  function isDebounced(targetSession) {
4387
4451
  const state = readDebounceState();
4388
- const lastSent = state[targetSession] ?? 0;
4389
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4452
+ const entry = state[targetSession];
4453
+ const lastSent = entry?.lastSent ?? 0;
4454
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4455
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4456
+ state[targetSession].pending++;
4457
+ writeDebounceState(state);
4458
+ return true;
4459
+ }
4460
+ return false;
4390
4461
  }
4391
4462
  function recordDebounce(targetSession) {
4392
4463
  const state = readDebounceState();
4393
- state[targetSession] = Date.now();
4464
+ const batched = state[targetSession]?.pending ?? 0;
4465
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4394
4466
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4395
4467
  for (const key of Object.keys(state)) {
4396
- if ((state[key] ?? 0) < cutoff) delete state[key];
4468
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4397
4469
  }
4398
4470
  writeDebounceState(state);
4471
+ return batched;
4399
4472
  }
4400
4473
  function logIntercom(msg) {
4401
4474
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4440,7 +4513,7 @@ function sendIntercom(targetSession) {
4440
4513
  return "skipped_exe";
4441
4514
  }
4442
4515
  if (isDebounced(targetSession)) {
4443
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4516
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4444
4517
  return "debounced";
4445
4518
  }
4446
4519
  try {
@@ -4452,14 +4525,14 @@ function sendIntercom(targetSession) {
4452
4525
  const sessionState = getSessionState(targetSession);
4453
4526
  if (sessionState === "no_claude") {
4454
4527
  queueIntercom(targetSession, "claude not running in session");
4455
- recordDebounce(targetSession);
4456
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4528
+ const batched2 = recordDebounce(targetSession);
4529
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4457
4530
  return "queued";
4458
4531
  }
4459
4532
  if (sessionState === "thinking" || sessionState === "tool") {
4460
4533
  queueIntercom(targetSession, "session busy at send time");
4461
- recordDebounce(targetSession);
4462
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4534
+ const batched2 = recordDebounce(targetSession);
4535
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4463
4536
  return "queued";
4464
4537
  }
4465
4538
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4467,8 +4540,8 @@ function sendIntercom(targetSession) {
4467
4540
  transport.sendKeys(targetSession, "q");
4468
4541
  }
4469
4542
  transport.sendKeys(targetSession, "/exe-intercom");
4470
- recordDebounce(targetSession);
4471
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4543
+ const batched = recordDebounce(targetSession);
4544
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4472
4545
  return "delivered";
4473
4546
  } catch {
4474
4547
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4570,26 +4643,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4570
4643
  const transport = getTransport();
4571
4644
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4572
4645
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4573
- const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4574
- const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4575
- if (!existsSync11(logDir)) {
4576
- mkdirSync5(logDir, { recursive: true });
4646
+ const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
4647
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4648
+ if (!existsSync12(logDir)) {
4649
+ mkdirSync6(logDir, { recursive: true });
4577
4650
  }
4578
4651
  transport.kill(sessionName);
4579
4652
  let cleanupSuffix = "";
4580
4653
  try {
4581
4654
  const thisFile = fileURLToPath2(import.meta.url);
4582
- const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4583
- if (existsSync11(cleanupScript)) {
4655
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4656
+ if (existsSync12(cleanupScript)) {
4584
4657
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4585
4658
  }
4586
4659
  } catch {
4587
4660
  }
4588
4661
  try {
4589
- const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
4662
+ const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
4590
4663
  let claudeJson = {};
4591
4664
  try {
4592
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
4665
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4593
4666
  } catch {
4594
4667
  }
4595
4668
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4597,17 +4670,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4597
4670
  const trustDir = opts?.cwd ?? projectDir;
4598
4671
  if (!projects[trustDir]) projects[trustDir] = {};
4599
4672
  projects[trustDir].hasTrustDialogAccepted = true;
4600
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4673
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4601
4674
  } catch {
4602
4675
  }
4603
4676
  try {
4604
- const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
4677
+ const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
4605
4678
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4606
- const projSettingsDir = path14.join(settingsDir, normalizedKey);
4607
- const settingsPath = path14.join(projSettingsDir, "settings.json");
4679
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
4680
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
4608
4681
  let settings = {};
4609
4682
  try {
4610
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
4683
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4611
4684
  } catch {
4612
4685
  }
4613
4686
  const perms = settings.permissions ?? {};
@@ -4635,20 +4708,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4635
4708
  if (changed) {
4636
4709
  perms.allow = allow;
4637
4710
  settings.permissions = perms;
4638
- mkdirSync5(projSettingsDir, { recursive: true });
4639
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4711
+ mkdirSync6(projSettingsDir, { recursive: true });
4712
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4640
4713
  }
4641
4714
  } catch {
4642
4715
  }
4643
4716
  const spawnCwd = opts?.cwd ?? projectDir;
4644
4717
  const useExeAgent = !!(opts?.model && opts?.provider);
4645
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4718
+ const agentRtConfig = getAgentRuntime(employeeName);
4719
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4720
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4721
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4646
4722
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4647
4723
  let identityFlag = "";
4648
4724
  let behaviorsFlag = "";
4649
4725
  let legacyFallbackWarned = false;
4650
4726
  if (!useExeAgent && !useBinSymlink) {
4651
- const identityPath = path14.join(
4727
+ const identityPath = path15.join(
4652
4728
  os7.homedir(),
4653
4729
  ".exe-os",
4654
4730
  "identity",
@@ -4658,13 +4734,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4658
4734
  const hasAgentFlag = claudeSupportsAgentFlag();
4659
4735
  if (hasAgentFlag) {
4660
4736
  identityFlag = ` --agent ${employeeName}`;
4661
- } else if (existsSync11(identityPath)) {
4737
+ } else if (existsSync12(identityPath)) {
4662
4738
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4663
4739
  legacyFallbackWarned = true;
4664
4740
  }
4665
4741
  const behaviorsFile = exportBehaviorsSync(
4666
4742
  employeeName,
4667
- path14.basename(spawnCwd),
4743
+ path15.basename(spawnCwd),
4668
4744
  sessionName
4669
4745
  );
4670
4746
  if (behaviorsFile) {
@@ -4679,16 +4755,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4679
4755
  }
4680
4756
  let sessionContextFlag = "";
4681
4757
  try {
4682
- const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4683
- mkdirSync5(ctxDir, { recursive: true });
4684
- const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4758
+ const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
4759
+ mkdirSync6(ctxDir, { recursive: true });
4760
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4685
4761
  const ctxContent = [
4686
4762
  `## Session Context`,
4687
4763
  `You are running in tmux session: ${sessionName}.`,
4688
4764
  `Your parent coordinator session is ${exeSession}.`,
4689
4765
  `Your employees (if any) use the -${exeSession} suffix.`
4690
4766
  ].join("\n");
4691
- writeFileSync6(ctxFile, ctxContent);
4767
+ writeFileSync7(ctxFile, ctxContent);
4692
4768
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4693
4769
  } catch {
4694
4770
  }
@@ -4702,9 +4778,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4702
4778
  }
4703
4779
  }
4704
4780
  }
4781
+ if (useCodex) {
4782
+ const codexCfg = RUNTIME_TABLE.codex;
4783
+ if (codexCfg?.apiKeyEnv) {
4784
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4785
+ if (keyVal) {
4786
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4787
+ }
4788
+ }
4789
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4790
+ }
4791
+ if (useOpencode) {
4792
+ const ocCfg = PROVIDER_TABLE.opencode;
4793
+ if (ocCfg?.apiKeyEnv) {
4794
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4795
+ if (keyVal) {
4796
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4797
+ }
4798
+ }
4799
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4800
+ }
4801
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4802
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4803
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4804
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4805
+ }
4806
+ }
4705
4807
  let spawnCommand;
4706
4808
  if (useExeAgent) {
4707
4809
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4810
+ } else if (useCodex) {
4811
+ process.stderr.write(
4812
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4813
+ `
4814
+ );
4815
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
4816
+ } else if (useOpencode) {
4817
+ const binName = `${employeeName}-opencode`;
4818
+ process.stderr.write(
4819
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
4820
+ `
4821
+ );
4822
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4708
4823
  } else if (useBinSymlink) {
4709
4824
  const binName = `${employeeName}-${ccProvider}`;
4710
4825
  process.stderr.write(
@@ -4726,11 +4841,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4726
4841
  transport.pipeLog(sessionName, logFile);
4727
4842
  try {
4728
4843
  const mySession = getMySession();
4729
- const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4730
- writeFileSync6(dispatchInfo, JSON.stringify({
4844
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4845
+ writeFileSync7(dispatchInfo, JSON.stringify({
4731
4846
  dispatchedBy: mySession,
4732
4847
  rootExe: exeSession,
4733
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4848
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4849
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4850
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4734
4851
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4735
4852
  }));
4736
4853
  } catch {
@@ -4748,6 +4865,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4748
4865
  booted = true;
4749
4866
  break;
4750
4867
  }
4868
+ } else if (useCodex) {
4869
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4870
+ booted = true;
4871
+ break;
4872
+ }
4751
4873
  } else {
4752
4874
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4753
4875
  booted = true;
@@ -4759,9 +4881,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4759
4881
  }
4760
4882
  if (!booted) {
4761
4883
  releaseSpawnLock2(sessionName);
4762
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4884
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
4885
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4763
4886
  }
4764
- if (!useExeAgent) {
4887
+ if (!useExeAgent && !useCodex) {
4765
4888
  try {
4766
4889
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4767
4890
  } catch {
@@ -4788,17 +4911,19 @@ var init_tmux_routing = __esm({
4788
4911
  init_cc_agent_support();
4789
4912
  init_mcp_prefix();
4790
4913
  init_provider_table();
4914
+ init_agent_config();
4915
+ init_runtime_table();
4791
4916
  init_intercom_queue();
4792
4917
  init_plan_limits();
4793
4918
  init_employees();
4794
- SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4795
- SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4919
+ SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
4920
+ SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
4796
4921
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4797
4922
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4798
4923
  VERIFY_PANE_LINES = 200;
4799
4924
  INTERCOM_DEBOUNCE_MS = 3e4;
4800
- INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4801
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4925
+ INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
4926
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4802
4927
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4803
4928
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4804
4929
  }
@@ -4839,14 +4964,14 @@ var init_memory = __esm({
4839
4964
 
4840
4965
  // src/lib/keychain.ts
4841
4966
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4842
- import { existsSync as existsSync12 } from "fs";
4843
- import path15 from "path";
4967
+ import { existsSync as existsSync13 } from "fs";
4968
+ import path16 from "path";
4844
4969
  import os8 from "os";
4845
4970
  function getKeyDir() {
4846
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
4971
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
4847
4972
  }
4848
4973
  function getKeyPath() {
4849
- return path15.join(getKeyDir(), "master.key");
4974
+ return path16.join(getKeyDir(), "master.key");
4850
4975
  }
4851
4976
  async function tryKeytar() {
4852
4977
  try {
@@ -4867,7 +4992,7 @@ async function getMasterKey() {
4867
4992
  }
4868
4993
  }
4869
4994
  const keyPath = getKeyPath();
4870
- if (!existsSync12(keyPath)) {
4995
+ if (!existsSync13(keyPath)) {
4871
4996
  process.stderr.write(
4872
4997
  `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4873
4998
  `
@@ -4907,13 +5032,13 @@ __export(shard_manager_exports, {
4907
5032
  listShards: () => listShards,
4908
5033
  shardExists: () => shardExists
4909
5034
  });
4910
- import path16 from "path";
4911
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5035
+ import path17 from "path";
5036
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
4912
5037
  import { createClient as createClient2 } from "@libsql/client";
4913
5038
  function initShardManager(encryptionKey) {
4914
5039
  _encryptionKey = encryptionKey;
4915
- if (!existsSync13(SHARDS_DIR)) {
4916
- mkdirSync6(SHARDS_DIR, { recursive: true });
5040
+ if (!existsSync14(SHARDS_DIR)) {
5041
+ mkdirSync7(SHARDS_DIR, { recursive: true });
4917
5042
  }
4918
5043
  _shardingEnabled = true;
4919
5044
  }
@@ -4933,7 +5058,7 @@ function getShardClient(projectName) {
4933
5058
  }
4934
5059
  const cached = _shards.get(safeName);
4935
5060
  if (cached) return cached;
4936
- const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
5061
+ const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
4937
5062
  const client = createClient2({
4938
5063
  url: `file:${dbPath}`,
4939
5064
  encryptionKey: _encryptionKey
@@ -4943,10 +5068,10 @@ function getShardClient(projectName) {
4943
5068
  }
4944
5069
  function shardExists(projectName) {
4945
5070
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4946
- return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
5071
+ return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
4947
5072
  }
4948
5073
  function listShards() {
4949
- if (!existsSync13(SHARDS_DIR)) return [];
5074
+ if (!existsSync14(SHARDS_DIR)) return [];
4950
5075
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4951
5076
  }
4952
5077
  async function ensureShardSchema(client) {
@@ -5132,7 +5257,7 @@ var init_shard_manager = __esm({
5132
5257
  "src/lib/shard-manager.ts"() {
5133
5258
  "use strict";
5134
5259
  init_config();
5135
- SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
5260
+ SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5136
5261
  _shards = /* @__PURE__ */ new Map();
5137
5262
  _encryptionKey = null;
5138
5263
  _shardingEnabled = false;