@askexenow/exe-os 0.8.85 → 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 (57) hide show
  1. package/dist/bin/cleanup-stale-review-tasks.js +57 -19
  2. package/dist/bin/cli.js +507 -337
  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 +47 -2
  24. package/dist/gateway/index.js +257 -132
  25. package/dist/hooks/bug-report-worker.js +242 -117
  26. package/dist/hooks/commit-complete.js +389 -264
  27. package/dist/hooks/error-recall.js +6 -2
  28. package/dist/hooks/ingest-worker.js +314 -193
  29. package/dist/hooks/post-compact.js +84 -46
  30. package/dist/hooks/pre-compact.js +272 -147
  31. package/dist/hooks/pre-tool-use.js +104 -66
  32. package/dist/hooks/prompt-submit.js +126 -66
  33. package/dist/hooks/session-end.js +277 -152
  34. package/dist/hooks/session-start.js +70 -28
  35. package/dist/hooks/stop.js +90 -52
  36. package/dist/hooks/subagent-stop.js +84 -46
  37. package/dist/hooks/summary-worker.js +175 -114
  38. package/dist/index.js +296 -171
  39. package/dist/lib/agent-config.js +167 -0
  40. package/dist/lib/cloud-sync.js +25 -2
  41. package/dist/lib/exe-daemon.js +338 -213
  42. package/dist/lib/hybrid-search.js +7 -2
  43. package/dist/lib/messaging.js +95 -39
  44. package/dist/lib/runtime-table.js +16 -0
  45. package/dist/lib/session-wrappers.js +22 -0
  46. package/dist/lib/tasks.js +242 -117
  47. package/dist/lib/tmux-routing.js +314 -189
  48. package/dist/mcp/server.js +573 -274
  49. package/dist/mcp/tools/create-task.js +260 -135
  50. package/dist/mcp/tools/list-tasks.js +68 -30
  51. package/dist/mcp/tools/send-message.js +100 -44
  52. package/dist/mcp/tools/update-task.js +123 -67
  53. package/dist/runtime/index.js +276 -151
  54. package/dist/tui/App.js +479 -354
  55. package/package.json +1 -1
  56. package/src/commands/exe/agent-config.md +27 -0
  57. package/src/commands/exe/cc-doctor.md +10 -0
@@ -269,123 +269,19 @@ var init_provider_table = __esm({
269
269
  }
270
270
  });
271
271
 
