nexus-agents 2.174.2 → 2.176.0

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 (145) hide show
  1. package/dist/{child-mcp-config-GWB7JLIS.js → child-mcp-config-HPWYQFLG.js} +9 -7
  2. package/dist/child-mcp-config-HPWYQFLG.js.map +1 -0
  3. package/dist/{chunk-E35RUETV.js → chunk-2DE5L734.js} +2 -2
  4. package/dist/{chunk-AXTD4TFK.js → chunk-2HUC6NZN.js} +2 -2
  5. package/dist/{chunk-DJZGCMNU.js → chunk-46XEDJRB.js} +2 -2
  6. package/dist/{chunk-OCTNNEXT.js → chunk-4OPEUDHY.js} +10 -10
  7. package/dist/{chunk-H5KLPUAL.js → chunk-4S6S6S2S.js} +3 -3
  8. package/dist/{chunk-Z77MIV3C.js → chunk-5ALUADB6.js} +2 -2
  9. package/dist/{chunk-YE2XMU3T.js → chunk-5LTRHNOI.js} +3 -3
  10. package/dist/{chunk-KWKGX7LC.js → chunk-5YXHHEAG.js} +5 -5
  11. package/dist/{chunk-JSIX5L3C.js → chunk-7TXEBDRM.js} +2 -2
  12. package/dist/{chunk-2CB6QMRX.js → chunk-AHXJCVMV.js} +39 -39
  13. package/dist/{chunk-YUCJ7VSJ.js → chunk-APXGDZV4.js} +13 -10
  14. package/dist/chunk-APXGDZV4.js.map +1 -0
  15. package/dist/{chunk-OGR5Q6QO.js → chunk-CGD63GSC.js} +7 -7
  16. package/dist/{chunk-Y5JA7XEO.js → chunk-CNHPSIHY.js} +21 -16
  17. package/dist/chunk-CNHPSIHY.js.map +1 -0
  18. package/dist/{chunk-PINS44XA.js → chunk-DVKS77O5.js} +2 -2
  19. package/dist/{chunk-OM2VVQKB.js → chunk-ECBV4JNL.js} +2 -2
  20. package/dist/{chunk-VSETLO4Q.js → chunk-G42V4A5Q.js} +2 -2
  21. package/dist/{chunk-PN43VH6Z.js → chunk-G5URP3V7.js} +7 -7
  22. package/dist/{chunk-ASJTVA7W.js → chunk-H4LZLG3P.js} +6 -6
  23. package/dist/{chunk-UB6APBIY.js → chunk-J4BGV5VE.js} +2 -2
  24. package/dist/{chunk-RAIFOYRM.js → chunk-J55NMZ7R.js} +2 -2
  25. package/dist/{chunk-3P7SMHBU.js → chunk-JGL5HYZL.js} +3 -3
  26. package/dist/{chunk-HUYP2XXH.js → chunk-JRKF4FYO.js} +2 -2
  27. package/dist/{chunk-K6NXLBHG.js → chunk-N3XDPW64.js} +13 -11
  28. package/dist/{chunk-K6NXLBHG.js.map → chunk-N3XDPW64.js.map} +1 -1
  29. package/dist/{chunk-EDWON3X5.js → chunk-NQU4LNN7.js} +6 -6
  30. package/dist/{chunk-DHVMSIT5.js → chunk-OQLFRJCW.js} +9 -1
  31. package/dist/chunk-OQLFRJCW.js.map +1 -0
  32. package/dist/{chunk-6RRAQFSX.js → chunk-QI5U2YUL.js} +2 -2
  33. package/dist/{chunk-MRRKFMYJ.js → chunk-QQ3CUUBU.js} +8 -216
  34. package/dist/chunk-QQ3CUUBU.js.map +1 -0
  35. package/dist/{chunk-IVINLVGP.js → chunk-U5CV76ZW.js} +2 -2
  36. package/dist/{chunk-DGR27REV.js → chunk-WMV2CXBB.js} +2 -2
  37. package/dist/{chunk-JVBI4WKP.js → chunk-WT4BJODZ.js} +5 -5
  38. package/dist/{chunk-5QVJCBM4.js → chunk-XAWRBQB7.js} +4 -4
  39. package/dist/{chunk-KYF4YAZO.js → chunk-XES5DPHV.js} +4 -4
  40. package/dist/{chunk-KU2HPCJG.js → chunk-XZJU6WLL.js} +4 -4
  41. package/dist/chunk-YAF73ZMR.js +38 -0
  42. package/dist/chunk-YAF73ZMR.js.map +1 -0
  43. package/dist/{chunk-SGIWJYLJ.js → chunk-YB4QE7MD.js} +2 -2
  44. package/dist/{chunk-KAL22DYO.js → chunk-YU2JCUM7.js} +2 -2
  45. package/dist/{chunk-4PTAG65V.js → chunk-Z55WHIRU.js} +2 -2
  46. package/dist/{cli-circuit-breaker-EINPV2CX.js → cli-circuit-breaker-PJOQTGFZ.js} +5 -5
  47. package/dist/cli.js +58 -48
  48. package/dist/cli.js.map +1 -1
  49. package/dist/{composite-router-CUJAG7O4.js → composite-router-IRXFQSB6.js} +4 -4
  50. package/dist/{consensus-vote-BLCQCWPV.js → consensus-vote-O6Q266QE.js} +20 -19
  51. package/dist/{context-retriever-MTJR3AQM.js → context-retriever-GO6ET3MF.js} +11 -11
  52. package/dist/doctor-deep-6HCHHMRP.js +13 -0
  53. package/dist/{expert-bridge-2W2JMFQW.js → expert-bridge-6OLG6THF.js} +6 -6
  54. package/dist/factory-7OM526WB.js +25 -0
  55. package/dist/factory-MJFGYMNE.js +15 -0
  56. package/dist/{improvement-review-ZMUJQSX3.js → improvement-review-PALAAG7V.js} +7 -7
  57. package/dist/index.d.ts +19 -137
  58. package/dist/index.js +36 -39
  59. package/dist/index.js.map +1 -1
  60. package/dist/{init-opencode-I5XSFY34.js → init-opencode-NUMHNVIF.js} +8 -8
  61. package/dist/issue-triage-N7GFXNPY.js +18 -0
  62. package/dist/{learning-persistence-6D5GGT7D.js → learning-persistence-EGY4VKKO.js} +3 -3
  63. package/dist/{pr-reviewer-helpers-CZDMP5UJ.js → pr-reviewer-helpers-6EIUJPXS.js} +6 -6
  64. package/dist/{registry-command-5J6RJZMS.js → registry-command-SVTX7FKB.js} +4 -4
  65. package/dist/{repo-security-plan-MKSCD567.js → repo-security-plan-AT7LOXLG.js} +5 -5
  66. package/dist/research-helpers-synthesize-R722YWRS.js +15 -0
  67. package/dist/{routing-memory-HAWL7RKO.js → routing-memory-4FXQT5H6.js} +4 -4
  68. package/dist/{session-memory-RPSM7Y4Q.js → session-memory-ZQJ6AUSQ.js} +5 -5
  69. package/dist/setup-command-VAAYESH5.js +43 -0
  70. package/dist/setup-config-MYLZ2UHR.js +11 -0
  71. package/dist/{setup-custom-api-BZSHB2M5.js → setup-custom-api-WZPLTGRQ.js} +6 -6
  72. package/dist/{tool-memory-FCEJMKEH.js → tool-memory-5REJXBCB.js} +8 -8
  73. package/dist/{tool-memory-registry-adapters-PMMKJQF5.js → tool-memory-registry-adapters-YQLOSV4S.js} +3 -3
  74. package/dist/unified-registry-FM3QB2VN.js +26 -0
  75. package/dist/{weather-report-VNABZRH7.js → weather-report-Y2GYJA6I.js} +4 -4
  76. package/package.json +1 -1
  77. package/dist/child-mcp-config-GWB7JLIS.js.map +0 -1
  78. package/dist/chunk-DHVMSIT5.js.map +0 -1
  79. package/dist/chunk-MRRKFMYJ.js.map +0 -1
  80. package/dist/chunk-Y5JA7XEO.js.map +0 -1
  81. package/dist/chunk-YUCJ7VSJ.js.map +0 -1
  82. package/dist/doctor-deep-X2KP2G4M.js +0 -13
  83. package/dist/factory-7UFJ6LXW.js +0 -24
  84. package/dist/factory-SC5D5P3C.js +0 -15
  85. package/dist/issue-triage-KOIPTNDH.js +0 -18
  86. package/dist/research-helpers-synthesize-TIT43XF4.js +0 -15
  87. package/dist/setup-command-SORJLYU3.js +0 -42
  88. package/dist/setup-config-YDA4MY4N.js +0 -11
  89. package/dist/unified-registry-JWPD2ES2.js +0 -27
  90. /package/dist/{chunk-E35RUETV.js.map → chunk-2DE5L734.js.map} +0 -0
  91. /package/dist/{chunk-AXTD4TFK.js.map → chunk-2HUC6NZN.js.map} +0 -0
  92. /package/dist/{chunk-DJZGCMNU.js.map → chunk-46XEDJRB.js.map} +0 -0
  93. /package/dist/{chunk-OCTNNEXT.js.map → chunk-4OPEUDHY.js.map} +0 -0
  94. /package/dist/{chunk-H5KLPUAL.js.map → chunk-4S6S6S2S.js.map} +0 -0
  95. /package/dist/{chunk-Z77MIV3C.js.map → chunk-5ALUADB6.js.map} +0 -0
  96. /package/dist/{chunk-YE2XMU3T.js.map → chunk-5LTRHNOI.js.map} +0 -0
  97. /package/dist/{chunk-KWKGX7LC.js.map → chunk-5YXHHEAG.js.map} +0 -0
  98. /package/dist/{chunk-JSIX5L3C.js.map → chunk-7TXEBDRM.js.map} +0 -0
  99. /package/dist/{chunk-2CB6QMRX.js.map → chunk-AHXJCVMV.js.map} +0 -0
  100. /package/dist/{chunk-OGR5Q6QO.js.map → chunk-CGD63GSC.js.map} +0 -0
  101. /package/dist/{chunk-PINS44XA.js.map → chunk-DVKS77O5.js.map} +0 -0
  102. /package/dist/{chunk-OM2VVQKB.js.map → chunk-ECBV4JNL.js.map} +0 -0
  103. /package/dist/{chunk-VSETLO4Q.js.map → chunk-G42V4A5Q.js.map} +0 -0
  104. /package/dist/{chunk-PN43VH6Z.js.map → chunk-G5URP3V7.js.map} +0 -0
  105. /package/dist/{chunk-ASJTVA7W.js.map → chunk-H4LZLG3P.js.map} +0 -0
  106. /package/dist/{chunk-UB6APBIY.js.map → chunk-J4BGV5VE.js.map} +0 -0
  107. /package/dist/{chunk-RAIFOYRM.js.map → chunk-J55NMZ7R.js.map} +0 -0
  108. /package/dist/{chunk-3P7SMHBU.js.map → chunk-JGL5HYZL.js.map} +0 -0
  109. /package/dist/{chunk-HUYP2XXH.js.map → chunk-JRKF4FYO.js.map} +0 -0
  110. /package/dist/{chunk-EDWON3X5.js.map → chunk-NQU4LNN7.js.map} +0 -0
  111. /package/dist/{chunk-6RRAQFSX.js.map → chunk-QI5U2YUL.js.map} +0 -0
  112. /package/dist/{chunk-IVINLVGP.js.map → chunk-U5CV76ZW.js.map} +0 -0
  113. /package/dist/{chunk-DGR27REV.js.map → chunk-WMV2CXBB.js.map} +0 -0
  114. /package/dist/{chunk-JVBI4WKP.js.map → chunk-WT4BJODZ.js.map} +0 -0
  115. /package/dist/{chunk-5QVJCBM4.js.map → chunk-XAWRBQB7.js.map} +0 -0
  116. /package/dist/{chunk-KYF4YAZO.js.map → chunk-XES5DPHV.js.map} +0 -0
  117. /package/dist/{chunk-KU2HPCJG.js.map → chunk-XZJU6WLL.js.map} +0 -0
  118. /package/dist/{chunk-SGIWJYLJ.js.map → chunk-YB4QE7MD.js.map} +0 -0
  119. /package/dist/{chunk-KAL22DYO.js.map → chunk-YU2JCUM7.js.map} +0 -0
  120. /package/dist/{chunk-4PTAG65V.js.map → chunk-Z55WHIRU.js.map} +0 -0
  121. /package/dist/{cli-circuit-breaker-EINPV2CX.js.map → cli-circuit-breaker-PJOQTGFZ.js.map} +0 -0
  122. /package/dist/{composite-router-CUJAG7O4.js.map → composite-router-IRXFQSB6.js.map} +0 -0
  123. /package/dist/{consensus-vote-BLCQCWPV.js.map → consensus-vote-O6Q266QE.js.map} +0 -0
  124. /package/dist/{context-retriever-MTJR3AQM.js.map → context-retriever-GO6ET3MF.js.map} +0 -0
  125. /package/dist/{doctor-deep-X2KP2G4M.js.map → doctor-deep-6HCHHMRP.js.map} +0 -0
  126. /package/dist/{expert-bridge-2W2JMFQW.js.map → expert-bridge-6OLG6THF.js.map} +0 -0
  127. /package/dist/{factory-7UFJ6LXW.js.map → factory-7OM526WB.js.map} +0 -0
  128. /package/dist/{factory-SC5D5P3C.js.map → factory-MJFGYMNE.js.map} +0 -0
  129. /package/dist/{improvement-review-ZMUJQSX3.js.map → improvement-review-PALAAG7V.js.map} +0 -0
  130. /package/dist/{init-opencode-I5XSFY34.js.map → init-opencode-NUMHNVIF.js.map} +0 -0
  131. /package/dist/{issue-triage-KOIPTNDH.js.map → issue-triage-N7GFXNPY.js.map} +0 -0
  132. /package/dist/{learning-persistence-6D5GGT7D.js.map → learning-persistence-EGY4VKKO.js.map} +0 -0
  133. /package/dist/{pr-reviewer-helpers-CZDMP5UJ.js.map → pr-reviewer-helpers-6EIUJPXS.js.map} +0 -0
  134. /package/dist/{registry-command-5J6RJZMS.js.map → registry-command-SVTX7FKB.js.map} +0 -0
  135. /package/dist/{repo-security-plan-MKSCD567.js.map → repo-security-plan-AT7LOXLG.js.map} +0 -0
  136. /package/dist/{research-helpers-synthesize-TIT43XF4.js.map → research-helpers-synthesize-R722YWRS.js.map} +0 -0
  137. /package/dist/{routing-memory-HAWL7RKO.js.map → routing-memory-4FXQT5H6.js.map} +0 -0
  138. /package/dist/{session-memory-RPSM7Y4Q.js.map → session-memory-ZQJ6AUSQ.js.map} +0 -0
  139. /package/dist/{setup-command-SORJLYU3.js.map → setup-command-VAAYESH5.js.map} +0 -0
  140. /package/dist/{setup-config-YDA4MY4N.js.map → setup-config-MYLZ2UHR.js.map} +0 -0
  141. /package/dist/{setup-custom-api-BZSHB2M5.js.map → setup-custom-api-WZPLTGRQ.js.map} +0 -0
  142. /package/dist/{tool-memory-FCEJMKEH.js.map → tool-memory-5REJXBCB.js.map} +0 -0
  143. /package/dist/{tool-memory-registry-adapters-PMMKJQF5.js.map → tool-memory-registry-adapters-YQLOSV4S.js.map} +0 -0
  144. /package/dist/{unified-registry-JWPD2ES2.js.map → unified-registry-FM3QB2VN.js.map} +0 -0
  145. /package/dist/{weather-report-VNABZRH7.js.map → weather-report-Y2GYJA6I.js.map} +0 -0
