@askexenow/exe-os 0.8.85 → 0.8.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/bin/cleanup-stale-review-tasks.js +57 -19
  2. package/dist/bin/cli.js +510 -340
  3. package/dist/bin/exe-agent-config.js +242 -0
  4. package/dist/bin/exe-agent.js +3 -3
  5. package/dist/bin/exe-boot.js +344 -346
  6. package/dist/bin/exe-dispatch.js +375 -250
  7. package/dist/bin/exe-forget.js +5 -1
  8. package/dist/bin/exe-gateway.js +260 -135
  9. package/dist/bin/exe-healthcheck.js +133 -1
  10. package/dist/bin/exe-heartbeat.js +72 -31
  11. package/dist/bin/exe-link.js +25 -2
  12. package/dist/bin/exe-new-employee.js +22 -0
  13. package/dist/bin/exe-pending-messages.js +55 -17
  14. package/dist/bin/exe-pending-reviews.js +57 -19
  15. package/dist/bin/exe-search.js +6 -2
  16. package/dist/bin/exe-session-cleanup.js +260 -135
  17. package/dist/bin/exe-start-codex.js +2598 -0
  18. package/dist/bin/exe-start.sh +15 -3
  19. package/dist/bin/exe-status.js +57 -19
  20. package/dist/bin/git-sweep.js +391 -266
  21. package/dist/bin/install.js +22 -0
  22. package/dist/bin/scan-tasks.js +394 -269
  23. package/dist/bin/setup.js +50 -5
  24. package/dist/gateway/index.js +257 -132
  25. package/dist/hooks/bug-report-worker.js +242 -117
  26. package/dist/hooks/commit-complete.js +389 -264
  27. package/dist/hooks/error-recall.js +6 -2
  28. package/dist/hooks/ingest-worker.js +314 -193
  29. package/dist/hooks/post-compact.js +84 -46
  30. package/dist/hooks/pre-compact.js +272 -147
  31. package/dist/hooks/pre-tool-use.js +104 -66
  32. package/dist/hooks/prompt-submit.js +126 -66
  33. package/dist/hooks/session-end.js +277 -152
  34. package/dist/hooks/session-start.js +70 -28
  35. package/dist/hooks/stop.js +90 -52
  36. package/dist/hooks/subagent-stop.js +84 -46
  37. package/dist/hooks/summary-worker.js +175 -114
  38. package/dist/index.js +296 -171
  39. package/dist/lib/agent-config.js +167 -0
  40. package/dist/lib/cloud-sync.js +25 -2
  41. package/dist/lib/exe-daemon.js +338 -213
  42. package/dist/lib/hybrid-search.js +7 -2
  43. package/dist/lib/messaging.js +95 -39
  44. package/dist/lib/runtime-table.js +16 -0
  45. package/dist/lib/session-wrappers.js +22 -0
  46. package/dist/lib/tasks.js +242 -117
  47. package/dist/lib/tmux-routing.js +314 -189
  48. package/dist/mcp/server.js +573 -274
  49. package/dist/mcp/tools/create-task.js +260 -135
  50. package/dist/mcp/tools/list-tasks.js +68 -30
  51. package/dist/mcp/tools/send-message.js +100 -44
  52. package/dist/mcp/tools/update-task.js +123 -67
  53. package/dist/runtime/index.js +276 -151
  54. package/dist/tui/App.js +479 -354
  55. package/package.json +1 -1
  56. package/src/commands/exe/agent-config.md +27 -0
  57. package/src/commands/exe/cc-doctor.md +10 -0
@@ -269,123 +269,19 @@ var init_provider_table = __esm({
269
269
  }
270
270
  });
271
271
 
