@askexenow/exe-os 0.8.83 → 0.8.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/bin/backfill-conversations.js +746 -595
  2. package/dist/bin/backfill-responses.js +745 -594
  3. package/dist/bin/backfill-vectors.js +312 -226
  4. package/dist/bin/cleanup-stale-review-tasks.js +154 -21
  5. package/dist/bin/cli.js +14678 -12676
  6. package/dist/bin/exe-agent-config.js +242 -0
  7. package/dist/bin/exe-agent.js +100 -91
  8. package/dist/bin/exe-assign.js +1003 -854
  9. package/dist/bin/exe-boot.js +1420 -485
  10. package/dist/bin/exe-call.js +10 -0
  11. package/dist/bin/exe-cloud.js +29 -6
  12. package/dist/bin/exe-dispatch.js +572 -271
  13. package/dist/bin/exe-doctor.js +403 -6
  14. package/dist/bin/exe-export-behaviors.js +175 -72
  15. package/dist/bin/exe-forget.js +102 -3
  16. package/dist/bin/exe-gateway.js +796 -292
  17. package/dist/bin/exe-healthcheck.js +134 -1
  18. package/dist/bin/exe-heartbeat.js +172 -36
  19. package/dist/bin/exe-kill.js +175 -72
  20. package/dist/bin/exe-launch-agent.js +189 -76
  21. package/dist/bin/exe-link.js +927 -82
  22. package/dist/bin/exe-new-employee.js +60 -8
  23. package/dist/bin/exe-pending-messages.js +151 -19
  24. package/dist/bin/exe-pending-notifications.js +97 -2
  25. package/dist/bin/exe-pending-reviews.js +155 -22
  26. package/dist/bin/exe-rename.js +564 -23
  27. package/dist/bin/exe-review.js +231 -73
  28. package/dist/bin/exe-search.js +995 -228
  29. package/dist/bin/exe-session-cleanup.js +4930 -1664
  30. package/dist/bin/exe-settings.js +20 -5
  31. package/dist/bin/exe-start-codex.js +2598 -0
  32. package/dist/bin/exe-start.sh +15 -3
  33. package/dist/bin/exe-status.js +154 -21
  34. package/dist/bin/exe-team.js +97 -2
  35. package/dist/bin/git-sweep.js +1180 -363
  36. package/dist/bin/graph-backfill.js +175 -72
  37. package/dist/bin/graph-export.js +175 -72
  38. package/dist/bin/install.js +60 -7
  39. package/dist/bin/list-providers.js +1 -0
  40. package/dist/bin/scan-tasks.js +1185 -367
  41. package/dist/bin/setup.js +914 -270
  42. package/dist/bin/shard-migrate.js +175 -72
  43. package/dist/bin/update.js +1 -0
  44. package/dist/bin/wiki-sync.js +175 -72
  45. package/dist/gateway/index.js +792 -285
  46. package/dist/hooks/bug-report-worker.js +445 -135
  47. package/dist/hooks/commit-complete.js +1178 -361
  48. package/dist/hooks/error-recall.js +994 -228
  49. package/dist/hooks/ingest-worker.js +1799 -1234
  50. package/dist/hooks/ingest.js +3 -0
  51. package/dist/hooks/instructions-loaded.js +707 -97
  52. package/dist/hooks/notification.js +699 -89
  53. package/dist/hooks/post-compact.js +757 -109
  54. package/dist/hooks/pre-compact.js +1061 -244
  55. package/dist/hooks/pre-tool-use.js +787 -130
  56. package/dist/hooks/prompt-ingest-worker.js +242 -101
  57. package/dist/hooks/prompt-submit.js +1121 -299
  58. package/dist/hooks/response-ingest-worker.js +242 -101
  59. package/dist/hooks/session-end.js +4063 -397
  60. package/dist/hooks/session-start.js +1071 -254
  61. package/dist/hooks/stop.js +768 -120
  62. package/dist/hooks/subagent-stop.js +757 -109
  63. package/dist/hooks/summary-worker.js +1706 -1011
  64. package/dist/index.js +1821 -1098
  65. package/dist/lib/agent-config.js +167 -0
  66. package/dist/lib/cloud-sync.js +932 -88
  67. package/dist/lib/consolidation.js +2 -1
  68. package/dist/lib/database.js +642 -87
  69. package/dist/lib/db-daemon-client.js +503 -0
  70. package/dist/lib/device-registry.js +547 -7
  71. package/dist/lib/embedder.js +14 -28
  72. package/dist/lib/employee-templates.js +84 -74
  73. package/dist/lib/employees.js +9 -0
  74. package/dist/lib/exe-daemon-client.js +16 -29
  75. package/dist/lib/exe-daemon.js +2733 -1575
  76. package/dist/lib/hybrid-search.js +995 -228
  77. package/dist/lib/identity.js +87 -67
  78. package/dist/lib/keychain.js +9 -1
  79. package/dist/lib/messaging.js +103 -40
  80. package/dist/lib/reminders.js +91 -74
  81. package/dist/lib/runtime-table.js +16 -0
  82. package/dist/lib/schedules.js +96 -2
  83. package/dist/lib/session-wrappers.js +22 -0
  84. package/dist/lib/skill-learning.js +103 -85
  85. package/dist/lib/store.js +234 -73
  86. package/dist/lib/tasks.js +348 -134
  87. package/dist/lib/tmux-routing.js +422 -208
  88. package/dist/lib/token-spend.js +273 -0
  89. package/dist/lib/ws-client.js +11 -0
  90. package/dist/mcp/server.js +5742 -696
  91. package/dist/mcp/tools/complete-reminder.js +94 -77
  92. package/dist/mcp/tools/create-reminder.js +94 -77
  93. package/dist/mcp/tools/create-task.js +375 -152
  94. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  95. package/dist/mcp/tools/list-reminders.js +94 -77
  96. package/dist/mcp/tools/list-tasks.js +99 -31
  97. package/dist/mcp/tools/send-message.js +108 -45
  98. package/dist/mcp/tools/update-task.js +162 -77
  99. package/dist/runtime/index.js +1075 -258
  100. package/dist/tui/App.js +1333 -506
  101. package/package.json +6 -1
  102. package/src/commands/exe/agent-config.md +27 -0
  103. package/src/commands/exe/cc-doctor.md +10 -0
package/dist/tui/App.js CHANGED
@@ -322,110 +322,6 @@ var init_provider_table = __esm({
322
322
  }
323
323
  });
324
324
 