@@ -5,17 +5,17 @@ import {
5
5
  } from "./chunk-6T3EPABN.js";
6
6
  import {
7
7
  SessionMemory
8
- } from "./chunk-IVINLVGP.js";
8
+ } from "./chunk-U5CV76ZW.js";
9
9
  import {
10
10
  StatsOnlyAdapter,
11
11
  ensureSharedMemoryRegistry
12
- } from "./chunk-PINS44XA.js";
12
+ } from "./chunk-DVKS77O5.js";
13
13
  import {
14
14
  getMemoryRegistry
15
15
  } from "./chunk-KSQSWEFH.js";
16
16
  import {
17
17
  generateId
18
- } from "./chunk-SGIWJYLJ.js";
18
+ } from "./chunk-YB4QE7MD.js";
19
19
  import {
20
20
  ErrorCode,
21
21
  NexusError,
@@ -28,10 +28,10 @@ import {
28
28
  getTimeProvider,
29
29
  ok,
30
30
  setSharedMobiMemDbPathResolver
31
- } from "./chunk-Y5JA7XEO.js";
31
+ } from "./chunk-CNHPSIHY.js";
32
32
  import {
33
33
  nexusDataPath
34
- } from "./chunk-DHVMSIT5.js";
34
+ } from "./chunk-OQLFRJCW.js";
35
35
 
36
36
  // src/mcp/tools/tool-memory.ts
37
37
  import * as fs3 from "fs";
@@ -5552,4 +5552,4 @@ export {
5552
5552
  reinitializeMemoryBackends,
5553
5553
  ToolMemoryManager
5554
5554
  };