272
- // src/lib/intercom-queue.ts
273
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
274
- import path2 from "path";
275
- import os2 from "os";
276
- function ensureDir() {
277
- const dir = path2.dirname(QUEUE_PATH);
278
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
279
- }
280
- function readQueue() {
281
- try {
282
- if (!existsSync2(QUEUE_PATH)) return [];
283
- return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
284
- } catch {
285
- return [];
286
- }
287
- }
288
- function writeQueue(queue) {
289
- ensureDir();
290
- const tmp = `${QUEUE_PATH}.tmp`;
291
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
292
- renameSync(tmp, QUEUE_PATH);
293
- }
294
- function queueIntercom(targetSession, reason) {
295
- const queue = readQueue();
296
- const existing = queue.find((q) => q.targetSession === targetSession);
297
- if (existing) {
298
- existing.attempts++;
299
- existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
300
- existing.reason = reason;
301
- } else {
302
- queue.push({
303
- targetSession,
304
- queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
305
- attempts: 0,
306
- reason
307
- });
308
- }
309
- writeQueue(queue);
310
- }
311
- var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
312
- var init_intercom_queue = __esm({
313
- "src/lib/intercom-queue.ts"() {
314
- "use strict";
315
- QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
316
- TTL_MS = 60 * 60 * 1e3;
317
- INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
318
- }
319
- });
320
-
321
- // src/lib/db-retry.ts
322
- function isBusyError(err) {
323
- if (err instanceof Error) {
324
- const msg = err.message.toLowerCase();
325
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
326
- }
327
- return false;
328
- }
329
- function delay(ms) {
330
- return new Promise((resolve) => setTimeout(resolve, ms));
331
- }
332
- async function retryOnBusy(fn, label) {
333
- let lastError;
334
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
335
- try {
336
- return await fn();
337
- } catch (err) {
338
- lastError = err;
339
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
340
- throw err;
341
- }
342
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
343
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
344
- process.stderr.write(
345
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
346
- `
347
- );
348
- await delay(backoff + jitter);
349
- }
350
- }
351
- throw lastError;
352
- }
353
- function wrapWithRetry(client) {
354
- return new Proxy(client, {
355
- get(target, prop, receiver) {
356
- if (prop === "execute") {
357
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
358
- }
359
- if (prop === "batch") {
360
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
361
- }
362
- return Reflect.get(target, prop, receiver);
363
- }
364
- });
365
- }
366
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
367
- var init_db_retry = __esm({
368
- "src/lib/db-retry.ts"() {
369
- "use strict";
370
- MAX_RETRIES = 3;
371
- BASE_DELAY_MS = 200;
372
- MAX_JITTER_MS = 300;
373
- }
374
- });
375
-
376
272
  // src/lib/config.ts
377
273
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
378
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
379
- import path3 from "path";
380
- import os3 from "os";
274
+ import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
275
+ import path2 from "path";
276
+ import os2 from "os";
381
277
  function resolveDataDir() {
382
278
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
383
279
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
384
- const newDir = path3.join(os3.homedir(), ".exe-os");
385
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
386
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
280
+ const newDir = path2.join(os2.homedir(), ".exe-os");
281
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
282
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
387
283
  try {
388
- renameSync2(legacyDir, newDir);
284
+ renameSync(legacyDir, newDir);
389
285
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
390
286
  `);
391
287
  } catch {
@@ -447,9 +343,9 @@ function normalizeAutoUpdate(raw) {
447
343
  async function loadConfig() {
448
344
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
449
345
  await mkdir(dir, { recursive: true });
450
- const configPath = path3.join(dir, "config.json");
451
- if (!existsSync3(configPath)) {
452
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
346
+ const configPath = path2.join(dir, "config.json");
347
+ if (!existsSync2(configPath)) {
348
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
453
349
  }
454
350
  const raw = await readFile(configPath, "utf-8");
455
351
  try {
@@ -467,13 +363,13 @@ async function loadConfig() {
467
363
  normalizeScalingRoadmap(migratedCfg);
468
364
  normalizeSessionLifecycle(migratedCfg);
469
365
  normalizeAutoUpdate(migratedCfg);
470
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
366
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
471
367
  if (config.dbPath.startsWith("~")) {
472
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
368
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
473
369
  }
474
370
  return config;
475
371
  } catch {
476
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
372
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
477
373
  }
478
374
  }
479
375
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -481,10 +377,10 @@ var init_config = __esm({
481
377
  "src/lib/config.ts"() {
482
378
  "use strict";
483
379
  EXE_AI_DIR = resolveDataDir();
484
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
485
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
486
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
487
- LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
380
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
381
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
382
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
383
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
488
384
  CURRENT_CONFIG_VERSION = 1;
489
385
  DEFAULT_CONFIG = {
490
386
  config_version: CURRENT_CONFIG_VERSION,
@@ -556,11 +452,166 @@ var init_config = __esm({
556
452
  }
557
453
  });
558
454
 
455
+ // src/lib/runtime-table.ts
456
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
457
+ var init_runtime_table = __esm({
458
+ "src/lib/runtime-table.ts"() {
459
+ "use strict";
460
+ RUNTIME_TABLE = {
461
+ codex: {
462
+ binary: "codex",
463
+ launchMode: "exec",
464
+ autoApproveFlag: "--full-auto",
465
+ inlineFlag: "--no-alt-screen",
466
+ apiKeyEnv: "OPENAI_API_KEY",
467
+ defaultModel: "gpt-5.4"
468
+ }
469
+ };
470
+ DEFAULT_RUNTIME = "claude";
471
+ }
472
+ });
473
+
474
+ // src/lib/agent-config.ts
475
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
476
+ import path3 from "path";
477
+ function loadAgentConfig() {
478
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
479
+ try {
480
+ return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
481
+ } catch {
482
+ return {};
483
+ }
484
+ }
485
+ function getAgentRuntime(agentId) {
486
+ const config = loadAgentConfig();
487
+ const entry = config[agentId];
488
+ if (entry) return entry;
489
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
490
+ }
491
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
492
+ var init_agent_config = __esm({
493
+ "src/lib/agent-config.ts"() {
494
+ "use strict";
495
+ init_config();
496
+ init_runtime_table();
497
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
498
+ DEFAULT_MODELS = {
499
+ claude: "claude-opus-4",
500
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
501
+ opencode: "minimax-m2.7"
502
+ };
503
+ }
504
+ });
505
+
506
+ // src/lib/intercom-queue.ts
507
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
508
+ import path4 from "path";
509
+ import os3 from "os";
510
+ function ensureDir() {
511
+ const dir = path4.dirname(QUEUE_PATH);
512
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
513
+ }
514
+ function readQueue() {
515
+ try {
516
+ if (!existsSync4(QUEUE_PATH)) return [];
517
+ return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ function writeQueue(queue) {
523
+ ensureDir();
524
+ const tmp = `${QUEUE_PATH}.tmp`;
525
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
526
+ renameSync2(tmp, QUEUE_PATH);
527
+ }
528
+ function queueIntercom(targetSession, reason) {
529
+ const queue = readQueue();
530
+ const existing = queue.find((q) => q.targetSession === targetSession);
531
+ if (existing) {
532
+ existing.attempts++;
533
+ existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
534
+ existing.reason = reason;
535
+ } else {
536
+ queue.push({
537
+ targetSession,
538
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
539
+ attempts: 0,
540
+ reason
541
+ });
542
+ }
543
+ writeQueue(queue);
544
+ }
545
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
546
+ var init_intercom_queue = __esm({
547
+ "src/lib/intercom-queue.ts"() {
548
+ "use strict";
549
+ QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
550
+ TTL_MS = 60 * 60 * 1e3;
551
+ INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
552
+ }
553
+ });
554
+
555
+ // src/lib/db-retry.ts
556
+ function isBusyError(err) {
557
+ if (err instanceof Error) {
558
+ const msg = err.message.toLowerCase();
559
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
560
+ }
561
+ return false;
562
+ }
563
+ function delay(ms) {
564
+ return new Promise((resolve) => setTimeout(resolve, ms));
565
+ }
566
+ async function retryOnBusy(fn, label) {
567
+ let lastError;
568
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
569
+ try {
570
+ return await fn();
571
+ } catch (err) {
572
+ lastError = err;
573
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
574
+ throw err;
575
+ }
576
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
577
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
578
+ process.stderr.write(
579
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
580
+ `
581
+ );
582
+ await delay(backoff + jitter);
583
+ }
584
+ }
585
+ throw lastError;
586
+ }
587
+ function wrapWithRetry(client) {
588
+ return new Proxy(client, {
589
+ get(target, prop, receiver) {
590
+ if (prop === "execute") {
591
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
592
+ }
593
+ if (prop === "batch") {
594
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
595
+ }
596
+ return Reflect.get(target, prop, receiver);
597
+ }
598
+ });
599
+ }
600
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
601
+ var init_db_retry = __esm({
602
+ "src/lib/db-retry.ts"() {
603
+ "use strict";
604
+ MAX_RETRIES = 3;
605
+ BASE_DELAY_MS = 200;
606
+ MAX_JITTER_MS = 300;
607
+ }
608
+ });
609
+
559
610
  // src/lib/employees.ts
560
611
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
561
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
612
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
562
613
  import { execSync as execSync3 } from "child_process";
563
- import path4 from "path";
614
+ import path5 from "path";
564
615
  import os4 from "os";
565
616
  function normalizeRole(role) {
566
617
  return (role ?? "").trim().toLowerCase();
@@ -579,9 +630,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
579
630
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
580
631
  }
581
632
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
582
- if (!existsSync4(employeesPath)) return [];
633
+ if (!existsSync5(employeesPath)) return [];
583
634
  try {
584
- return JSON.parse(readFileSync4(employeesPath, "utf-8"));
635
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
585
636
  } catch {
586
637
  return [];
587
638
  }
@@ -600,7 +651,7 @@ var init_employees = __esm({
600
651
  "src/lib/employees.ts"() {
601
652
  "use strict";
602
653
  init_config();
603
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
654
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
604
655
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
605
656
  COORDINATOR_ROLE = "COO";
606
657
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
@@ -611,8 +662,8 @@ var init_employees = __esm({
611
662
  import net from "net";
612
663
  import { spawn } from "child_process";
613
664
  import { randomUUID } from "crypto";
614
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
615
- import path5 from "path";
665
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
666
+ import path6 from "path";
616
667
  import { fileURLToPath as fileURLToPath2 } from "url";
617
668
  function handleData(chunk) {
618
669
  _buffer += chunk.toString();
@@ -640,9 +691,9 @@ function handleData(chunk) {
640
691
  }
641
692
  }
642
693
  function cleanupStaleFiles() {
643
- if (existsSync5(PID_PATH)) {
694
+ if (existsSync6(PID_PATH)) {
644
695
  try {
645
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
696
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
646
697
  if (pid > 0) {
647
698
  try {
648
699
  process.kill(pid, 0);
@@ -663,11 +714,11 @@ function cleanupStaleFiles() {
663
714
  }
664
715
  }
665
716
  function findPackageRoot() {
666
- let dir = path5.dirname(fileURLToPath2(import.meta.url));
667
- const { root } = path5.parse(dir);
717
+ let dir = path6.dirname(fileURLToPath2(import.meta.url));
718
+ const { root } = path6.parse(dir);
668
719
  while (dir !== root) {
669
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
670
- dir = path5.dirname(dir);
720
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
721
+ dir = path6.dirname(dir);
671
722
  }
672
723
  return null;
673
724
  }
@@ -677,8 +728,8 @@ function spawnDaemon() {
677
728
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
678
729
  return;
679
730
  }
680
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
681
- if (!existsSync5(daemonPath)) {
731
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
732
+ if (!existsSync6(daemonPath)) {
682
733
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
683
734
  `);
684
735
  return;
@@ -686,7 +737,7 @@ function spawnDaemon() {
686
737
  const resolvedPath = daemonPath;
687
738
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
688
739
  `);
689
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
740
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
690
741
  let stderrFd = "ignore";
691
742
  try {
692
743
  stderrFd = openSync(logPath, "a");
@@ -831,9 +882,9 @@ var init_exe_daemon_client = __esm({
831
882
  "src/lib/exe-daemon-client.ts"() {
832
883
  "use strict";
833
884
  init_config();
834
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
835
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
836
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
885
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
886
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
887
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
837
888
  SPAWN_LOCK_STALE_MS = 3e4;
838
889
  CONNECT_TIMEOUT_MS = 15e3;
839
890
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2058,18 +2109,18 @@ var init_database = __esm({
2058
2109
  });
2059
2110
 
2060
2111
  // src/lib/license.ts
2061
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2112
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2062
2113
  import { randomUUID as randomUUID2 } from "crypto";
2063
- import path6 from "path";
2114
+ import path7 from "path";
2064
2115
  import { jwtVerify, importSPKI } from "jose";
2065
2116
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2066
2117
  var init_license = __esm({
2067
2118
  "src/lib/license.ts"() {
2068
2119
  "use strict";
2069
2120
  init_config();
2070
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
2071
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
2072
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2121
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2122
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2123
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2073
2124
  PLAN_LIMITS = {
2074
2125
  free: { devices: 1, employees: 1, memories: 5e3 },
2075
2126
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2081,12 +2132,12 @@ var init_license = __esm({
2081
2132
  });
2082
2133
 
2083
2134
  // src/lib/plan-limits.ts
2084
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2085
- import path7 from "path";
2135
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2136
+ import path8 from "path";
2086
2137
  function getLicenseSync() {
2087
2138
  try {
2088
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2089
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2139
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2140
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2090
2141
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2091
2142
  const parts = raw.token.split(".");
2092
2143
  if (parts.length !== 3) return freeLicense();
@@ -2124,8 +2175,8 @@ function assertEmployeeLimitSync(rosterPath) {
2124
2175
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2125
2176
  let count = 0;
2126
2177
  try {
2127
- if (existsSync7(filePath)) {
2128
- const raw = readFileSync7(filePath, "utf8");
2178
+ if (existsSync8(filePath)) {
2179
+ const raw = readFileSync8(filePath, "utf8");
2129
2180
  const employees = JSON.parse(raw);
2130
2181
  count = Array.isArray(employees) ? employees.length : 0;
2131
2182
  }
@@ -2154,19 +2205,19 @@ var init_plan_limits = __esm({
2154
2205
  this.name = "PlanLimitError";
2155
2206
  }
2156
2207
  };
2157
- CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
2208
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2158
2209
  }
2159
2210
  });
2160
2211
 
2161
2212
  // src/lib/notifications.ts
2162
2213
  import crypto from "crypto";
2163
- import path8 from "path";
2214
+ import path9 from "path";
2164
2215
  import os5 from "os";
2165
2216
  import {
2166
- readFileSync as readFileSync8,
2217
+ readFileSync as readFileSync9,
2167
2218
  readdirSync,
2168
2219
  unlinkSync as unlinkSync3,
2169
- existsSync as existsSync8,
2220
+ existsSync as existsSync9,
2170
2221
  rmdirSync
2171
2222
  } from "fs";
2172
2223
  async function writeNotification(notification) {
@@ -2301,11 +2352,11 @@ var init_state_bus = __esm({
2301
2352
 
2302
2353
  // src/lib/tasks-crud.ts
2303
2354
  import crypto3 from "crypto";
2304
- import path9 from "path";
2355
+ import path10 from "path";
2305
2356
  import os6 from "os";
2306
2357
  import { execSync as execSync4 } from "child_process";
2307
2358
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2308
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2359
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2309
2360
  async function writeCheckpoint(input) {
2310
2361
  const client = getClient();
2311
2362
  const row = await resolveTask(client, input.taskId);
@@ -2480,8 +2531,8 @@ ${laneWarning}` : laneWarning;
2480
2531
  }
2481
2532
  if (input.baseDir) {
2482
2533
  try {
2483
- await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
2484
- await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2534
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2535
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2485
2536
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2486
2537
  await ensureGitignoreExe(input.baseDir);
2487
2538
  } catch {
@@ -2517,10 +2568,10 @@ ${laneWarning}` : laneWarning;
2517
2568
  });
2518
2569
  if (input.baseDir) {
2519
2570
  try {
2520
- const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
2521
- const mdPath = path9.join(EXE_OS_DIR, taskFile);
2522
- const mdDir = path9.dirname(mdPath);
2523
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2571
+ const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
2572
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
2573
+ const mdDir = path10.dirname(mdPath);
2574
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2524
2575
  const reviewer = input.reviewer ?? input.assignedBy;
2525
2576
  const mdContent = `# ${input.title}
2526
2577
 
@@ -2545,7 +2596,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2545
2596
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2546
2597
  `;
2547
2598
  await writeFile3(mdPath, mdContent, "utf-8");
2548
- } catch {
2599
+ } catch (err) {
2600
+ process.stderr.write(
2601
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2602
+ `
2603
+ );
2549
2604
  }
2550
2605
  }
2551
2606
  return {
@@ -2805,9 +2860,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2805
2860
  return { taskFile, assignedTo, assignedBy, taskSlug };
2806
2861
  }
2807
2862
  async function ensureArchitectureDoc(baseDir, projectName) {
2808
- const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
2863
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2809
2864
  try {
2810
- if (existsSync9(archPath)) return;
2865
+ if (existsSync10(archPath)) return;
2811
2866
  const template = [
2812
2867
  `# ${projectName} \u2014 System Architecture`,
2813
2868
  "",
@@ -2840,10 +2895,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2840
2895
  }
2841
2896
  }
2842
2897
  async function ensureGitignoreExe(baseDir) {
2843
- const gitignorePath = path9.join(baseDir, ".gitignore");
2898
+ const gitignorePath = path10.join(baseDir, ".gitignore");
2844
2899
  try {
2845
- if (existsSync9(gitignorePath)) {
2846
- const content = readFileSync9(gitignorePath, "utf-8");
2900
+ if (existsSync10(gitignorePath)) {
2901
+ const content = readFileSync10(gitignorePath, "utf-8");
2847
2902
  if (/^\/?exe\/?$/m.test(content)) return;
2848
2903
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2849
2904
  } else {
@@ -2874,8 +2929,8 @@ var init_tasks_crud = __esm({
2874
2929
  });
2875
2930
 
2876
2931
  // src/lib/tasks-review.ts
2877
- import path10 from "path";
2878
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2932
+ import path11 from "path";
2933
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2879
2934
  async function countPendingReviews(sessionScope) {
2880
2935
  const client = getClient();
2881
2936
  if (sessionScope) {
@@ -3056,11 +3111,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3056
3111
  );
3057
3112
  }
3058
3113
  try {
3059
- const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
3060
- if (existsSync10(cacheDir)) {
3114
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3115
+ if (existsSync11(cacheDir)) {
3061
3116
  for (const f of readdirSync2(cacheDir)) {
3062
3117
  if (f.startsWith("review-notified-")) {
3063
- unlinkSync4(path10.join(cacheDir, f));
3118
+ unlinkSync4(path11.join(cacheDir, f));
3064
3119
  }
3065
3120
  }
3066
3121
  }
@@ -3081,7 +3136,7 @@ var init_tasks_review = __esm({
3081
3136
  });
3082
3137
 
3083
3138
  // src/lib/tasks-chain.ts
3084
- import path11 from "path";
3139
+ import path12 from "path";
3085
3140
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3086
3141
  async function cascadeUnblock(taskId, baseDir, now) {
3087
3142
  const client = getClient();
@@ -3098,7 +3153,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3098
3153
  });
3099
3154
  for (const ur of unblockedRows.rows) {
3100
3155
  try {
3101
- const ubFile = path11.join(baseDir, String(ur.task_file));
3156
+ const ubFile = path12.join(baseDir, String(ur.task_file));
3102
3157
  let ubContent = await readFile3(ubFile, "utf-8");
3103
3158
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3104
3159
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3172,7 +3227,7 @@ __export(project_name_exports, {
3172
3227
  getProjectName: () => getProjectName
3173
3228
  });
3174
3229
  import { execSync as execSync5 } from "child_process";
3175
- import path12 from "path";
3230
+ import path13 from "path";
3176
3231
  function getProjectName(cwd) {
3177
3232
  const dir = cwd ?? process.cwd();
3178
3233
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3185,7 +3240,7 @@ function getProjectName(cwd) {
3185
3240
  timeout: 2e3,
3186
3241
  stdio: ["pipe", "pipe", "pipe"]
3187
3242
  }).trim();
3188
- repoRoot = path12.dirname(gitCommonDir);
3243
+ repoRoot = path13.dirname(gitCommonDir);
3189
3244
  } catch {
3190
3245
  repoRoot = execSync5("git rev-parse --show-toplevel", {
3191
3246
  cwd: dir,
@@ -3194,11 +3249,11 @@ function getProjectName(cwd) {
3194
3249
  stdio: ["pipe", "pipe", "pipe"]
3195
3250
  }).trim();
3196
3251
  }
3197
- _cached2 = path12.basename(repoRoot);
3252
+ _cached2 = path13.basename(repoRoot);
3198
3253
  _cachedCwd = dir;
3199
3254
  return _cached2;
3200
3255
  } catch {
3201
- _cached2 = path12.basename(dir);
3256
+ _cached2 = path13.basename(dir);
3202
3257
  _cachedCwd = dir;
3203
3258
  return _cached2;
3204
3259
  }
@@ -3675,8 +3730,8 @@ __export(tasks_exports, {
3675
3730
  updateTaskStatus: () => updateTaskStatus,
3676
3731
  writeCheckpoint: () => writeCheckpoint
3677
3732
  });
3678
- import path13 from "path";
3679
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
3733
+ import path14 from "path";
3734
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3680
3735
  async function createTask(input) {
3681
3736
  const result = await createTaskCore(input);
3682
3737
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3695,11 +3750,11 @@ async function updateTask(input) {
3695
3750
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3696
3751
  try {
3697
3752
  const agent = String(row.assigned_to);
3698
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3699
- const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3753
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3754
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3700
3755
  if (input.status === "in_progress") {
3701
- mkdirSync4(cacheDir, { recursive: true });
3702
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3756
+ mkdirSync5(cacheDir, { recursive: true });
3757
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3703
3758
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3704
3759
  try {
3705
3760
  unlinkSync5(cachePath);
@@ -4166,13 +4221,13 @@ __export(tmux_routing_exports, {
4166
4221
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4167
4222
  });
4168
4223
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
4169
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4170
- import path14 from "path";
4224
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4225
+ import path15 from "path";
4171
4226
  import os7 from "os";
4172
4227
  import { fileURLToPath as fileURLToPath3 } from "url";
4173
4228
  import { unlinkSync as unlinkSync6 } from "fs";
4174
4229
  function spawnLockPath(sessionName) {
4175
- return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4230
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4176
4231
  }
4177
4232
  function isProcessAlive(pid) {
4178
4233
  try {
@@ -4183,13 +4238,13 @@ function isProcessAlive(pid) {
4183
4238
  }
4184
4239
  }
4185
4240
  function acquireSpawnLock2(sessionName) {
4186
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4187
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4241
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4242
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4188
4243
  }
4189
4244
  const lockFile = spawnLockPath(sessionName);
4190
- if (existsSync11(lockFile)) {
4245
+ if (existsSync12(lockFile)) {
4191
4246
  try {
4192
- const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4247
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
4193
4248
  const age = Date.now() - lock.timestamp;
4194
4249
  if (isProcessAlive(lock.pid) && age < 6e4) {
4195
4250
  return false;
@@ -4197,7 +4252,7 @@ function acquireSpawnLock2(sessionName) {
4197
4252
  } catch {
4198
4253
  }
4199
4254
  }
4200
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4255
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4201
4256
  return true;
4202
4257
  }
4203
4258
  function releaseSpawnLock2(sessionName) {
@@ -4209,13 +4264,13 @@ function releaseSpawnLock2(sessionName) {
4209
4264
  function resolveBehaviorsExporterScript() {
4210
4265
  try {
4211
4266
  const thisFile = fileURLToPath3(import.meta.url);
4212
- const scriptPath = path14.join(
4213
- path14.dirname(thisFile),
4267
+ const scriptPath = path15.join(
4268
+ path15.dirname(thisFile),
4214
4269
  "..",
4215
4270
  "bin",
4216
4271
  "exe-export-behaviors.js"
4217
4272
  );
4218
- return existsSync11(scriptPath) ? scriptPath : null;
4273
+ return existsSync12(scriptPath) ? scriptPath : null;
4219
4274
  } catch {
4220
4275
  return null;
4221
4276
  }
@@ -4281,12 +4336,12 @@ function extractRootExe(name) {
4281
4336
  return parts.length > 0 ? parts[parts.length - 1] : null;
4282
4337
  }
4283
4338
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4284
- if (!existsSync11(SESSION_CACHE)) {
4285
- mkdirSync5(SESSION_CACHE, { recursive: true });
4339
+ if (!existsSync12(SESSION_CACHE)) {
4340
+ mkdirSync6(SESSION_CACHE, { recursive: true });
4286
4341
  }
4287
4342
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4288
- const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4289
- writeFileSync6(filePath, JSON.stringify({
4343
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4344
+ writeFileSync7(filePath, JSON.stringify({
4290
4345
  parentExe: rootExe,
4291
4346
  dispatchedBy: dispatchedBy || rootExe,
4292
4347
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4294,7 +4349,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4294
4349
  }
4295
4350
  function getParentExe(sessionKey) {
4296
4351
  try {
4297
- const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4352
+ const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4298
4353
  return data.parentExe || null;
4299
4354
  } catch {
4300
4355
  return null;
@@ -4302,8 +4357,8 @@ function getParentExe(sessionKey) {
4302
4357
  }
4303
4358
  function getDispatchedBy(sessionKey) {
4304
4359
  try {
4305
- const data = JSON.parse(readFileSync10(
4306
- path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4360
+ const data = JSON.parse(readFileSync11(
4361
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4307
4362
  "utf8"
4308
4363
  ));
4309
4364
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4364,32 +4419,50 @@ async function verifyPaneAtCapacity(sessionName) {
4364
4419
  }
4365
4420
  function readDebounceState() {
4366
4421
  try {
4367
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4368
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4422
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4423
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4424
+ const state = {};
4425
+ for (const [key, val] of Object.entries(raw)) {
4426
+ if (typeof val === "number") {
4427
+ state[key] = { lastSent: val, pending: 0 };
4428
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4429
+ state[key] = val;
4430
+ }
4431
+ }
4432
+ return state;
4369
4433
  } catch {
4370
4434
  return {};
4371
4435
  }
4372
4436
  }
4373
4437
  function writeDebounceState(state) {
4374
4438
  try {
4375
- if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4376
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4439
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4440
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4377
4441
  } catch {
4378
4442
  }
4379
4443
  }
4380
4444
  function isDebounced(targetSession) {
4381
4445
  const state = readDebounceState();
4382
- const lastSent = state[targetSession] ?? 0;
4383
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4446
+ const entry = state[targetSession];
4447
+ const lastSent = entry?.lastSent ?? 0;
4448
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4449
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4450
+ state[targetSession].pending++;
4451
+ writeDebounceState(state);
4452
+ return true;
4453
+ }
4454
+ return false;
4384
4455
  }
4385
4456
  function recordDebounce(targetSession) {
4386
4457
  const state = readDebounceState();
4387
- state[targetSession] = Date.now();
4458
+ const batched = state[targetSession]?.pending ?? 0;
4459
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4388
4460
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4389
4461
  for (const key of Object.keys(state)) {
4390
- if ((state[key] ?? 0) < cutoff) delete state[key];
4462
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4391
4463
  }
4392
4464
  writeDebounceState(state);
4465
+ return batched;
4393
4466
  }
4394
4467
  function logIntercom(msg) {
4395
4468
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4434,7 +4507,7 @@ function sendIntercom(targetSession) {
4434
4507
  return "skipped_exe";
4435
4508
  }
4436
4509
  if (isDebounced(targetSession)) {
4437
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4510
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4438
4511
  return "debounced";
4439
4512
  }
4440
4513
  try {
@@ -4446,14 +4519,14 @@ function sendIntercom(targetSession) {
4446
4519
  const sessionState = getSessionState(targetSession);
4447
4520
  if (sessionState === "no_claude") {
4448
4521
  queueIntercom(targetSession, "claude not running in session");
4449
- recordDebounce(targetSession);
4450
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4522
+ const batched2 = recordDebounce(targetSession);
4523
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4451
4524
  return "queued";
4452
4525
  }
4453
4526
  if (sessionState === "thinking" || sessionState === "tool") {
4454
4527
  queueIntercom(targetSession, "session busy at send time");
4455
- recordDebounce(targetSession);
4456
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4528
+ const batched2 = recordDebounce(targetSession);
4529
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4457
4530
  return "queued";
4458
4531
  }
4459
4532
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4461,8 +4534,8 @@ function sendIntercom(targetSession) {
4461
4534
  transport.sendKeys(targetSession, "q");
4462
4535
  }
4463
4536
  transport.sendKeys(targetSession, "/exe-intercom");
4464
- recordDebounce(targetSession);
4465
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4537
+ const batched = recordDebounce(targetSession);
4538
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4466
4539
  return "delivered";
4467
4540
  } catch {
4468
4541
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4564,26 +4637,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4564
4637
  const transport = getTransport();
4565
4638
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4566
4639
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4567
- const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4568
- const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4569
- if (!existsSync11(logDir)) {
4570
- mkdirSync5(logDir, { recursive: true });
4640
+ const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
4641
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4642
+ if (!existsSync12(logDir)) {
4643
+ mkdirSync6(logDir, { recursive: true });
4571
4644
  }
4572
4645
  transport.kill(sessionName);
4573
4646
  let cleanupSuffix = "";
4574
4647
  try {
4575
4648
  const thisFile = fileURLToPath3(import.meta.url);
4576
- const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4577
- if (existsSync11(cleanupScript)) {
4649
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4650
+ if (existsSync12(cleanupScript)) {
4578
4651
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4579
4652
  }
4580
4653
  } catch {
4581
4654
  }
4582
4655
  try {
4583
- const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
4656
+ const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
4584
4657
  let claudeJson = {};
4585
4658
  try {
4586
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
4659
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4587
4660
  } catch {
4588
4661
  }
4589
4662
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4591,17 +4664,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4591
4664
  const trustDir = opts?.cwd ?? projectDir;
4592
4665
  if (!projects[trustDir]) projects[trustDir] = {};
4593
4666
  projects[trustDir].hasTrustDialogAccepted = true;
4594
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4667
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4595
4668
  } catch {
4596
4669
  }
4597
4670
  try {
4598
- const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
4671
+ const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
4599
4672
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4600
- const projSettingsDir = path14.join(settingsDir, normalizedKey);
4601
- const settingsPath = path14.join(projSettingsDir, "settings.json");
4673
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
4674
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
4602
4675
  let settings = {};
4603
4676
  try {
4604
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
4677
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4605
4678
  } catch {
4606
4679
  }
4607
4680
  const perms = settings.permissions ?? {};
@@ -4629,20 +4702,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4629
4702
  if (changed) {
4630
4703
  perms.allow = allow;
4631
4704
  settings.permissions = perms;
4632
- mkdirSync5(projSettingsDir, { recursive: true });
4633
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4705
+ mkdirSync6(projSettingsDir, { recursive: true });
4706
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4634
4707
  }
4635
4708
  } catch {
4636
4709
  }
4637
4710
  const spawnCwd = opts?.cwd ?? projectDir;
4638
4711
  const useExeAgent = !!(opts?.model && opts?.provider);
4639
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4712
+ const agentRtConfig = getAgentRuntime(employeeName);
4713
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4714
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4715
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4640
4716
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4641
4717
  let identityFlag = "";
4642
4718
  let behaviorsFlag = "";
4643
4719
  let legacyFallbackWarned = false;
4644
4720
  if (!useExeAgent && !useBinSymlink) {
4645
- const identityPath = path14.join(
4721
+ const identityPath = path15.join(
4646
4722
  os7.homedir(),
4647
4723
  ".exe-os",
4648
4724
  "identity",
@@ -4652,13 +4728,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4652
4728
  const hasAgentFlag = claudeSupportsAgentFlag();
4653
4729
  if (hasAgentFlag) {
4654
4730
  identityFlag = ` --agent ${employeeName}`;
4655
- } else if (existsSync11(identityPath)) {
4731
+ } else if (existsSync12(identityPath)) {
4656
4732
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4657
4733
  legacyFallbackWarned = true;
4658
4734
  }
4659
4735
  const behaviorsFile = exportBehaviorsSync(
4660
4736
  employeeName,
4661
- path14.basename(spawnCwd),
4737
+ path15.basename(spawnCwd),
4662
4738
  sessionName
4663
4739
  );
4664
4740
  if (behaviorsFile) {
@@ -4673,16 +4749,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4673
4749
  }
4674
4750
  let sessionContextFlag = "";
4675
4751
  try {
4676
- const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4677
- mkdirSync5(ctxDir, { recursive: true });
4678
- const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4752
+ const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
4753
+ mkdirSync6(ctxDir, { recursive: true });
4754
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4679
4755
  const ctxContent = [
4680
4756
  `## Session Context`,
4681
4757
  `You are running in tmux session: ${sessionName}.`,
4682
4758
  `Your parent coordinator session is ${exeSession}.`,
4683
4759
  `Your employees (if any) use the -${exeSession} suffix.`
4684
4760
  ].join("\n");
4685
- writeFileSync6(ctxFile, ctxContent);
4761
+ writeFileSync7(ctxFile, ctxContent);
4686
4762
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4687
4763
  } catch {
4688
4764
  }
@@ -4696,9 +4772,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4696
4772
  }
4697
4773
  }
4698
4774
  }
4775
+ if (useCodex) {
4776
+ const codexCfg = RUNTIME_TABLE.codex;
4777
+ if (codexCfg?.apiKeyEnv) {
4778
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4779
+ if (keyVal) {
4780
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4781
+ }
4782
+ }
4783
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4784
+ }
4785
+ if (useOpencode) {
4786
+ const ocCfg = PROVIDER_TABLE.opencode;
4787
+ if (ocCfg?.apiKeyEnv) {
4788
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4789
+ if (keyVal) {
4790
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4791
+ }
4792
+ }
4793
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4794
+ }
4795
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4796
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4797
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4798
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4799
+ }
4800
+ }
4699
4801
  let spawnCommand;
4700
4802
  if (useExeAgent) {
4701
4803
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4804
+ } else if (useCodex) {
4805
+ process.stderr.write(
4806
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4807
+ `
4808
+ );
4809
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
4810
+ } else if (useOpencode) {
4811
+ const binName = `${employeeName}-opencode`;
4812
+ process.stderr.write(
4813
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
4814
+ `
4815
+ );
4816
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4702
4817
  } else if (useBinSymlink) {
4703
4818
  const binName = `${employeeName}-${ccProvider}`;
4704
4819
  process.stderr.write(
@@ -4720,11 +4835,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4720
4835
  transport.pipeLog(sessionName, logFile);
4721
4836
  try {
4722
4837
  const mySession = getMySession();
4723
- const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4724
- writeFileSync6(dispatchInfo, JSON.stringify({
4838
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4839
+ writeFileSync7(dispatchInfo, JSON.stringify({
4725
4840
  dispatchedBy: mySession,
4726
4841
  rootExe: exeSession,
4727
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4842
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4843
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4844
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4728
4845
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4729
4846
  }));
4730
4847
  } catch {
@@ -4742,6 +4859,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4742
4859
  booted = true;
4743
4860
  break;
4744
4861
  }
4862
+ } else if (useCodex) {
4863
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4864
+ booted = true;
4865
+ break;
4866
+ }
4745
4867
  } else {
4746
4868
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4747
4869
  booted = true;
@@ -4753,9 +4875,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4753
4875
  }
4754
4876
  if (!booted) {
4755
4877
  releaseSpawnLock2(sessionName);
4756
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4878
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
4879
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4757
4880
  }
4758
- if (!useExeAgent) {
4881
+ if (!useExeAgent && !useCodex) {
4759
4882
  try {
4760
4883
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4761
4884
  } catch {
@@ -4782,17 +4905,19 @@ var init_tmux_routing = __esm({
4782
4905
  init_cc_agent_support();
4783
4906
  init_mcp_prefix();
4784
4907
  init_provider_table();
4908
+ init_agent_config();
4909
+ init_runtime_table();
4785
4910
  init_intercom_queue();
4786
4911
  init_plan_limits();
4787
4912
  init_employees();
4788
- SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4789
- SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4913
+ SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
4914
+ SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
4790
4915
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4791
4916
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4792
4917
  VERIFY_PANE_LINES = 200;
4793
4918
  INTERCOM_DEBOUNCE_MS = 3e4;
4794
- INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4795
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4919
+ INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
4920
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4796
4921
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4797
4922
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4798
4923
  }
@@ -4833,14 +4958,14 @@ var init_memory = __esm({
4833
4958
 
4834
4959
  // src/lib/keychain.ts
4835
4960
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4836
- import { existsSync as existsSync12 } from "fs";
4837
- import path15 from "path";
4961
+ import { existsSync as existsSync13 } from "fs";
4962
+ import path16 from "path";
4838
4963
  import os8 from "os";
4839
4964
  function getKeyDir() {
4840
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
4965
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
4841
4966
  }
4842
4967
  function getKeyPath() {
4843
- return path15.join(getKeyDir(), "master.key");
4968
+ return path16.join(getKeyDir(), "master.key");
4844
4969
  }
4845
4970
  async function tryKeytar() {
4846
4971
  try {
@@ -4861,7 +4986,7 @@ async function getMasterKey() {
4861
4986
  }
4862
4987
  }
4863
4988
  const keyPath = getKeyPath();
4864
- if (!existsSync12(keyPath)) {
4989
+ if (!existsSync13(keyPath)) {
4865
4990
  process.stderr.write(
4866
4991
  `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4867
4992
  `
@@ -4901,13 +5026,13 @@ __export(shard_manager_exports, {
4901
5026
  listShards: () => listShards,
4902
5027
  shardExists: () => shardExists
4903
5028
  });
4904
- import path16 from "path";
4905
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5029
+ import path17 from "path";
5030
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
4906
5031
  import { createClient as createClient2 } from "@libsql/client";
4907
5032
  function initShardManager(encryptionKey) {
4908
5033
  _encryptionKey = encryptionKey;
4909
- if (!existsSync13(SHARDS_DIR)) {
4910
- mkdirSync6(SHARDS_DIR, { recursive: true });
5034
+ if (!existsSync14(SHARDS_DIR)) {
5035
+ mkdirSync7(SHARDS_DIR, { recursive: true });
4911
5036
  }
4912
5037
  _shardingEnabled = true;
4913
5038
  }
@@ -4927,7 +5052,7 @@ function getShardClient(projectName) {
4927
5052
  }
4928
5053
  const cached = _shards.get(safeName);
4929
5054
  if (cached) return cached;
4930
- const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
5055
+ const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
4931
5056
  const client = createClient2({
4932
5057
  url: `file:${dbPath}`,
4933
5058
  encryptionKey: _encryptionKey
@@ -4937,10 +5062,10 @@ function getShardClient(projectName) {
4937
5062
  }
4938
5063
  function shardExists(projectName) {
4939
5064
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4940
- return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
5065
+ return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
4941
5066
  }
4942
5067
  function listShards() {
4943
- if (!existsSync13(SHARDS_DIR)) return [];
5068
+ if (!existsSync14(SHARDS_DIR)) return [];
4944
5069
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4945
5070
  }
4946
5071
  async function ensureShardSchema(client) {
@@ -5126,7 +5251,7 @@ var init_shard_manager = __esm({
5126
5251
  "src/lib/shard-manager.ts"() {
5127
5252
  "use strict";
5128
5253
  init_config();
5129
- SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
5254
+ SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5130
5255
  _shards = /* @__PURE__ */ new Map();
5131
5256
  _encryptionKey = null;
5132
5257
  _shardingEnabled = false;
@@ -5883,8 +6008,8 @@ var init_store = __esm({
5883
6008
  });
5884
6009
 
5885
6010
  // src/bin/scan-tasks.ts
5886
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5887
- import path17 from "path";
6011
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
6012
+ import path18 from "path";
5888
6013
  import os9 from "os";
5889
6014
 
5890
6015
  // src/lib/is-main.ts
@@ -5906,14 +6031,14 @@ function isMainModule(importMetaUrl) {
5906
6031
  init_task_scope();
5907
6032
  function checkMcpHealth() {
5908
6033
  try {
5909
- const claudeJson = path17.join(os9.homedir(), ".claude.json");
5910
- if (!existsSync14(claudeJson)) {
6034
+ const claudeJson = path18.join(os9.homedir(), ".claude.json");
6035
+ if (!existsSync15(claudeJson)) {
5911
6036
  process.stderr.write(
5912
6037
  "\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n"
5913
6038
  );
5914
6039
  return;
5915
6040
  }
5916
- const config = JSON.parse(readFileSync11(claudeJson, "utf8"));
6041
+ const config = JSON.parse(readFileSync12(claudeJson, "utf8"));
5917
6042
  const servers = config.mcpServers;
5918
6043
  if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
5919
6044
  process.stderr.write(