325
- // src/lib/intercom-queue.ts
326
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
327
- import path2 from "path";
328
- import os2 from "os";
329
- function ensureDir() {
330
- const dir = path2.dirname(QUEUE_PATH);
331
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
332
- }
333
- function readQueue() {
334
- try {
335
- if (!existsSync3(QUEUE_PATH)) return [];
336
- return JSON.parse(readFileSync3(QUEUE_PATH, "utf8"));
337
- } catch {
338
- return [];
339
- }
340
- }
341
- function writeQueue(queue) {
342
- ensureDir();
343
- const tmp = `${QUEUE_PATH}.tmp`;
344
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
345
- renameSync(tmp, QUEUE_PATH);
346
- }
347
- function queueIntercom(targetSession, reason) {
348
- const queue = readQueue();
349
- const existing = queue.find((q) => q.targetSession === targetSession);
350
- if (existing) {
351
- existing.attempts++;
352
- existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
353
- existing.reason = reason;
354
- } else {
355
- queue.push({
356
- targetSession,
357
- queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
358
- attempts: 0,
359
- reason
360
- });
361
- }
362
- writeQueue(queue);
363
- }
364
- var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
365
- var init_intercom_queue = __esm({
366
- "src/lib/intercom-queue.ts"() {
367
- "use strict";
368
- QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
369
- TTL_MS = 60 * 60 * 1e3;
370
- INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
371
- }
372
- });
373
-
374
- // src/lib/db-retry.ts
375
- function isBusyError(err) {
376
- if (err instanceof Error) {
377
- const msg = err.message.toLowerCase();
378
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
379
- }
380
- return false;
381
- }
382
- function delay(ms) {
383
- return new Promise((resolve) => setTimeout(resolve, ms));
384
- }
385
- async function retryOnBusy(fn, label) {
386
- let lastError;
387
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
388
- try {
389
- return await fn();
390
- } catch (err) {
391
- lastError = err;
392
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
393
- throw err;
394
- }
395
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
396
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
397
- process.stderr.write(
398
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
399
- `
400
- );
401
- await delay(backoff + jitter);
402
- }
403
- }
404
- throw lastError;
405
- }
406
- function wrapWithRetry(client) {
407
- return new Proxy(client, {
408
- get(target, prop, receiver) {
409
- if (prop === "execute") {
410
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
411
- }
412
- if (prop === "batch") {
413
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
414
- }
415
- return Reflect.get(target, prop, receiver);
416
- }
417
- });
418
- }
419
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
420
- var init_db_retry = __esm({
421
- "src/lib/db-retry.ts"() {
422
- "use strict";
423
- MAX_RETRIES = 3;
424
- BASE_DELAY_MS = 200;
425
- MAX_JITTER_MS = 300;
426
- }
427
- });
428
-
429
325
  // src/lib/config.ts
430
326
  var config_exports = {};
431
327
  __export(config_exports, {
@@ -443,17 +339,17 @@ __export(config_exports, {
443
339
  saveConfig: () => saveConfig
444
340
  });
445
341
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
446
- import { readFileSync as readFileSync4, existsSync as existsSync4, renameSync as renameSync2 } from "fs";
447
- import path3 from "path";
448
- import os3 from "os";
342
+ import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync } from "fs";
343
+ import path2 from "path";
344
+ import os2 from "os";
449
345
  function resolveDataDir() {
450
346
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
451
347
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
452
- const newDir = path3.join(os3.homedir(), ".exe-os");
453
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
454
- if (!existsSync4(newDir) && existsSync4(legacyDir)) {
348
+ const newDir = path2.join(os2.homedir(), ".exe-os");
349
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
350
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
455
351
  try {
456
- renameSync2(legacyDir, newDir);
352
+ renameSync(legacyDir, newDir);
457
353
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
458
354
  `);
459
355
  } catch {
@@ -515,9 +411,9 @@ function normalizeAutoUpdate(raw) {
515
411
  async function loadConfig() {
516
412
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
517
413
  await mkdir(dir, { recursive: true });
518
- const configPath = path3.join(dir, "config.json");
519
- if (!existsSync4(configPath)) {
520
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
414
+ const configPath = path2.join(dir, "config.json");
415
+ if (!existsSync3(configPath)) {
416
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
521
417
  }
522
418
  const raw = await readFile(configPath, "utf-8");
523
419
  try {
@@ -535,38 +431,38 @@ async function loadConfig() {
535
431
  normalizeScalingRoadmap(migratedCfg);
536
432
  normalizeSessionLifecycle(migratedCfg);
537
433
  normalizeAutoUpdate(migratedCfg);
538
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
434
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
539
435
  if (config.dbPath.startsWith("~")) {
540
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
436
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
541
437
  }
542
438
  return config;
543
439
  } catch {
544
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
440
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
545
441
  }
546
442
  }
547
443
  function loadConfigSync() {
548
444
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
549
- const configPath = path3.join(dir, "config.json");
550
- if (!existsSync4(configPath)) {
551
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
445
+ const configPath = path2.join(dir, "config.json");
446
+ if (!existsSync3(configPath)) {
447
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
552
448
  }
553
449
  try {
554
- const raw = readFileSync4(configPath, "utf-8");
450
+ const raw = readFileSync3(configPath, "utf-8");
555
451
  let parsed = JSON.parse(raw);
556
452
  parsed = migrateLegacyConfig(parsed);
557
453
  const { config: migratedCfg } = migrateConfig(parsed);
558
454
  normalizeScalingRoadmap(migratedCfg);
559
455
  normalizeSessionLifecycle(migratedCfg);
560
456
  normalizeAutoUpdate(migratedCfg);
561
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
457
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
562
458
  } catch {
563
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
459
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
564
460
  }
565
461
  }
566
462
  async function saveConfig(config) {
567
463
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
568
464
  await mkdir(dir, { recursive: true });
569
- const configPath = path3.join(dir, "config.json");
465
+ const configPath = path2.join(dir, "config.json");
570
466
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
571
467
  if (config.cloud?.apiKey) {
572
468
  await chmod(configPath, 384);
@@ -591,10 +487,10 @@ var init_config = __esm({
591
487
  "src/lib/config.ts"() {
592
488
  "use strict";
593
489
  EXE_AI_DIR = resolveDataDir();
594
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
595
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
596
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
597
- LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
490
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
491
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
492
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
493
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
598
494
  CURRENT_CONFIG_VERSION = 1;
599
495
  DEFAULT_CONFIG = {
600
496
  config_version: CURRENT_CONFIG_VERSION,
@@ -666,6 +562,161 @@ var init_config = __esm({
666
562
  }
667
563
  });
668
564
 
565
+ // src/lib/runtime-table.ts
566
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
567
+ var init_runtime_table = __esm({
568
+ "src/lib/runtime-table.ts"() {
569
+ "use strict";
570
+ RUNTIME_TABLE = {
571
+ codex: {
572
+ binary: "codex",
573
+ launchMode: "exec",
574
+ autoApproveFlag: "--full-auto",
575
+ inlineFlag: "--no-alt-screen",
576
+ apiKeyEnv: "OPENAI_API_KEY",
577
+ defaultModel: "gpt-5.4"
578
+ }
579
+ };
580
+ DEFAULT_RUNTIME = "claude";
581
+ }
582
+ });
583
+
584
+ // src/lib/agent-config.ts
585
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
586
+ import path3 from "path";
587
+ function loadAgentConfig() {
588
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
589
+ try {
590
+ return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
591
+ } catch {
592
+ return {};
593
+ }
594
+ }
595
+ function getAgentRuntime(agentId) {
596
+ const config = loadAgentConfig();
597
+ const entry = config[agentId];
598
+ if (entry) return entry;
599
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
600
+ }
601
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
602
+ var init_agent_config = __esm({
603
+ "src/lib/agent-config.ts"() {
604
+ "use strict";
605
+ init_config();
606
+ init_runtime_table();
607
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
608
+ DEFAULT_MODELS = {
609
+ claude: "claude-opus-4",
610
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
611
+ opencode: "minimax-m2.7"
612
+ };
613
+ }
614
+ });
615
+
616
+ // src/lib/intercom-queue.ts
617
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
618
+ import path4 from "path";
619
+ import os3 from "os";
620
+ function ensureDir() {
621
+ const dir = path4.dirname(QUEUE_PATH);
622
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
623
+ }
624
+ function readQueue() {
625
+ try {
626
+ if (!existsSync5(QUEUE_PATH)) return [];
627
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
628
+ } catch {
629
+ return [];
630
+ }
631
+ }
632
+ function writeQueue(queue) {
633
+ ensureDir();
634
+ const tmp = `${QUEUE_PATH}.tmp`;
635
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
636
+ renameSync2(tmp, QUEUE_PATH);
637
+ }
638
+ function queueIntercom(targetSession, reason) {
639
+ const queue = readQueue();
640
+ const existing = queue.find((q) => q.targetSession === targetSession);
641
+ if (existing) {
642
+ existing.attempts++;
643
+ existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
644
+ existing.reason = reason;
645
+ } else {
646
+ queue.push({
647
+ targetSession,
648
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
649
+ attempts: 0,
650
+ reason
651
+ });
652
+ }
653
+ writeQueue(queue);
654
+ }
655
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
656
+ var init_intercom_queue = __esm({
657
+ "src/lib/intercom-queue.ts"() {
658
+ "use strict";
659
+ QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
660
+ TTL_MS = 60 * 60 * 1e3;
661
+ INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
662
+ }
663
+ });
664
+
665
+ // src/lib/db-retry.ts
666
+ function isBusyError(err) {
667
+ if (err instanceof Error) {
668
+ const msg = err.message.toLowerCase();
669
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
670
+ }
671
+ return false;
672
+ }
673
+ function delay(ms) {
674
+ return new Promise((resolve) => setTimeout(resolve, ms));
675
+ }
676
+ async function retryOnBusy(fn, label) {
677
+ let lastError;
678
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
679
+ try {
680
+ return await fn();
681
+ } catch (err) {
682
+ lastError = err;
683
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
684
+ throw err;
685
+ }
686
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
687
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
688
+ process.stderr.write(
689
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
690
+ `
691
+ );
692
+ await delay(backoff + jitter);
693
+ }
694
+ }
695
+ throw lastError;
696
+ }
697
+ function wrapWithRetry(client) {
698
+ return new Proxy(client, {
699
+ get(target, prop, receiver) {
700
+ if (prop === "execute") {
701
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
702
+ }
703
+ if (prop === "batch") {
704
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
705
+ }
706
+ return Reflect.get(target, prop, receiver);
707
+ }
708
+ });
709
+ }
710
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
711
+ var init_db_retry = __esm({
712
+ "src/lib/db-retry.ts"() {
713
+ "use strict";
714
+ MAX_RETRIES = 3;
715
+ BASE_DELAY_MS = 200;
716
+ MAX_JITTER_MS = 300;
717
+ }
718
+ });
719
+
669
720
  // src/lib/employees.ts
670
721
  var employees_exports = {};
671
722
  __export(employees_exports, {
@@ -673,6 +724,7 @@ __export(employees_exports, {
673
724
  DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
674
725
  EMPLOYEES_PATH: () => EMPLOYEES_PATH,
675
726
  addEmployee: () => addEmployee,
727
+ baseAgentName: () => baseAgentName,
676
728
  canCoordinate: () => canCoordinate,
677
729
  getCoordinatorEmployee: () => getCoordinatorEmployee,
678
730
  getCoordinatorName: () => getCoordinatorName,
@@ -692,9 +744,9 @@ __export(employees_exports, {
692
744
  validateEmployeeName: () => validateEmployeeName
693
745
  });
694
746
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
695
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
747
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
696
748
  import { execSync as execSync4 } from "child_process";
697
- import path4 from "path";
749
+ import path5 from "path";
698
750
  import os4 from "os";
699
751
  function normalizeRole(role) {
700
752
  return (role ?? "").trim().toLowerCase();
@@ -731,7 +783,7 @@ function validateEmployeeName(name) {
731
783
  return { valid: true };
732
784
  }
733
785
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
734
- if (!existsSync5(employeesPath)) {
786
+ if (!existsSync6(employeesPath)) {
735
787
  return [];
736
788
  }
737
789
  const raw = await readFile2(employeesPath, "utf-8");
@@ -742,13 +794,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
742
794
  }
743
795
  }
744
796
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
745
- await mkdir2(path4.dirname(employeesPath), { recursive: true });
797
+ await mkdir2(path5.dirname(employeesPath), { recursive: true });
746
798
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
747
799
  }
748
800
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
749
- if (!existsSync5(employeesPath)) return [];
801
+ if (!existsSync6(employeesPath)) return [];
750
802
  try {
751
- return JSON.parse(readFileSync5(employeesPath, "utf-8"));
803
+ return JSON.parse(readFileSync6(employeesPath, "utf-8"));
752
804
  } catch {
753
805
  return [];
754
806
  }
@@ -769,6 +821,14 @@ function hasRole(agentName, role) {
769
821
  const emp = getEmployee(employees, agentName);
770
822
  return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
771
823
  }
824
+ function baseAgentName(name, employees) {
825
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
826
+ if (!match) return name;
827
+ const base = match[1];
828
+ const roster = employees ?? loadEmployeesSync();
829
+ if (getEmployee(roster, base)) return base;
830
+ return name;
831
+ }
772
832
  function isMultiInstance(agentName, employees) {
773
833
  const roster = employees ?? loadEmployeesSync();
774
834
  const emp = getEmployee(roster, agentName);
@@ -791,14 +851,14 @@ async function normalizeRosterCase(rosterPath) {
791
851
  emp.name = emp.name.toLowerCase();
792
852
  changed = true;
793
853
  try {
794
- const identityDir = path4.join(os4.homedir(), ".exe-os", "identity");
795
- const oldPath = path4.join(identityDir, `${oldName}.md`);
796
- const newPath = path4.join(identityDir, `${emp.name}.md`);
797
- if (existsSync5(oldPath) && !existsSync5(newPath)) {
854
+ const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
855
+ const oldPath = path5.join(identityDir, `${oldName}.md`);
856
+ const newPath = path5.join(identityDir, `${emp.name}.md`);
857
+ if (existsSync6(oldPath) && !existsSync6(newPath)) {
798
858
  renameSync3(oldPath, newPath);
799
- } else if (existsSync5(oldPath) && oldPath !== newPath) {
800
- const content = readFileSync5(oldPath, "utf-8");
801
- writeFileSync3(newPath, content, "utf-8");
859
+ } else if (existsSync6(oldPath) && oldPath !== newPath) {
860
+ const content = readFileSync6(oldPath, "utf-8");
861
+ writeFileSync4(newPath, content, "utf-8");
802
862
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
803
863
  unlinkSync(oldPath);
804
864
  }
@@ -819,48 +879,485 @@ function findExeBin() {
819
879
  return null;
820
880
  }
821
881
  }
822
- function registerBinSymlinks(name) {
823
- const created = [];
824
- const skipped = [];
825
- const errors = [];
826
- const exeBinPath = findExeBin();
827
- if (!exeBinPath) {
828
- errors.push("Could not find 'exe-os' in PATH");
829
- return { created, skipped, errors };
882
+ function registerBinSymlinks(name) {
883
+ const created = [];
884
+ const skipped = [];
885
+ const errors = [];
886
+ const exeBinPath = findExeBin();
887
+ if (!exeBinPath) {
888
+ errors.push("Could not find 'exe-os' in PATH");
889
+ return { created, skipped, errors };
890
+ }
891
+ const binDir = path5.dirname(exeBinPath);
892
+ let target;
893
+ try {
894
+ target = readlinkSync(exeBinPath);
895
+ } catch {
896
+ errors.push("Could not read 'exe' symlink");
897
+ return { created, skipped, errors };
898
+ }
899
+ for (const suffix of ["", "-opencode"]) {
900
+ const linkName = `${name}${suffix}`;
901
+ const linkPath = path5.join(binDir, linkName);
902
+ if (existsSync6(linkPath)) {
903
+ skipped.push(linkName);
904
+ continue;
905
+ }
906
+ try {
907
+ symlinkSync(target, linkPath);
908
+ created.push(linkName);
909
+ } catch (err) {
910
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
911
+ }
912
+ }
913
+ return { created, skipped, errors };
914
+ }
915
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
916
+ var init_employees = __esm({
917
+ "src/lib/employees.ts"() {
918
+ "use strict";
919
+ init_config();
920
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
921
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
922
+ COORDINATOR_ROLE = "COO";
923
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
924
+ }
925
+ });
926
+
927
+ // src/lib/exe-daemon-client.ts
928
+ import net from "net";
929
+ import { spawn } from "child_process";
930
+ import { randomUUID } from "crypto";
931
+ import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
932
+ import path6 from "path";
933
+ import { fileURLToPath } from "url";
934
+ function handleData(chunk) {
935
+ _buffer += chunk.toString();
936
+ if (_buffer.length > MAX_BUFFER) {
937
+ _buffer = "";
938
+ return;
939
+ }
940
+ let newlineIdx;
941
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
942
+ const line = _buffer.slice(0, newlineIdx).trim();
943
+ _buffer = _buffer.slice(newlineIdx + 1);
944
+ if (!line) continue;
945
+ try {
946
+ const response = JSON.parse(line);
947
+ const id = response.id;
948
+ if (!id) continue;
949
+ const entry = _pending.get(id);
950
+ if (entry) {
951
+ clearTimeout(entry.timer);
952
+ _pending.delete(id);
953
+ entry.resolve(response);
954
+ }
955
+ } catch {
956
+ }
957
+ }
958
+ }
959
+ function cleanupStaleFiles() {
960
+ if (existsSync7(PID_PATH)) {
961
+ try {
962
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
963
+ if (pid > 0) {
964
+ try {
965
+ process.kill(pid, 0);
966
+ return;
967
+ } catch {
968
+ }
969
+ }
970
+ } catch {
971
+ }
972
+ try {
973
+ unlinkSync2(PID_PATH);
974
+ } catch {
975
+ }
976
+ try {
977
+ unlinkSync2(SOCKET_PATH);
978
+ } catch {
979
+ }
980
+ }
981
+ }
982
+ function findPackageRoot() {
983
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
984
+ const { root } = path6.parse(dir);
985
+ while (dir !== root) {
986
+ if (existsSync7(path6.join(dir, "package.json"))) return dir;
987
+ dir = path6.dirname(dir);
988
+ }
989
+ return null;
990
+ }
991
+ function spawnDaemon() {
992
+ const pkgRoot = findPackageRoot();
993
+ if (!pkgRoot) {
994
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
995
+ return;
996
+ }
997
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
998
+ if (!existsSync7(daemonPath)) {
999
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1000
+ `);
1001
+ return;
1002
+ }
1003
+ const resolvedPath = daemonPath;
1004
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1005
+ `);
1006
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1007
+ let stderrFd = "ignore";
1008
+ try {
1009
+ stderrFd = openSync(logPath, "a");
1010
+ } catch {
1011
+ }
1012
+ const child = spawn(process.execPath, [resolvedPath], {
1013
+ detached: true,
1014
+ stdio: ["ignore", "ignore", stderrFd],
1015
+ env: {
1016
+ ...process.env,
1017
+ TMUX: void 0,
1018
+ // Daemon is global — must not inherit session scope
1019
+ TMUX_PANE: void 0,
1020
+ // Prevents resolveExeSession() from scoping to one session
1021
+ EXE_DAEMON_SOCK: SOCKET_PATH,
1022
+ EXE_DAEMON_PID: PID_PATH
1023
+ }
1024
+ });
1025
+ child.unref();
1026
+ if (typeof stderrFd === "number") {
1027
+ try {
1028
+ closeSync(stderrFd);
1029
+ } catch {
1030
+ }
1031
+ }
1032
+ }
1033
+ function acquireSpawnLock() {
1034
+ try {
1035
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1036
+ closeSync(fd);
1037
+ return true;
1038
+ } catch {
1039
+ try {
1040
+ const stat = statSync(SPAWN_LOCK_PATH);
1041
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1042
+ try {
1043
+ unlinkSync2(SPAWN_LOCK_PATH);
1044
+ } catch {
1045
+ }
1046
+ try {
1047
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
1048
+ closeSync(fd);
1049
+ return true;
1050
+ } catch {
1051
+ }
1052
+ }
1053
+ } catch {
1054
+ }
1055
+ return false;
1056
+ }
1057
+ }
1058
+ function releaseSpawnLock() {
1059
+ try {
1060
+ unlinkSync2(SPAWN_LOCK_PATH);
1061
+ } catch {
1062
+ }
1063
+ }
1064
+ function connectToSocket() {
1065
+ return new Promise((resolve) => {
1066
+ if (_socket && _connected) {
1067
+ resolve(true);
1068
+ return;
1069
+ }
1070
+ const socket = net.createConnection({ path: SOCKET_PATH });
1071
+ const connectTimeout = setTimeout(() => {
1072
+ socket.destroy();
1073
+ resolve(false);
1074
+ }, 2e3);
1075
+ socket.on("connect", () => {
1076
+ clearTimeout(connectTimeout);
1077
+ _socket = socket;
1078
+ _connected = true;
1079
+ _buffer = "";
1080
+ socket.on("data", handleData);
1081
+ socket.on("close", () => {
1082
+ _connected = false;
1083
+ _socket = null;
1084
+ for (const [id, entry] of _pending) {
1085
+ clearTimeout(entry.timer);
1086
+ _pending.delete(id);
1087
+ entry.resolve({ error: "Connection closed" });
1088
+ }
1089
+ });
1090
+ socket.on("error", () => {
1091
+ _connected = false;
1092
+ _socket = null;
1093
+ });
1094
+ resolve(true);
1095
+ });
1096
+ socket.on("error", () => {
1097
+ clearTimeout(connectTimeout);
1098
+ resolve(false);
1099
+ });
1100
+ });
1101
+ }
1102
+ async function connectEmbedDaemon() {
1103
+ if (_socket && _connected) return true;
1104
+ if (await connectToSocket()) return true;
1105
+ if (acquireSpawnLock()) {
1106
+ try {
1107
+ cleanupStaleFiles();
1108
+ spawnDaemon();
1109
+ } finally {
1110
+ releaseSpawnLock();
1111
+ }
1112
+ }
1113
+ const start = Date.now();
1114
+ let delay2 = 100;
1115
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1116
+ await new Promise((r) => setTimeout(r, delay2));
1117
+ if (await connectToSocket()) return true;
1118
+ delay2 = Math.min(delay2 * 2, 3e3);
1119
+ }
1120
+ return false;
1121
+ }
1122
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1123
+ return new Promise((resolve) => {
1124
+ if (!_socket || !_connected) {
1125
+ resolve({ error: "Not connected" });
1126
+ return;
1127
+ }
1128
+ const id = randomUUID();
1129
+ const timer = setTimeout(() => {
1130
+ _pending.delete(id);
1131
+ resolve({ error: "Request timeout" });
1132
+ }, timeoutMs);
1133
+ _pending.set(id, { resolve, timer });
1134
+ try {
1135
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1136
+ } catch {
1137
+ clearTimeout(timer);
1138
+ _pending.delete(id);
1139
+ resolve({ error: "Write failed" });
1140
+ }
1141
+ });
1142
+ }
1143
+ function isClientConnected() {
1144
+ return _connected;
1145
+ }
1146
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1147
+ var init_exe_daemon_client = __esm({
1148
+ "src/lib/exe-daemon-client.ts"() {
1149
+ "use strict";
1150
+ init_config();
1151
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1152
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1153
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1154
+ SPAWN_LOCK_STALE_MS = 3e4;
1155
+ CONNECT_TIMEOUT_MS = 15e3;
1156
+ REQUEST_TIMEOUT_MS = 3e4;
1157
+ _socket = null;
1158
+ _connected = false;
1159
+ _buffer = "";
1160
+ _pending = /* @__PURE__ */ new Map();
1161
+ MAX_BUFFER = 1e7;
1162
+ }
1163
+ });
1164
+
1165
+ // src/lib/daemon-protocol.ts
1166
+ function serializeValue(v) {
1167
+ if (v === null || v === void 0) return null;
1168
+ if (typeof v === "bigint") return Number(v);
1169
+ if (typeof v === "boolean") return v ? 1 : 0;
1170
+ if (v instanceof Uint8Array) {
1171
+ return { __blob: Buffer.from(v).toString("base64") };
1172
+ }
1173
+ if (ArrayBuffer.isView(v)) {
1174
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
1175
+ }
1176
+ if (v instanceof ArrayBuffer) {
1177
+ return { __blob: Buffer.from(v).toString("base64") };
1178
+ }
1179
+ if (typeof v === "string" || typeof v === "number") return v;
1180
+ return String(v);
1181
+ }
1182
+ function deserializeValue(v) {
1183
+ if (v === null) return null;
1184
+ if (typeof v === "object" && v !== null && "__blob" in v) {
1185
+ const buf = Buffer.from(v.__blob, "base64");
1186
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
830
1187
  }
831
- const binDir = path4.dirname(exeBinPath);
832
- let target;
833
- try {
834
- target = readlinkSync(exeBinPath);
835
- } catch {
836
- errors.push("Could not read 'exe' symlink");
837
- return { created, skipped, errors };
1188
+ return v;
1189
+ }
1190
+ function deserializeResultSet(srs) {
1191
+ const rows = srs.rows.map((obj) => {
1192
+ const values = srs.columns.map(
1193
+ (col) => deserializeValue(obj[col] ?? null)
1194
+ );
1195
+ const row = values;
1196
+ for (let i = 0; i < srs.columns.length; i++) {
1197
+ const col = srs.columns[i];
1198
+ if (col !== void 0) {
1199
+ row[col] = values[i] ?? null;
1200
+ }
1201
+ }
1202
+ Object.defineProperty(row, "length", {
1203
+ value: values.length,
1204
+ enumerable: false
1205
+ });
1206
+ return row;
1207
+ });
1208
+ return {
1209
+ columns: srs.columns,
1210
+ columnTypes: srs.columnTypes ?? [],
1211
+ rows,
1212
+ rowsAffected: srs.rowsAffected,
1213
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
1214
+ toJSON: () => ({
1215
+ columns: srs.columns,
1216
+ columnTypes: srs.columnTypes ?? [],
1217
+ rows: srs.rows,
1218
+ rowsAffected: srs.rowsAffected,
1219
+ lastInsertRowid: srs.lastInsertRowid
1220
+ })
1221
+ };
1222
+ }
1223
+ var init_daemon_protocol = __esm({
1224
+ "src/lib/daemon-protocol.ts"() {
1225
+ "use strict";
838
1226
  }
839
- for (const suffix of ["", "-opencode"]) {
840
- const linkName = `${name}${suffix}`;
841
- const linkPath = path4.join(binDir, linkName);
842
- if (existsSync5(linkPath)) {
843
- skipped.push(linkName);
844
- continue;
1227
+ });
1228
+
1229
+ // src/lib/db-daemon-client.ts
1230
+ var db_daemon_client_exports = {};
1231
+ __export(db_daemon_client_exports, {
1232
+ createDaemonDbClient: () => createDaemonDbClient,
1233
+ initDaemonDbClient: () => initDaemonDbClient
1234
+ });
1235
+ function normalizeStatement(stmt) {
1236
+ if (typeof stmt === "string") {
1237
+ return { sql: stmt, args: [] };
1238
+ }
1239
+ const sql = stmt.sql;
1240
+ let args = [];
1241
+ if (Array.isArray(stmt.args)) {
1242
+ args = stmt.args.map((v) => serializeValue(v));
1243
+ } else if (stmt.args && typeof stmt.args === "object") {
1244
+ const named = {};
1245
+ for (const [key, val] of Object.entries(stmt.args)) {
1246
+ named[key] = serializeValue(val);
1247
+ }
1248
+ return { sql, args: named };
1249
+ }
1250
+ return { sql, args };
1251
+ }
1252
+ function createDaemonDbClient(fallbackClient) {
1253
+ let _useDaemon = false;
1254
+ const client = {
1255
+ async execute(stmt) {
1256
+ if (!_useDaemon || !isClientConnected()) {
1257
+ return fallbackClient.execute(stmt);
1258
+ }
1259
+ const { sql, args } = normalizeStatement(stmt);
1260
+ const response = await sendDaemonRequest({
1261
+ type: "db-execute",
1262
+ sql,
1263
+ args
1264
+ });
1265
+ if (response.error) {
1266
+ const errMsg = String(response.error);
1267
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1268
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1269
+ `);
1270
+ return fallbackClient.execute(stmt);
1271
+ }
1272
+ throw new Error(errMsg);
1273
+ }
1274
+ if (response.db) {
1275
+ return deserializeResultSet(response.db);
1276
+ }
1277
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1278
+ return fallbackClient.execute(stmt);
1279
+ },
1280
+ async batch(stmts, mode) {
1281
+ if (!_useDaemon || !isClientConnected()) {
1282
+ return fallbackClient.batch(stmts, mode);
1283
+ }
1284
+ const statements = stmts.map(normalizeStatement);
1285
+ const response = await sendDaemonRequest({
1286
+ type: "db-batch",
1287
+ statements,
1288
+ mode: mode ?? "deferred"
1289
+ });
1290
+ if (response.error) {
1291
+ const errMsg = String(response.error);
1292
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1293
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1294
+ `);
1295
+ return fallbackClient.batch(stmts, mode);
1296
+ }
1297
+ throw new Error(errMsg);
1298
+ }
1299
+ const batchResults = response["db-batch"];
1300
+ if (batchResults) {
1301
+ return batchResults.map(deserializeResultSet);
1302
+ }
1303
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1304
+ return fallbackClient.batch(stmts, mode);
1305
+ },
1306
+ // Transaction support — delegate to fallback (transactions need direct connection)
1307
+ async transaction(mode) {
1308
+ return fallbackClient.transaction(mode);
1309
+ },
1310
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1311
+ async executeMultiple(sql) {
1312
+ return fallbackClient.executeMultiple(sql);
1313
+ },
1314
+ // migrate — delegate to fallback
1315
+ async migrate(stmts) {
1316
+ return fallbackClient.migrate(stmts);
1317
+ },
1318
+ // Sync mode — delegate to fallback
1319
+ sync() {
1320
+ return fallbackClient.sync();
1321
+ },
1322
+ close() {
1323
+ _useDaemon = false;
1324
+ },
1325
+ get closed() {
1326
+ return fallbackClient.closed;
1327
+ },
1328
+ get protocol() {
1329
+ return fallbackClient.protocol;
845
1330
  }
846
- try {
847
- symlinkSync(target, linkPath);
848
- created.push(linkName);
849
- } catch (err) {
850
- errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1331
+ };
1332
+ return {
1333
+ ...client,
1334
+ /** Enable daemon routing (call after confirming daemon is connected) */
1335
+ _enableDaemon() {
1336
+ _useDaemon = true;
1337
+ },
1338
+ /** Check if daemon routing is active */
1339
+ _isDaemonActive() {
1340
+ return _useDaemon && isClientConnected();
851
1341
  }
1342
+ };
1343
+ }
1344
+ async function initDaemonDbClient(fallbackClient) {
1345
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1346
+ const connected = await connectEmbedDaemon();
1347
+ if (!connected) {
1348
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1349
+ return null;
852
1350
  }
853
- return { created, skipped, errors };
1351
+ const client = createDaemonDbClient(fallbackClient);
1352
+ client._enableDaemon();
1353
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1354
+ return client;
854
1355
  }
855
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
856
- var init_employees = __esm({
857
- "src/lib/employees.ts"() {
1356
+ var init_db_daemon_client = __esm({
1357
+ "src/lib/db-daemon-client.ts"() {
858
1358
  "use strict";
859
- init_config();
860
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
861
- DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
862
- COORDINATOR_ROLE = "COO";
863
- MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1359
+ init_exe_daemon_client();
1360
+ init_daemon_protocol();
864
1361
  }
865
1362
  });
866
1363
 
@@ -872,6 +1369,7 @@ __export(database_exports, {
872
1369
  ensureSchema: () => ensureSchema,
873
1370
  getClient: () => getClient,
874
1371
  getRawClient: () => getRawClient,
1372
+ initDaemonClient: () => initDaemonClient,
875
1373
  initDatabase: () => initDatabase,
876
1374
  initTurso: () => initTurso,
877
1375
  isInitialized: () => isInitialized
@@ -899,8 +1397,27 @@ function getClient() {
899
1397
  if (!_resilientClient) {
900
1398
  throw new Error("Database client not initialized. Call initDatabase() first.");
901
1399
  }
1400
+ if (process.env.EXE_IS_DAEMON === "1") {
1401
+ return _resilientClient;
1402
+ }
1403
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1404
+ return _daemonClient;
1405
+ }
902
1406
  return _resilientClient;
903
1407
  }
1408
+ async function initDaemonClient() {
1409
+ if (process.env.EXE_IS_DAEMON === "1") return;
1410
+ if (!_resilientClient) return;
1411
+ try {
1412
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1413
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1414
+ } catch (err) {
1415
+ process.stderr.write(
1416
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1417
+ `
1418
+ );
1419
+ }
1420
+ }
904
1421
  function getRawClient() {
905
1422
  if (!_client) {
906
1423
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -1387,6 +1904,12 @@ async function ensureSchema() {
1387
1904
  } catch {
1388
1905
  }
1389
1906
  }
1907
+ try {
1908
+ await client.execute(
1909
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1910
+ );
1911
+ } catch {
1912
+ }
1390
1913
  await client.executeMultiple(`
1391
1914
  CREATE TABLE IF NOT EXISTS entities (
1392
1915
  id TEXT PRIMARY KEY,
@@ -1439,7 +1962,30 @@ async function ensureSchema() {
1439
1962
  entity_id TEXT NOT NULL,
1440
1963
  PRIMARY KEY (hyperedge_id, entity_id)
1441
1964
  );
1965
+
1966
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1967
+ name,
1968
+ content=entities,
1969
+ content_rowid=rowid
1970
+ );
1971
+
1972
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1973
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1974
+ END;
1975
+
1976
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1977
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1978
+ END;
1979
+
1980
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1981
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1982
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1983
+ END;
1442
1984
  `);
1985
+ try {
1986
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1987
+ } catch {
1988
+ }
1443
1989
  await client.executeMultiple(`
1444
1990
  CREATE TABLE IF NOT EXISTS entity_aliases (
1445
1991
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1620,6 +2166,33 @@ async function ensureSchema() {
1620
2166
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1621
2167
  ON conversations(channel_id);
1622
2168
  `);
2169
+ await client.executeMultiple(`
2170
+ CREATE TABLE IF NOT EXISTS session_agent_map (
2171
+ session_uuid TEXT PRIMARY KEY,
2172
+ agent_id TEXT NOT NULL,
2173
+ session_name TEXT,
2174
+ task_id TEXT,
2175
+ project_name TEXT,
2176
+ started_at TEXT NOT NULL
2177
+ );
2178
+
2179
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2180
+ ON session_agent_map(agent_id);
2181
+ `);
2182
+ try {
2183
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2184
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
2185
+ await client.execute({
2186
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
2187
+ SELECT session_id, agent_id, '', MIN(timestamp)
2188
+ FROM memories
2189
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
2190
+ GROUP BY session_id, agent_id`,
2191
+ args: []
2192
+ });
2193
+ }
2194
+ } catch {
2195
+ }
1623
2196
  try {
1624
2197
  await client.execute({
1625
2198
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1753,15 +2326,41 @@ async function ensureSchema() {
1753
2326
  });
1754
2327
  } catch {
1755
2328
  }
2329
+ for (const col of [
2330
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2331
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2332
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2333
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2334
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2335
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2336
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2337
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2338
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2339
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2340
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2341
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2342
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2343
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2344
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2345
+ ]) {
2346
+ try {
2347
+ await client.execute(col);
2348
+ } catch {
2349
+ }
2350
+ }
1756
2351
  }
1757
2352
  async function disposeDatabase() {
2353
+ if (_daemonClient) {
2354
+ _daemonClient.close();
2355
+ _daemonClient = null;
2356
+ }
1758
2357
  if (_client) {
1759
2358
  _client.close();
1760
2359
  _client = null;
1761
2360
  _resilientClient = null;
1762
2361
  }
1763
2362
  }
1764
- var _client, _resilientClient, initTurso, disposeTurso;
2363
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1765
2364
  var init_database = __esm({
1766
2365
  "src/lib/database.ts"() {
1767
2366
  "use strict";
@@ -1769,6 +2368,7 @@ var init_database = __esm({
1769
2368
  init_employees();
1770
2369
  _client = null;
1771
2370
  _resilientClient = null;
2371
+ _daemonClient = null;
1772
2372
  initTurso = initDatabase;
1773
2373
  disposeTurso = disposeDatabase;
1774
2374
  }
@@ -1791,9 +2391,9 @@ __export(license_exports, {
1791
2391
  stopLicenseRevalidation: () => stopLicenseRevalidation,
1792
2392
  validateLicense: () => validateLicense
1793
2393
  });
1794
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
1795
- import { randomUUID } from "crypto";
1796
- import path5 from "path";
2394
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2395
+ import { randomUUID as randomUUID2 } from "crypto";
2396
+ import path7 from "path";
1797
2397
  import { jwtVerify, importSPKI } from "jose";
1798
2398
  async function fetchRetry(url, init) {
1799
2399
  try {
@@ -1804,37 +2404,37 @@ async function fetchRetry(url, init) {
1804
2404
  }
1805
2405
  }
1806
2406
  function loadDeviceId() {
1807
- const deviceJsonPath = path5.join(EXE_AI_DIR, "device.json");
2407
+ const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
1808
2408
  try {
1809
- if (existsSync6(deviceJsonPath)) {
1810
- const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
2409
+ if (existsSync8(deviceJsonPath)) {
2410
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
1811
2411
  if (data.deviceId) return data.deviceId;
1812
2412
  }
1813
2413
  } catch {
1814
2414
  }
1815
2415
  try {
1816
- if (existsSync6(DEVICE_ID_PATH)) {
1817
- const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
2416
+ if (existsSync8(DEVICE_ID_PATH)) {
2417
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
1818
2418
  if (id2) return id2;
1819
2419
  }
1820
2420
  } catch {
1821
2421
  }
1822
- const id = randomUUID();
1823
- mkdirSync3(EXE_AI_DIR, { recursive: true });
1824
- writeFileSync4(DEVICE_ID_PATH, id, "utf8");
2422
+ const id = randomUUID2();
2423
+ mkdirSync4(EXE_AI_DIR, { recursive: true });
2424
+ writeFileSync5(DEVICE_ID_PATH, id, "utf8");
1825
2425
  return id;
1826
2426
  }
1827
2427
  function loadLicense() {
1828
2428
  try {
1829
- if (!existsSync6(LICENSE_PATH)) return null;
1830
- return readFileSync6(LICENSE_PATH, "utf8").trim();
2429
+ if (!existsSync8(LICENSE_PATH)) return null;
2430
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
1831
2431
  } catch {
1832
2432
  return null;
1833
2433
  }
1834
2434
  }
1835
2435
  function saveLicense(apiKey) {
1836
- mkdirSync3(EXE_AI_DIR, { recursive: true });
1837
- writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2436
+ mkdirSync4(EXE_AI_DIR, { recursive: true });
2437
+ writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
1838
2438
  }
1839
2439
  async function verifyLicenseJwt(token) {
1840
2440
  try {
@@ -1860,8 +2460,8 @@ async function verifyLicenseJwt(token) {
1860
2460
  }
1861
2461
  async function getCachedLicense() {
1862
2462
  try {
1863
- if (!existsSync6(CACHE_PATH)) return null;
1864
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
2463
+ if (!existsSync8(CACHE_PATH)) return null;
2464
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
1865
2465
  if (!raw.token || typeof raw.token !== "string") return null;
1866
2466
  return await verifyLicenseJwt(raw.token);
1867
2467
  } catch {
@@ -1870,8 +2470,8 @@ async function getCachedLicense() {
1870
2470
  }
1871
2471
  function readCachedToken() {
1872
2472
  try {
1873
- if (!existsSync6(CACHE_PATH)) return null;
1874
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
2473
+ if (!existsSync8(CACHE_PATH)) return null;
2474
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
1875
2475
  return typeof raw.token === "string" ? raw.token : null;
1876
2476
  } catch {
1877
2477
  return null;
@@ -1905,7 +2505,7 @@ function getRawCachedPlan() {
1905
2505
  }
1906
2506
  function cacheResponse(token) {
1907
2507
  try {
1908
- writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
2508
+ writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
1909
2509
  } catch {
1910
2510
  }
1911
2511
  }
@@ -1958,8 +2558,8 @@ async function validateLicense(apiKey, deviceId) {
1958
2558
  }
1959
2559
  function getCacheAgeMs() {
1960
2560
  try {
1961
- const { statSync } = __require("fs");
1962
- const s = statSync(CACHE_PATH);
2561
+ const { statSync: statSync2 } = __require("fs");
2562
+ const s = statSync2(CACHE_PATH);
1963
2563
  return Date.now() - s.mtimeMs;
1964
2564
  } catch {
1965
2565
  return Infinity;
@@ -1969,9 +2569,9 @@ async function checkLicense() {
1969
2569
  let key = loadLicense();
1970
2570
  if (!key) {
1971
2571
  try {
1972
- const configPath = path5.join(EXE_AI_DIR, "config.json");
1973
- if (existsSync6(configPath)) {
1974
- const raw = JSON.parse(readFileSync6(configPath, "utf8"));
2572
+ const configPath = path7.join(EXE_AI_DIR, "config.json");
2573
+ if (existsSync8(configPath)) {
2574
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
1975
2575
  const cloud = raw.cloud;
1976
2576
  if (cloud?.apiKey) {
1977
2577
  key = cloud.apiKey;
@@ -2130,9 +2730,9 @@ var init_license = __esm({
2130
2730
  "src/lib/license.ts"() {
2131
2731
  "use strict";
2132
2732
  init_config();
2133
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
2134
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
2135
- DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
2733
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2734
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2735
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2136
2736
  API_BASE = "https://askexe.com/cloud";
2137
2737
  RETRY_DELAY_MS = 500;
2138
2738
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -2162,12 +2762,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
2162
2762
  });
2163
2763
 
2164
2764
  // src/lib/plan-limits.ts
2165
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2166
- import path6 from "path";
2765
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2766
+ import path8 from "path";
2167
2767
  function getLicenseSync() {
2168
2768
  try {
2169
- if (!existsSync7(CACHE_PATH2)) return freeLicense();
2170
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2769
+ if (!existsSync9(CACHE_PATH2)) return freeLicense();
2770
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2171
2771
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2172
2772
  const parts = raw.token.split(".");
2173
2773
  if (parts.length !== 3) return freeLicense();
@@ -2205,8 +2805,8 @@ function assertEmployeeLimitSync(rosterPath) {
2205
2805
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2206
2806
  let count = 0;
2207
2807
  try {
2208
- if (existsSync7(filePath)) {
2209
- const raw = readFileSync7(filePath, "utf8");
2808
+ if (existsSync9(filePath)) {
2809
+ const raw = readFileSync9(filePath, "utf8");
2210
2810
  const employees = JSON.parse(raw);
2211
2811
  count = Array.isArray(employees) ? employees.length : 0;
2212
2812
  }
@@ -2235,19 +2835,19 @@ var init_plan_limits = __esm({
2235
2835
  this.name = "PlanLimitError";
2236
2836
  }
2237
2837
  };
2238
- CACHE_PATH2 = path6.join(EXE_AI_DIR, "license-cache.json");
2838
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2239
2839
  }
2240
2840
  });
2241
2841
 
2242
2842
  // src/lib/notifications.ts
2243
2843
  import crypto from "crypto";
2244
- import path7 from "path";
2844
+ import path9 from "path";
2245
2845
  import os5 from "os";
2246
2846
  import {
2247
- readFileSync as readFileSync8,
2847
+ readFileSync as readFileSync10,
2248
2848
  readdirSync,
2249
- unlinkSync as unlinkSync2,
2250
- existsSync as existsSync8,
2849
+ unlinkSync as unlinkSync3,
2850
+ existsSync as existsSync10,
2251
2851
  rmdirSync
2252
2852
  } from "fs";
2253
2853
  async function writeNotification(notification) {
@@ -2398,10 +2998,11 @@ __export(tasks_crud_exports, {
2398
2998
  writeCheckpoint: () => writeCheckpoint
2399
2999
  });
2400
3000
  import crypto3 from "crypto";
2401
- import path8 from "path";
3001
+ import path10 from "path";
3002
+ import os6 from "os";
2402
3003
  import { execSync as execSync5 } from "child_process";
2403
3004
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2404
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
3005
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
2405
3006
  async function writeCheckpoint(input) {
2406
3007
  const client = getClient();
2407
3008
  const row = await resolveTask(client, input.taskId);
@@ -2442,6 +3043,35 @@ function extractParentFromContext(contextBody) {
2442
3043
  function slugify(title) {
2443
3044
  return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2444
3045
  }
3046
+ function buildKeywordIndex() {
3047
+ const idx = /* @__PURE__ */ new Map();
3048
+ for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
3049
+ for (const kw of keywords) {
3050
+ const existing = idx.get(kw) ?? [];
3051
+ existing.push(role);
3052
+ idx.set(kw, existing);
3053
+ }
3054
+ }
3055
+ return idx;
3056
+ }
3057
+ function checkLaneAffinity(title, context, assigneeName) {
3058
+ const employees = loadEmployeesSync();
3059
+ const employee = employees.find((e) => e.name === assigneeName);
3060
+ if (!employee) return void 0;
3061
+ const assigneeRole = employee.role;
3062
+ const text = `${title} ${context}`.toLowerCase();
3063
+ const matchedRoles = /* @__PURE__ */ new Set();
3064
+ for (const [keyword, roles] of KEYWORD_INDEX) {
3065
+ if (text.includes(keyword)) {
3066
+ for (const role of roles) matchedRoles.add(role);
3067
+ }
3068
+ }
3069
+ if (matchedRoles.size === 0) return void 0;
3070
+ if (matchedRoles.has(assigneeRole)) return void 0;
3071
+ if (assigneeRole === "COO") return void 0;
3072
+ const expectedRoles = Array.from(matchedRoles).join(" or ");
3073
+ return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
3074
+ }
2445
3075
  async function resolveTask(client, identifier, scopeSession) {
2446
3076
  const scope = sessionScopeFilter(scopeSession);
2447
3077
  let result = await client.execute({
@@ -2491,7 +3121,14 @@ async function createTaskCore(input) {
2491
3121
  const id = crypto3.randomUUID();
2492
3122
  const now = (/* @__PURE__ */ new Date()).toISOString();
2493
3123
  const slug = slugify(input.title);
2494
- const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
3124
+ let earlySessionScope = null;
3125
+ try {
3126
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3127
+ earlySessionScope = resolveExeSession2();
3128
+ } catch {
3129
+ }
3130
+ const scope = earlySessionScope ?? "default";
3131
+ const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
2495
3132
  let blockedById = null;
2496
3133
  const initialStatus = input.blockedBy ? "blocked" : "open";
2497
3134
  if (input.blockedBy) {
@@ -2531,22 +3168,24 @@ async function createTaskCore(input) {
2531
3168
  if (dupCheck.rows.length > 0) {
2532
3169
  warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
2533
3170
  }
3171
+ if (!process.env.DISABLE_LANE_AFFINITY) {
3172
+ const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
3173
+ if (laneWarning) {
3174
+ warning = warning ? `${warning}
3175
+ ${laneWarning}` : laneWarning;
3176
+ }
3177
+ }
2534
3178
  if (input.baseDir) {
2535
3179
  try {
2536
- await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
2537
- await mkdir3(path8.join(input.baseDir, "exe", "research"), { recursive: true });
3180
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
3181
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2538
3182
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2539
3183
  await ensureGitignoreExe(input.baseDir);
2540
3184
  } catch {
2541
3185
  }
2542
3186
  }
2543
3187
  const complexity = input.complexity ?? "standard";
2544
- let sessionScope = null;
2545
- try {
2546
- const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2547
- sessionScope = resolveExeSession2();
2548
- } catch {
2549
- }
3188
+ const sessionScope = earlySessionScope;
2550
3189
  await client.execute({
2551
3190
  sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
2552
3191
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -2573,6 +3212,43 @@ async function createTaskCore(input) {
2573
3212
  now
2574
3213
  ]
2575
3214
  });
3215
+ if (input.baseDir) {
3216
+ try {
3217
+ const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
3218
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
3219
+ const mdDir = path10.dirname(mdPath);
3220
+ if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
3221
+ const reviewer = input.reviewer ?? input.assignedBy;
3222
+ const mdContent = `# ${input.title}
3223
+
3224
+ **ID:** ${id}
3225
+ **Status:** ${initialStatus}
3226
+ **Priority:** ${input.priority}
3227
+ **Assigned by:** ${input.assignedBy}
3228
+ **Assigned to:** ${input.assignedTo}
3229
+ **Project:** ${input.projectName}
3230
+ **Created:** ${now.split("T")[0]}${parentTaskId ? `
3231
+ **Parent task:** ${parentTaskId}` : ""}
3232
+ **Reviewer:** ${reviewer}
3233
+
3234
+ ## Context
3235
+
3236
+ ${input.context}
3237
+
3238
+ ## MANDATORY: When done
3239
+
3240
+ You MUST call update_task with status "done" and a result summary when finished.
3241
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3242
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3243
+ `;
3244
+ await writeFile3(mdPath, mdContent, "utf-8");
3245
+ } catch (err) {
3246
+ process.stderr.write(
3247
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
3248
+ `
3249
+ );
3250
+ }
3251
+ }
2576
3252
  return {
2577
3253
  id,
2578
3254
  title: input.title,
@@ -2765,7 +3441,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2765
3441
  return { row, taskFile, now, taskId };
2766
3442
  }
2767
3443
  }
2768
- if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
3444
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
2769
3445
  process.stderr.write(
2770
3446
  `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2771
3447
  `
@@ -2830,9 +3506,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2830
3506
  return { taskFile, assignedTo, assignedBy, taskSlug };
2831
3507
  }
2832
3508
  async function ensureArchitectureDoc(baseDir, projectName) {
2833
- const archPath = path8.join(baseDir, "exe", "ARCHITECTURE.md");
3509
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2834
3510
  try {
2835
- if (existsSync9(archPath)) return;
3511
+ if (existsSync11(archPath)) return;
2836
3512
  const template = [
2837
3513
  `# ${projectName} \u2014 System Architecture`,
2838
3514
  "",
@@ -2865,10 +3541,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2865
3541
  }
2866
3542
  }
2867
3543
  async function ensureGitignoreExe(baseDir) {
2868
- const gitignorePath = path8.join(baseDir, ".gitignore");
3544
+ const gitignorePath = path10.join(baseDir, ".gitignore");
2869
3545
  try {
2870
- if (existsSync9(gitignorePath)) {
2871
- const content = readFileSync9(gitignorePath, "utf-8");
3546
+ if (existsSync11(gitignorePath)) {
3547
+ const content = readFileSync11(gitignorePath, "utf-8");
2872
3548
  if (/^\/?exe\/?$/m.test(content)) return;
2873
3549
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2874
3550
  } else {
@@ -2877,20 +3553,30 @@ async function ensureGitignoreExe(baseDir) {
2877
3553
  } catch {
2878
3554
  }
2879
3555
  }
2880
- var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
3556
+ var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2881
3557
  var init_tasks_crud = __esm({
2882
3558
  "src/lib/tasks-crud.ts"() {
2883
3559
  "use strict";
2884
3560
  init_database();
2885
3561
  init_task_scope();
3562
+ init_employees();
3563
+ LANE_KEYWORDS = {
3564
+ CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
3565
+ CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
3566
+ "Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
3567
+ "Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
3568
+ "Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
3569
+ "AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
3570
+ };
3571
+ KEYWORD_INDEX = buildKeywordIndex();
2886
3572
  DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2887
3573
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2888
3574
  }
2889
3575
  });
2890
3576
 
2891
3577
  // src/lib/tasks-review.ts
2892
- import path9 from "path";
2893
- import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3578
+ import path11 from "path";
3579
+ import { existsSync as existsSync12, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2894
3580
  async function countPendingReviews(sessionScope) {
2895
3581
  const client = getClient();
2896
3582
  if (sessionScope) {
@@ -2912,7 +3598,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
2912
3598
  const result2 = await client.execute({
2913
3599
  sql: `SELECT COUNT(*) as cnt FROM tasks
2914
3600
  WHERE status = 'needs_review' AND updated_at > ?
2915
- AND (session_scope = ? OR session_scope IS NULL)`,
3601
+ AND session_scope = ?`,
2916
3602
  args: [sinceIso, sessionScope]
2917
3603
  });
2918
3604
  return Number(result2.rows[0]?.cnt) || 0;
@@ -2930,7 +3616,7 @@ async function listPendingReviews(limit, sessionScope) {
2930
3616
  const result2 = await client.execute({
2931
3617
  sql: `SELECT title, assigned_to, project_name FROM tasks
2932
3618
  WHERE status = 'needs_review'
2933
- AND (session_scope = ? OR session_scope IS NULL)
3619
+ AND session_scope = ?
2934
3620
  ORDER BY priority ASC, created_at DESC LIMIT ?`,
2935
3621
  args: [sessionScope, limit]
2936
3622
  });
@@ -3051,14 +3737,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3051
3737
  if (parts.length >= 3 && parts[0] === "review") {
3052
3738
  const agent = parts[1];
3053
3739
  const slug = parts.slice(2).join("-");
3054
- const originalTaskFile = `exe/${agent}/${slug}.md`;
3740
+ const legacyTaskFile = `exe/${agent}/${slug}.md`;
3055
3741
  const result = await client.execute({
3056
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
3057
- args: [now, originalTaskFile]
3742
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
3743
+ args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
3058
3744
  });
3059
3745
  if (result.rowsAffected > 0) {
3060
3746
  process.stderr.write(
3061
- `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
3747
+ `[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
3062
3748
  `
3063
3749
  );
3064
3750
  }
@@ -3071,11 +3757,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3071
3757
  );
3072
3758
  }
3073
3759
  try {
3074
- const cacheDir = path9.join(EXE_AI_DIR, "session-cache");
3075
- if (existsSync10(cacheDir)) {
3760
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3761
+ if (existsSync12(cacheDir)) {
3076
3762
  for (const f of readdirSync2(cacheDir)) {
3077
3763
  if (f.startsWith("review-notified-")) {
3078
- unlinkSync3(path9.join(cacheDir, f));
3764
+ unlinkSync4(path11.join(cacheDir, f));
3079
3765
  }
3080
3766
  }
3081
3767
  }
@@ -3096,7 +3782,7 @@ var init_tasks_review = __esm({
3096
3782
  });
3097
3783
 
3098
3784
  // src/lib/tasks-chain.ts
3099
- import path10 from "path";
3785
+ import path12 from "path";
3100
3786
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3101
3787
  async function cascadeUnblock(taskId, baseDir, now) {
3102
3788
  const client = getClient();
@@ -3113,7 +3799,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3113
3799
  });
3114
3800
  for (const ur of unblockedRows.rows) {
3115
3801
  try {
3116
- const ubFile = path10.join(baseDir, String(ur.task_file));
3802
+ const ubFile = path12.join(baseDir, String(ur.task_file));
3117
3803
  let ubContent = await readFile3(ubFile, "utf-8");
3118
3804
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3119
3805
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3182,7 +3868,7 @@ var init_tasks_chain = __esm({
3182
3868
 
3183
3869
  // src/lib/project-name.ts
3184
3870
  import { execSync as execSync6 } from "child_process";
3185
- import path11 from "path";
3871
+ import path13 from "path";
3186
3872
  function getProjectName(cwd2) {
3187
3873
  const dir = cwd2 ?? process.cwd();
3188
3874
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3195,7 +3881,7 @@ function getProjectName(cwd2) {
3195
3881
  timeout: 2e3,
3196
3882
  stdio: ["pipe", "pipe", "pipe"]
3197
3883
  }).trim();
3198
- repoRoot = path11.dirname(gitCommonDir);
3884
+ repoRoot = path13.dirname(gitCommonDir);
3199
3885
  } catch {
3200
3886
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3201
3887
  cwd: dir,
@@ -3204,11 +3890,11 @@ function getProjectName(cwd2) {
3204
3890
  stdio: ["pipe", "pipe", "pipe"]
3205
3891
  }).trim();
3206
3892
  }
3207
- _cached2 = path11.basename(repoRoot);
3893
+ _cached2 = path13.basename(repoRoot);
3208
3894
  _cachedCwd = dir;
3209
3895
  return _cached2;
3210
3896
  } catch {
3211
- _cached2 = path11.basename(dir);
3897
+ _cached2 = path13.basename(dir);
3212
3898
  _cachedCwd = dir;
3213
3899
  return _cached2;
3214
3900
  }
@@ -3240,7 +3926,7 @@ function findSessionForProject(projectName) {
3240
3926
  const sessions = listSessions();
3241
3927
  for (const s of sessions) {
3242
3928
  const proj = s.projectDir.split("/").filter(Boolean).pop();
3243
- if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3929
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
3244
3930
  }
3245
3931
  return null;
3246
3932
  }
@@ -3286,7 +3972,7 @@ var init_session_scope = __esm({
3286
3972
 
3287
3973
  // src/lib/tasks-notify.ts
3288
3974
  async function dispatchTaskToEmployee(input) {
3289
- if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3975
+ if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3290
3976
  let crossProject = false;
3291
3977
  if (input.projectName) {
3292
3978
  try {
@@ -3681,8 +4367,8 @@ __export(tasks_exports, {
3681
4367
  updateTaskStatus: () => updateTaskStatus,
3682
4368
  writeCheckpoint: () => writeCheckpoint
3683
4369
  });
3684
- import path12 from "path";
3685
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
4370
+ import path14 from "path";
4371
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3686
4372
  async function createTask(input) {
3687
4373
  const result = await createTaskCore(input);
3688
4374
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3701,14 +4387,14 @@ async function updateTask(input) {
3701
4387
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3702
4388
  try {
3703
4389
  const agent = String(row.assigned_to);
3704
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3705
- const cachePath = path12.join(cacheDir, `current-task-${agent}.json`);
4390
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4391
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3706
4392
  if (input.status === "in_progress") {
3707
- mkdirSync4(cacheDir, { recursive: true });
3708
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4393
+ mkdirSync5(cacheDir, { recursive: true });
4394
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3709
4395
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3710
4396
  try {
3711
- unlinkSync4(cachePath);
4397
+ unlinkSync5(cachePath);
3712
4398
  } catch {
3713
4399
  }
3714
4400
  }
@@ -3765,7 +4451,7 @@ async function updateTask(input) {
3765
4451
  }
3766
4452
  const isTerminal = input.status === "done" || input.status === "needs_review";
3767
4453
  if (isTerminal) {
3768
- const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
4454
+ const isCoordinator = isCoordinatorName(String(row.assigned_to));
3769
4455
  if (!isCoordinator) {
3770
4456
  notifyTaskDone();
3771
4457
  }
@@ -3790,7 +4476,7 @@ async function updateTask(input) {
3790
4476
  }
3791
4477
  }
3792
4478
  }
3793
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4479
+ if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3794
4480
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3795
4481
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3796
4482
  taskId,
@@ -3806,7 +4492,7 @@ async function updateTask(input) {
3806
4492
  });
3807
4493
  }
3808
4494
  let nextTask;
3809
- if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
4495
+ if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
3810
4496
  try {
3811
4497
  nextTask = await findNextTask(String(row.assigned_to));
3812
4498
  } catch {
@@ -4150,7 +4836,7 @@ var init_capacity_monitor = __esm({
4150
4836
  // src/lib/tmux-routing.ts
4151
4837
  var tmux_routing_exports = {};
4152
4838
  __export(tmux_routing_exports, {
4153
- acquireSpawnLock: () => acquireSpawnLock,
4839
+ acquireSpawnLock: () => acquireSpawnLock2,
4154
4840
  employeeSessionName: () => employeeSessionName,
4155
4841
  ensureEmployee: () => ensureEmployee,
4156
4842
  extractRootExe: () => extractRootExe,
@@ -4165,20 +4851,20 @@ __export(tmux_routing_exports, {
4165
4851
  notifyParentExe: () => notifyParentExe,
4166
4852
  parseParentExe: () => parseParentExe,
4167
4853
  registerParentExe: () => registerParentExe,
4168
- releaseSpawnLock: () => releaseSpawnLock,
4854
+ releaseSpawnLock: () => releaseSpawnLock2,
4169
4855
  resolveExeSession: () => resolveExeSession,
4170
4856
  sendIntercom: () => sendIntercom,
4171
4857
  spawnEmployee: () => spawnEmployee,
4172
4858
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4173
4859
  });
4174
4860
  import { execFileSync as execFileSync3, execSync as execSync7 } from "child_process";
4175
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4176
- import path13 from "path";
4177
- import os6 from "os";
4178
- import { fileURLToPath } from "url";
4179
- import { unlinkSync as unlinkSync5 } from "fs";
4861
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync13, appendFileSync } from "fs";
4862
+ import path15 from "path";
4863
+ import os7 from "os";
4864
+ import { fileURLToPath as fileURLToPath2 } from "url";
4865
+ import { unlinkSync as unlinkSync6 } from "fs";
4180
4866
  function spawnLockPath(sessionName) {
4181
- return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4867
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4182
4868
  }
4183
4869
  function isProcessAlive(pid) {
4184
4870
  try {
@@ -4188,14 +4874,14 @@ function isProcessAlive(pid) {
4188
4874
  return false;
4189
4875
  }
4190
4876
  }
4191
- function acquireSpawnLock(sessionName) {
4192
- if (!existsSync11(SPAWN_LOCK_DIR)) {
4193
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4877
+ function acquireSpawnLock2(sessionName) {
4878
+ if (!existsSync13(SPAWN_LOCK_DIR)) {
4879
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4194
4880
  }
4195
4881
  const lockFile = spawnLockPath(sessionName);
4196
- if (existsSync11(lockFile)) {
4882
+ if (existsSync13(lockFile)) {
4197
4883
  try {
4198
- const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
4884
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4199
4885
  const age = Date.now() - lock.timestamp;
4200
4886
  if (isProcessAlive(lock.pid) && age < 6e4) {
4201
4887
  return false;
@@ -4203,25 +4889,25 @@ function acquireSpawnLock(sessionName) {
4203
4889
  } catch {
4204
4890
  }
4205
4891
  }
4206
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4892
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4207
4893
  return true;
4208
4894
  }
4209
- function releaseSpawnLock(sessionName) {
4895
+ function releaseSpawnLock2(sessionName) {
4210
4896
  try {
4211
- unlinkSync5(spawnLockPath(sessionName));
4897
+ unlinkSync6(spawnLockPath(sessionName));
4212
4898
  } catch {
4213
4899
  }
4214
4900
  }
4215
4901
  function resolveBehaviorsExporterScript() {
4216
4902
  try {
4217
- const thisFile = fileURLToPath(import.meta.url);
4218
- const scriptPath = path13.join(
4219
- path13.dirname(thisFile),
4903
+ const thisFile = fileURLToPath2(import.meta.url);
4904
+ const scriptPath = path15.join(
4905
+ path15.dirname(thisFile),
4220
4906
  "..",
4221
4907
  "bin",
4222
4908
  "exe-export-behaviors.js"
4223
4909
  );
4224
- return existsSync11(scriptPath) ? scriptPath : null;
4910
+ return existsSync13(scriptPath) ? scriptPath : null;
4225
4911
  } catch {
4226
4912
  return null;
4227
4913
  }
@@ -4287,12 +4973,12 @@ function extractRootExe(name) {
4287
4973
  return parts.length > 0 ? parts[parts.length - 1] : null;
4288
4974
  }
4289
4975
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4290
- if (!existsSync11(SESSION_CACHE)) {
4291
- mkdirSync5(SESSION_CACHE, { recursive: true });
4976
+ if (!existsSync13(SESSION_CACHE)) {
4977
+ mkdirSync6(SESSION_CACHE, { recursive: true });
4292
4978
  }
4293
4979
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4294
- const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4295
- writeFileSync6(filePath, JSON.stringify({
4980
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4981
+ writeFileSync7(filePath, JSON.stringify({
4296
4982
  parentExe: rootExe,
4297
4983
  dispatchedBy: dispatchedBy || rootExe,
4298
4984
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4300,7 +4986,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4300
4986
  }
4301
4987
  function getParentExe(sessionKey) {
4302
4988
  try {
4303
- const data = JSON.parse(readFileSync10(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4989
+ const data = JSON.parse(readFileSync12(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4304
4990
  return data.parentExe || null;
4305
4991
  } catch {
4306
4992
  return null;
@@ -4308,8 +4994,8 @@ function getParentExe(sessionKey) {
4308
4994
  }
4309
4995
  function getDispatchedBy(sessionKey) {
4310
4996
  try {
4311
- const data = JSON.parse(readFileSync10(
4312
- path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4997
+ const data = JSON.parse(readFileSync12(
4998
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4313
4999
  "utf8"
4314
5000
  ));
4315
5001
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4335,10 +5021,10 @@ function isEmployeeAlive(sessionName) {
4335
5021
  }
4336
5022
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
4337
5023
  const base = employeeSessionName(employeeName, exeSession);
4338
- if (!isAlive(base) && acquireSpawnLock(base)) return 0;
5024
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
4339
5025
  for (let i = 2; i <= maxInstances; i++) {
4340
5026
  const candidate = employeeSessionName(employeeName, exeSession, i);
4341
- if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
5027
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
4342
5028
  }
4343
5029
  return null;
4344
5030
  }
@@ -4370,32 +5056,50 @@ async function verifyPaneAtCapacity(sessionName) {
4370
5056
  }
4371
5057
  function readDebounceState() {
4372
5058
  try {
4373
- if (!existsSync11(DEBOUNCE_FILE)) return {};
4374
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
5059
+ if (!existsSync13(DEBOUNCE_FILE)) return {};
5060
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5061
+ const state = {};
5062
+ for (const [key, val] of Object.entries(raw)) {
5063
+ if (typeof val === "number") {
5064
+ state[key] = { lastSent: val, pending: 0 };
5065
+ } else if (val && typeof val === "object" && "lastSent" in val) {
5066
+ state[key] = val;
5067
+ }
5068
+ }
5069
+ return state;
4375
5070
  } catch {
4376
5071
  return {};
4377
5072
  }
4378
5073
  }
4379
5074
  function writeDebounceState(state) {
4380
5075
  try {
4381
- if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4382
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
5076
+ if (!existsSync13(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5077
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
4383
5078
  } catch {
4384
5079
  }
4385
5080
  }
4386
5081
  function isDebounced(targetSession) {
4387
5082
  const state = readDebounceState();
4388
- const lastSent = state[targetSession] ?? 0;
4389
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
5083
+ const entry = state[targetSession];
5084
+ const lastSent = entry?.lastSent ?? 0;
5085
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
5086
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
5087
+ state[targetSession].pending++;
5088
+ writeDebounceState(state);
5089
+ return true;
5090
+ }
5091
+ return false;
4390
5092
  }
4391
5093
  function recordDebounce(targetSession) {
4392
5094
  const state = readDebounceState();
4393
- state[targetSession] = Date.now();
5095
+ const batched = state[targetSession]?.pending ?? 0;
5096
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
4394
5097
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
4395
5098
  for (const key of Object.keys(state)) {
4396
- if ((state[key] ?? 0) < cutoff) delete state[key];
5099
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
4397
5100
  }
4398
5101
  writeDebounceState(state);
5102
+ return batched;
4399
5103
  }
4400
5104
  function logIntercom(msg) {
4401
5105
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -4440,7 +5144,7 @@ function sendIntercom(targetSession) {
4440
5144
  return "skipped_exe";
4441
5145
  }
4442
5146
  if (isDebounced(targetSession)) {
4443
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
5147
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
4444
5148
  return "debounced";
4445
5149
  }
4446
5150
  try {
@@ -4452,14 +5156,14 @@ function sendIntercom(targetSession) {
4452
5156
  const sessionState = getSessionState(targetSession);
4453
5157
  if (sessionState === "no_claude") {
4454
5158
  queueIntercom(targetSession, "claude not running in session");
4455
- recordDebounce(targetSession);
4456
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
5159
+ const batched2 = recordDebounce(targetSession);
5160
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4457
5161
  return "queued";
4458
5162
  }
4459
5163
  if (sessionState === "thinking" || sessionState === "tool") {
4460
5164
  queueIntercom(targetSession, "session busy at send time");
4461
- recordDebounce(targetSession);
4462
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
5165
+ const batched2 = recordDebounce(targetSession);
5166
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
4463
5167
  return "queued";
4464
5168
  }
4465
5169
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -4467,8 +5171,8 @@ function sendIntercom(targetSession) {
4467
5171
  transport.sendKeys(targetSession, "q");
4468
5172
  }
4469
5173
  transport.sendKeys(targetSession, "/exe-intercom");
4470
- recordDebounce(targetSession);
4471
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
5174
+ const batched = recordDebounce(targetSession);
5175
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
4472
5176
  return "delivered";
4473
5177
  } catch {
4474
5178
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -4498,7 +5202,7 @@ function notifyParentExe(sessionKey) {
4498
5202
  return true;
4499
5203
  }
4500
5204
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4501
- if (employeeName === "exe" || isCoordinatorName(employeeName)) {
5205
+ if (isCoordinatorName(employeeName)) {
4502
5206
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
4503
5207
  }
4504
5208
  try {
@@ -4570,26 +5274,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4570
5274
  const transport = getTransport();
4571
5275
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4572
5276
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4573
- const logDir = path13.join(os6.homedir(), ".exe-os", "session-logs");
4574
- const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4575
- if (!existsSync11(logDir)) {
4576
- mkdirSync5(logDir, { recursive: true });
5277
+ const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
5278
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5279
+ if (!existsSync13(logDir)) {
5280
+ mkdirSync6(logDir, { recursive: true });
4577
5281
  }
4578
5282
  transport.kill(sessionName);
4579
5283
  let cleanupSuffix = "";
4580
5284
  try {
4581
- const thisFile = fileURLToPath(import.meta.url);
4582
- const cleanupScript = path13.join(path13.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4583
- if (existsSync11(cleanupScript)) {
5285
+ const thisFile = fileURLToPath2(import.meta.url);
5286
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5287
+ if (existsSync13(cleanupScript)) {
4584
5288
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4585
5289
  }
4586
5290
  } catch {
4587
5291
  }
4588
5292
  try {
4589
- const claudeJsonPath = path13.join(os6.homedir(), ".claude.json");
5293
+ const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
4590
5294
  let claudeJson = {};
4591
5295
  try {
4592
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
5296
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
4593
5297
  } catch {
4594
5298
  }
4595
5299
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4597,17 +5301,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4597
5301
  const trustDir = opts?.cwd ?? projectDir;
4598
5302
  if (!projects[trustDir]) projects[trustDir] = {};
4599
5303
  projects[trustDir].hasTrustDialogAccepted = true;
4600
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5304
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4601
5305
  } catch {
4602
5306
  }
4603
5307
  try {
4604
- const settingsDir = path13.join(os6.homedir(), ".claude", "projects");
5308
+ const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
4605
5309
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4606
- const projSettingsDir = path13.join(settingsDir, normalizedKey);
4607
- const settingsPath = path13.join(projSettingsDir, "settings.json");
5310
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
5311
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
4608
5312
  let settings = {};
4609
5313
  try {
4610
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
5314
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
4611
5315
  } catch {
4612
5316
  }
4613
5317
  const perms = settings.permissions ?? {};
@@ -4635,21 +5339,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4635
5339
  if (changed) {
4636
5340
  perms.allow = allow;
4637
5341
  settings.permissions = perms;
4638
- mkdirSync5(projSettingsDir, { recursive: true });
4639
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5342
+ mkdirSync6(projSettingsDir, { recursive: true });
5343
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4640
5344
  }
4641
5345
  } catch {
4642
5346
  }
4643
5347
  const spawnCwd = opts?.cwd ?? projectDir;
4644
5348
  const useExeAgent = !!(opts?.model && opts?.provider);
4645
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
5349
+ const agentRtConfig = getAgentRuntime(employeeName);
5350
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
5351
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
5352
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4646
5353
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4647
5354
  let identityFlag = "";
4648
5355
  let behaviorsFlag = "";
4649
5356
  let legacyFallbackWarned = false;
4650
5357
  if (!useExeAgent && !useBinSymlink) {
4651
- const identityPath = path13.join(
4652
- os6.homedir(),
5358
+ const identityPath = path15.join(
5359
+ os7.homedir(),
4653
5360
  ".exe-os",
4654
5361
  "identity",
4655
5362
  `${employeeName}.md`
@@ -4658,13 +5365,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4658
5365
  const hasAgentFlag = claudeSupportsAgentFlag();
4659
5366
  if (hasAgentFlag) {
4660
5367
  identityFlag = ` --agent ${employeeName}`;
4661
- } else if (existsSync11(identityPath)) {
5368
+ } else if (existsSync13(identityPath)) {
4662
5369
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4663
5370
  legacyFallbackWarned = true;
4664
5371
  }
4665
5372
  const behaviorsFile = exportBehaviorsSync(
4666
5373
  employeeName,
4667
- path13.basename(spawnCwd),
5374
+ path15.basename(spawnCwd),
4668
5375
  sessionName
4669
5376
  );
4670
5377
  if (behaviorsFile) {
@@ -4679,16 +5386,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4679
5386
  }
4680
5387
  let sessionContextFlag = "";
4681
5388
  try {
4682
- const ctxDir = path13.join(os6.homedir(), ".exe-os", "session-cache");
4683
- mkdirSync5(ctxDir, { recursive: true });
4684
- const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
5389
+ const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
5390
+ mkdirSync6(ctxDir, { recursive: true });
5391
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4685
5392
  const ctxContent = [
4686
5393
  `## Session Context`,
4687
5394
  `You are running in tmux session: ${sessionName}.`,
4688
5395
  `Your parent coordinator session is ${exeSession}.`,
4689
5396
  `Your employees (if any) use the -${exeSession} suffix.`
4690
5397
  ].join("\n");
4691
- writeFileSync6(ctxFile, ctxContent);
5398
+ writeFileSync7(ctxFile, ctxContent);
4692
5399
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4693
5400
  } catch {
4694
5401
  }
@@ -4702,9 +5409,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4702
5409
  }
4703
5410
  }
4704
5411
  }
5412
+ if (useCodex) {
5413
+ const codexCfg = RUNTIME_TABLE.codex;
5414
+ if (codexCfg?.apiKeyEnv) {
5415
+ const keyVal = process.env[codexCfg.apiKeyEnv];
5416
+ if (keyVal) {
5417
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
5418
+ }
5419
+ }
5420
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
5421
+ }
5422
+ if (useOpencode) {
5423
+ const ocCfg = PROVIDER_TABLE.opencode;
5424
+ if (ocCfg?.apiKeyEnv) {
5425
+ const keyVal = process.env[ocCfg.apiKeyEnv];
5426
+ if (keyVal) {
5427
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
5428
+ }
5429
+ }
5430
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
5431
+ }
5432
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
5433
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
5434
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
5435
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
5436
+ }
5437
+ }
4705
5438
  let spawnCommand;
4706
5439
  if (useExeAgent) {
4707
5440
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
5441
+ } else if (useCodex) {
5442
+ process.stderr.write(
5443
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
5444
+ `
5445
+ );
5446
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
5447
+ } else if (useOpencode) {
5448
+ const binName = `${employeeName}-opencode`;
5449
+ process.stderr.write(
5450
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
5451
+ `
5452
+ );
5453
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4708
5454
  } else if (useBinSymlink) {
4709
5455
  const binName = `${employeeName}-${ccProvider}`;
4710
5456
  process.stderr.write(
@@ -4720,17 +5466,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4720
5466
  command: spawnCommand
4721
5467
  });
4722
5468
  if (spawnResult.error) {
4723
- releaseSpawnLock(sessionName);
5469
+ releaseSpawnLock2(sessionName);
4724
5470
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4725
5471
  }
4726
5472
  transport.pipeLog(sessionName, logFile);
4727
5473
  try {
4728
5474
  const mySession = getMySession();
4729
- const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4730
- writeFileSync6(dispatchInfo, JSON.stringify({
5475
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5476
+ writeFileSync7(dispatchInfo, JSON.stringify({
4731
5477
  dispatchedBy: mySession,
4732
5478
  rootExe: exeSession,
4733
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
5479
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
5480
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
5481
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4734
5482
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4735
5483
  }));
4736
5484
  } catch {
@@ -4748,6 +5496,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4748
5496
  booted = true;
4749
5497
  break;
4750
5498
  }
5499
+ } else if (useCodex) {
5500
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
5501
+ booted = true;
5502
+ break;
5503
+ }
4751
5504
  } else {
4752
5505
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4753
5506
  booted = true;
@@ -4758,10 +5511,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4758
5511
  }
4759
5512
  }
4760
5513
  if (!booted) {
4761
- releaseSpawnLock(sessionName);
4762
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
5514
+ releaseSpawnLock2(sessionName);
5515
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
5516
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4763
5517
  }
4764
- if (!useExeAgent) {
5518
+ if (!useExeAgent && !useCodex) {
4765
5519
  try {
4766
5520
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4767
5521
  } catch {
@@ -4775,7 +5529,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4775
5529
  pid: 0,
4776
5530
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4777
5531
  });
4778
- releaseSpawnLock(sessionName);
5532
+ releaseSpawnLock2(sessionName);
4779
5533
  return { sessionName };
4780
5534
  }
4781
5535
  var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
@@ -4788,17 +5542,19 @@ var init_tmux_routing = __esm({
4788
5542
  init_cc_agent_support();
4789
5543
  init_mcp_prefix();
4790
5544
  init_provider_table();
5545
+ init_agent_config();
5546
+ init_runtime_table();
4791
5547
  init_intercom_queue();
4792
5548
  init_plan_limits();
4793
5549
  init_employees();
4794
- SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
4795
- SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
5550
+ SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
5551
+ SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
4796
5552
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4797
5553
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4798
5554
  VERIFY_PANE_LINES = 200;
4799
5555
  INTERCOM_DEBOUNCE_MS = 3e4;
4800
- INTERCOM_LOG2 = path13.join(os6.homedir(), ".exe-os", "intercom.log");
4801
- DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
5556
+ INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
5557
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4802
5558
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4803
5559
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4804
5560
  }
@@ -6191,7 +6947,7 @@ __export(global_procedures_exports, {
6191
6947
  loadGlobalProcedures: () => loadGlobalProcedures,
6192
6948
  storeGlobalProcedure: () => storeGlobalProcedure
6193
6949
  });
6194
- import { randomUUID as randomUUID2 } from "crypto";
6950
+ import { randomUUID as randomUUID3 } from "crypto";
6195
6951
  async function loadGlobalProcedures() {
6196
6952
  const client = getClient();
6197
6953
  const result = await client.execute({
@@ -6220,7 +6976,7 @@ ${sections.join("\n\n")}
6220
6976
  `;
6221
6977
  }
6222
6978
  async function storeGlobalProcedure(input) {
6223
- const id = randomUUID2();
6979
+ const id = randomUUID3();
6224
6980
  const now = (/* @__PURE__ */ new Date()).toISOString();
6225
6981
  const client = getClient();
6226
6982
  await client.execute({
@@ -6358,7 +7114,7 @@ var init_anthropic = __esm({
6358
7114
 
6359
7115
  // src/gateway/providers/openai-compat.ts
6360
7116
  import OpenAI from "openai";
6361
- import { randomUUID as randomUUID3 } from "crypto";
7117
+ import { randomUUID as randomUUID4 } from "crypto";
6362
7118
  var OpenAICompatProvider;
6363
7119
  var init_openai_compat = __esm({
6364
7120
  "src/gateway/providers/openai-compat.ts"() {
@@ -6475,7 +7231,7 @@ var init_openai_compat = __esm({
6475
7231
  }
6476
7232
  content.push({
6477
7233
  type: "tool_use",
6478
- id: call.id ?? randomUUID3(),
7234
+ id: call.id ?? randomUUID4(),
6479
7235
  name: fn.name,
6480
7236
  input
6481
7237
  });
@@ -6667,10 +7423,10 @@ var init_hooks = __esm({
6667
7423
  });
6668
7424
 
6669
7425
  // src/runtime/safety-checks.ts
6670
- import path14 from "path";
6671
- import os7 from "os";
7426
+ import path16 from "path";
7427
+ import os8 from "os";
6672
7428
  function checkPathSafety(filePath) {
6673
- const resolved = path14.resolve(filePath);
7429
+ const resolved = path16.resolve(filePath);
6674
7430
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
6675
7431
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
6676
7432
  if (matches) {
@@ -6680,7 +7436,7 @@ function checkPathSafety(filePath) {
6680
7436
  return { safe: true, bypassImmune: true };
6681
7437
  }
6682
7438
  function checkReadPathSafety(filePath) {
6683
- const resolved = path14.resolve(filePath);
7439
+ const resolved = path16.resolve(filePath);
6684
7440
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
6685
7441
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
6686
7442
  );
@@ -6695,7 +7451,7 @@ var HOME, BYPASS_IMMUNE_PATTERNS;
6695
7451
  var init_safety_checks = __esm({
6696
7452
  "src/runtime/safety-checks.ts"() {
6697
7453
  "use strict";
6698
- HOME = os7.homedir();
7454
+ HOME = os8.homedir();
6699
7455
  BYPASS_IMMUNE_PATTERNS = [
6700
7456
  {
6701
7457
  pattern: /\/\.git\/hooks\//,
@@ -6706,11 +7462,11 @@ var init_safety_checks = __esm({
6706
7462
  reason: "Git config can set hooks and command execution"
6707
7463
  },
6708
7464
  {
6709
- pattern: (p) => p.startsWith(path14.join(HOME, ".claude")),
7465
+ pattern: (p) => p.startsWith(path16.join(HOME, ".claude")),
6710
7466
  reason: "Claude configuration files are protected"
6711
7467
  },
6712
7468
  {
6713
- pattern: (p) => p.startsWith(path14.join(HOME, ".exe-os")),
7469
+ pattern: (p) => p.startsWith(path16.join(HOME, ".exe-os")),
6714
7470
  reason: "exe-os configuration files are protected"
6715
7471
  },
6716
7472
  {
@@ -6727,7 +7483,7 @@ var init_safety_checks = __esm({
6727
7483
  },
6728
7484
  {
6729
7485
  pattern: (p) => {
6730
- const name = path14.basename(p);
7486
+ const name = path16.basename(p);
6731
7487
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
6732
7488
  },
6733
7489
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -6754,7 +7510,7 @@ __export(file_read_exports, {
6754
7510
  FileReadTool: () => FileReadTool
6755
7511
  });
6756
7512
  import fs3 from "fs/promises";
6757
- import path15 from "path";
7513
+ import path17 from "path";
6758
7514
  import { z } from "zod";
6759
7515
  function isBinary(buf) {
6760
7516
  for (let i = 0; i < buf.length; i++) {
@@ -6790,7 +7546,7 @@ var init_file_read = __esm({
6790
7546
  return { behavior: "allow" };
6791
7547
  },
6792
7548
  async call(input, context) {
6793
- const filePath = path15.isAbsolute(input.file_path) ? input.file_path : path15.resolve(context.cwd, input.file_path);
7549
+ const filePath = path17.isAbsolute(input.file_path) ? input.file_path : path17.resolve(context.cwd, input.file_path);
6794
7550
  let stat;
6795
7551
  try {
6796
7552
  stat = await fs3.stat(filePath);
@@ -6830,7 +7586,7 @@ __export(glob_exports, {
6830
7586
  GlobTool: () => GlobTool
6831
7587
  });
6832
7588
  import fs4 from "fs/promises";
6833
- import path16 from "path";
7589
+ import path18 from "path";
6834
7590
  import { z as z2 } from "zod";
6835
7591
  async function walkDir(dir, maxDepth = 10) {
6836
7592
  const results = [];
@@ -6846,7 +7602,7 @@ async function walkDir(dir, maxDepth = 10) {
6846
7602
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
6847
7603
  continue;
6848
7604
  }
6849
- const fullPath = path16.join(current, entry.name);
7605
+ const fullPath = path18.join(current, entry.name);
6850
7606
  if (entry.isDirectory()) {
6851
7607
  await walk(fullPath, depth + 1);
6852
7608
  } else {
@@ -6880,11 +7636,11 @@ var init_glob = __esm({
6880
7636
  inputSchema: inputSchema2,
6881
7637
  isReadOnly: true,
6882
7638
  async call(input, context) {
6883
- const baseDir = input.path ? path16.isAbsolute(input.path) ? input.path : path16.resolve(context.cwd, input.path) : context.cwd;
7639
+ const baseDir = input.path ? path18.isAbsolute(input.path) ? input.path : path18.resolve(context.cwd, input.path) : context.cwd;
6884
7640
  try {
6885
7641
  const entries = await walkDir(baseDir);
6886
7642
  const matched = entries.filter(
6887
- (e) => simpleGlobMatch(path16.relative(baseDir, e.path), input.pattern)
7643
+ (e) => simpleGlobMatch(path18.relative(baseDir, e.path), input.pattern)
6888
7644
  );
6889
7645
  matched.sort((a, b) => b.mtime - a.mtime);
6890
7646
  if (matched.length === 0) {
@@ -6908,9 +7664,9 @@ var grep_exports = {};
6908
7664
  __export(grep_exports, {
6909
7665
  GrepTool: () => GrepTool
6910
7666
  });
6911
- import { spawn } from "child_process";
7667
+ import { spawn as spawn2 } from "child_process";
6912
7668
  import fs5 from "fs/promises";
6913
- import path17 from "path";
7669
+ import path19 from "path";
6914
7670
  import { z as z3 } from "zod";
6915
7671
  function runRipgrep(input, searchPath, context) {
6916
7672
  return new Promise((resolve, reject) => {
@@ -6922,7 +7678,7 @@ function runRipgrep(input, searchPath, context) {
6922
7678
  args.push("--glob", input.include);
6923
7679
  }
6924
7680
  args.push(input.pattern, searchPath);
6925
- const child = spawn("rg", args, {
7681
+ const child = spawn2("rg", args, {
6926
7682
  cwd: searchPath,
6927
7683
  timeout: 3e4,
6928
7684
  stdio: ["ignore", "pipe", "pipe"]
@@ -6964,7 +7720,7 @@ async function nodeGrep(input, searchPath) {
6964
7720
  }
6965
7721
  for (const entry of entries) {
6966
7722
  if (entry.name === "node_modules" || entry.name === ".git") continue;
6967
- const fullPath = path17.join(dir, entry.name);
7723
+ const fullPath = path19.join(dir, entry.name);
6968
7724
  if (entry.isDirectory()) {
6969
7725
  await walk(fullPath);
6970
7726
  } else {
@@ -7010,7 +7766,7 @@ var init_grep = __esm({
7010
7766
  inputSchema: inputSchema3,
7011
7767
  isReadOnly: true,
7012
7768
  async call(input, context) {
7013
- const searchPath = input.path ? path17.isAbsolute(input.path) ? input.path : path17.resolve(context.cwd, input.path) : context.cwd;
7769
+ const searchPath = input.path ? path19.isAbsolute(input.path) ? input.path : path19.resolve(context.cwd, input.path) : context.cwd;
7014
7770
  try {
7015
7771
  const result = await runRipgrep(input, searchPath, context);
7016
7772
  return result;
@@ -7035,7 +7791,7 @@ __export(file_write_exports, {
7035
7791
  FileWriteTool: () => FileWriteTool
7036
7792
  });
7037
7793
  import fs6 from "fs/promises";
7038
- import path18 from "path";
7794
+ import path20 from "path";
7039
7795
  import { z as z4 } from "zod";
7040
7796
  var inputSchema4, FileWriteTool;
7041
7797
  var init_file_write = __esm({
@@ -7063,8 +7819,8 @@ var init_file_write = __esm({
7063
7819
  return { behavior: "allow" };
7064
7820
  },
7065
7821
  async call(input, context) {
7066
- const filePath = path18.isAbsolute(input.file_path) ? input.file_path : path18.resolve(context.cwd, input.file_path);
7067
- const dir = path18.dirname(filePath);
7822
+ const filePath = path20.isAbsolute(input.file_path) ? input.file_path : path20.resolve(context.cwd, input.file_path);
7823
+ const dir = path20.dirname(filePath);
7068
7824
  await fs6.mkdir(dir, { recursive: true });
7069
7825
  await fs6.writeFile(filePath, input.content, "utf-8");
7070
7826
  return {
@@ -7082,7 +7838,7 @@ __export(file_edit_exports, {
7082
7838
  FileEditTool: () => FileEditTool
7083
7839
  });
7084
7840
  import fs7 from "fs/promises";
7085
- import path19 from "path";
7841
+ import path21 from "path";
7086
7842
  import { z as z5 } from "zod";
7087
7843
  function countOccurrences(haystack, needle) {
7088
7844
  let count = 0;
@@ -7123,7 +7879,7 @@ var init_file_edit = __esm({
7123
7879
  return { behavior: "allow" };
7124
7880
  },
7125
7881
  async call(input, context) {
7126
- const filePath = path19.isAbsolute(input.file_path) ? input.file_path : path19.resolve(context.cwd, input.file_path);
7882
+ const filePath = path21.isAbsolute(input.file_path) ? input.file_path : path21.resolve(context.cwd, input.file_path);
7127
7883
  let content;
7128
7884
  try {
7129
7885
  content = await fs7.readFile(filePath, "utf-8");
@@ -7280,7 +8036,7 @@ var bash_exports = {};
7280
8036
  __export(bash_exports, {
7281
8037
  BashTool: () => BashTool
7282
8038
  });
7283
- import { spawn as spawn2 } from "child_process";
8039
+ import { spawn as spawn3 } from "child_process";
7284
8040
  import { z as z6 } from "zod";
7285
8041
  var DEFAULT_TIMEOUT_MS, inputSchema6, BashTool;
7286
8042
  var init_bash = __esm({
@@ -7311,7 +8067,7 @@ var init_bash = __esm({
7311
8067
  async call(input, context) {
7312
8068
  const timeout = input.timeout ?? DEFAULT_TIMEOUT_MS;
7313
8069
  return new Promise((resolve) => {
7314
- const child = spawn2("bash", ["-c", input.command], {
8070
+ const child = spawn3("bash", ["-c", input.command], {
7315
8071
  cwd: context.cwd,
7316
8072
  timeout,
7317
8073
  stdio: ["ignore", "pipe", "pipe"],
@@ -7363,7 +8119,7 @@ var init_bash = __esm({
7363
8119
  });
7364
8120
 
7365
8121
  // src/lib/task-router.ts
7366
- import { randomUUID as randomUUID4 } from "crypto";
8122
+ import { randomUUID as randomUUID5 } from "crypto";
7367
8123
  function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
7368
8124
  const tier = config.complexityToTier[complexity];
7369
8125
  const rule = config.tierRules[tier];
@@ -7788,14 +8544,14 @@ __export(keychain_exports, {
7788
8544
  setMasterKey: () => setMasterKey
7789
8545
  });
7790
8546
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
7791
- import { existsSync as existsSync12 } from "fs";
7792
- import path22 from "path";
7793
- import os8 from "os";
8547
+ import { existsSync as existsSync14 } from "fs";
8548
+ import path24 from "path";
8549
+ import os9 from "os";
7794
8550
  function getKeyDir() {
7795
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os8.homedir(), ".exe-os");
8551
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os9.homedir(), ".exe-os");
7796
8552
  }
7797
8553
  function getKeyPath() {
7798
- return path22.join(getKeyDir(), "master.key");
8554
+ return path24.join(getKeyDir(), "master.key");
7799
8555
  }
7800
8556
  async function tryKeytar() {
7801
8557
  try {
@@ -7816,13 +8572,21 @@ async function getMasterKey() {
7816
8572
  }
7817
8573
  }
7818
8574
  const keyPath = getKeyPath();
7819
- if (!existsSync12(keyPath)) {
8575
+ if (!existsSync14(keyPath)) {
8576
+ process.stderr.write(
8577
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
8578
+ `
8579
+ );
7820
8580
  return null;
7821
8581
  }
7822
8582
  try {
7823
8583
  const content = await readFile4(keyPath, "utf-8");
7824
8584
  return Buffer.from(content.trim(), "base64");
7825
- } catch {
8585
+ } catch (err) {
8586
+ process.stderr.write(
8587
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
8588
+ `
8589
+ );
7826
8590
  return null;
7827
8591
  }
7828
8592
  }
@@ -7851,7 +8615,7 @@ async function deleteMasterKey() {
7851
8615
  }
7852
8616
  }
7853
8617
  const keyPath = getKeyPath();
7854
- if (existsSync12(keyPath)) {
8618
+ if (existsSync14(keyPath)) {
7855
8619
  await unlink(keyPath);
7856
8620
  }
7857
8621
  }
@@ -8151,14 +8915,14 @@ __export(wiki_client_exports, {
8151
8915
  listDocuments: () => listDocuments,
8152
8916
  listWorkspaces: () => listWorkspaces
8153
8917
  });
8154
- async function wikiFetch(config, path24, method = "GET", body) {
8155
- const url = `${config.baseUrl}/api/v1${path24}`;
8918
+ async function wikiFetch(config, path26, method = "GET", body) {
8919
+ const url = `${config.baseUrl}/api/v1${path26}`;
8156
8920
  const headers = {
8157
8921
  Authorization: `Bearer ${config.apiKey}`,
8158
8922
  "Content-Type": "application/json"
8159
8923
  };
8160
8924
  const controller = new AbortController();
8161
- const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
8925
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
8162
8926
  try {
8163
8927
  let response;
8164
8928
  try {
@@ -8171,7 +8935,7 @@ async function wikiFetch(config, path24, method = "GET", body) {
8171
8935
  } catch {
8172
8936
  clearTimeout(timeout);
8173
8937
  const retryController = new AbortController();
8174
- const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS);
8938
+ const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS2);
8175
8939
  try {
8176
8940
  await new Promise((r) => setTimeout(r, 500));
8177
8941
  response = await fetch(url, {
@@ -8185,7 +8949,7 @@ async function wikiFetch(config, path24, method = "GET", body) {
8185
8949
  }
8186
8950
  }
8187
8951
  if (!response.ok) {
8188
- throw new Error(`Wiki API ${method} ${path24}: ${response.status} ${response.statusText}`);
8952
+ throw new Error(`Wiki API ${method} ${path26}: ${response.status} ${response.statusText}`);
8189
8953
  }
8190
8954
  return response.json();
8191
8955
  } finally {
@@ -8275,12 +9039,12 @@ async function getChatHistory(client, workspaceSlug, limit = 50) {
8275
9039
  sentAt: h.sentAt ?? 0
8276
9040
  }));
8277
9041
  }
8278
- var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS;
9042
+ var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
8279
9043
  var init_wiki_client = __esm({
8280
9044
  "src/lib/wiki-client.ts"() {
8281
9045
  "use strict";
8282
- LOCAL_WIKI_URL = "http://localhost:3001";
8283
- REQUEST_TIMEOUT_MS = 8e3;
9046
+ LOCAL_WIKI_URL = process.env.EXE_WIKI_URL || "http://localhost:3001";
9047
+ REQUEST_TIMEOUT_MS2 = 8e3;
8284
9048
  }
8285
9049
  });
8286
9050
 
@@ -8306,13 +9070,13 @@ __export(shard_manager_exports, {
8306
9070
  listShards: () => listShards,
8307
9071
  shardExists: () => shardExists
8308
9072
  });
8309
- import path23 from "path";
8310
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
9073
+ import path25 from "path";
9074
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
8311
9075
  import { createClient as createClient2 } from "@libsql/client";
8312
9076
  function initShardManager(encryptionKey) {
8313
9077
  _encryptionKey = encryptionKey;
8314
- if (!existsSync13(SHARDS_DIR)) {
8315
- mkdirSync6(SHARDS_DIR, { recursive: true });
9078
+ if (!existsSync15(SHARDS_DIR)) {
9079
+ mkdirSync7(SHARDS_DIR, { recursive: true });
8316
9080
  }
8317
9081
  _shardingEnabled = true;
8318
9082
  }
@@ -8332,7 +9096,7 @@ function getShardClient(projectName) {
8332
9096
  }
8333
9097
  const cached = _shards.get(safeName);
8334
9098
  if (cached) return cached;
8335
- const dbPath = path23.join(SHARDS_DIR, `${safeName}.db`);
9099
+ const dbPath = path25.join(SHARDS_DIR, `${safeName}.db`);
8336
9100
  const client = createClient2({
8337
9101
  url: `file:${dbPath}`,
8338
9102
  encryptionKey: _encryptionKey
@@ -8342,10 +9106,10 @@ function getShardClient(projectName) {
8342
9106
  }
8343
9107
  function shardExists(projectName) {
8344
9108
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
8345
- return existsSync13(path23.join(SHARDS_DIR, `${safeName}.db`));
9109
+ return existsSync15(path25.join(SHARDS_DIR, `${safeName}.db`));
8346
9110
  }
8347
9111
  function listShards() {
8348
- if (!existsSync13(SHARDS_DIR)) return [];
9112
+ if (!existsSync15(SHARDS_DIR)) return [];
8349
9113
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
8350
9114
  }
8351
9115
  async function ensureShardSchema(client) {
@@ -8531,7 +9295,7 @@ var init_shard_manager = __esm({
8531
9295
  "src/lib/shard-manager.ts"() {
8532
9296
  "use strict";
8533
9297
  init_config();
8534
- SHARDS_DIR = path23.join(EXE_AI_DIR, "shards");
9298
+ SHARDS_DIR = path25.join(EXE_AI_DIR, "shards");
8535
9299
  _shards = /* @__PURE__ */ new Map();
8536
9300
  _encryptionKey = null;
8537
9301
  _shardingEnabled = false;
@@ -8555,6 +9319,7 @@ __export(store_exports, {
8555
9319
  vectorToBlob: () => vectorToBlob,
8556
9320
  writeMemory: () => writeMemory
8557
9321
  });
9322
+ import { createHash } from "crypto";
8558
9323
  function isBusyError2(err) {
8559
9324
  if (err instanceof Error) {
8560
9325
  const msg = err.message.toLowerCase();
@@ -8628,12 +9393,52 @@ function classifyTier(record) {
8628
9393
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
8629
9394
  return 3;
8630
9395
  }
9396
+ function inferFilePaths(record) {
9397
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
9398
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
9399
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
9400
+ return match ? JSON.stringify([match[1]]) : null;
9401
+ }
9402
+ function inferCommitHash(record) {
9403
+ if (record.tool_name !== "Bash") return null;
9404
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
9405
+ return match ? match[1] : null;
9406
+ }
9407
+ function inferLanguageType(record) {
9408
+ const text = record.raw_text;
9409
+ if (!text || text.length < 10) return null;
9410
+ const trimmed = text.trimStart();
9411
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
9412
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
9413
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
9414
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
9415
+ return "mixed";
9416
+ }
9417
+ function inferDomain(record) {
9418
+ const proj = (record.project_name ?? "").toLowerCase();
9419
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
9420
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
9421
+ return null;
9422
+ }
8631
9423
  async function writeMemory(record) {
8632
9424
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
8633
9425
  throw new Error(
8634
9426
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
8635
9427
  );
8636
9428
  }
9429
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
9430
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
9431
+ return;
9432
+ }
9433
+ try {
9434
+ const client = getClient();
9435
+ const existing = await client.execute({
9436
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
9437
+ args: [contentHash, record.agent_id]
9438
+ });
9439
+ if (existing.rows.length > 0) return;
9440
+ } catch {
9441
+ }
8637
9442
  const dbRow = {
8638
9443
  id: record.id,
8639
9444
  agent_id: record.agent_id,
@@ -8663,7 +9468,23 @@ async function writeMemory(record) {
8663
9468
  supersedes_id: record.supersedes_id ?? null,
8664
9469
  draft: record.draft ? 1 : 0,
8665
9470
  memory_type: record.memory_type ?? "raw",
8666
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
9471
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
9472
+ content_hash: contentHash,
9473
+ intent: record.intent ?? null,
9474
+ outcome: record.outcome ?? null,
9475
+ domain: record.domain ?? inferDomain(record),
9476
+ referenced_entities: record.referenced_entities ?? null,
9477
+ retrieval_count: record.retrieval_count ?? 0,
9478
+ chain_position: record.chain_position ?? null,
9479
+ review_status: record.review_status ?? null,
9480
+ context_window_pct: record.context_window_pct ?? null,
9481
+ file_paths: record.file_paths ?? inferFilePaths(record),
9482
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
9483
+ duration_ms: record.duration_ms ?? null,
9484
+ token_cost: record.token_cost ?? null,
9485
+ audience: record.audience ?? null,
9486
+ language_type: record.language_type ?? inferLanguageType(record),
9487
+ parent_memory_id: record.parent_memory_id ?? null
8667
9488
  };
8668
9489
  _pendingRecords.push(dbRow);
8669
9490
  orgBus.emit({
@@ -8721,80 +9542,85 @@ async function flushBatch() {
8721
9542
  const draft = row.draft ? 1 : 0;
8722
9543
  const memoryType = row.memory_type ?? "raw";
8723
9544
  const trajectory = row.trajectory ?? null;
8724
- return {
8725
- sql: hasVector ? `INSERT OR IGNORE INTO memories
8726
- (id, agent_id, agent_role, session_id, timestamp,
8727
- tool_name, project_name,
8728
- has_error, raw_text, vector, version, task_id, importance, status,
8729
- confidence, last_accessed,
8730
- workspace_id, document_id, user_id, char_offset, page_number,
8731
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
8732
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
8733
- (id, agent_id, agent_role, session_id, timestamp,
9545
+ const contentHash = row.content_hash ?? null;
9546
+ const intent = row.intent ?? null;
9547
+ const outcome = row.outcome ?? null;
9548
+ const domain = row.domain ?? null;
9549
+ const referencedEntities = row.referenced_entities ?? null;
9550
+ const retrievalCount = row.retrieval_count ?? 0;
9551
+ const chainPosition = row.chain_position ?? null;
9552
+ const reviewStatus = row.review_status ?? null;
9553
+ const contextWindowPct = row.context_window_pct ?? null;
9554
+ const filePaths = row.file_paths ?? null;
9555
+ const commitHash = row.commit_hash ?? null;
9556
+ const durationMs = row.duration_ms ?? null;
9557
+ const tokenCost = row.token_cost ?? null;
9558
+ const audience = row.audience ?? null;
9559
+ const languageType = row.language_type ?? null;
9560
+ const parentMemoryId = row.parent_memory_id ?? null;
9561
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
8734
9562
  tool_name, project_name,
8735
9563
  has_error, raw_text, vector, version, task_id, importance, status,
8736
9564
  confidence, last_accessed,
8737
9565
  workspace_id, document_id, user_id, char_offset, page_number,
8738
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
8739
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
8740
- args: hasVector ? [
8741
- row.id,
8742
- row.agent_id,
8743
- row.agent_role,
8744
- row.session_id,
8745
- row.timestamp,
8746
- row.tool_name,
8747
- row.project_name,
8748
- row.has_error,
8749
- row.raw_text,
8750
- vectorToBlob(row.vector),
8751
- row.version,
8752
- taskId,
8753
- importance,
8754
- status,
8755
- confidence,
8756
- lastAccessed,
8757
- workspaceId,
8758
- documentId,
8759
- userId,
8760
- charOffset,
8761
- pageNumber,
8762
- sourcePath,
8763
- sourceType,
8764
- tier,
8765
- supersedesId,
8766
- draft,
8767
- memoryType,
8768
- trajectory
8769
- ] : [
8770
- row.id,
8771
- row.agent_id,
8772
- row.agent_role,
8773
- row.session_id,
8774
- row.timestamp,
8775
- row.tool_name,
8776
- row.project_name,
8777
- row.has_error,
8778
- row.raw_text,
8779
- row.version,
8780
- taskId,
8781
- importance,
8782
- status,
8783
- confidence,
8784
- lastAccessed,
8785
- workspaceId,
8786
- documentId,
8787
- userId,
8788
- charOffset,
8789
- pageNumber,
8790
- sourcePath,
8791
- sourceType,
8792
- tier,
8793
- supersedesId,
8794
- draft,
8795
- memoryType,
8796
- trajectory
8797
- ]
9566
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
9567
+ intent, outcome, domain, referenced_entities, retrieval_count,
9568
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
9569
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
9570
+ const metaArgs = [
9571
+ intent,
9572
+ outcome,
9573
+ domain,
9574
+ referencedEntities,
9575
+ retrievalCount,
9576
+ chainPosition,
9577
+ reviewStatus,
9578
+ contextWindowPct,
9579
+ filePaths,
9580
+ commitHash,
9581
+ durationMs,
9582
+ tokenCost,
9583
+ audience,
9584
+ languageType,
9585
+ parentMemoryId
9586
+ ];
9587
+ const baseArgs = [
9588
+ row.id,
9589
+ row.agent_id,
9590
+ row.agent_role,
9591
+ row.session_id,
9592
+ row.timestamp,
9593
+ row.tool_name,
9594
+ row.project_name,
9595
+ row.has_error,
9596
+ row.raw_text
9597
+ ];
9598
+ const sharedArgs = [
9599
+ row.version,
9600
+ taskId,
9601
+ importance,
9602
+ status,
9603
+ confidence,
9604
+ lastAccessed,
9605
+ workspaceId,
9606
+ documentId,
9607
+ userId,
9608
+ charOffset,
9609
+ pageNumber,
9610
+ sourcePath,
9611
+ sourceType,
9612
+ tier,
9613
+ supersedesId,
9614
+ draft,
9615
+ memoryType,
9616
+ trajectory,
9617
+ contentHash
9618
+ ];
9619
+ return {
9620
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
9621
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9622
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9623
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
8798
9624
  };
8799
9625
  };
8800
9626
  const globalClient = getClient();
@@ -13039,8 +13865,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
13039
13865
  }
13040
13866
 
13041
13867
  // src/tui/ink/components/ErrorOverview.js
13042
- var cleanupPath = (path24) => {
13043
- return path24?.replace(`file://${cwd()}/`, "");
13868
+ var cleanupPath = (path26) => {
13869
+ return path26?.replace(`file://${cwd()}/`, "");
13044
13870
  };
13045
13871
  var stackUtils = new StackUtils({
13046
13872
  cwd: cwd(),
@@ -14639,7 +15465,7 @@ var useInput = (inputHandler, options = {}) => {
14639
15465
  if (options.isActive === false) {
14640
15466
  return;
14641
15467
  }
14642
- const handleData = (data) => {
15468
+ const handleData2 = (data) => {
14643
15469
  const keypress = parse_keypress_default(data);
14644
15470
  const key = {
14645
15471
  upArrow: keypress.name === "up",
@@ -14698,9 +15524,9 @@ var useInput = (inputHandler, options = {}) => {
14698
15524
  });
14699
15525
  }
14700
15526
  };
14701
- internal_eventEmitter?.on("input", handleData);
15527
+ internal_eventEmitter?.on("input", handleData2);
14702
15528
  return () => {
14703
- internal_eventEmitter?.removeListener("input", handleData);
15529
+ internal_eventEmitter?.removeListener("input", handleData2);
14704
15530
  };
14705
15531
  }, [options.isActive, stdin, internal_exitOnCtrlC, inputHandler]);
14706
15532
  };
@@ -14972,11 +15798,11 @@ function Footer() {
14972
15798
  } catch {
14973
15799
  }
14974
15800
  try {
14975
- const { existsSync: existsSync14 } = await import("fs");
15801
+ const { existsSync: existsSync16 } = await import("fs");
14976
15802
  const { join } = await import("path");
14977
15803
  const home = process.env.HOME ?? "";
14978
15804
  const pidPath = join(home, ".exe-os", "exed.pid");
14979
- setDaemon(existsSync14(pidPath) ? "running" : "stopped");
15805
+ setDaemon(existsSync16(pidPath) ? "running" : "stopped");
14980
15806
  } catch {
14981
15807
  setDaemon("unknown");
14982
15808
  }
@@ -15058,7 +15884,7 @@ function Footer() {
15058
15884
  // src/tui/views/CommandCenter.tsx
15059
15885
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
15060
15886
  import TextInput from "ink-text-input";
15061
- import path20 from "path";
15887
+ import path22 from "path";
15062
15888
  import { homedir } from "os";
15063
15889
 
15064
15890
  // src/tui/components/StatusDot.tsx
@@ -15845,15 +16671,15 @@ function CommandCenterView({
15845
16671
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
15846
16672
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
15847
16673
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
15848
- const { readFileSync: readFileSync11, existsSync: existsSync14 } = await import("fs");
16674
+ const { readFileSync: readFileSync13, existsSync: existsSync16 } = await import("fs");
15849
16675
  const { join } = await import("path");
15850
16676
  const { homedir: homedir3 } = await import("os");
15851
16677
  const configPath = join(homedir3(), ".exe-os", "config.json");
15852
16678
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
15853
16679
  let providerConfigs = {};
15854
- if (existsSync14(configPath)) {
16680
+ if (existsSync16(configPath)) {
15855
16681
  try {
15856
- const raw = JSON.parse(readFileSync11(configPath, "utf8"));
16682
+ const raw = JSON.parse(readFileSync13(configPath, "utf8"));
15857
16683
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
15858
16684
  if (raw.providers && typeof raw.providers === "object") {
15859
16685
  providerConfigs = raw.providers;
@@ -15914,7 +16740,7 @@ function CommandCenterView({
15914
16740
  const markerDir = join(homedir3(), ".exe-os", "session-cache");
15915
16741
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
15916
16742
  for (const f of agentFiles) {
15917
- const data = JSON.parse(readFileSync11(join(markerDir, f), "utf8"));
16743
+ const data = JSON.parse(readFileSync13(join(markerDir, f), "utf8"));
15918
16744
  if (data.agentRole) {
15919
16745
  agentRole = data.agentRole;
15920
16746
  break;
@@ -16059,7 +16885,7 @@ function CommandCenterView({
16059
16885
  const demoEntries = DEMO_PROJECTS.map((p) => ({
16060
16886
  projectName: p.projectName,
16061
16887
  exeSession: p.exeSession,
16062
- projectDir: path20.join(homedir(), p.projectName),
16888
+ projectDir: path22.join(homedir(), p.projectName),
16063
16889
  employeeCount: p.employees.length,
16064
16890
  activeCount: p.employees.filter((e) => e.status === "active").length,
16065
16891
  memoryCount: p.employees.length * 4e3,
@@ -16097,7 +16923,7 @@ function CommandCenterView({
16097
16923
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
16098
16924
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
16099
16925
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16100
- const { existsSync: existsSync14 } = await import("fs");
16926
+ const { existsSync: existsSync16 } = await import("fs");
16101
16927
  const { join } = await import("path");
16102
16928
  const client = getClient2();
16103
16929
  if (!client) {
@@ -16142,12 +16968,12 @@ function CommandCenterView({
16142
16968
  const registry = listSessions2();
16143
16969
  const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
16144
16970
  const roster = await loadEmployees2();
16145
- const { getCoordinatorName: getCoordinatorName2, isCoordinatorRole: isCoordinatorRole2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16971
+ const { getCoordinatorName: getCoordinatorName2, isCoordinatorName: isCoordinatorName2, isCoordinatorRole: isCoordinatorRole2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16146
16972
  const coordinatorName = getCoordinatorName2(roster);
16147
16973
  const employeeNames = roster.filter((e) => !isCoordinatorRole2(e.role)).map((e) => e.name);
16148
16974
  const projectSessions = /* @__PURE__ */ new Map();
16149
16975
  for (const entry of registry) {
16150
- if ((entry.agentId === coordinatorName || entry.agentId === "exe") && tmuxSessions.has(entry.windowName)) {
16976
+ if ((entry.agentId === coordinatorName || isCoordinatorName2(entry.agentId)) && tmuxSessions.has(entry.windowName)) {
16151
16977
  const projName = entry.projectDir.split("/").filter(Boolean).pop() ?? "";
16152
16978
  if (projName) {
16153
16979
  projectSessions.set(projName, { exeSession: entry.windowName, projectDir: entry.projectDir });
@@ -16168,7 +16994,7 @@ function CommandCenterView({
16168
16994
  }
16169
16995
  const memoryCount = memoryCounts.get(name) ?? 0;
16170
16996
  const openTaskCount = openTaskCounts.get(name) ?? 0;
16171
- const hasGit = projectDir ? existsSync14(join(projectDir, ".git")) : false;
16997
+ const hasGit = projectDir ? existsSync16(join(projectDir, ".git")) : false;
16172
16998
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
16173
16999
  projectList.push({
16174
17000
  projectName: name,
@@ -16193,7 +17019,7 @@ function CommandCenterView({
16193
17019
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
16194
17020
  try {
16195
17021
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
16196
- setHealth((h) => ({ ...h, daemon: existsSync14(pidPath) ? "running" : "stopped" }));
17022
+ setHealth((h) => ({ ...h, daemon: existsSync16(pidPath) ? "running" : "stopped" }));
16197
17023
  } catch {
16198
17024
  }
16199
17025
  const activityResult = await client.execute(
@@ -16408,7 +17234,7 @@ function ChatMessageRow({ msg }) {
16408
17234
 
16409
17235
  // src/tui/views/Sessions.tsx
16410
17236
  import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
16411
- import path21 from "path";
17237
+ import path23 from "path";
16412
17238
  import { homedir as homedir2 } from "os";
16413
17239
 
16414
17240
  // src/tui/components/TmuxPane.tsx
@@ -16671,12 +17497,13 @@ function useOrchestrator(enabled = true) {
16671
17497
  }
16672
17498
 
16673
17499
  // src/tui/views/Sessions.tsx
17500
+ init_employees();
16674
17501
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
16675
17502
  var SESSION_REFRESH_MS = 5e3;
16676
17503
  var ACTIVE_PANE_PATTERN = /█|░|Calculating|tokens|writing|Reading/;
16677
17504
  var ACTIVITY_PREVIEW_MAX = 50;
16678
17505
  function isCoordinatorEntry(entry) {
16679
- return entry.role.toLowerCase() === "coo" || entry.name === "exe";
17506
+ return entry.role.toLowerCase() === "coo" || isCoordinatorName(entry.name);
16680
17507
  }
16681
17508
  function SessionsView({
16682
17509
  initialProject,
@@ -16710,7 +17537,7 @@ function SessionsView({
16710
17537
  if (demo) {
16711
17538
  setProjects(DEMO_PROJECTS.map((p) => ({
16712
17539
  ...p,
16713
- projectDir: path21.join(homedir2(), p.projectName),
17540
+ projectDir: path23.join(homedir2(), p.projectName),
16714
17541
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
16715
17542
  })));
16716
17543
  return;
@@ -16900,7 +17727,7 @@ function SessionsView({
16900
17727
  const coordinatorName = getCoordinatorName2(roster);
16901
17728
  const exeSessions = /* @__PURE__ */ new Map();
16902
17729
  for (const entry of registry) {
16903
- if ((entry.agentId === coordinatorName || entry.agentId === "exe") && tmuxSessions.has(entry.windowName)) {
17730
+ if ((entry.agentId === coordinatorName || isCoordinatorName(entry.agentId)) && tmuxSessions.has(entry.windowName)) {
16904
17731
  exeSessions.set(entry.windowName, entry.projectDir);
16905
17732
  }
16906
17733
  }
@@ -17607,12 +18434,12 @@ async function loadGatewayConfig() {
17607
18434
  state.running = false;
17608
18435
  }
17609
18436
  try {
17610
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
18437
+ const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
17611
18438
  const { join } = await import("path");
17612
18439
  const home = process.env.HOME ?? "";
17613
18440
  const configPath = join(home, ".exe-os", "gateway.json");
17614
- if (existsSync14(configPath)) {
17615
- const raw = JSON.parse(readFileSync11(configPath, "utf8"));
18441
+ if (existsSync16(configPath)) {
18442
+ const raw = JSON.parse(readFileSync13(configPath, "utf8"));
17616
18443
  state.port = raw.port ?? 3100;
17617
18444
  state.gatewayUrl = raw.gatewayUrl ?? "";
17618
18445
  if (raw.adapters) {
@@ -18138,12 +18965,12 @@ function TeamView({ onBack, onViewSessions }) {
18138
18965
  setMembers(teamData);
18139
18966
  setDbError(null);
18140
18967
  try {
18141
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
18968
+ const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
18142
18969
  const { join } = await import("path");
18143
18970
  const home = process.env.HOME ?? "";
18144
18971
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
18145
- if (existsSync14(gatewayConfig)) {
18146
- const raw = JSON.parse(readFileSync11(gatewayConfig, "utf8"));
18972
+ if (existsSync16(gatewayConfig)) {
18973
+ const raw = JSON.parse(readFileSync13(gatewayConfig, "utf8"));
18147
18974
  if (raw.agents && raw.agents.length > 0) {
18148
18975
  setExternals(raw.agents.map((a) => ({
18149
18976
  name: a.name,
@@ -18807,12 +19634,12 @@ function SettingsView({ onBack }) {
18807
19634
  }
18808
19635
  setProviders(providerList);
18809
19636
  try {
18810
- const { existsSync: existsSync14 } = await import("fs");
19637
+ const { existsSync: existsSync16 } = await import("fs");
18811
19638
  const { join } = await import("path");
18812
19639
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
18813
19640
  const cfg = await loadConfig2();
18814
19641
  const home = process.env.HOME ?? "";
18815
- const hasKey = existsSync14(join(home, ".exe-os", "master.key"));
19642
+ const hasKey = existsSync16(join(home, ".exe-os", "master.key"));
18816
19643
  if (cfg.cloud) {
18817
19644
  setCloud({
18818
19645
  configured: true,
@@ -18825,22 +19652,22 @@ function SettingsView({ onBack }) {
18825
19652
  const pidPath = join(home, ".exe-os", "exed.pid");
18826
19653
  let daemon = "unknown";
18827
19654
  try {
18828
- daemon = existsSync14(pidPath) ? "running" : "stopped";
19655
+ daemon = existsSync16(pidPath) ? "running" : "stopped";
18829
19656
  } catch {
18830
19657
  }
18831
19658
  let version = "unknown";
18832
19659
  try {
18833
- const { readFileSync: readFileSync11 } = await import("fs");
19660
+ const { readFileSync: readFileSync13 } = await import("fs");
18834
19661
  const { createRequire } = await import("module");
18835
19662
  const require2 = createRequire(import.meta.url);
18836
19663
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
18837
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
19664
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
18838
19665
  version = pkg.version;
18839
19666
  } catch {
18840
19667
  try {
18841
- const { readFileSync: readFileSync11 } = await import("fs");
19668
+ const { readFileSync: readFileSync13 } = await import("fs");
18842
19669
  const { join: joinPath } = await import("path");
18843
- const pkg = JSON.parse(readFileSync11(joinPath(process.cwd(), "package.json"), "utf8"));
19670
+ const pkg = JSON.parse(readFileSync13(joinPath(process.cwd(), "package.json"), "utf8"));
18844
19671
  version = pkg.version;
18845
19672
  } catch {
18846
19673
  }