5555
- //# sourceMappingURL=chunk-EDWON3X5.js.map
5555
+ //# sourceMappingURL=chunk-NQU4LNN7.js.map
@@ -206,6 +206,14 @@ function detectContainerHeuristic() {
206
206
 
207
207
  // src/config/nexus-data-dir.ts
208
208
  var PER_REPO_SUBDIRS = /* @__PURE__ */ new Set([
209
+ // Scratch space for short-lived work (worktrees, prompt files, MCP configs).
210
+ // Per-repo because a throwaway worktree of repo A is meaningless to repo B,
211
+ // and because containing it here keeps it inside the already-gitignored
212
+ // `.nexus-agents/` tree instead of accumulating in a shared `/tmp` nobody
213
+ // reaps. A full `/tmp` is not a theoretical problem: it silently broke this
214
+ // repo's own test suite, with ~1,100 files failing to COLLECT and zero
215
+ // assertion failures, which reads like a code fault and is not one.
216
+ "tmp",
209
217
  "sessions",
210
218
  "checkpoints",
211
219
  "traces",
@@ -375,4 +383,4 @@ export {
375
383
  getPerRepoSubdirs,
376
384
  sessionsDbPath
377
385
  };
378
- //# sourceMappingURL=chunk-DHVMSIT5.js.map
386
+ //# sourceMappingURL=chunk-OQLFRJCW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/repo-root-detection.ts","../src/config/portable-mode.ts","../src/config/nexus-data-dir.ts","../src/config/sandbox-detection.ts"],"sourcesContent":["/**\n * Repo-root detection — walks upward from a starting directory looking for\n * `.git` so callers can scope state to the current git repo. Built for\n * epic #2872 (issue #2882) to support the per-repo `.nexus-agents/` data\n * directory ratified by vote #2876.\n *\n * Defenses adopted:\n * - Walks upward but stops at filesystem root (no infinite loop on\n * symlinks-to-self).\n * - Stops at filesystem boundary (different `stat.dev`) — refuses to\n * escape across mount points. Prevents a sandboxed workdir from\n * resolving a `.git` on the host filesystem.\n * - Detects git worktrees: `.git` may be a *file* containing\n * `gitdir: <path>` rather than a directory.\n * - Realpath's the final root and rejects results that escape the\n * starting cwd by symlink.\n *\n * Deferred (tracked separately; not blocking #2882):\n * - CVE-2022-24765-style ownership check (refuse `.git` owned by a\n * different uid than the running process). The git CLI added\n * `safe.directory` for this; we don't have a comparable allowlist\n * surface yet. In CI the heuristic is too noisy to be useful\n * (runners often clone as a different uid than the workload).\n * File-system isolation (the `stat.dev` check above) covers the\n * bulk of the attack class.\n *\n * @module config/repo-root-detection\n */\n\nimport { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';\nimport { dirname, isAbsolute, resolve } from 'node:path';\n\n/**\n * Walks upward from `start` looking for `.git` (file or directory).\n * Returns the absolute path to the directory containing it, or `null`.\n *\n * Worktrees: a `.git` file (not dir) with contents `gitdir: <path>` is\n * still recognised as marking the repo root. The pointed-to gitdir is\n * NOT followed — we just need the worktree's own root.\n *\n * Filesystem boundary: stops walking if the next ancestor lives on a\n * different filesystem (different `stat.dev`). This prevents a workdir\n * mounted inside a sandbox from finding a host-side `.git`.\n */\nexport function findRepoRoot(start: string): string | null {\n if (!isAbsolute(start)) {\n start = resolve(start);\n }\n\n let current: string;\n try {\n current = realpathSync(start);\n } catch {\n return null;\n }\n\n let startDev: number | undefined;\n try {\n startDev = statSync(current).dev;\n } catch {\n return null;\n }\n\n // Walk up. Depth cap is paranoid but bounds the loop on pathological\n // symlinks even though realpathSync above should already prevent cycles.\n for (let i = 0; i < 64; i++) {\n if (isRepoRoot(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Hit filesystem root.\n return null;\n }\n try {\n if (statSync(parent).dev !== startDev) {\n // Crossed a mount point; refuse to escape.\n return null;\n }\n } catch {\n return null;\n }\n current = parent;\n }\n return null;\n}\n\n/** True iff `dir/.git` exists as a directory or a git-worktree marker file. */\nexport function isRepoRoot(dir: string): boolean {\n const gitPath = `${dir}/.git`;\n if (!existsSync(gitPath)) return false;\n try {\n const st = statSync(gitPath);\n if (st.isDirectory()) return true;\n if (st.isFile()) {\n // Worktree: first line should be `gitdir: <path>`.\n const head = readFileSync(gitPath, 'utf-8').slice(0, 256);\n return head.startsWith('gitdir:');\n }\n } catch {\n return false;\n }\n return false;\n}\n","/**\n * portable-mode — auto-detect sandboxed execution environments and\n * redirect nexus-agents data to a workspace-local directory.\n *\n * The existing `getNexusDataDir()` already respects `NEXUS_DATA_DIR` and\n * `NEXUS_PORTABLE_MODE`. This module wires up the *auto-detection* path\n * so operators running inside a Docker / restricted-FS / sandboxed\n * environment don't have to set the env vars manually — nexus-agents\n * notices and announces the flip.\n *\n * Source: Issue #2471 (epic #2467 child).\n *\n * Detection rules (first match wins):\n * 1. `NEXUS_DATA_DIR` already set → respect, no auto-detect\n * 2. `NEXUS_PORTABLE_MODE=0` → operator opted out, no auto-detect\n * 3. `NEXUS_PORTABLE_MODE=1` → portable mode, no detection needed\n * 4. Heuristic: home directory not writable\n * 5. Heuristic: container env vars set (KUBERNETES_SERVICE_HOST,\n * DOCKER_CONTAINER, ECS_CONTAINER_METADATA_URI, SANDBOX, etc.)\n *\n * When portable mode fires:\n * - Sets `process.env.NEXUS_DATA_DIR` to `<cwd>/.nexus-agents`\n * - Announces once on stderr (subsequent calls are silent)\n * - If cwd is a git repo, appends `.nexus-agents/` to .gitignore\n *\n * To force off: `NEXUS_PORTABLE_MODE=0`. To force on without heuristics:\n * `NEXUS_PORTABLE_MODE=1`. To override target dir: `NEXUS_DATA_DIR=...`.\n */\n\nimport { accessSync, constants, existsSync, readFileSync, appendFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\n/** Container / sandbox environment variables that strongly imply portable mode. */\nconst SANDBOX_ENV_VARS: readonly string[] = [\n 'KUBERNETES_SERVICE_HOST',\n 'DOCKER_CONTAINER',\n 'ECS_CONTAINER_METADATA_URI',\n 'ECS_CONTAINER_METADATA_URI_V4',\n 'SANDBOX',\n 'NEXUS_SANDBOX',\n];\n\nlet DETECTED = false;\n\ninterface DetectionResult {\n readonly portable: boolean;\n readonly dataDir: string;\n readonly reason:\n | 'env-data-dir'\n | 'env-opt-out'\n | 'env-opt-in'\n | 'home-unwritable'\n | 'container-env'\n | 'default';\n}\n\nfunction isHomeWritable(): boolean {\n try {\n const home = homedir();\n if (!existsSync(home)) return false;\n accessSync(home, constants.W_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction inSandboxEnv(): boolean {\n for (const v of SANDBOX_ENV_VARS) {\n const val = process.env[v];\n if (val !== undefined && val !== '') return true;\n }\n return false;\n}\n\nfunction checkExplicitEnv(cwd: string): DetectionResult | null {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return { portable: false, dataDir: resolve(fromEnv), reason: 'env-data-dir' };\n }\n const portableEnv = process.env['NEXUS_PORTABLE_MODE'];\n if (portableEnv === '0' || portableEnv === 'false') {\n return { portable: false, dataDir: join(homedir(), '.nexus-agents'), reason: 'env-opt-out' };\n }\n if (portableEnv === '1' || portableEnv === 'true') {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'env-opt-in' };\n }\n return null;\n}\n\nfunction checkHeuristics(cwd: string): DetectionResult | null {\n if (!isHomeWritable()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'home-unwritable' };\n }\n if (inSandboxEnv()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'container-env' };\n }\n return null;\n}\n\n/**\n * Decide whether to flip nexus-agents to portable mode and what data dir\n * to use. Pure function; no side effects beyond reading env + filesystem.\n */\nexport function detectPortableMode(cwd: string = process.cwd()): DetectionResult {\n return (\n checkExplicitEnv(cwd) ??\n checkHeuristics(cwd) ?? {\n portable: false,\n dataDir: join(homedir(), '.nexus-agents'),\n reason: 'default',\n }\n );\n}\n\n/**\n * Apply detected portable mode at process startup. Idempotent (subsequent\n * calls are no-ops). Announces on stderr once when the auto-detection\n * heuristic triggers — env-opt-in/out doesn't announce because the\n * operator already knows.\n */\nexport function applyPortableMode(cwd: string = process.cwd()): void {\n if (DETECTED) return;\n DETECTED = true;\n\n const result = detectPortableMode(cwd);\n\n // No env, no heuristic match — nothing to do.\n if (!result.portable) return;\n\n // The heuristic-driven cases announce; explicit opt-in is silent.\n if (result.reason === 'home-unwritable' || result.reason === 'container-env') {\n process.stderr.write(\n `[portable-mode] Sandbox detected (${result.reason}). Using ${result.dataDir} for all nexus-agents data.\\n` +\n ` Set NEXUS_PORTABLE_MODE=0 to override; see docs/guides/SANDBOXED-USAGE.md\\n`\n );\n }\n\n process.env['NEXUS_DATA_DIR'] = result.dataDir;\n\n if (isInsideGitRepo(cwd)) {\n ensureGitignored(cwd, '.nexus-agents/');\n }\n}\n\nfunction isInsideGitRepo(cwd: string): boolean {\n try {\n const gitDir = join(cwd, '.git');\n return existsSync(gitDir) && statSync(gitDir).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Append `entry` to `<cwd>/.gitignore` if not already present. Stderr\n * announce on the first append. Exported because the repo-preferred\n * resolver in `nexus-data-dir.ts` reuses the same auto-gitignore behavior\n * when `<repo>/.nexus-agents/` becomes the active data dir (epic #2872).\n */\nexport function ensureGitignored(cwd: string, entry: string): void {\n const path = join(cwd, '.gitignore');\n try {\n const existing = existsSync(path) ? readFileSync(path, 'utf-8') : '';\n const lines = existing.split('\\n').map((l) => l.trim());\n if (lines.includes(entry) || lines.includes(entry.replace(/\\/$/, ''))) return;\n const sep = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n appendFileSync(path, `${sep}${entry}\\n`, 'utf-8');\n process.stderr.write(\n `[portable-mode] Added '${entry}' to ${path} so nexus-agents data is gitignored.\\n`\n );\n } catch (e: unknown) {\n // Non-fatal — operator can add the line themselves.\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[portable-mode] Could not auto-update ${path} (${msg}). Add '${entry}' manually if you don't want nexus-agents data tracked by git.\\n`\n );\n }\n}\n\n/** Test-only reset of the once-only state. */\nexport function _resetDetectedForTests(): void {\n DETECTED = false;\n}\n","/**\n * Nexus runtime data directory resolver (#2302, child of #2301).\n *\n * Returns the absolute path under which nexus-agents stores runtime state.\n * Two categories of state exist (epic #2872, vote #2876):\n *\n * - **Per-repo** state (sessions, checkpoints, traces, runs, audit,\n * pipeline, tasks) — tied to the work happening in a specific codebase.\n * When `NEXUS_REPO_PREFERRED=1` is set and the caller is inside a git\n * repo, these resolve to `<repo-root>/.nexus-agents/<subdir>/`.\n * - **Cross-repo** state (learning, voting, memory, weather, research,\n * auth, usage, models manifest) — accumulates across the operator's\n * whole workflow. Always resolves to `~/.nexus-agents/<subdir>/` so the\n * learning/routing feedback loop from #1389 / #1407 keeps working.\n *\n * Resolution order (first match wins):\n * 1. `NEXUS_DATA_DIR` env var if set + non-empty. Wins for both categories\n * — the operator's explicit choice overrides the split.\n * 2. Sandbox-mode default (#2501): when `NEXUS_SANDBOX` is set, use\n * `${NEXUS_SANDBOX_ROOT ?? '/'}/.nexus-agents`. Sandboxed deployments\n * typically mount a multi-repo root; state goes there, shared across\n * repo subfolders rather than buried inside one.\n * 3. Per-repo subdirs (when a repo root is known AND `NEXUS_REPO_PREFERRED`\n * is not explicitly `'0'`): `<repo-root>/.nexus-agents/<subdir>/`. The\n * repo root is the active workspace root when one has been set via\n * `setActiveWorkspaceRoot()` (the MCP server derives it from the client's\n * declared `roots`, MCP spec — see #3991), otherwise `findRepoRoot(cwd)`.\n * The active-root path exists because a globally-installed MCP server runs\n * with `process.cwd()` outside the repo, so cwd-based detection would\n * wrongly route per-repo state to homedir. Auto-adds `.nexus-agents/` to\n * the repo's `.gitignore` on first resolution (fail-closed).\n * 4. Cross-repo subdirs when `~/.nexus-agents/` is unwritable AND we're\n * in a repo: per-repo fallback at `<repo-root>/.nexus-agents/<subdir>/`\n * with one-time stderr announce. Issue #2888 — gives sandbox users\n * a working `research/`, `memory/`, etc. without env-var wrangling\n * while preserving the vote #2876 default for normal-machine users.\n * 5. `<homedir>/.nexus-agents` (cross-repo state on normal machines,\n * plus everything when not in a git repo or when\n * `NEXUS_REPO_PREFERRED=0`).\n *\n * `NEXUS_REPO_PREFERRED` defaults **ON** as of this release per vote\n * #2876. Escape hatches preserved:\n * - `NEXUS_REPO_PREFERRED=0` — fully opt out (homedir for everything).\n * - `NEXUS_DATA_DIR=~/.nexus-agents` — explicit override wins over\n * the tier and the categorization both.\n * - `nexus-agents migrate` (#2879) — relocate existing homedir state\n * into `<repo>/.nexus-agents/` before flipping in production.\n *\n * @module config/nexus-data-dir\n */\n\nimport {\n accessSync,\n constants as fsConstants,\n copyFileSync,\n existsSync,\n mkdirSync,\n realpathSync,\n renameSync,\n statSync,\n unlinkSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport { detectSandbox } from './sandbox-detection.js';\nimport { findRepoRoot } from './repo-root-detection.js';\nimport { ensureGitignored } from './portable-mode.js';\n\n/**\n * Subdirs scoped to a single repo's work (per the epic #2872 vote).\n * When `NEXUS_REPO_PREFERRED=1` and the caller is inside a git repo, these\n * route to `<repo-root>/.nexus-agents/`; otherwise homedir. The complement\n * (everything not in this set) always goes to homedir so cross-project\n * learning state stays intact.\n *\n * Source-of-truth for the categorization: vote #2876 conditions + audit\n * thread on epic #2872. Adding a new entry here is the appropriate way to\n * mark new state as per-repo — do that as a deliberate decision, not by\n * default.\n */\nconst PER_REPO_SUBDIRS: ReadonlySet<string> = new Set([\n // Scratch space for short-lived work (worktrees, prompt files, MCP configs).\n // Per-repo because a throwaway worktree of repo A is meaningless to repo B,\n // and because containing it here keeps it inside the already-gitignored\n // `.nexus-agents/` tree instead of accumulating in a shared `/tmp` nobody\n // reaps. A full `/tmp` is not a theoretical problem: it silently broke this\n // repo's own test suite, with ~1,100 files failing to COLLECT and zero\n // assertion failures, which reads like a code fault and is not one.\n 'tmp',\n 'sessions',\n 'checkpoints',\n 'traces',\n 'runs',\n 'audit',\n 'pipeline',\n 'tasks',\n // Async-mode job results (#3042 / epic #2631). Tied to work happening\n // in a specific codebase — a job dispatched on repo A shouldn't be\n // polled-against on repo B, which would happen if jobs/ were homedir-scoped.\n 'jobs',\n // CI health-check events (#3076 / #3084). Per-repo because outages\n // reported via `ci_health_check({ repo })` are repo-correlated; a wedge\n // on repo A's queue does not predict repo B's health.\n 'ci-health',\n // Governance artifacts — the runtime authentic-vote-record ledger\n // (`governance/vote-records.jsonl`, #3897/#3927/#3991). Per-repo because a\n // promotion proposal is scoped to the codebase being changed; with\n // `NEXUS_REPO_PREFERRED=1` (default) these route to\n // `<repo>/.nexus-agents/governance/` (gitignored), and to\n // `~/.nexus-agents/governance/` otherwise (global / non-repo install). This\n // is DISTINCT from the committed `<repo>/governance/vote-records.jsonl`\n // ledger the promotion gate reads — that artifact is reached only via the\n // explicit `NEXUS_VOTE_RECORDS_PATH` override (#3991).\n 'governance',\n]);\n\n/** Returns the absolute path to the nexus-agents data directory. */\nexport function getNexusDataDir(): string {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return resolve(fromEnv);\n }\n const sandbox = detectSandbox();\n if (sandbox.active) {\n return resolve(sandbox.root ?? '/', '.nexus-agents');\n }\n return join(homedir(), '.nexus-agents');\n}\n\n/**\n * No-op kept for source-compatibility with consumers that called this\n * earlier in development. The resolver is no longer cached, so resetting\n * is unnecessary. Kept exported (rather than removed) to avoid breaking\n * imports in tests that may have already adopted it.\n */\nexport function resetNexusDataDirCache(): void {\n // intentionally empty — see module docstring\n}\n\n/**\n * Tracks per-process state for the auto-gitignore wiring: we only need\n * to (a) probe + append to `.gitignore` once per repo, and (b) bail\n * cleanly if the operator has explicitly silenced the auto-add via\n * `NEXUS_GITIGNORE_AUTO=0`. Reset in tests via the helper below.\n */\nconst gitignoredRoots = new Set<string>();\n\n/** Test helper — clears the auto-gitignore \"already did this\" memo. */\nexport function _resetGitignoreMemoForTests(): void {\n gitignoredRoots.clear();\n}\n\n/**\n * Active workspace root, when known from a source more authoritative than\n * `process.cwd()`. Set by the MCP server (#3991) from the client's declared\n * `roots` (MCP spec) once the initialize handshake completes, because a\n * globally-installed server runs with cwd OUTSIDE the repo being worked on —\n * so cwd-based repo detection would route per-repo `.nexus-agents` state to\n * homedir instead of `<repo>/.nexus-agents/`. CLI / in-repo callers never set\n * this, so they keep resolving via `findRepoRoot(cwd)` exactly as before.\n */\nlet activeWorkspaceRoot: string | undefined;\n\n/**\n * Records the active workspace root for repo-scoped data resolution. The\n * value is cross-trust-boundary input (it arrives over the MCP transport from\n * the client), so it is validated before use: resolved to an absolute path,\n * canonicalized via `realpathSync` (defeats symlink games), and required to\n * be an existing directory. Returns `true` when accepted, `false` when the\n * value is empty/invalid (in which case the previous root is left untouched).\n * Passing `null`/empty explicitly clears any previously-set root.\n */\nexport function setActiveWorkspaceRoot(root: string | null | undefined): boolean {\n const trimmed = root?.trim();\n if (trimmed === undefined || trimmed === '') {\n activeWorkspaceRoot = undefined;\n return false;\n }\n try {\n const canonical = realpathSync(resolve(trimmed));\n if (!isAbsolute(canonical) || !statSync(canonical).isDirectory()) return false;\n activeWorkspaceRoot = canonical;\n return true;\n } catch {\n // Nonexistent path, permission error, or broken symlink — reject and keep\n // whatever was set before so a bad late signal can't corrupt resolution.\n return false;\n }\n}\n\n/** Returns the active workspace root if one has been set, else `undefined`. */\nexport function getActiveWorkspaceRoot(): string | undefined {\n return activeWorkspaceRoot;\n}\n\n/** Test helper — clears the active workspace root memo. */\nexport function _resetActiveWorkspaceRootForTests(): void {\n activeWorkspaceRoot = undefined;\n}\n\n/**\n * Returns the repo-scoped `.nexus-agents/` directory if all of the\n * following hold: `NEXUS_REPO_PREFERRED` is NOT explicitly `'0'` (it\n * defaults ON as of vote #2876), `NEXUS_DATA_DIR` is not explicitly set\n * (explicit override always wins), no sandbox is active, and a repo root is\n * known — either the active workspace root (set via\n * `setActiveWorkspaceRoot()`) or an ancestor `.git` found by\n * `findRepoRoot(cwd)`.\n *\n * Side effect: on the first successful resolution per process per repo,\n * appends `.nexus-agents/` to the repo's `.gitignore` if not already\n * present. The operator can silence this with `NEXUS_GITIGNORE_AUTO=0`\n * (e.g. on CI runners with a frozen working tree).\n *\n * Returns `null` when any precondition fails; callers fall back to\n * `getNexusDataDir()` (homedir).\n */\nexport function getNexusRepoDir(): string | null {\n if (process.env['NEXUS_REPO_PREFERRED'] === '0') return null;\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') return null;\n if (detectSandbox().active) return null;\n // Prefer the MCP-client-declared workspace root (#3991) when the server has\n // set it; fall back to walking up from cwd for CLI / in-repo callers.\n const root = activeWorkspaceRoot ?? findRepoRoot(process.cwd());\n if (root === null) return null;\n maybeAutoGitignore(root);\n return join(root, '.nexus-agents');\n}\n\n/**\n * Best-effort fail-closed wiring: once per process per repo, ensure\n * `.nexus-agents/` is in `<repo>/.gitignore`. Silenced via\n * `NEXUS_GITIGNORE_AUTO=0`. Failures are non-fatal — the helper logs\n * to stderr and continues.\n */\nfunction maybeAutoGitignore(repoRoot: string): void {\n if (process.env['NEXUS_GITIGNORE_AUTO'] === '0') return;\n if (gitignoredRoots.has(repoRoot)) return;\n gitignoredRoots.add(repoRoot);\n // Only attempt when the repo root looks real — avoid spamming stderr\n // from temp-dir test fixtures that race the lifecycle.\n if (!existsSync(repoRoot)) return;\n ensureGitignored(repoRoot, '.nexus-agents/');\n}\n\n/**\n * Per-process memo: was the homedir base directory found writable on first\n * probe? Used to short-circuit the sandbox-fallback decision in\n * `nexusDataPath()` without spending a syscall on every call.\n */\nlet homedirWritable: boolean | undefined;\n\n/** Per-process memo: have we already announced a fallback for this subdir? */\nconst announcedFallbacks = new Set<string>();\n\n/** Test helper — clears the homedir-writability + announcement memos. */\nexport function _resetWritabilityMemoForTests(): void {\n homedirWritable = undefined;\n announcedFallbacks.clear();\n}\n\n/**\n * Returns true if `getNexusDataDir()` resolves to a writable location. Probed\n * once per process and memo'd — flips the sandbox-fallback on/off for\n * cross-repo subdirs. `mkdirSync(recursive: true)` is the safest probe\n * because it both creates the dir if missing and surfaces ENOENT/EACCES\n * issues at the same point. Failures here are non-fatal — they just route\n * cross-repo state to the per-repo fallback (issue #2888).\n */\nfunction isHomedirBaseWritable(): boolean {\n if (homedirWritable !== undefined) return homedirWritable;\n const base = getNexusDataDir();\n try {\n mkdirSync(base, { recursive: true });\n accessSync(base, fsConstants.W_OK);\n homedirWritable = true;\n } catch {\n homedirWritable = false;\n }\n return homedirWritable;\n}\n\n/**\n * Emit a one-time stderr warning when a cross-repo subdir falls back to\n * the per-repo location. Operators in sandboxes get a clear signal about\n * what happened without per-call noise.\n */\nfunction announceCrossRepoFallback(subdir: string, repoPath: string): void {\n if (announcedFallbacks.has(subdir)) return;\n announcedFallbacks.add(subdir);\n process.stderr.write(\n `[nexus] ${subdir}: homedir ~/.nexus-agents is not writable; ` +\n `using per-repo fallback at ${repoPath}. See docs/guides/SANDBOXED-USAGE.md.\\n`\n );\n}\n\n/**\n * Returns a path joined under the appropriate data directory for the\n * given subdir.\n *\n * Routing decision (first match wins):\n * 1. **Per-repo subdir + in a repo + `NEXUS_REPO_PREFERRED` not '0'** →\n * `<repo>/.nexus-agents/<subdir>/...`. Standard behavior since #2884.\n * 2. **Cross-repo subdir + homedir unwritable + in a repo** →\n * `<repo>/.nexus-agents/<subdir>/...` as a sandbox-friendly fallback\n * (issue #2888). Emits a one-time stderr warning per subdir.\n * 3. **Otherwise** → `<homedir>/.nexus-agents/<subdir>/...`. If homedir\n * is also unwritable, the actual write will surface the error.\n *\n * Existing callers don't need to change — the routing decision is driven\n * by the first segment, not by caller-declared intent.\n */\nexport function nexusDataPath(...segments: string[]): string {\n const first = segments[0];\n\n // Tier 1: per-repo subdir + repo-preferred default.\n if (first !== undefined && PER_REPO_SUBDIRS.has(first)) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n return join(repoDir, ...segments);\n }\n }\n\n // Tier 2: cross-repo subdir but homedir isn't reachable. Fall back to\n // the per-repo location if we're in a repo, so sandbox users get a\n // working `research/`, `memory/`, etc. without manual env-var setup.\n // Vote #2876 preserved: this only fires when homedir is physically\n // unreachable; normal-machine users see no change.\n if (first !== undefined && !isHomedirBaseWritable()) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n const fallbackPath = join(repoDir, ...segments);\n announceCrossRepoFallback(first, fallbackPath);\n return fallbackPath;\n }\n // No repo to fall back to AND homedir unreachable — return the\n // homedir path anyway. The caller's eventual write will surface\n // the underlying EACCES/ENOENT, which is the right error to show\n // because the environment is genuinely broken at that point.\n }\n\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Like `nexusDataPath()` but eagerly creates the parent directory so\n * callers don't have to remember `mkdirSync(dirname(p), { recursive: true })`\n * before every write. Issue #2890. Use when the returned path will be\n * written to immediately; for read-only resolution prefer `nexusDataPath()`.\n *\n * If the call resolves to a directory (single segment or no segments),\n * the target IS the resolved path. If it resolves to a file (multiple\n * segments), the target is its parent.\n */\nexport function nexusDataPathEnsure(...segments: string[]): string {\n const resolved = nexusDataPath(...segments);\n const target = segments.length > 1 ? dirname(resolved) : resolved;\n mkdirSync(target, { recursive: true });\n return resolved;\n}\n\n/**\n * Explicitly homedir-scoped path. Use this in code that handles\n * cross-project state (routing memory, model registry cache, auth,\n * research catalog) where you want a hard guarantee that the resolver\n * never routes per-repo, even if a new subdir gets accidentally added\n * to the `PER_REPO_SUBDIRS` allowlist later.\n */\nexport function nexusSharedPath(...segments: string[]): string {\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Returns the read-only categorization used by `nexusDataPath()`.\n * Exposed for tests and for tooling that wants to introspect which\n * subdirs are per-repo (e.g. the `nexus-agents migrate` command).\n */\nexport function getPerRepoSubdirs(): ReadonlySet<string> {\n return PER_REPO_SUBDIRS;\n}\n\n/** Per-process memo — the legacy session-DB relocation runs at most once. */\nlet legacySessionsDbMigrated = false;\n\n/** Test helper — clears the session-DB migration \"already did this\" memo. */\nexport function _resetSessionsDbMigrationMemoForTests(): void {\n legacySessionsDbMigrated = false;\n}\n\n/** SQLite sidecar suffixes that must travel with the main DB file. */\nconst SQLITE_SIDECAR_SUFFIXES = ['', '-wal', '-shm', '-journal'] as const;\n\n/**\n * Resolves the session-database path, performing a one-time relocation\n * of any pre-#2902 database on the first call per process.\n *\n * The session DB is per-repo episodic state and belongs in the\n * `sessions/` bucket alongside the session journals — vote #2876\n * categorized `sessions/` as per-repo, and issue #2902 (consensus\n * 3/3) extended that to the DB. Before #2902 the DB resolved via\n * `nexusDataPath('sessions.db')`; because `sessions.db` is not a\n * per-repo first segment, that landed cross-repo at\n * `~/.nexus-agents/sessions.db`, so a DB started in one repo leaked\n * into every other repo.\n *\n * If a legacy DB exists at the old location and none exists at the\n * new one, the legacy file (and any SQLite sidecars) is moved so\n * existing session history is preserved rather than silently\n * orphaned. The move is best-effort: a failure leaves the legacy DB\n * untouched for manual recovery and the caller gets a fresh DB at the\n * new path. `NEXUS_DATA_DIR` / `NEXUS_REPO_PREFERRED` / the\n * `NEXUS_SESSIONS_DB` override all sit upstream of this resolver.\n */\nexport function sessionsDbPath(): string {\n const target = nexusDataPath('sessions', 'sessions.db');\n if (!legacySessionsDbMigrated) {\n legacySessionsDbMigrated = true;\n migrateLegacySessionsDb(target);\n }\n return target;\n}\n\n/** One-time relocation of a pre-#2902 session DB. Best-effort — see `sessionsDbPath`. */\nfunction migrateLegacySessionsDb(target: string): void {\n const legacy = nexusDataPath('sessions.db'); // pre-#2902 resolution\n if (legacy === target || !existsSync(legacy) || existsSync(target)) return;\n try {\n mkdirSync(dirname(target), { recursive: true });\n for (const suffix of SQLITE_SIDECAR_SUFFIXES) {\n const from = `${legacy}${suffix}`;\n if (existsSync(from)) moveFile(from, `${target}${suffix}`);\n }\n } catch {\n // Best-effort — a migration failure must never break session storage.\n }\n}\n\n/** Moves a file, falling back to copy+unlink across a filesystem boundary. */\nfunction moveFile(from: string, to: string): void {\n try {\n renameSync(from, to);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EXDEV') throw err;\n copyFileSync(from, to);\n unlinkSync(from);\n }\n}\n","/**\n * Sandbox detection (#2501, child 1 of epic #2500).\n *\n * nexus-agents needs to know whether it's running inside a host-provided\n * sandbox (Docker Desktop Sandbox + OpenCode, Codex sandbox, locked-down\n * CI runner) so it can adjust behaviour:\n *\n * - default `NEXUS_DATA_DIR` to the multi-repo root rather than `~/...`\n * - skip CLI subprocess detection (binaries aren't there)\n * - fail-fast when the gateway is unreachable instead of degrading\n * - suppress diagnostics that don't apply\n *\n * The signal is **explicit**: the image author (Dockerfile, devcontainer,\n * harness wrapper) sets `NEXUS_SANDBOX=<flavor>`. The flavor string is\n * for diagnostics + per-flavor branching (`docker-opencode`, `codex`,\n * `claude-code`, `ci-restricted`, …); presence is the on/off signal.\n *\n * A heuristic check runs alongside for verification — if `NEXUS_SANDBOX`\n * claims `docker-opencode` but `/.dockerenv` is missing, that's a\n * misconfiguration worth surfacing to the operator.\n *\n * @module config/sandbox-detection\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\n/**\n * Heuristic detection result. Independent from the explicit\n * `NEXUS_SANDBOX` env var; produced by inspecting filesystem markers.\n */\nexport type SandboxHeuristic = 'docker' | 'podman' | 'unknown' | null;\n\nexport interface SandboxInfo {\n /**\n * True iff `NEXUS_SANDBOX` is set + non-empty. The explicit signal\n * the rest of the codebase keys off — never `true` by heuristic alone.\n */\n readonly active: boolean;\n /**\n * Operator-supplied flavor string, e.g. `docker-opencode`. Undefined\n * when `active === false` or the env var is empty.\n */\n readonly flavor: string | undefined;\n /**\n * Multi-repo root the user mounted. From `NEXUS_SANDBOX_ROOT`. Undefined\n * when unset; consumers that need a default substitute `/`.\n */\n readonly root: string | undefined;\n /**\n * Independent heuristic match — `null` when we couldn't run the check,\n * `'unknown'` when ran but no marker matched. Used by `doctor` to flag\n * mismatches between the explicit signal and the runtime environment.\n */\n readonly heuristicMatch: SandboxHeuristic;\n}\n\n/**\n * Detect whether nexus-agents is running inside a host-provided sandbox.\n * Pure read of env + filesystem; no caching (cheap enough to recompute,\n * and tests routinely mutate process.env).\n */\nexport function detectSandbox(): SandboxInfo {\n const flavorRaw = process.env['NEXUS_SANDBOX']?.trim();\n const flavor = flavorRaw !== undefined && flavorRaw !== '' ? flavorRaw : undefined;\n const active = flavor !== undefined;\n\n const rootRaw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n const root = rootRaw !== undefined && rootRaw !== '' ? rootRaw : undefined;\n\n return {\n active,\n flavor,\n root,\n heuristicMatch: detectContainerHeuristic(),\n };\n}\n\n/**\n * Look for filesystem markers indicating a container runtime. Used as a\n * cross-check against the explicit `NEXUS_SANDBOX` signal.\n *\n * Order of checks:\n * 1. `/.dockerenv` (Docker)\n * 2. `/run/.containerenv` (Podman)\n * 3. `/proc/1/cgroup` containing `docker` or `containerd` strings\n *\n * Returns `null` when none of the checks could run (non-Linux host with\n * no `/proc`, sandbox blocks `existsSync`, etc.) — distinct from\n * `'unknown'` (checks ran, no marker matched) so the doctor message can\n * differentiate \"we couldn't tell\" from \"we checked, no container\".\n */\nfunction detectContainerHeuristic(): SandboxHeuristic {\n try {\n if (existsSync('/.dockerenv')) return 'docker';\n if (existsSync('/run/.containerenv')) return 'podman';\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (/\\bdocker\\b/.test(cgroup)) return 'docker';\n if (/\\bcontainerd\\b/.test(cgroup)) return 'docker';\n }\n return 'unknown';\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","statSync","resolve","accessSync","existsSync","realpathSync","statSync","homedir","dirname","isAbsolute","join","resolve","existsSync","readFileSync","resolve","join","homedir","realpathSync","resolve","isAbsolute","statSync","join","existsSync","accessSync","join","dirname","existsSync","dirname"]}
@@ -11,7 +11,7 @@ import {
11
11
  isRateLimitLikeError,
12
12
  lookupInTreeCapability,
13
13
  ok
14
- } from "./chunk-Y5JA7XEO.js";
14
+ } from "./chunk-CNHPSIHY.js";
15
15
 
16
16
  // src/config/model-parameter-support.ts
17
17
  function parseClaudeMajorMinor(bareId) {
@@ -828,4 +828,4 @@ export {
828
828
  DEFAULT_COLLECT_STREAM_MAX_CHUNKS,
829
829
  collectStream
830
830
  };
831
- //# sourceMappingURL=chunk-6RRAQFSX.js.map
831
+ //# sourceMappingURL=chunk-QI5U2YUL.js.map
@@ -1,21 +1,18 @@
1
1
  import {
2
2
  createAutoAdapter
3
- } from "./chunk-K6NXLBHG.js";
3
+ } from "./chunk-N3XDPW64.js";
4
4
  import {
5
5
  mapModelErrorToCategory
6
- } from "./chunk-Z77MIV3C.js";
6
+ } from "./chunk-5ALUADB6.js";
7
7
  import {
8
8
  CLI_NAMES,
9
9
  CircularBuffer,
10
- ErrorCode,
11
10
  ModelError,
12
11
  TASK_SPECIALIZATION_MATRIX,
13
12
  createLogger,
14
13
  detectTaskCategory,
15
14
  err,
16
- getDefaultAvailableModelsCache,
17
15
  getDefaultModelForCli,
18
- getDefaultRegistry,
19
16
  getErrorMessage,
20
17
  getInTreeCapabilitiesMatrix,
21
18
  getRandomProvider,
@@ -24,7 +21,7 @@ import {
24
21
  ok,
25
22
  recordRateLimitEvent,
26
23
  toRateLimitError
27
- } from "./chunk-Y5JA7XEO.js";
24
+ } from "./chunk-CNHPSIHY.js";
28
25
 
29
26
  // src/agents/collaboration/event-bus-helpers.ts
30
27
  import { randomUUID } from "crypto";
@@ -615,193 +612,10 @@ function mapSelectionSource(selection) {
615
612
  return "api";
616
613
  }
617
614
 
618
- // src/adapters/model-not-found-fallback.ts
619
- var logger = createLogger({ component: "model-not-found-fallback" });
620
- function withModelNotFoundFallback(inner, options = {}) {
621
- const registry = options.registry ?? getDefaultRegistry();
622
- const cache = options.cache ?? getDefaultAvailableModelsCache();
623
- const resolvedOptions = {
624
- cache,
625
- registry,
626
- adapterFactory: options.adapterFactory,
627
- onRetirement: options.onRetirement
628
- };
629
- const wrapped = {
630
- providerId: inner.providerId,
631
- modelId: inner.modelId,
632
- capabilities: inner.capabilities,
633
- countTokens: (text) => inner.countTokens(text),
634
- validateConfig: () => inner.validateConfig(),
635
- stream: (request) => streamWithFallback(inner, request, resolvedOptions),
636
- complete: (request) => completeWithFallback(inner, request, resolvedOptions)
637
- };
638
- if (typeof inner.listModels === "function") {
639
- const list = inner.listModels.bind(inner);
640
- wrapped.listModels = () => list();
641
- }
642
- return wrapped;
643
- }
644
- async function completeWithFallback(inner, request, options) {
645
- const first = await inner.complete(request);
646
- if (first.ok) return first;
647
- if (first.error.code !== ErrorCode.MODEL_NOT_FOUND) return first;
648
- const fallback = await pickFallback(inner.modelId, options.cache, options.registry);
649
- if (fallback === null) {
650
- logger.warn("Model not found and no fallback available", {
651
- modelId: inner.modelId,
652
- providerId: inner.providerId,
653
- errorMessage: first.error.message
654
- });
655
- return first;
656
- }
657
- logger.info("Model retirement detected; retrying with fallback", {
658
- retiredModelId: inner.modelId,
659
- fallbackModelId: fallback,
660
- providerId: inner.providerId
661
- });
662
- notifyRetirement(options.onRetirement, {
663
- retiredModelId: inner.modelId,
664
- fallbackModelId: fallback,
665
- providerId: inner.providerId,
666
- errorMessage: first.error.message
667
- });
668
- if (options.adapterFactory === void 0) {
669
- return err(
670
- new ModelError(`${first.error.message} \u2014 suggested fallback: ${fallback}`, {
671
- code: ErrorCode.MODEL_NOT_FOUND,
672
- cause: first.error
673
- })
674
- );
675
- }
676
- const fallbackAdapter = options.adapterFactory(fallback);
677
- const retried = await fallbackAdapter.complete(request);
678
- if (retried.ok) return retried;
679
- return err(
680
- new ModelError(`Fallback ${fallback} also failed: ${retried.error.message}`, {
681
- code: retried.error.code,
682
- cause: retried.error
683
- })
684
- );
685
- }
686
- async function* streamWithFallback(inner, request, options) {
687
- try {
688
- for await (const chunk of inner.stream(request)) {
689
- yield chunk;
690
- }
691
- return;
692
- } catch (e) {
693
- if (!isModelNotFoundError(e)) throw e;
694
- const fallbackAdapter = await resolveStreamFallback(inner, e, options);
695
- for await (const chunk of fallbackAdapter.stream(request)) {
696
- yield chunk;
697
- }
698
- }
699
- }
700
- async function resolveStreamFallback(inner, cause, options) {
701
- const fallback = await pickFallback(inner.modelId, options.cache, options.registry);
702
- if (fallback === null) {
703
- logger.warn("Stream model not found and no fallback available", {
704
- modelId: inner.modelId,
705
- providerId: inner.providerId,
706
- errorMessage: cause instanceof Error ? cause.message : String(cause)
707
- });
708
- throw cause;
709
- }
710
- logger.info("Stream retirement detected; restarting with fallback (restart-from-zero)", {
711
- retiredModelId: inner.modelId,
712
- fallbackModelId: fallback,
713
- providerId: inner.providerId
714
- });
715
- notifyRetirement(options.onRetirement, {
716
- retiredModelId: inner.modelId,
717
- fallbackModelId: fallback,
718
- providerId: inner.providerId,
719
- errorMessage: cause instanceof Error ? cause.message : String(cause)
720
- });
721
- if (options.adapterFactory === void 0) {
722
- const message = `${cause instanceof Error ? cause.message : String(cause)} \u2014 suggested fallback: ${fallback}`;
723
- const causeErr = cause instanceof Error ? cause : void 0;
724
- throw new ModelError(
725
- message,
726
- causeErr !== void 0 ? { code: ErrorCode.MODEL_NOT_FOUND, cause: causeErr } : { code: ErrorCode.MODEL_NOT_FOUND }
727
- );
728
- }
729
- return options.adapterFactory(fallback);
730
- }
731
- function isModelNotFoundError(e) {
732
- if (e instanceof ModelError) {
733
- return e.code === ErrorCode.MODEL_NOT_FOUND;
734
- }
735
- if (typeof e === "object" && e !== null && "code" in e) {
736
- return e.code === ErrorCode.MODEL_NOT_FOUND;
737
- }
738
- return false;
739
- }
740
- function notifyRetirement(cb, info) {
741
- if (cb === void 0) return;
742
- try {
743
- cb(info);
744
- } catch (e) {
745
- logger.debug("onRetirement callback threw", {
746
- error: e instanceof Error ? e.message : String(e)
747
- });
748
- }
749
- }
750
- async function pickFallback(retiredModelId, cache, registry) {
751
- const refreshed = await cache.refresh();
752
- if (refreshed.length === 0) return null;
753
- const target = registry.getEntry(retiredModelId);
754
- const sameFamily = refreshed.find((m) => {
755
- if (m.id === retiredModelId) return false;
756
- const candidate = registry.getEntry(m.id);
757
- return candidate.family === target.family && candidate.vendor === target.vendor;
758
- });
759
- if (sameFamily !== void 0) return sameFamily.id;
760
- const sameVendor = refreshed.find((m) => {
761
- if (m.id === retiredModelId) return false;
762
- const candidate = registry.getEntry(m.id);
763
- return candidate.vendor === target.vendor;
764
- });
765
- return sameVendor?.id ?? null;
766
- }
767
- function wrapResilientWithFallback(inner, options = {}) {
768
- const wrapped = withModelNotFoundFallback(inner, options);
769
- const surface = {
770
- getHealth: inner.getHealth.bind(inner),
771
- refresh: inner.refresh.bind(inner),
772
- setPreferredCli: inner.setPreferredCli.bind(inner),
773
- onFailover: inner.onFailover.bind(inner),
774
- dispose: inner.dispose.bind(inner)
775
- };
776
- const explicit = Object.assign(wrapped, surface);
777
- const innerAsRecord = inner;
778
- return new Proxy(explicit, {
779
- get(target, prop, receiver) {
780
- if (prop in target) return Reflect.get(target, prop, receiver);
781
- const innerProp = innerAsRecord[prop];
782
- if (typeof innerProp === "function") {
783
- return innerProp.bind(inner);
784
- }
785
- return innerProp;
786
- }
787
- });
788
- }
789
-
790
615
  // src/adapters/unified-registry.ts
791
- function withDefaultOnRetirement(enabled, options, logger2) {
792
- if (!enabled) return options;
793
- return {
794
- ...options,
795
- onRetirement: options?.onRetirement ?? ((info) => {
796
- logger2.warn("Model retired via not-found fallback", { retirement: info });
797
- })
798
- };
799
- }
800
616
  var UnifiedAdapterRegistry = class {
801
617
  logger;
802
618
  defaultCliTimeoutMs;
803
- enableMissingModelFallback;
804
- missingModelFallbackOptions;
805
619
  /** Per-CLI adapter cache — max 3 entries (claude/gemini/codex). */
806
620
  cliAdapters = /* @__PURE__ */ new Map();
807
621
  /** Default adapter for unscoped requests. */
@@ -809,29 +623,11 @@ var UnifiedAdapterRegistry = class {
809
623
  constructor(config) {
810
624
  this.logger = config?.logger ?? createLogger({ component: "unified-registry" });
811
625
  this.defaultCliTimeoutMs = config?.defaultCliTimeoutMs;
812
- this.enableMissingModelFallback = config?.enableMissingModelFallback ?? false;
813
- this.missingModelFallbackOptions = withDefaultOnRetirement(
814
- this.enableMissingModelFallback,
815
- config?.missingModelFallbackOptions,
816
- this.logger
817
- );
818
626
  this.logger.info("UnifiedAdapterRegistry initialized", {
819
627
  categories: TASK_SPECIALIZATION_MATRIX.length,
820
- models: getInTreeCapabilitiesMatrix().models.length,
821
- missingModelFallback: this.enableMissingModelFallback
628
+ models: getInTreeCapabilitiesMatrix().models.length
822
629
  });
823
630
  }
824
- /**
825
- * Apply the MODEL_NOT_FOUND fallback decorator if enabled, otherwise
826
- * return the adapter unchanged. The recursion guard from the original
827
- * issue is unnecessary here: the decorator's retry path uses the
828
- * caller-supplied `adapterFactory`; without one, it surfaces an
829
- * enriched error and never re-enters the resilient layer.
830
- */
831
- maybeWrap(adapter) {
832
- if (!this.enableMissingModelFallback) return adapter;
833
- return wrapResilientWithFallback(adapter, this.missingModelFallbackOptions);
834
- }
835
631
  // --------------------------------------------------------------------------
836
632
  // Public API
837
633
  // --------------------------------------------------------------------------
@@ -881,11 +677,10 @@ var UnifiedAdapterRegistry = class {
881
677
  defaultCliTimeoutMs: this.defaultCliTimeoutMs
882
678
  }
883
679
  });
884
- const adapter = this.maybeWrap(raw);
680
+ const adapter = raw;
885
681
  this.cliAdapters.set(cli, adapter);
886
682
  this.logger.info("Created CLI-specific adapter", {
887
- cli,
888
- missingModelFallback: this.enableMissingModelFallback
683
+ cli
889
684
  });
890
685
  return adapter;
891
686
  }