272
- // src/lib/intercom-queue.ts
273
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
274
- import path2 from "path";
275
- import os2 from "os";
276
- function ensureDir() {
277
- const dir = path2.dirname(QUEUE_PATH);
278
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
279
- }
280
- function readQueue() {
281
- try {
282
- if (!existsSync2(QUEUE_PATH)) return [];
283
- return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
284
- } catch {
285
- return [];
286
- }
287
- }
288
- function writeQueue(queue) {
289
- ensureDir();
290
- const tmp = `${QUEUE_PATH}.tmp`;
291
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
292
- renameSync(tmp, QUEUE_PATH);
293
- }
294
- function queueIntercom(targetSession, reason) {
295
- const queue = readQueue();
296
- const existing = queue.find((q) => q.targetSession === targetSession);
297
- if (existing) {
298
- existing.attempts++;
299
- existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
300
- existing.reason = reason;
301
- } else {
302
- queue.push({
303
- targetSession,
304
- queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
305
- attempts: 0,
306
- reason
307
- });
308
- }
309
- writeQueue(queue);
310
- }
311
- var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
312
- var init_intercom_queue = __esm({
313
- "src/lib/intercom-queue.ts"() {
314
- "use strict";
315
- QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
316
- TTL_MS = 60 * 60 * 1e3;
317
- INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
318
- }
319
- });
320
-
321
- // src/lib/db-retry.ts
322
- function isBusyError(err) {
323
- if (err instanceof Error) {
324
- const msg = err.message.toLowerCase();
325
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
326
- }
327
- return false;
328
- }
329
- function delay(ms) {
330
- return new Promise((resolve) => setTimeout(resolve, ms));
331
- }
332
- async function retryOnBusy(fn, label) {
333
- let lastError;
334
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
335
- try {
336
- return await fn();
337
- } catch (err) {
338
- lastError = err;
339
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
340
- throw err;
341
- }
342
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
343
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
344
- process.stderr.write(
345
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
346
- `
347
- );
348
- await delay(backoff + jitter);
349
- }
350
- }
351
- throw lastError;
352
- }
353
- function wrapWithRetry(client) {
354
- return new Proxy(client, {
355
- get(target, prop, receiver) {
356
- if (prop === "execute") {
357
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
358
- }
359
- if (prop === "batch") {
360
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
361
- }
362
- return Reflect.get(target, prop, receiver);
363
- }
364
- });
365
- }
366
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
367
- var init_db_retry = __esm({
368
- "src/lib/db-retry.ts"() {
369
- "use strict";
370
- MAX_RETRIES = 3;
371
- BASE_DELAY_MS = 200;
372
- MAX_JITTER_MS = 300;
373
- }
374
- });
375
-
376
272
  // src/lib/config.ts
377
273
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
378
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
379
- import path3 from "path";
380
- import os3 from "os";
274
+ import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
275
+ import path2 from "path";
276
+ import os2 from "os";
381
277
  function resolveDataDir() {
382
278
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
383
279
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
384
- const newDir = path3.join(os3.homedir(), ".exe-os");
385
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
386
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
280
+ const newDir = path2.join(os2.homedir(), ".exe-os");
281
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
282
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
387
283
  try {
388
- renameSync2(legacyDir, newDir);
284
+ renameSync(legacyDir, newDir);
389
285
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
390
286
  `);
391
287
  } catch {
@@ -447,9 +343,9 @@ function normalizeAutoUpdate(raw) {
447
343
  async function loadConfig() {
448
344
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
449
345
  await mkdir(dir, { recursive: true });
450
- const configPath = path3.join(dir, "config.json");
451
- if (!existsSync3(configPath)) {
452
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
346
+ const configPath = path2.join(dir, "config.json");
347
+ if (!existsSync2(configPath)) {
348
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
453
349
  }
454
350
  const raw = await readFile(configPath, "utf-8");
455
351
  try {
@@ -467,13 +363,13 @@ async function loadConfig() {
467
363
  normalizeScalingRoadmap(migratedCfg);
468
364
  normalizeSessionLifecycle(migratedCfg);
469
365
  normalizeAutoUpdate(migratedCfg);
470
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
366
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
471
367
  if (config.dbPath.startsWith("~")) {
472
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
368
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
473
369
  }
474
370
  return config;
475
371
  } catch {
476
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
372
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
477
373
  }
478
374
  }
479
375
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -481,10 +377,10 @@ var init_config = __esm({
481
377
  "src/lib/config.ts"() {
482
378
  "use strict";
483
379
  EXE_AI_DIR = resolveDataDir();
484
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
485
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
486
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
487
- LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
380
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
381
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
382
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
383
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
488
384
  CURRENT_CONFIG_VERSION = 1;
489
385
  DEFAULT_CONFIG = {
490
386
  config_version: CURRENT_CONFIG_VERSION,
@@ -556,11 +452,166 @@ var init_config = __esm({
556
452
  }
557
453
  });
558
454
 
455
+ // src/lib/runtime-table.ts
456
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
457
+ var init_runtime_table = __esm({
458
+ "src/lib/runtime-table.ts"() {
459
+ "use strict";
460
+ RUNTIME_TABLE = {
461
+ codex: {
462
+ binary: "codex",
463
+ launchMode: "exec",
464
+ autoApproveFlag: "--full-auto",
465
+ inlineFlag: "--no-alt-screen",
466
+ apiKeyEnv: "OPENAI_API_KEY",
467
+ defaultModel: "gpt-5.4"
468
+ }
469
+ };
470
+ DEFAULT_RUNTIME = "claude";
471
+ }
472
+ });
473
+
474
+ // src/lib/agent-config.ts
475
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
476
+ import path3 from "path";
477
+ function loadAgentConfig() {
478
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
479
+ try {
480
+ return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
481
+ } catch {
482
+ return {};
483
+ }
484
+ }
485
+ function getAgentRuntime(agentId) {
486
+ const config = loadAgentConfig();
487
+ const entry = config[agentId];
488
+ if (entry) return entry;
489
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
490
+ }
491
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
492
+ var init_agent_config = __esm({
493
+ "src/lib/agent-config.ts"() {
494
+ "use strict";
495
+ init_config();
496
+ init_runtime_table();
497
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
498
+ DEFAULT_MODELS = {
499
+ claude: "claude-opus-4",
500
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
501
+ opencode: "minimax-m2.7"
502
+ };
503
+ }
504
+ });
505
+
506
+ // src/lib/intercom-queue.ts
507
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
508
+ import path4 from "path";
509
+ import os3 from "os";
510
+ function ensureDir() {
511
+ const dir = path4.dirname(QUEUE_PATH);
512
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
513
+ }
514
+ function readQueue() {
515
+ try {
516
+ if (!existsSync4(QUEUE_PATH)) return [];
517
+ return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ function writeQueue(queue) {
523
+ ensureDir();
524
+ const tmp = `${QUEUE_PATH}.tmp`;
525
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
526
+ renameSync2(tmp, QUEUE_PATH);
527
+ }
528
+ function queueIntercom(targetSession, reason) {
529
+ const queue = readQueue();
530
+ const existing = queue.find((q) => q.targetSession === targetSession);
531
+ if (existing) {
532
+ existing.attempts++;
533
+ existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
534
+ existing.reason = reason;
535
+ } else {
536
+ queue.push({
537
+ targetSession,
538
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
539
+ attempts: 0,
540
+ reason
541
+ });
542
+ }
543
+ writeQueue(queue);
544
+ }
545
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
546
+ var init_intercom_queue = __esm({
547
+ "src/lib/intercom-queue.ts"() {
548
+ "use strict";
549
+ QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
550
+ TTL_MS = 60 * 60 * 1e3;
551
+ INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
552
+ }
553
+ });
554
+
555
+ // src/lib/db-retry.ts
556
+ function isBusyError(err) {
557
+ if (err instanceof Error) {
558
+ const msg = err.message.toLowerCase();
559
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
560
+ }
561
+ return false;
562
+ }
563
+ function delay(ms) {
564
+ return new Promise((resolve) => setTimeout(resolve, ms));
565
+ }
566
+ async function retryOnBusy(fn, label) {
567
+ let lastError;
568
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
569
+ try {
570
+ return await fn();
571
+ } catch (err) {
572
+ lastError = err;
573
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
574
+ throw err;
575
+ }
576
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
577
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
578
+ process.stderr.write(
579
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
580
+ `
581
+ );
582
+ await delay(backoff + jitter);
583
+ }
584
+ }
585
+ throw lastError;
586
+ }
587
+ function wrapWithRetry(client) {
588
+ return new Proxy(client, {
589
+ get(target, prop, receiver) {
590
+ if (prop === "execute") {
591
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
592
+ }
593
+ if (prop === "batch") {
594
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
595
+ }
596
+ return Reflect.get(target, prop, receiver);
597
+ }
598
+ });
599
+ }
600
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
601
+ var init_db_retry = __esm({
602
+ "src/lib/db-retry.ts"() {
603
+ "use strict";
604
+ MAX_RETRIES = 3;
605
+ BASE_DELAY_MS = 200;
606
+ MAX_JITTER_MS = 300;
607
+ }
608
+ });
609
+
559
610
  // src/lib/employees.ts
560
611
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
561
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
612
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
562
613
  import { execSync as execSync3 } from "child_process";
563
- import path4 from "path";
614
+ import path5 from "path";
564
615
  import os4 from "os";
565
616
  function normalizeRole(role) {
566
617
  return (role ?? "").trim().toLowerCase();
@@ -579,9 +630,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
579
630
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
580
631
  }
581
632
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
582
- if (!existsSync4(employeesPath)) return [];
633
+ if (!existsSync5(employeesPath)) return [];
583
634
  try {
584
- return JSON.parse(readFileSync4(employeesPath, "utf-8"));
635
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
585
636
  } catch {
586
637
  return [];
587
638
  }
@@ -600,7 +651,7 @@ var init_employees = __esm({
600
651
  "src/lib/employees.ts"() {
601
652
  "use strict";
602
653
  init_config();
603
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
654
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
604
655
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
605
656
  COORDINATOR_ROLE = "COO";
606
657
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
@@ -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 } 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(fileURLToPath(import.meta.url));
667
- const { root } = path5.parse(dir);
717
+ let dir = path6.dirname(fileURLToPath(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) {
@@ -2317,11 +2368,11 @@ __export(tasks_crud_exports, {
2317
2368
  writeCheckpoint: () => writeCheckpoint
2318
2369
  });
2319
2370
  import crypto3 from "crypto";
2320
- import path9 from "path";
2371
+ import path10 from "path";
2321
2372
  import os6 from "os";
2322
2373
  import { execSync as execSync4 } from "child_process";
2323
2374
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2324
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
2375
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2325
2376
  async function writeCheckpoint(input) {
2326
2377
  const client = getClient();
2327
2378
  const row = await resolveTask(client, input.taskId);
@@ -2496,8 +2547,8 @@ ${laneWarning}` : laneWarning;
2496
2547
  }
2497
2548
  if (input.baseDir) {
2498
2549
  try {
2499
- await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
2500
- await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2550
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2551
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2501
2552
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2502
2553
  await ensureGitignoreExe(input.baseDir);
2503
2554
  } catch {
@@ -2533,10 +2584,10 @@ ${laneWarning}` : laneWarning;
2533
2584
  });
2534
2585
  if (input.baseDir) {
2535
2586
  try {
2536
- const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
2537
- const mdPath = path9.join(EXE_OS_DIR, taskFile);
2538
- const mdDir = path9.dirname(mdPath);
2539
- if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2587
+ const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
2588
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
2589
+ const mdDir = path10.dirname(mdPath);
2590
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2540
2591
  const reviewer = input.reviewer ?? input.assignedBy;
2541
2592
  const mdContent = `# ${input.title}
2542
2593
 
@@ -2561,7 +2612,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
2561
2612
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
2562
2613
  `;
2563
2614
  await writeFile3(mdPath, mdContent, "utf-8");
2564
- } catch {
2615
+ } catch (err) {
2616
+ process.stderr.write(
2617
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2618
+ `
2619
+ );
2565
2620
  }
2566
2621
  }
2567
2622
  return {
@@ -2821,9 +2876,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2821
2876
  return { taskFile, assignedTo, assignedBy, taskSlug };
2822
2877
  }
2823
2878
  async function ensureArchitectureDoc(baseDir, projectName2) {
2824
- const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
2879
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2825
2880
  try {
2826
- if (existsSync9(archPath)) return;
2881
+ if (existsSync10(archPath)) return;
2827
2882
  const template = [
2828
2883
  `# ${projectName2} \u2014 System Architecture`,
2829
2884
  "",
@@ -2856,10 +2911,10 @@ async function ensureArchitectureDoc(baseDir, projectName2) {
2856
2911
  }
2857
2912
  }
2858
2913
  async function ensureGitignoreExe(baseDir) {
2859
- const gitignorePath = path9.join(baseDir, ".gitignore");
2914
+ const gitignorePath = path10.join(baseDir, ".gitignore");
2860
2915
  try {
2861
- if (existsSync9(gitignorePath)) {
2862
- const content = readFileSync9(gitignorePath, "utf-8");
2916
+ if (existsSync10(gitignorePath)) {
2917
+ const content = readFileSync10(gitignorePath, "utf-8");
2863
2918
  if (/^\/?exe\/?$/m.test(content)) return;
2864
2919
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2865
2920
  } else {
@@ -2890,8 +2945,8 @@ var init_tasks_crud = __esm({
2890
2945
  });
2891
2946
 
2892
2947
  // src/lib/tasks-review.ts
2893
- import path10 from "path";
2894
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2948
+ import path11 from "path";
2949
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2895
2950
  async function countPendingReviews(sessionScope) {
2896
2951
  const client = getClient();
2897
2952
  if (sessionScope) {
@@ -3072,11 +3127,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3072
3127
  );
3073
3128
  }
3074
3129
  try {
3075
- const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
3076
- if (existsSync10(cacheDir)) {
3130
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3131
+ if (existsSync11(cacheDir)) {
3077
3132
  for (const f of readdirSync2(cacheDir)) {
3078
3133
  if (f.startsWith("review-notified-")) {
3079
- unlinkSync4(path10.join(cacheDir, f));
3134
+ unlinkSync4(path11.join(cacheDir, f));
3080
3135
  }
3081
3136
  }
3082
3137
  }
@@ -3097,7 +3152,7 @@ var init_tasks_review = __esm({
3097
3152
  });
3098
3153
 
3099
3154
  // src/lib/tasks-chain.ts
3100
- import path11 from "path";
3155
+ import path12 from "path";
3101
3156
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3102
3157
  async function cascadeUnblock(taskId, baseDir, now) {
3103
3158
  const client = getClient();
@@ -3114,7 +3169,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3114
3169
  });
3115
3170
  for (const ur of unblockedRows.rows) {
3116
3171
  try {
3117
- const ubFile = path11.join(baseDir, String(ur.task_file));
3172
+ const ubFile = path12.join(baseDir, String(ur.task_file));
3118
3173
  let ubContent = await readFile3(ubFile, "utf-8");
3119
3174
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3120
3175
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3183,7 +3238,7 @@ var init_tasks_chain = __esm({
3183
3238
 
3184
3239
  // src/lib/project-name.ts
3185
3240
  import { execSync as execSync5 } from "child_process";
3186
- import path12 from "path";
3241
+ import path13 from "path";
3187
3242
  function getProjectName(cwd) {
3188
3243
  const dir = cwd ?? process.cwd();
3189
3244
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3196,7 +3251,7 @@ function getProjectName(cwd) {
3196
3251
  timeout: 2e3,
3197
3252
  stdio: ["pipe", "pipe", "pipe"]
3198
3253
  }).trim();
3199
- repoRoot = path12.dirname(gitCommonDir);
3254
+ repoRoot = path13.dirname(gitCommonDir);
3200
3255
  } catch {
3201
3256
  repoRoot = execSync5("git rev-parse --show-toplevel", {
3202
3257
  cwd: dir,
@@ -3205,11 +3260,11 @@ function getProjectName(cwd) {
3205
3260
  stdio: ["pipe", "pipe", "pipe"]
3206
3261
  }).trim();
3207
3262
  }
3208
- _cached2 = path12.basename(repoRoot);
3263
+ _cached2 = path13.basename(repoRoot);
3209
3264
  _cachedCwd = dir;
3210
3265
  return _cached2;
3211
3266
  } catch {
3212
- _cached2 = path12.basename(dir);
3267
+ _cached2 = path13.basename(dir);
3213
3268
  _cachedCwd = dir;
3214
3269
  return _cached2;
3215
3270
  }
@@ -3682,8 +3737,8 @@ __export(tasks_exports, {
3682
3737
  updateTaskStatus: () => updateTaskStatus,
3683
3738
  writeCheckpoint: () => writeCheckpoint
3684
3739
  });
3685
- import path13 from "path";
3686
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
3740
+ import path14 from "path";
3741
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3687
3742
  async function createTask(input) {
3688
3743
  const result = await createTaskCore(input);
3689
3744
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3702,11 +3757,11 @@ async function updateTask(input) {
3702
3757
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3703
3758
  try {
3704
3759
  const agent = String(row.assigned_to);
3705
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3706
- const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3760
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3761
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3707
3762
  if (input.status === "in_progress") {
3708
- mkdirSync4(cacheDir, { recursive: true });
3709
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3763
+ mkdirSync5(cacheDir, { recursive: true });
3764
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3710
3765
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3711
3766
  try {
3712
3767
  unlinkSync5(cachePath);
@@ -4173,13 +4228,13 @@ __export(tmux_routing_exports, {
4173
4228
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4174
4229
  });
4175
4230
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
4176
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4177
- import path14 from "path";
4231
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4232
+ import path15 from "path";
4178
4233
  import os7 from "os";
4179
4234
  import { fileURLToPath as fileURLToPath2 } from "url";
4180
4235
  import { unlinkSync as unlinkSync6 } from "fs";
4181
4236
  function spawnLockPath(sessionName) {
4182
- return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4237
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4183
4238
  }
4184
4239
  function isProcessAlive(pid) {
4185
4240
  try {
@@ -4190,13 +4245,13 @@ function isProcessAlive(pid) {
4190
4245
  }
4191
4246
  }
4192
4247
  function acquireSpawnLock2(sessionName) {
4193
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4194
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4248
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4249
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4195
4250
  }
4196
4251
  const lockFile = spawnLockPath(sessionName);
4197
- if (existsSync11(lockFile)) {
4252
+ if (existsSync12(lockFile)) {
4198
4253
  try {
4199
- const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4254
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
4200
4255
  const age = Date.now() - lock.timestamp;
4201
4256
  if (isProcessAlive(lock.pid) && age < 6e4) {
4202
4257
  return false;
@@ -4204,7 +4259,7 @@ function acquireSpawnLock2(sessionName) {
4204
4259
  } catch {
4205
4260
  }
4206
4261
  }
4207
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4262
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4208
4263
  return true;
4209
4264
  }
4210
4265
  function releaseSpawnLock2(sessionName) {
@@ -4216,13 +4271,13 @@ function releaseSpawnLock2(sessionName) {
4216
4271
  function resolveBehaviorsExporterScript() {
4217
4272
  try {
4218
4273
  const thisFile = fileURLToPath2(import.meta.url);
4219
- const scriptPath = path14.join(
4220
- path14.dirname(thisFile),
4274
+ const scriptPath = path15.join(
4275
+ path15.dirname(thisFile),
4221
4276
  "..",
4222
4277
  "bin",
4223
4278
  "exe-export-behaviors.js"
4224
4279
  );
4225
- return existsSync11(scriptPath) ? scriptPath : null;
4280
+ return existsSync12(scriptPath) ? scriptPath : null;
4226
4281
  } catch {
4227
4282
  return null;
4228
4283
  }
@@ -4288,12 +4343,12 @@ function extractRootExe(name) {
4288
4343
  return parts.length > 0 ? parts[parts.length - 1] : null;
4289
4344
  }
4290
4345
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4291
- if (!existsSync11(SESSION_CACHE)) {
4292
- mkdirSync5(SESSION_CACHE, { recursive: true });
4346
+ if (!existsSync12(SESSION_CACHE)) {
4347
+ mkdirSync6(SESSION_CACHE, { recursive: true });
4293
4348
  }
4294
4349
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4295
- const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4296
- writeFileSync6(filePath, JSON.stringify({
4350
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4351
+ writeFileSync7(filePath, JSON.stringify({
4297
4352
  parentExe: rootExe,
4298
4353
  dispatchedBy: dispatchedBy || rootExe,
4299
4354
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4301,7 +4356,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4301
4356
  }
4302
4357
  function getParentExe(sessionKey) {
4303
4358
  try {
4304
- const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4359
+ const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4305
4360
  return data.parentExe || null;
4306
4361
  } catch {
4307
4362
  return null;
@@ -4309,8 +4364,8 @@ function getParentExe(sessionKey) {
4309
4364
  }
4310
4365
  function getDispatchedBy(sessionKey) {
4311
4366
  try {
4312
- const data = JSON.parse(readFileSync10(
4313
- path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4367
+ const data = JSON.parse(readFileSync11(
4368
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4314
4369
  "utf8"
4315
4370
  ));
4316
4371
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4371,32 +4426,50 @@ async function verifyPaneAtCapacity(sessionName) {
4371
4426
  }
4372
4427
  function readDebounceState() {
4373
4428
  try {
4374
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4375
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
4429
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4430
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4431
+ const state = {};
4432
+ for (const [key, val] of Object.entries(raw)) {
4433
+ if (typeof val === "number") {
4434
+ state[key] = { lastSent: val, pending: 0 };
4435
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4436
+ state[key] = val;
4437
+ }
4438
+ }
4439
+ return state;
4376
4440
  } catch {
4377
4441
  return {};
4378
4442
  }
4379
4443
  }
4380
4444
  function writeDebounceState(state) {
4381
4445
  try {
4382
- if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4383
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4446
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4447
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4384
4448
  } catch {
4385
4449
  }
4386
4450
  }
4387
4451
  function isDebounced(targetSession) {
4388
4452
  const state = readDebounceState();
4389
- const lastSent = state[targetSession] ?? 0;
4390
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4453
+ const entry = state[targetSession];
4454
+ const lastSent = entry?.lastSent ?? 0;
4455
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4456
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4457
+ state[targetSession].pending++;
4458
+ writeDebounceState(state);
4459
+ return true;
4460
+ }
4461
+ return false;
4391
4462
  }
4392
4463
  function recordDebounce(targetSession) {
4393
4464
  const state = readDebounceState();
4394
- state[targetSession] = Date.now();
4465
+ const batched = state[targetSession]?.pending ?? 0;
4466
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4395
4467
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4396
4468
  for (const key of Object.keys(state)) {
4397
- if ((state[key] ?? 0) < cutoff) delete state[key];
4469
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4398
4470
  }
4399
4471
  writeDebounceState(state);
4472
+ return batched;
4400
4473
  }
4401
4474
  function logIntercom(msg) {
4402
4475
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4441,7 +4514,7 @@ function sendIntercom(targetSession) {
4441
4514
  return "skipped_exe";
4442
4515
  }
4443
4516
  if (isDebounced(targetSession)) {
4444
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4517
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4445
4518
  return "debounced";
4446
4519
  }
4447
4520
  try {
@@ -4453,14 +4526,14 @@ function sendIntercom(targetSession) {
4453
4526
  const sessionState = getSessionState(targetSession);
4454
4527
  if (sessionState === "no_claude") {
4455
4528
  queueIntercom(targetSession, "claude not running in session");
4456
- recordDebounce(targetSession);
4457
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4529
+ const batched2 = recordDebounce(targetSession);
4530
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4458
4531
  return "queued";
4459
4532
  }
4460
4533
  if (sessionState === "thinking" || sessionState === "tool") {
4461
4534
  queueIntercom(targetSession, "session busy at send time");
4462
- recordDebounce(targetSession);
4463
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4535
+ const batched2 = recordDebounce(targetSession);
4536
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4464
4537
  return "queued";
4465
4538
  }
4466
4539
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4468,8 +4541,8 @@ function sendIntercom(targetSession) {
4468
4541
  transport.sendKeys(targetSession, "q");
4469
4542
  }
4470
4543
  transport.sendKeys(targetSession, "/exe-intercom");
4471
- recordDebounce(targetSession);
4472
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4544
+ const batched = recordDebounce(targetSession);
4545
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4473
4546
  return "delivered";
4474
4547
  } catch {
4475
4548
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4571,26 +4644,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4571
4644
  const transport = getTransport();
4572
4645
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4573
4646
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4574
- const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4575
- const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4576
- if (!existsSync11(logDir)) {
4577
- mkdirSync5(logDir, { recursive: true });
4647
+ const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
4648
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4649
+ if (!existsSync12(logDir)) {
4650
+ mkdirSync6(logDir, { recursive: true });
4578
4651
  }
4579
4652
  transport.kill(sessionName);
4580
4653
  let cleanupSuffix = "";
4581
4654
  try {
4582
4655
  const thisFile = fileURLToPath2(import.meta.url);
4583
- const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4584
- if (existsSync11(cleanupScript)) {
4656
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4657
+ if (existsSync12(cleanupScript)) {
4585
4658
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4586
4659
  }
4587
4660
  } catch {
4588
4661
  }
4589
4662
  try {
4590
- const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
4663
+ const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
4591
4664
  let claudeJson = {};
4592
4665
  try {
4593
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
4666
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4594
4667
  } catch {
4595
4668
  }
4596
4669
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4598,17 +4671,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4598
4671
  const trustDir = opts?.cwd ?? projectDir;
4599
4672
  if (!projects[trustDir]) projects[trustDir] = {};
4600
4673
  projects[trustDir].hasTrustDialogAccepted = true;
4601
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4674
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4602
4675
  } catch {
4603
4676
  }
4604
4677
  try {
4605
- const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
4678
+ const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
4606
4679
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4607
- const projSettingsDir = path14.join(settingsDir, normalizedKey);
4608
- const settingsPath = path14.join(projSettingsDir, "settings.json");
4680
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
4681
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
4609
4682
  let settings = {};
4610
4683
  try {
4611
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
4684
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4612
4685
  } catch {
4613
4686
  }
4614
4687
  const perms = settings.permissions ?? {};
@@ -4636,20 +4709,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4636
4709
  if (changed) {
4637
4710
  perms.allow = allow;
4638
4711
  settings.permissions = perms;
4639
- mkdirSync5(projSettingsDir, { recursive: true });
4640
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4712
+ mkdirSync6(projSettingsDir, { recursive: true });
4713
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4641
4714
  }
4642
4715
  } catch {
4643
4716
  }
4644
4717
  const spawnCwd = opts?.cwd ?? projectDir;
4645
4718
  const useExeAgent = !!(opts?.model && opts?.provider);
4646
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4719
+ const agentRtConfig = getAgentRuntime(employeeName);
4720
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4721
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4722
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4647
4723
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4648
4724
  let identityFlag = "";
4649
4725
  let behaviorsFlag = "";
4650
4726
  let legacyFallbackWarned = false;
4651
4727
  if (!useExeAgent && !useBinSymlink) {
4652
- const identityPath = path14.join(
4728
+ const identityPath = path15.join(
4653
4729
  os7.homedir(),
4654
4730
  ".exe-os",
4655
4731
  "identity",
@@ -4659,13 +4735,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4659
4735
  const hasAgentFlag = claudeSupportsAgentFlag();
4660
4736
  if (hasAgentFlag) {
4661
4737
  identityFlag = ` --agent ${employeeName}`;
4662
- } else if (existsSync11(identityPath)) {
4738
+ } else if (existsSync12(identityPath)) {
4663
4739
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4664
4740
  legacyFallbackWarned = true;
4665
4741
  }
4666
4742
  const behaviorsFile = exportBehaviorsSync(
4667
4743
  employeeName,
4668
- path14.basename(spawnCwd),
4744
+ path15.basename(spawnCwd),
4669
4745
  sessionName
4670
4746
  );
4671
4747
  if (behaviorsFile) {
@@ -4680,16 +4756,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4680
4756
  }
4681
4757
  let sessionContextFlag = "";
4682
4758
  try {
4683
- const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4684
- mkdirSync5(ctxDir, { recursive: true });
4685
- const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4759
+ const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
4760
+ mkdirSync6(ctxDir, { recursive: true });
4761
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4686
4762
  const ctxContent = [
4687
4763
  `## Session Context`,
4688
4764
  `You are running in tmux session: ${sessionName}.`,
4689
4765
  `Your parent coordinator session is ${exeSession}.`,
4690
4766
  `Your employees (if any) use the -${exeSession} suffix.`
4691
4767
  ].join("\n");
4692
- writeFileSync6(ctxFile, ctxContent);
4768
+ writeFileSync7(ctxFile, ctxContent);
4693
4769
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4694
4770
  } catch {
4695
4771
  }
@@ -4703,9 +4779,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4703
4779
  }
4704
4780
  }
4705
4781
  }
4782
+ if (useCodex) {
4783
+ const codexCfg = RUNTIME_TABLE.codex;
4784
+ if (codexCfg?.apiKeyEnv) {
4785
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4786
+ if (keyVal) {
4787
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4788
+ }
4789
+ }
4790
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4791
+ }
4792
+ if (useOpencode) {
4793
+ const ocCfg = PROVIDER_TABLE.opencode;
4794
+ if (ocCfg?.apiKeyEnv) {
4795
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4796
+ if (keyVal) {
4797
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4798
+ }
4799
+ }
4800
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4801
+ }
4802
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4803
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4804
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4805
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4806
+ }
4807
+ }
4706
4808
  let spawnCommand;
4707
4809
  if (useExeAgent) {
4708
4810
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4811
+ } else if (useCodex) {
4812
+ process.stderr.write(
4813
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4814
+ `
4815
+ );
4816
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
4817
+ } else if (useOpencode) {
4818
+ const binName = `${employeeName}-opencode`;
4819
+ process.stderr.write(
4820
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
4821
+ `
4822
+ );
4823
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4709
4824
  } else if (useBinSymlink) {
4710
4825
  const binName = `${employeeName}-${ccProvider}`;
4711
4826
  process.stderr.write(
@@ -4727,11 +4842,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4727
4842
  transport.pipeLog(sessionName, logFile);
4728
4843
  try {
4729
4844
  const mySession = getMySession();
4730
- const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4731
- writeFileSync6(dispatchInfo, JSON.stringify({
4845
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4846
+ writeFileSync7(dispatchInfo, JSON.stringify({
4732
4847
  dispatchedBy: mySession,
4733
4848
  rootExe: exeSession,
4734
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4849
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4850
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4851
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4735
4852
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4736
4853
  }));
4737
4854
  } catch {
@@ -4749,6 +4866,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4749
4866
  booted = true;
4750
4867
  break;
4751
4868
  }
4869
+ } else if (useCodex) {
4870
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4871
+ booted = true;
4872
+ break;
4873
+ }
4752
4874
  } else {
4753
4875
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4754
4876
  booted = true;
@@ -4760,9 +4882,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4760
4882
  }
4761
4883
  if (!booted) {
4762
4884
  releaseSpawnLock2(sessionName);
4763
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4885
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
4886
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4764
4887
  }
4765
- if (!useExeAgent) {
4888
+ if (!useExeAgent && !useCodex) {
4766
4889
  try {
4767
4890
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4768
4891
  } catch {
@@ -4789,17 +4912,19 @@ var init_tmux_routing = __esm({
4789
4912
  init_cc_agent_support();
4790
4913
  init_mcp_prefix();
4791
4914
  init_provider_table();
4915
+ init_agent_config();
4916
+ init_runtime_table();
4792
4917
  init_intercom_queue();
4793
4918
  init_plan_limits();
4794
4919
  init_employees();
4795
- SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4796
- SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4920
+ SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
4921
+ SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
4797
4922
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4798
4923
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4799
4924
  VERIFY_PANE_LINES = 200;
4800
4925
  INTERCOM_DEBOUNCE_MS = 3e4;
4801
- INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4802
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4926
+ INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
4927
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4803
4928
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4804
4929
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4805
4930
  }
@@ -4840,14 +4965,14 @@ var init_memory = __esm({
4840
4965
 
4841
4966
  // src/lib/keychain.ts
4842
4967
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4843
- import { existsSync as existsSync12 } from "fs";
4844
- import path15 from "path";
4968
+ import { existsSync as existsSync13 } from "fs";
4969
+ import path16 from "path";
4845
4970
  import os8 from "os";
4846
4971
  function getKeyDir() {
4847
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
4972
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
4848
4973
  }
4849
4974
  function getKeyPath() {
4850
- return path15.join(getKeyDir(), "master.key");
4975
+ return path16.join(getKeyDir(), "master.key");
4851
4976
  }
4852
4977
  async function tryKeytar() {
4853
4978
  try {
@@ -4868,7 +4993,7 @@ async function getMasterKey() {
4868
4993
  }
4869
4994
  }
4870
4995
  const keyPath = getKeyPath();
4871
- if (!existsSync12(keyPath)) {
4996
+ if (!existsSync13(keyPath)) {
4872
4997
  process.stderr.write(
4873
4998
  `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4874
4999
  `
@@ -4908,13 +5033,13 @@ __export(shard_manager_exports, {
4908
5033
  listShards: () => listShards,
4909
5034
  shardExists: () => shardExists
4910
5035
  });
4911
- import path16 from "path";
4912
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5036
+ import path17 from "path";
5037
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
4913
5038
  import { createClient as createClient2 } from "@libsql/client";
4914
5039
  function initShardManager(encryptionKey) {
4915
5040
  _encryptionKey = encryptionKey;
4916
- if (!existsSync13(SHARDS_DIR)) {
4917
- mkdirSync6(SHARDS_DIR, { recursive: true });
5041
+ if (!existsSync14(SHARDS_DIR)) {
5042
+ mkdirSync7(SHARDS_DIR, { recursive: true });
4918
5043
  }
4919
5044
  _shardingEnabled = true;
4920
5045
  }
@@ -4934,7 +5059,7 @@ function getShardClient(projectName2) {
4934
5059
  }
4935
5060
  const cached = _shards.get(safeName);
4936
5061
  if (cached) return cached;
4937
- const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
5062
+ const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
4938
5063
  const client = createClient2({
4939
5064
  url: `file:${dbPath}`,
4940
5065
  encryptionKey: _encryptionKey
@@ -4944,10 +5069,10 @@ function getShardClient(projectName2) {
4944
5069
  }
4945
5070
  function shardExists(projectName2) {
4946
5071
  const safeName = projectName2.replace(/[^a-zA-Z0-9_-]/g, "_");
4947
- return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
5072
+ return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
4948
5073
  }
4949
5074
  function listShards() {
4950
- if (!existsSync13(SHARDS_DIR)) return [];
5075
+ if (!existsSync14(SHARDS_DIR)) return [];
4951
5076
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4952
5077
  }
4953
5078
  async function ensureShardSchema(client) {
@@ -5133,7 +5258,7 @@ var init_shard_manager = __esm({
5133
5258
  "src/lib/shard-manager.ts"() {
5134
5259
  "use strict";
5135
5260
  init_config();
5136
- SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
5261
+ SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
5137
5262
  _shards = /* @__PURE__ */ new Map();
5138
5263
  _encryptionKey = null;
5139
5264
  _shardingEnabled = false;
@@ -6094,13 +6219,13 @@ async function sweepTasks(projectName2, options = {}) {
6094
6219
  }
6095
6220
 
6096
6221
  // src/bin/git-sweep.ts
6097
- import path17 from "path";
6222
+ import path18 from "path";
6098
6223
  var args = process.argv.slice(2);
6099
6224
  var dryRun = args.includes("--dry-run");
6100
6225
  var projectIdx = args.indexOf("--project");
6101
6226
  var limitIdx = args.indexOf("--limit");
6102
6227
  var staleIdx = args.indexOf("--stale");
6103
- var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path17.sep).pop();
6228
+ var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path18.sep).pop();
6104
6229
  var commitLimit = limitIdx >= 0 ? Number(args[limitIdx + 1]) : void 0;
6105
6230
  var staleMinutes = staleIdx >= 0 ? Number(args[staleIdx + 1]) : void 0;
6106
6231
  async function main() {