@@ -933,7 +728,7 @@ var UnifiedAdapterRegistry = class {
933
728
  defaultCliTimeoutMs: this.defaultCliTimeoutMs
934
729
  }
935
730
  });
936
- this.defaultAdapter = this.maybeWrap(raw);
731
+ this.defaultAdapter = raw;
937
732
  return this.defaultAdapter;
938
733
  }
939
734
  /**
@@ -1033,12 +828,9 @@ export {
1033
828
  getGlobalEventBus,
1034
829
  createEvent,
1035
830
  EventTopics,
1036
- withModelNotFoundFallback,
1037
- wrapResilientWithFallback,
1038
- withDefaultOnRetirement,
1039
831
  UnifiedAdapterRegistry,
1040
832
  createUnifiedRegistry,
1041
833
  getGlobalRegistry,
1042
834
  resetGlobalRegistry
1043
835
  };
1044
- //# sourceMappingURL=chunk-MRRKFMYJ.js.map
836
+ //# sourceMappingURL=chunk-QQ3CUUBU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agents/collaboration/event-bus-helpers.ts","../src/agents/collaboration/event-bus.ts","../src/agents/collaboration/event-bus-topics.ts","../src/adapters/resilient-adapter.ts","../src/adapters/unified-registry.ts"],"sourcesContent":["/**\n * Event Bus Helper Functions\n *\n * Pure utility functions for event bus operations.\n * Extracted from event-bus.ts to maintain file size limits.\n *\n * @module agents/collaboration/event-bus-helpers\n * (Source: Issue #182, ARCHITECTURE.md Hybrid Architecture)\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { getTimeProvider } from '../../core/index.js';\nimport type { SubscriptionId, TopicPattern, DomainEvent, EventFilter } from './event-bus-types.js';\n\n/** Default maximum history size */\nexport const DEFAULT_MAX_HISTORY_SIZE = 1000;\n\n/** Maximum number of subscriptions per bus */\nexport const MAX_SUBSCRIPTIONS = 500;\n\n/**\n * Internal subscription record.\n */\nexport interface SubscriptionRecord {\n readonly id: SubscriptionId;\n readonly pattern: TopicPattern;\n readonly regex: RegExp;\n readonly listener: (event: DomainEvent) => void | Promise<void>;\n}\n\n/** Maximum allowed topic pattern length */\nconst MAX_TOPIC_PATTERN_LENGTH = 200;\n\n/**\n * Convert a topic pattern to a regex for matching.\n * Supports wildcard patterns:\n * - 'session.created' -> exact match\n * - 'session.*' -> matches session.anything\n * - '*' -> matches everything\n *\n * Security Note (Issue #341): This function is safe from ReDoS because:\n * 1. The input is fully escaped before regex construction\n * 2. Only `*` wildcards are converted to `[^.]+` (bounded, non-greedy)\n * 3. Pattern length is bounded to prevent memory issues\n *\n * @throws Error if pattern exceeds MAX_TOPIC_PATTERN_LENGTH\n */\nexport function patternToRegex(pattern: TopicPattern): RegExp {\n // Validate length to prevent memory issues (Issue #341)\n if (pattern.length > MAX_TOPIC_PATTERN_LENGTH) {\n throw new Error(`Topic pattern exceeds maximum length of ${String(MAX_TOPIC_PATTERN_LENGTH)}`);\n }\n\n if (pattern === '*') {\n return /^.+$/;\n }\n // Use placeholder for * before escaping, then restore as regex wildcard\n // WILDCARD_PLACEHOLDER is a static constant, safe for RegExp (Issue #341)\n const WILDCARD_PLACEHOLDER = '\\x00WILDCARD\\x00';\n const withPlaceholder = pattern.replace(/\\*/g, WILDCARD_PLACEHOLDER);\n // Escape all regex special characters - this makes the pattern safe\n const escaped = withPlaceholder.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n // Replace placeholder with bounded wildcard - safe since input was escaped\n const withWildcards = escaped.replace(new RegExp(WILDCARD_PLACEHOLDER, 'g'), '[^.]+');\n return new RegExp(`^${withWildcards}$`);\n}\n\n/**\n * Check if a topic matches a pattern.\n */\nexport function topicMatchesPattern(topic: string, regex: RegExp): boolean {\n return regex.test(topic);\n}\n\n/**\n * Generate a unique event ID.\n */\nexport function generateEventId(): string {\n return `evt-${String(getTimeProvider().now())}-${randomUUID().slice(0, 8)}`;\n}\n\n/**\n * Generate a unique subscription ID.\n */\nexport function generateSubscriptionId(): SubscriptionId {\n return `sub-${String(getTimeProvider().now())}-${randomUUID().slice(0, 8)}`;\n}\n\n/**\n * Generate a unique correlation ID for request tracing.\n * (Source: Issue #224, Sprint #228)\n *\n * @example\n * ```typescript\n * const correlationId = generateCorrelationId();\n * // -> 'cor_a1b2c3d4'\n * ```\n */\nexport function generateCorrelationId(): string {\n return `cor_${randomUUID().slice(0, 8)}`;\n}\n\n/**\n * Create a child correlation ID that chains to a parent.\n * Enables hierarchical tracing of subtasks.\n * (Source: Issue #224, Sprint #228)\n *\n * @param parentCorrelationId - The parent correlation ID to chain from\n * @returns A new correlation ID in format 'parentId.child_xxxxxxxx'\n *\n * @example\n * ```typescript\n * const parentId = generateCorrelationId();\n * // -> 'cor_a1b2c3d4'\n *\n * const childId = createChildCorrelationId(parentId);\n * // -> 'cor_a1b2c3d4.child_e5f6g7h8'\n * ```\n */\nexport function createChildCorrelationId(parentCorrelationId: string): string {\n return `${parentCorrelationId}.child_${randomUUID().slice(0, 8)}`;\n}\n\n/**\n * Apply topic, session, correlation, and timestamp filters to events.\n */\nexport function applyHistoryFilters(history: DomainEvent[], filter: EventFilter): DomainEvent[] {\n let result: DomainEvent[] = history;\n\n if (filter.topic !== undefined && filter.topic !== '') {\n const regex = patternToRegex(filter.topic);\n result = result.filter((e) => topicMatchesPattern(e.topic, regex));\n }\n\n if (filter.sessionId !== undefined && filter.sessionId !== '') {\n result = result.filter((e) => e.sessionId === filter.sessionId);\n }\n\n if (filter.correlationId !== undefined && filter.correlationId !== '') {\n result = result.filter((e) => e.correlationId === filter.correlationId);\n }\n\n return applyTimestampFilters(result, filter);\n}\n\n/**\n * Apply timestamp-based filters to events.\n */\nexport function applyTimestampFilters(result: DomainEvent[], filter: EventFilter): DomainEvent[] {\n let filtered = result;\n\n if (filter.after !== undefined && filter.after !== '') {\n const afterTimestamp = filter.after;\n filtered = filtered.filter((e) => e.timestamp > afterTimestamp);\n }\n\n if (filter.before !== undefined && filter.before !== '') {\n const beforeTimestamp = filter.before;\n filtered = filtered.filter((e) => e.timestamp < beforeTimestamp);\n }\n\n return filtered;\n}\n\n/**\n * Apply limit to history results.\n */\nexport function applyHistoryLimit(result: DomainEvent[], filter: EventFilter): DomainEvent[] {\n if (filter.limit !== undefined && filter.limit > 0) {\n return result.slice(-filter.limit);\n }\n return result;\n}\n\n/**\n * Enrich event with generated fields if missing.\n */\nexport function enrichEvent(event: DomainEvent): DomainEvent {\n return {\n ...event,\n eventId: event.eventId || generateEventId(),\n timestamp: event.timestamp || getTimeProvider().nowIso(),\n };\n}\n\n/**\n * Count subscribers matching a topic from a subscription map.\n */\nexport function countMatchingSubscribers(\n topic: string,\n subscriptions: Map<SubscriptionId, SubscriptionRecord>\n): number {\n let count = 0;\n for (const record of subscriptions.values()) {\n if (topicMatchesPattern(topic, record.regex)) {\n count++;\n }\n }\n return count;\n}\n","/**\n * Event Bus Implementation\n *\n * Async message passing for agent-to-agent communication.\n * Supports topic-based routing, wildcard patterns, and event history.\n *\n * @module agents/collaboration/event-bus\n * (Source: Issue #182, ARCHITECTURE.md Hybrid Architecture)\n */\n\nimport { getTimeProvider } from '../../core/index.js';\nimport type {\n IEventBus,\n DomainEvent,\n EventListener,\n Subscription,\n SubscriptionId,\n TopicPattern,\n EventFilter,\n EventBusOptions,\n EventBusStats,\n} from './event-bus-types.js';\n\nimport {\n DEFAULT_MAX_HISTORY_SIZE,\n MAX_SUBSCRIPTIONS,\n type SubscriptionRecord,\n patternToRegex,\n topicMatchesPattern,\n generateEventId,\n generateSubscriptionId,\n applyHistoryFilters,\n applyHistoryLimit,\n enrichEvent,\n countMatchingSubscribers,\n} from './event-bus-helpers.js';\n\nimport { CircularBuffer } from '../../core/circular-buffer.js';\n\n// Re-export correlation ID helpers for public API\nexport { generateCorrelationId, createChildCorrelationId } from './event-bus-helpers.js';\n\n/**\n * Event Bus for agent-to-agent communication.\n *\n * Features:\n * - Topic-based pub/sub with wildcard support\n * - Async and sync event emission\n * - Event history with filtering\n * - Error isolation (one listener failure doesn't block others)\n *\n * @example\n * ```typescript\n * const bus = new EventBus();\n *\n * // Subscribe to all session events\n * const sub = bus.subscribe('session.*', (event) => {\n * console.log('Session event:', event.topic);\n * });\n *\n * // Emit an event\n * bus.emit(createSessionCreatedEvent({ sessionId: '123', ... }));\n *\n * // Cleanup\n * sub.unsubscribe();\n * ```\n */\nexport class EventBus implements IEventBus {\n private readonly subscriptions: Map<SubscriptionId, SubscriptionRecord> = new Map();\n private readonly history: CircularBuffer<DomainEvent>;\n private readonly asyncHandling: boolean;\n private readonly logger?: EventBusOptions['logger'];\n\n private stats: EventBusStats = {\n eventsEmitted: 0,\n subscriptionsCreated: 0,\n activeSubscriptions: 0,\n historySize: 0,\n errorCount: 0,\n };\n\n constructor(options: EventBusOptions = {}) {\n const maxHistorySize = options.maxHistorySize ?? DEFAULT_MAX_HISTORY_SIZE;\n this.history = new CircularBuffer<DomainEvent>(maxHistorySize);\n this.asyncHandling = options.asyncHandling ?? false;\n this.logger = options.logger;\n }\n\n /**\n * Emit an event synchronously to all matching subscribers.\n */\n emit(event: DomainEvent): void {\n const enrichedEvent = enrichEvent(event);\n this.addToHistory(enrichedEvent);\n this.stats.eventsEmitted++;\n\n this.logger?.debug('Event emitted', {\n topic: enrichedEvent.topic,\n eventId: enrichedEvent.eventId,\n subscriberCount: countMatchingSubscribers(enrichedEvent.topic, this.subscriptions),\n });\n\n for (const record of this.subscriptions.values()) {\n if (topicMatchesPattern(enrichedEvent.topic, record.regex)) {\n this.invokeListener(record, enrichedEvent);\n }\n }\n }\n\n /**\n * Emit an event and wait for all async handlers to complete.\n */\n async emitAsync(event: DomainEvent): Promise<void> {\n const enrichedEvent = enrichEvent(event);\n this.addToHistory(enrichedEvent);\n this.stats.eventsEmitted++;\n\n this.logger?.debug('Async event emitted', {\n topic: enrichedEvent.topic,\n eventId: enrichedEvent.eventId,\n });\n\n const promises: Promise<void>[] = [];\n\n for (const record of this.subscriptions.values()) {\n if (topicMatchesPattern(enrichedEvent.topic, record.regex)) {\n promises.push(this.invokeListenerAsync(record, enrichedEvent));\n }\n }\n\n await Promise.all(promises);\n }\n\n /**\n * Subscribe to events matching a topic pattern.\n */\n subscribe<T extends DomainEvent = DomainEvent>(\n pattern: TopicPattern,\n listener: EventListener<T>\n ): Subscription {\n if (this.subscriptions.size >= MAX_SUBSCRIPTIONS) {\n throw new Error(\n `Maximum subscription limit reached (${String(MAX_SUBSCRIPTIONS)}). ` +\n 'Unsubscribe from unused patterns before adding new ones.'\n );\n }\n\n const id = generateSubscriptionId();\n const regex = patternToRegex(pattern);\n\n const record: SubscriptionRecord = {\n id,\n pattern,\n regex,\n listener: listener as EventListener,\n };\n\n this.subscriptions.set(id, record);\n this.stats.subscriptionsCreated++;\n this.stats.activeSubscriptions = this.subscriptions.size;\n\n this.logger?.debug('Subscription created', { id, pattern });\n\n return {\n id,\n pattern,\n unsubscribe: () => {\n this.unsubscribe(id);\n },\n };\n }\n\n /**\n * Unsubscribe from events.\n */\n unsubscribe(subscriptionId: SubscriptionId): void {\n const existed = this.subscriptions.delete(subscriptionId);\n if (existed) {\n this.stats.activeSubscriptions = this.subscriptions.size;\n this.logger?.debug('Subscription removed', { subscriptionId });\n }\n }\n\n /**\n * Get event history matching filter criteria.\n */\n getHistory(filter?: EventFilter): readonly DomainEvent[] {\n const historyArray = this.history.toArray();\n if (filter === undefined) {\n return historyArray;\n }\n\n const filtered = applyHistoryFilters(historyArray, filter);\n return applyHistoryLimit(filtered, filter);\n }\n\n /**\n * Clear event history.\n */\n clearHistory(): void {\n this.history.clear();\n this.stats.historySize = 0;\n this.logger?.debug('History cleared');\n }\n\n /**\n * Get event bus statistics.\n */\n getStats(): EventBusStats {\n return { ...this.stats };\n }\n\n /**\n * Check if a topic pattern has any subscribers.\n */\n hasSubscribers(pattern: TopicPattern): boolean {\n const regex = patternToRegex(pattern);\n for (const record of this.subscriptions.values()) {\n // Check if the patterns could overlap\n if (\n topicMatchesPattern(pattern, record.regex) ||\n topicMatchesPattern(record.pattern, regex)\n ) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Add event to history, maintaining size limit via O(1) circular buffer.\n * @see Issue #407 - Performance optimization\n */\n private addToHistory(event: DomainEvent): void {\n this.history.push(event);\n this.stats.historySize = this.history.size;\n }\n\n /**\n * Invoke a listener synchronously with error isolation.\n */\n private invokeListener(record: SubscriptionRecord, event: DomainEvent): void {\n try {\n const result = record.listener(event);\n // If async handling is enabled and listener returns a promise, don't wait\n if (this.asyncHandling && result instanceof Promise) {\n result.catch((error: unknown) => {\n this.handleListenerError(record, event, error);\n });\n }\n } catch (error) {\n this.handleListenerError(record, event, error);\n }\n }\n\n /**\n * Invoke a listener asynchronously.\n */\n private async invokeListenerAsync(record: SubscriptionRecord, event: DomainEvent): Promise<void> {\n try {\n await record.listener(event);\n } catch (error) {\n this.handleListenerError(record, event, error);\n }\n }\n\n /**\n * Handle listener errors without disrupting other listeners.\n */\n private handleListenerError(\n record: SubscriptionRecord,\n event: DomainEvent,\n error: unknown\n ): void {\n this.stats.errorCount++;\n const errorObj = error instanceof Error ? error : new Error(String(error));\n this.logger?.error('Event listener error', errorObj, {\n subscriptionId: record.id,\n pattern: record.pattern,\n eventTopic: event.topic,\n eventId: event.eventId,\n });\n }\n}\n\n/**\n * Create a singleton event bus instance for global coordination.\n * Use with caution - prefer dependency injection for testability.\n */\nlet globalBus: EventBus | null = null;\n\nexport function getGlobalEventBus(options?: EventBusOptions): EventBus {\n globalBus ??= new EventBus(options);\n return globalBus;\n}\n\n/**\n * Reset the global event bus (for testing).\n */\nexport function resetGlobalEventBus(): void {\n globalBus = null;\n}\n\n/**\n * Factory functions for creating typed events.\n */\nexport function createEvent<T extends DomainEvent>(\n topic: T['topic'],\n payload: T['payload'],\n options?: {\n sessionId?: string;\n correlationId?: string;\n }\n): T {\n return {\n eventId: generateEventId(),\n timestamp: getTimeProvider().nowIso(),\n topic,\n payload,\n sessionId: options?.sessionId,\n correlationId: options?.correlationId,\n } as T;\n}\n","/**\n * Event Bus Topic Constants\n *\n * Type-safe topic constants for event subscription.\n *\n * @module agents/collaboration/event-bus-topics\n * (Source: Issue #182, ARCHITECTURE.md Hybrid Architecture)\n */\n\n/**\n * Topic constants for type-safe subscription.\n */\nexport const EventTopics = {\n // Session events\n SESSION_CREATED: 'session.created',\n SESSION_STATUS_CHANGED: 'session.status_changed',\n SESSION_PARTICIPANT_JOINED: 'session.participant_joined',\n SESSION_RESULT_SUBMITTED: 'session.result_submitted',\n SESSION_FINALIZED: 'session.finalized',\n SESSION_ALL: 'session.*',\n\n // Message events\n MESSAGE_SENT: 'message.sent',\n MESSAGE_RECEIVED: 'message.received',\n MESSAGE_ALL: 'message.*',\n\n // Agent events\n AGENT_TASK_DELEGATED: 'agent.task_delegated',\n AGENT_RESULT_BROADCAST: 'agent.result_broadcast',\n AGENT_ALL: 'agent.*',\n\n // Consensus events\n CONSENSUS_VOTE_REQUESTED: 'consensus.vote_requested',\n CONSENSUS_VOTE_CAST: 'consensus.vote_cast',\n CONSENSUS_REACHED: 'consensus.reached',\n CONSENSUS_ALL: 'consensus.*',\n\n // Protocol events\n PROTOCOL_STARTED: 'protocol.started',\n PROTOCOL_ITERATION: 'protocol.iteration',\n PROTOCOL_COMPLETED: 'protocol.completed',\n PROTOCOL_ALL: 'protocol.*',\n\n // Aegean phase events (Issue #216)\n AEGEAN_ROUND_STARTED: 'protocol.aegean.round_started',\n AEGEAN_VOTE_COLLECTED: 'protocol.aegean.vote_collected',\n AEGEAN_QUORUM_DETECTED: 'protocol.aegean.quorum_detected',\n AEGEAN_ALL: 'protocol.aegean.*',\n\n // Reflexion phase events (Issue #216)\n REFLEXION_CRITIQUE_STARTED: 'protocol.reflexion.critique_started',\n REFLEXION_CRITIQUE_COMPLETED: 'protocol.reflexion.critique_completed',\n REFLEXION_SYNTHESIS: 'protocol.reflexion.synthesis',\n REFLEXION_ALL: 'protocol.reflexion.*',\n\n // Trinity phase events (Issue #216)\n TRINITY_PHASE_STARTED: 'protocol.trinity.phase_started',\n TRINITY_PHASE_COMPLETED: 'protocol.trinity.phase_completed',\n TRINITY_ALL: 'protocol.trinity.*',\n\n // Byzantine detection events (Issue #218)\n BYZANTINE_WEIGHT_UPDATED: 'byzantine.weight_updated',\n BYZANTINE_PATTERN_DETECTED: 'byzantine.pattern_detected',\n BYZANTINE_AGENT_FLAGGED: 'byzantine.agent_flagged',\n BYZANTINE_COLLUSION_SUSPECTED: 'byzantine.collusion_suspected',\n BYZANTINE_ALL: 'byzantine.*',\n\n // Wildcard\n ALL: '*',\n} as const;\n","/**\n * Resilient Model Adapter\n *\n * Transparent proxy implementing IModelAdapter with lazy detection,\n * automatic failover on circuit breaker events, and observable health.\n *\n * Key design: ResilientAdapter IS an IModelAdapter. Every existing consumer\n * works unchanged. The health/failover API lives on IResilientAdapter for\n * dashboard and monitoring consumers only.\n *\n * @module adapters/resilient-adapter\n * (Source: Issue #811 - Resilient model adapter architecture)\n */\n\nimport type {\n IModelAdapter,\n CompletionRequest,\n CompletionResponse,\n StreamChunk,\n ModelCapability,\n} from '../core/types/model.js';\nimport type { Result } from '../core/result.js';\nimport { ok, err } from '../core/result.js';\nimport { ModelError, ConfigError } from '../core/errors.js';\nimport type { ILogger } from '../core/index.js';\nimport { getTimeProvider, getRandomProvider } from '../core/index.js';\nimport { getErrorMessage, createLogger } from '../core/index.js';\n\nimport { createAutoAdapter, type AdapterSelection } from './auto-adapter.js';\nimport {\n isRateLimitLikeError,\n toRateLimitError,\n recordRateLimitEvent,\n} from './rate-limit-detector.js';\nimport type { CliName } from '../cli-adapters/types.js';\nimport type {\n IResilientAdapter,\n AdapterHealthInfo,\n ResilientAdapterConfig,\n} from './resilient-adapter-types.js';\nimport type { CircuitStateChangeEvent } from '../cli-adapters/circuit-breaker-types.js';\nimport type { CircuitBreakerRegistry } from '../cli-adapters/circuit-breaker.js';\nimport { mapModelErrorToCategory } from '../cli-adapters/circuit-breaker.js';\nimport { getGlobalEventBus } from '../core/event-bus.js';\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Creates a resilient adapter with lazy detection and automatic failover.\n *\n * @example\n * ```typescript\n * const adapter = createResilientAdapter({ logger });\n * // No detection at creation time — detection happens on first use\n * const result = await adapter.complete(request); // triggers detection\n * ```\n */\nexport function createResilientAdapter(config?: ResilientAdapterConfig): IResilientAdapter {\n return new ResilientAdapter(config);\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Resilient adapter proxy. Implements IModelAdapter transparently.\n */\nexport class ResilientAdapter implements IResilientAdapter {\n private readonly logger: ILogger;\n private currentAdapter: IModelAdapter | undefined;\n private currentSelection: AdapterSelection | undefined;\n private hasEverDetected = false;\n private health: AdapterHealthInfo | undefined;\n private preferredCli: CliName | undefined;\n private readonly failoverCallbacks = new Set<(info: AdapterHealthInfo) => void>();\n private circuitBreakerRegistry: CircuitBreakerRegistry | undefined;\n private circuitListener: ((event: CircuitStateChangeEvent) => void) | undefined;\n private disposed = false;\n private readonly defaultCliTimeoutMs: number | undefined;\n /** Inflight detection promise for coalescing concurrent calls (Issue #1423). */\n private detectionPromise: Promise<IModelAdapter | undefined> | undefined;\n\n constructor(config?: ResilientAdapterConfig) {\n this.logger = config?.logger ?? createLogger({ component: 'resilient-adapter' });\n this.preferredCli = config?.preferredCli;\n this.defaultCliTimeoutMs = config?.defaultCliTimeoutMs;\n }\n\n // --- IModelAdapter properties (forwarded) ---\n\n get providerId(): string {\n return this.currentAdapter?.providerId ?? 'resilient-proxy';\n }\n\n get modelId(): string {\n return this.currentAdapter?.modelId ?? 'pending-detection';\n }\n\n get capabilities(): readonly ModelCapability[] {\n return this.currentAdapter?.capabilities ?? [];\n }\n\n // --- IModelAdapter methods ---\n\n async complete(request: CompletionRequest): Promise<Result<CompletionResponse, ModelError>> {\n const adapter = await this.ensureAdapter();\n if (adapter === undefined) {\n return err(new ModelError('No model adapter available'));\n }\n const result = await adapter.complete(request);\n if (!result.ok) {\n if (isRateLimitLikeError(result.error)) {\n const rlError = toRateLimitError(result.error, adapter.providerId);\n recordRateLimitEvent({\n provider: adapter.providerId,\n timestamp: getTimeProvider().now(),\n retryAfterMs: rlError.retryAfterMs,\n });\n this.logger.warn('Rate limit detected', {\n provider: adapter.providerId,\n retryAfterMs: rlError.retryAfterMs,\n });\n }\n this.recordBreakerFailure(adapter, result.error);\n }\n return result;\n }\n\n async *stream(request: CompletionRequest): AsyncIterable<StreamChunk> {\n const adapter = await this.ensureAdapter();\n if (adapter === undefined) {\n // #3105: error like complete() rather than yielding an empty stream — a\n // silent-empty stream masks \"no adapter available\" as a clean empty\n // completion, so streamWithFallback can't tell failure from a\n // legitimately-empty result.\n throw new ModelError('No model adapter available');\n }\n yield* adapter.stream(request);\n }\n\n async countTokens(text: string): Promise<number> {\n const adapter = await this.ensureAdapter();\n if (adapter === undefined) {\n return 0;\n }\n return adapter.countTokens(text);\n }\n\n validateConfig(): Result<void, ConfigError> {\n if (this.currentAdapter !== undefined) {\n return this.currentAdapter.validateConfig();\n }\n // Before first use, config is trivially valid\n return ok(undefined);\n }\n\n // --- IResilientAdapter methods ---\n\n getHealth(): AdapterHealthInfo | undefined {\n return this.health;\n }\n\n async refresh(): Promise<void> {\n this.currentAdapter = undefined;\n this.currentSelection = undefined;\n this.detectionPromise = undefined;\n await this.ensureAdapter();\n }\n\n setPreferredCli(cli: CliName | undefined): void {\n this.preferredCli = cli;\n this.currentAdapter = undefined;\n this.currentSelection = undefined;\n }\n\n onFailover(cb: (info: AdapterHealthInfo) => void): () => void {\n this.failoverCallbacks.add(cb);\n return () => {\n this.failoverCallbacks.delete(cb);\n };\n }\n\n /**\n * Attach a circuit breaker registry for automatic failover.\n * When the current adapter's circuit opens, the cached adapter\n * is cleared so the next call triggers re-detection.\n */\n attachCircuitBreakerRegistry(registry: CircuitBreakerRegistry): void {\n this.detachCircuitBreakerRegistry();\n this.circuitBreakerRegistry = registry;\n this.circuitListener = (event: CircuitStateChangeEvent) => {\n this.handleCircuitStateChange(event);\n };\n registry.addGlobalStateChangeListener(this.circuitListener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.detachCircuitBreakerRegistry();\n this.failoverCallbacks.clear();\n }\n\n // --- Private methods ---\n\n private async ensureAdapter(): Promise<IModelAdapter | undefined> {\n if (this.currentAdapter !== undefined) {\n return this.currentAdapter;\n }\n // Coalesce concurrent detection calls into a single probe (Issue #1423)\n this.detectionPromise ??= this.detectAdapter().finally(() => {\n this.detectionPromise = undefined;\n });\n return this.detectionPromise;\n }\n\n private async detectAdapter(): Promise<IModelAdapter | undefined> {\n try {\n this.logger.info('Detecting model adapter (lazy)');\n const config = {\n logger: this.logger,\n ...(this.preferredCli !== undefined && { preferredCli: this.preferredCli }),\n ...(this.defaultCliTimeoutMs !== undefined && {\n defaultCliTimeoutMs: this.defaultCliTimeoutMs,\n }),\n };\n const selection = await createAutoAdapter(config);\n this.applySelection(selection);\n return this.currentAdapter;\n } catch (error) {\n const message = getErrorMessage(error);\n this.logger.warn('No model adapter available', { error: message });\n this.health = {\n source: 'api',\n state: 'unavailable',\n selectedAt: new Date(),\n failoverCount: this.health?.failoverCount ?? 0,\n lastError: message,\n };\n return undefined;\n }\n }\n\n private applySelection(selection: AdapterSelection): void {\n const isFailover = this.hasEverDetected;\n this.currentAdapter = selection.adapter;\n this.currentSelection = selection;\n this.hasEverDetected = true;\n\n const source = mapSelectionSource(selection);\n const failoverCount = (this.health?.failoverCount ?? 0) + (isFailover ? 1 : 0);\n\n this.health = {\n source,\n state: 'healthy',\n selectedAt: new Date(),\n failoverCount,\n };\n\n this.logger.info('Adapter selected', {\n source: selection.source,\n name: selection.name,\n model: selection.adapter.modelId,\n provider: selection.adapter.providerId,\n failover: isFailover,\n });\n\n if (isFailover) {\n this.emitFailover();\n }\n }\n\n /**\n * Record a direct-API failure to the circuit breaker so API adapters get the\n * same degradation/failover learning that CLI subprocess failures get (#3423).\n *\n * The breaker key is `currentSelection.name` — distinct from the `api:<vendor>`\n * arm key used by the ModelToCliAdapter path (#3422), so the two paths never\n * double-count the same failure.\n *\n * Rate-limit failures are deliberately skipped here: they are already recorded\n * by the `recordRateLimitEvent` telemetry branch in `complete()`, and the\n * breaker counts rate limits as failures by default\n * (`countRateLimitsAsFailures`), so recording them again would double-count.\n *\n * Security (#3422 constraint): the logged payload carries only `provider` and\n * `category` — never `error.message`, the request, or the ModelError object,\n * any of which could embed credentials.\n */\n private recordBreakerFailure(adapter: IModelAdapter, error: ModelError): void {\n if (this.circuitBreakerRegistry === undefined || this.currentSelection === undefined) {\n return;\n }\n\n const category = mapModelErrorToCategory(error);\n // Skip rate limits: already accounted for by the rate-limit telemetry branch\n // (and the breaker counts them by default). Check BOTH the mapped category\n // AND the telemetry predicate — their rate-limit pattern lists differ\n // (`isRateLimitLikeError` matches \"quota exceeded\"/\"throttl\"/… that\n // `categorizeError` does not), so a code-less MODEL_ERROR could otherwise\n // fire the telemetry branch yet fall through to a counted failure here,\n // double-counting against the threshold (#3423 review).\n if (category === 'rate_limit' || isRateLimitLikeError(error)) {\n return;\n }\n\n this.circuitBreakerRegistry\n .getBreaker(this.currentSelection.name as CliName)\n .recordFailure(category);\n\n this.logger.warn('API failure recorded to circuit breaker', {\n provider: adapter.providerId,\n category,\n });\n }\n\n private handleCircuitStateChange(event: CircuitStateChangeEvent): void {\n if (event.newState !== 'open') return;\n\n const currentSource = this.currentSelection?.name;\n if (currentSource === undefined || currentSource !== event.cliName) {\n return; // Not our current adapter\n }\n\n this.logger.warn('Current adapter circuit opened, clearing cache for re-detection', {\n cli: event.cliName,\n reason: event.reason,\n });\n\n this.currentAdapter = undefined;\n this.currentSelection = undefined;\n\n if (this.health !== undefined) {\n this.health = {\n ...this.health,\n state: 'degraded',\n lastError: event.reason,\n };\n }\n }\n\n private emitFailover(): void {\n const info = this.health;\n if (info === undefined) return;\n\n for (const cb of this.failoverCallbacks) {\n try {\n cb(info);\n } catch (error: unknown) {\n this.logger.debug('Failover callback error', { error: getErrorMessage(error) });\n }\n }\n\n try {\n const eventBus = getGlobalEventBus();\n eventBus.emit({\n eventId: `failover-${getTimeProvider().now().toString(36)}-${getRandomProvider().random().toString(36).slice(2, 8)}`,\n timestamp: new Date().toISOString(),\n topic: 'adapter.failover',\n payload: info,\n });\n } catch (error: unknown) {\n this.logger.debug('EventBus emit failed during failover', {\n error: getErrorMessage(error),\n });\n }\n }\n\n private detachCircuitBreakerRegistry(): void {\n if (this.circuitBreakerRegistry !== undefined && this.circuitListener !== undefined) {\n this.circuitBreakerRegistry.removeGlobalStateChangeListener(this.circuitListener);\n }\n this.circuitBreakerRegistry = undefined;\n this.circuitListener = undefined;\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction mapSelectionSource(selection: AdapterSelection): CliName | 'api' {\n if (selection.source === 'cli') {\n return selection.name as CliName;\n }\n return 'api';\n}\n","/**\n * Unified Adapter Registry — single entry point for all model adapter access.\n *\n * Pre-computes task-to-CLI routing from the canonical model registry and\n * task specialization matrix at creation time. All consumers get adapters\n * through this registry instead of calling createAutoAdapter/createResilientAdapter\n * directly.\n *\n * Design:\n * - One IResilientAdapter per CLI (claude/gemini/codex), created lazily on first access\n * - One \"default\" adapter for unscoped requests (uses createAutoAdapter priority)\n * - Task routing is deterministic: category → primary CLI → cached adapter\n * - Session-scoped: create once at MCP startup, reuse for the session lifetime\n *\n * @module adapters/unified-registry\n * (Source: Issue #1149 — Unified Adapter Registry)\n * (Source: Issue #1151 — Single adapter entry point)\n */\n\nimport type { ILogger } from '../core/index.js';\nimport { createLogger } from '../core/index.js';\nimport { createResilientAdapter } from './resilient-adapter.js';\nimport type { IResilientAdapter } from './resilient-adapter-types.js';\nimport type { CliName } from '../cli-adapters/types.js';\nimport { TASK_SPECIALIZATION_MATRIX, detectTaskCategory } from '../config/task-specialization.js';\nimport type { TaskCategory } from '../config/task-specialization-types.js';\nimport {\n getDefaultModelForCli,\n getInTreeCapabilitiesMatrix,\n} from '../config/model-config-helpers.js';\nimport type { CliNameLiteral } from '../config/model-capabilities-types.js';\nimport { CLI_NAMES } from '../config/model-capabilities-types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Configuration for the unified registry. */\nexport interface UnifiedRegistryConfig {\n /** Logger instance */\n readonly logger?: ILogger;\n /** Default CLI timeout for subprocess calls (ms) */\n readonly defaultCliTimeoutMs?: number;\n}\n\n/** Summary of the pre-computed task routing table. */\nexport interface TaskRoutingEntry {\n readonly category: TaskCategory;\n readonly primaryCli: CliName;\n readonly secondaryCli: CliName;\n readonly primaryModel: string;\n}\n\n/** Snapshot of registry state for observability. */\nexport interface RegistrySnapshot {\n readonly taskRouting: readonly TaskRoutingEntry[];\n readonly cachedAdapters: readonly CliName[];\n readonly availableModels: number;\n}\n\n// ============================================================================\n// Registry\n// ============================================================================\n\n/**\n * Unified adapter registry. Centralizes all adapter creation and task routing.\n *\n * Usage:\n * ```typescript\n * const registry = createUnifiedRegistry({ logger });\n * const adapter = registry.getAdapter('code_generation'); // → codex adapter\n * const adapter2 = registry.getAdapterForCli('claude'); // → claude adapter\n * const adapter3 = registry.getDefault(); // → best available\n * ```\n */\n\nexport class UnifiedAdapterRegistry {\n private readonly logger: ILogger;\n private readonly defaultCliTimeoutMs: number | undefined;\n\n /** Per-CLI adapter cache — max 3 entries (claude/gemini/codex). */\n private readonly cliAdapters = new Map<CliName, IResilientAdapter>();\n\n /** Default adapter for unscoped requests. */\n private defaultAdapter: IResilientAdapter | undefined;\n\n constructor(config?: UnifiedRegistryConfig) {\n this.logger = config?.logger ?? createLogger({ component: 'unified-registry' });\n this.defaultCliTimeoutMs = config?.defaultCliTimeoutMs;\n this.logger.info('UnifiedAdapterRegistry initialized', {\n categories: TASK_SPECIALIZATION_MATRIX.length,\n models: getInTreeCapabilitiesMatrix().models.length,\n });\n }\n\n // --------------------------------------------------------------------------\n // Public API\n // --------------------------------------------------------------------------\n\n /** Logger used by this registry. Exposed so singleton helpers can warn. */\n getLogger(): ILogger {\n return this.logger;\n }\n\n /**\n * Get adapter for a task category. Routing is re-resolved on every read\n * (#3185) so a post-startup overlay/registry update propagates without a\n * restart. Falls back to default adapter if category unknown.\n */\n getAdapter(category: TaskCategory): IResilientAdapter {\n const routing = this.getRouting(category);\n if (routing === undefined) {\n this.logger.warn('Unknown task category, using default', { category });\n return this.getDefault();\n }\n return this.getAdapterForCli(routing.primaryCli);\n }\n\n /**\n * Get adapter for a free-text task description.\n * Detects category from keywords, falls back to default.\n */\n getAdapterForTask(taskDescription: string): IResilientAdapter {\n const match = detectTaskCategory(taskDescription);\n if (match !== null) {\n this.logger.debug('Task category detected', {\n category: match.category,\n cli: match.primaryCli,\n });\n return this.getAdapterForCli(match.primaryCli);\n }\n return this.getDefault();\n }\n\n /**\n * Get adapter pinned to a specific CLI.\n * Creates and caches one IResilientAdapter per CLI.\n */\n getAdapterForCli(cli: CliName): IResilientAdapter {\n const cached = this.cliAdapters.get(cli);\n if (cached !== undefined) return cached;\n\n const raw = createResilientAdapter({\n logger: this.logger,\n preferredCli: cli,\n ...(this.defaultCliTimeoutMs !== undefined && {\n defaultCliTimeoutMs: this.defaultCliTimeoutMs,\n }),\n });\n const adapter = raw;\n this.cliAdapters.set(cli, adapter);\n this.logger.info('Created CLI-specific adapter', {\n cli,\n });\n return adapter;\n }\n\n /**\n * Get adapter for a model preference string (e.g., \"claude-opus-4-6\").\n * Resolves the model to its CLI via the canonical registry.\n * Falls back to default adapter if model not recognized.\n */\n getAdapterForModel(modelPreference: string): IResilientAdapter {\n // Prefer exact matches (id / cliAlias / cliModelName) over prefix matches.\n // For prefix fallback, longest-prefix-wins so a future registry containing\n // both 'gemini-pro' and 'gemini-pro-experimental' resolves correctly even\n // though the registry's natural array order isn't sorted by id length.\n // Defense-in-depth — no current registry has prefix overlaps. (#2192)\n const allModels = getInTreeCapabilitiesMatrix().models;\n const exact = allModels.find(\n (m) =>\n m.id === modelPreference ||\n m.cliAlias === modelPreference ||\n m.cliModelName === modelPreference\n );\n const prefix =\n exact ??\n [...allModels]\n .filter((m) => modelPreference.startsWith(m.id))\n .sort((a, b) => b.id.length - a.id.length)[0];\n const model = prefix;\n if (model !== undefined) {\n this.logger.debug('Model resolved to CLI', {\n model: modelPreference,\n cli: model.cliName,\n });\n return this.getAdapterForCli(model.cliName as CliName);\n }\n this.logger.debug('Model not in registry, using default', {\n model: modelPreference,\n });\n return this.getDefault();\n }\n\n /**\n * Get adapter for an expert role (e.g., \"code_expert\").\n * Uses ROLE_TO_TASK_CATEGORY mapping → task specialization → CLI.\n */\n getAdapterForRole(role: string): IResilientAdapter {\n const category = ROLE_TO_CATEGORY[role];\n if (category !== undefined) return this.getAdapter(category);\n return this.getDefault();\n }\n\n /**\n * Get the default adapter (no CLI preference — auto-detection priority).\n */\n getDefault(): IResilientAdapter {\n if (this.defaultAdapter !== undefined) return this.defaultAdapter;\n const raw = createResilientAdapter({\n logger: this.logger,\n ...(this.defaultCliTimeoutMs !== undefined && {\n defaultCliTimeoutMs: this.defaultCliTimeoutMs,\n }),\n });\n this.defaultAdapter = raw;\n return this.defaultAdapter;\n }\n\n /**\n * Get snapshot of registry state for observability/debugging. Routing is\n * re-resolved on read (#3185) so the snapshot reflects the live registry.\n */\n getSnapshot(): RegistrySnapshot {\n return {\n taskRouting: TASK_SPECIALIZATION_MATRIX.map((spec) => this.resolveRouting(spec)),\n cachedAdapters: [...this.cliAdapters.keys()],\n availableModels: getInTreeCapabilitiesMatrix().models.length,\n };\n }\n\n /**\n * Resolve the routing for a specific category.\n *\n * Computed on every read (#3185) rather than cached at construction, so a\n * post-startup model-registry / overlay update (e.g. a default-model change\n * surfaced via `getDefaultModelForCli`) propagates to routing decisions\n * without a process restart. The matrix is ~10 categories — the per-read\n * resolution cost is negligible.\n */\n getRouting(category: TaskCategory): TaskRoutingEntry | undefined {\n const spec = TASK_SPECIALIZATION_MATRIX.find((s) => s.category === category);\n if (spec === undefined) return undefined;\n return this.resolveRouting(spec);\n }\n\n /**\n * Dispose all cached adapters.\n */\n dispose(): void {\n for (const adapter of this.cliAdapters.values()) {\n adapter.dispose();\n }\n this.cliAdapters.clear();\n this.defaultAdapter?.dispose();\n this.defaultAdapter = undefined;\n this.logger.info('UnifiedAdapterRegistry disposed');\n }\n\n // --------------------------------------------------------------------------\n // Private\n // --------------------------------------------------------------------------\n\n /**\n * Resolve one specialization-matrix row into a routing entry, re-reading the\n * primary model from the (overlay-aware) model registry each call (#3185).\n */\n private resolveRouting(spec: (typeof TASK_SPECIALIZATION_MATRIX)[number]): TaskRoutingEntry {\n return {\n category: spec.category,\n primaryCli: spec.primaryCli,\n secondaryCli: spec.secondaryCli,\n primaryModel: resolveDefaultModel(spec.primaryCli),\n };\n }\n}\n\n// ============================================================================\n// Role → Category Mapping (consolidated from create-expert-routing.ts)\n// ============================================================================\n\n/** Maps expert roles to task categories for CLI specialization. */\nconst ROLE_TO_CATEGORY: Record<string, TaskCategory> = {\n code_expert: 'code_generation',\n architecture_expert: 'architecture',\n security_expert: 'security_review',\n documentation_expert: 'documentation',\n testing_expert: 'testing',\n devops_expert: 'devops',\n research_expert: 'research',\n pm_expert: 'planning',\n ux_expert: 'planning',\n infrastructure_expert: 'devops',\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Resolve the default model name for a CLI from the canonical registry. */\nfunction resolveDefaultModel(cli: string): string {\n if ((CLI_NAMES as readonly string[]).includes(cli)) {\n return getDefaultModelForCli(cli as CliNameLiteral);\n }\n return cli;\n}\n\n// ============================================================================\n// Factory & Singleton\n// ============================================================================\n\nlet globalRegistry: UnifiedAdapterRegistry | undefined;\n\n/**\n * Create a new UnifiedAdapterRegistry instance.\n * For most uses, prefer `getGlobalRegistry()` instead.\n */\nexport function createUnifiedRegistry(config?: UnifiedRegistryConfig): UnifiedAdapterRegistry {\n return new UnifiedAdapterRegistry(config);\n}\n\n/**\n * Get the global singleton registry.\n * Creates it on first access with default config.\n *\n * If the singleton already exists and a non-empty config is supplied, the\n * config is ignored — callers get the pre-existing instance. A warning is\n * emitted so this asymmetry is not silent.\n */\nexport function getGlobalRegistry(config?: UnifiedRegistryConfig): UnifiedAdapterRegistry {\n if (globalRegistry === undefined) {\n globalRegistry = new UnifiedAdapterRegistry(config);\n return globalRegistry;\n }\n if (config !== undefined && Object.keys(config).length > 0) {\n globalRegistry\n .getLogger()\n .warn(\n 'UnifiedAdapterRegistry singleton already initialized; provided config ignored. ' +\n 'Call resetGlobalRegistry() first if reconfiguration is intentional.',\n { providedKeys: Object.keys(config) }\n );\n }\n return globalRegistry;\n}\n\n/** Reset the global registry (for testing). */\nexport function resetGlobalRegistry(): void {\n globalRegistry?.dispose();\n globalRegistry = undefined;\n}\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  getErrorMessage,
5
5
  getTimeProvider,
6
6
  ok
7
- } from "./chunk-Y5JA7XEO.js";
7
+ } from "./chunk-CNHPSIHY.js";
8
8
 
9
9
  // src/context/session-memory.ts
10
10
  import * as fs from "fs";
@@ -352,4 +352,4 @@ export {
352
352
  SessionMemory,
353
353
  createSessionMemory
354
354
  };
355
- //# sourceMappingURL=chunk-IVINLVGP.js.map
355
+ //# sourceMappingURL=chunk-U5CV76ZW.js.map
@@ -11,7 +11,7 @@ import {
11
11
  createGetRetryConfig,
12
12
  createGetTimeout,
13
13
  createGetToolRateLimit
14
- } from "./chunk-Y5JA7XEO.js";
14
+ } from "./chunk-CNHPSIHY.js";
15
15
 
16
16
  // src/config/defaults.ts
17
17
  var CUSTOM_API_DEFAULT_MODEL = "gpt-5.5";
@@ -230,4 +230,4 @@ export {
230
230
  CUSTOM_API_DEFAULT_MODEL,
231
231
  DEFAULTS
232
232
  };
233
- //# sourceMappingURL=chunk-DGR27REV.js.map
233
+ //# sourceMappingURL=chunk-WMV2CXBB.js.map