@agimon-ai/doompi-config 0.0.1-alpha.8 → 0.0.1-alpha.80

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 (269) hide show
  1. package/README.md +152 -15
  2. package/dist/agentPluginMcp.cjs +322 -0
  3. package/dist/agentPluginMcp.cjs.map +1 -0
  4. package/dist/agentPluginMcp.d.mts +38 -0
  5. package/dist/agentPluginMcp.d.mts.map +1 -0
  6. package/dist/agentPluginMcp.mjs +318 -0
  7. package/dist/agentPluginMcp.mjs.map +1 -0
  8. package/dist/apiContracts.cjs +203 -0
  9. package/dist/apiContracts.cjs.map +1 -0
  10. package/dist/apiContracts.d.mts +53 -0
  11. package/dist/apiContracts.d.mts.map +1 -0
  12. package/dist/apiContracts.mjs +198 -0
  13. package/dist/apiContracts.mjs.map +1 -0
  14. package/dist/atomicJson-1W0WhuFF.mjs +44 -0
  15. package/dist/atomicJson-1W0WhuFF.mjs.map +1 -0
  16. package/dist/atomicJson-Dt2z_DXl.cjs +52 -0
  17. package/dist/atomicJson-Dt2z_DXl.cjs.map +1 -0
  18. package/dist/atomicJson.cjs +3 -2
  19. package/dist/atomicJson.d.mts +2 -6
  20. package/dist/atomicJson.mjs +2 -2
  21. package/dist/config-BCi2gXiR.mjs +2 -0
  22. package/dist/config-CX8ZZ0Uq.mjs +137 -0
  23. package/dist/config-CX8ZZ0Uq.mjs.map +1 -0
  24. package/dist/config-CXEqETDt.cjs +1 -0
  25. package/dist/config-CwvQBVEc.cjs +188 -0
  26. package/dist/config-CwvQBVEc.cjs.map +1 -0
  27. package/dist/config-XC40aztM.d.mts +312 -0
  28. package/dist/config-XC40aztM.d.mts.map +1 -0
  29. package/dist/config.cjs +24 -2
  30. package/dist/config.d.mts +3 -24
  31. package/dist/config.mjs +3 -2
  32. package/dist/configPolicy-C5mOnEg9.mjs +714 -0
  33. package/dist/configPolicy-C5mOnEg9.mjs.map +1 -0
  34. package/dist/configPolicy-CeiDS_fY.cjs +791 -0
  35. package/dist/configPolicy-CeiDS_fY.cjs.map +1 -0
  36. package/dist/configSchema.d.mts +2 -0
  37. package/dist/configSchema.mjs +1 -0
  38. package/dist/configWriter-BzjSW0Nj.mjs +130 -0
  39. package/dist/configWriter-BzjSW0Nj.mjs.map +1 -0
  40. package/dist/configWriter-DCcjsPdx.cjs +150 -0
  41. package/dist/configWriter-DCcjsPdx.cjs.map +1 -0
  42. package/dist/configWriter.cjs +5 -2
  43. package/dist/configWriter.d.mts +2 -52
  44. package/dist/configWriter.mjs +2 -2
  45. package/dist/domains-CTBa5Jx2.cjs +727 -0
  46. package/dist/domains-CTBa5Jx2.cjs.map +1 -0
  47. package/dist/domains-DWx97ZVK.mjs +633 -0
  48. package/dist/domains-DWx97ZVK.mjs.map +1 -0
  49. package/dist/domains.cjs +19 -2
  50. package/dist/domains.d.mts +3 -77
  51. package/dist/domains.mjs +3 -2
  52. package/dist/extensions/mcp.cjs +164 -0
  53. package/dist/extensions/mcp.cjs.map +1 -0
  54. package/dist/extensions/mcp.d.mts +5 -0
  55. package/dist/extensions/mcp.d.mts.map +1 -0
  56. package/dist/extensions/mcp.mjs +159 -0
  57. package/dist/extensions/mcp.mjs.map +1 -0
  58. package/dist/extensions/pi.cjs +153 -1
  59. package/dist/extensions/pi.cjs.map +1 -1
  60. package/dist/extensions/pi.d.mts +3 -6
  61. package/dist/extensions/pi.d.mts.map +1 -1
  62. package/dist/extensions/pi.mjs +148 -1
  63. package/dist/extensions/pi.mjs.map +1 -1
  64. package/dist/extensions/server.cjs +400 -0
  65. package/dist/extensions/server.cjs.map +1 -0
  66. package/dist/extensions/server.d.mts +5 -0
  67. package/dist/extensions/server.d.mts.map +1 -0
  68. package/dist/extensions/server.mjs +395 -0
  69. package/dist/extensions/server.mjs.map +1 -0
  70. package/dist/harnessState-BP7bwXNf.cjs +187 -0
  71. package/dist/harnessState-BP7bwXNf.cjs.map +1 -0
  72. package/dist/harnessState-DjUvqbVa.mjs +162 -0
  73. package/dist/harnessState-DjUvqbVa.mjs.map +1 -0
  74. package/dist/harnessState.cjs +6 -2
  75. package/dist/harnessState.d.mts +2 -50
  76. package/dist/harnessState.mjs +2 -2
  77. package/dist/harnessStore-BtWteJvc.cjs +326 -0
  78. package/dist/harnessStore-BtWteJvc.cjs.map +1 -0
  79. package/dist/harnessStore-DLkt4WL4.mjs +251 -0
  80. package/dist/harnessStore-DLkt4WL4.mjs.map +1 -0
  81. package/dist/harnessStore.cjs +14 -2
  82. package/dist/harnessStore.d.mts +2 -102
  83. package/dist/harnessStore.mjs +2 -2
  84. package/dist/index-BSNx1X3s.d.mts +118 -0
  85. package/dist/index-BSNx1X3s.d.mts.map +1 -0
  86. package/dist/index-BZTZFQLX.d.mts +34 -0
  87. package/dist/index-BZTZFQLX.d.mts.map +1 -0
  88. package/dist/{atomicJson.d.cts → index-C6GaYjq-.d.mts} +3 -3
  89. package/dist/index-C6GaYjq-.d.mts.map +1 -0
  90. package/dist/index-CVlciCJF.d.mts +40 -0
  91. package/dist/index-CVlciCJF.d.mts.map +1 -0
  92. package/dist/{configWriter.d.cts → index-Cvdt-Y-i.d.mts} +21 -7
  93. package/dist/index-Cvdt-Y-i.d.mts.map +1 -0
  94. package/dist/index-D1orojWT.d.mts +56 -0
  95. package/dist/index-D1orojWT.d.mts.map +1 -0
  96. package/dist/index-D3eI-8Vy.d.mts +142 -0
  97. package/dist/index-D3eI-8Vy.d.mts.map +1 -0
  98. package/dist/{harnessStore.d.cts → index-D3iKH7c8.d.mts} +22 -10
  99. package/dist/index-D3iKH7c8.d.mts.map +1 -0
  100. package/dist/index-YMuEaEJr.d.mts +114 -0
  101. package/dist/index-YMuEaEJr.d.mts.map +1 -0
  102. package/dist/index-ZZd-GRFZ.d.mts +50 -0
  103. package/dist/index-ZZd-GRFZ.d.mts.map +1 -0
  104. package/dist/{layeredConfig.d.cts → index-orVmUmlT.d.mts} +12 -7
  105. package/dist/index-orVmUmlT.d.mts.map +1 -0
  106. package/dist/index-wy5ZnHCJ.d.mts +39 -0
  107. package/dist/index-wy5ZnHCJ.d.mts.map +1 -0
  108. package/dist/index.cjs +120 -1
  109. package/dist/index.d.mts +15 -16
  110. package/dist/index.mjs +15 -1
  111. package/dist/init-BfQsRfhM.cjs +448 -0
  112. package/dist/init-BfQsRfhM.cjs.map +1 -0
  113. package/dist/init-DUwZV5Ob.mjs +415 -0
  114. package/dist/init-DUwZV5Ob.mjs.map +1 -0
  115. package/dist/init.cjs +7 -76
  116. package/dist/init.d.mts +2 -33
  117. package/dist/init.mjs +2 -76
  118. package/dist/layeredConfig-BRCSrgC8.mjs +65 -0
  119. package/dist/layeredConfig-BRCSrgC8.mjs.map +1 -0
  120. package/dist/layeredConfig-Dj_YR8Tk.cjs +92 -0
  121. package/dist/layeredConfig-Dj_YR8Tk.cjs.map +1 -0
  122. package/dist/layeredConfig.cjs +6 -2
  123. package/dist/layeredConfig.d.mts +2 -49
  124. package/dist/layeredConfig.mjs +2 -2
  125. package/dist/majorModes-DQ8PKowc.cjs +375 -0
  126. package/dist/majorModes-DQ8PKowc.cjs.map +1 -0
  127. package/dist/majorModes-DXX5jfvO.mjs +325 -0
  128. package/dist/majorModes-DXX5jfvO.mjs.map +1 -0
  129. package/dist/majorModes.cjs +10 -2
  130. package/dist/majorModes.d.mts +2 -75
  131. package/dist/majorModes.mjs +2 -2
  132. package/dist/{config/schema.d.cts → piConfig-Bx26HMb_.d.mts} +3 -3
  133. package/dist/piConfig-Bx26HMb_.d.mts.map +1 -0
  134. package/dist/piConfig-CN4FFnCZ.mjs +203 -0
  135. package/dist/piConfig-CN4FFnCZ.mjs.map +1 -0
  136. package/dist/piConfig-l5YPuZOt.cjs +266 -0
  137. package/dist/piConfig-l5YPuZOt.cjs.map +1 -0
  138. package/dist/piConfig.cjs +12 -0
  139. package/dist/piConfig.d.mts +2 -0
  140. package/dist/piConfig.mjs +2 -0
  141. package/dist/piContext.cjs +18 -2
  142. package/dist/piContext.d.mts +3 -19
  143. package/dist/piContext.mjs +2 -2
  144. package/dist/profiles-CTrUCmIh.d.mts +27 -0
  145. package/dist/profiles-CTrUCmIh.d.mts.map +1 -0
  146. package/dist/profiles-CwTxhbYq.mjs +400 -0
  147. package/dist/profiles-CwTxhbYq.mjs.map +1 -0
  148. package/dist/profiles-UGBJK65f.cjs +463 -0
  149. package/dist/profiles-UGBJK65f.cjs.map +1 -0
  150. package/dist/profiles.cjs +12 -4
  151. package/dist/profiles.d.mts +16 -30
  152. package/dist/profiles.d.mts.map +1 -1
  153. package/dist/profiles.mjs +2 -4
  154. package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
  155. package/dist/selectionSwitch.cjs +70 -0
  156. package/dist/selectionSwitch.cjs.map +1 -0
  157. package/dist/selectionSwitch.d.mts +30 -0
  158. package/dist/selectionSwitch.d.mts.map +1 -0
  159. package/dist/selectionSwitch.mjs +64 -0
  160. package/dist/selectionSwitch.mjs.map +1 -0
  161. package/dist/sessionConfig-BBw0mub6.cjs +305 -0
  162. package/dist/sessionConfig-BBw0mub6.cjs.map +1 -0
  163. package/dist/sessionConfig-M1M9PNQI.mjs +209 -0
  164. package/dist/sessionConfig-M1M9PNQI.mjs.map +1 -0
  165. package/dist/types.cjs +9 -2
  166. package/dist/types.d.mts +2 -132
  167. package/dist/types.mjs +2 -2
  168. package/llms.txt +12 -0
  169. package/package.json +94 -38
  170. package/src/prompts/doompi-author-config/SKILL.md +26 -0
  171. package/src/prompts/doompi-author-config/agents/openai.yaml +4 -0
  172. package/src/prompts/doompi-author-config/references/config-contract.md +201 -0
  173. package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs +0 -1
  174. package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs +0 -1
  175. package/dist/_virtual/_rolldown/runtime.cjs +0 -1
  176. package/dist/atomicJson.cjs.map +0 -1
  177. package/dist/atomicJson.d.cts.map +0 -1
  178. package/dist/atomicJson.d.mts.map +0 -1
  179. package/dist/atomicJson.mjs.map +0 -1
  180. package/dist/config/piConfig.cjs +0 -2
  181. package/dist/config/piConfig.cjs.map +0 -1
  182. package/dist/config/piConfig.d.cts +0 -7
  183. package/dist/config/piConfig.d.cts.map +0 -1
  184. package/dist/config/piConfig.d.mts +0 -7
  185. package/dist/config/piConfig.d.mts.map +0 -1
  186. package/dist/config/piConfig.mjs +0 -2
  187. package/dist/config/piConfig.mjs.map +0 -1
  188. package/dist/config/schema.d.cts.map +0 -1
  189. package/dist/config/schema.d.mts +0 -33
  190. package/dist/config/schema.d.mts.map +0 -1
  191. package/dist/config/schema.mjs +0 -1
  192. package/dist/config.cjs.map +0 -1
  193. package/dist/config.d.cts +0 -24
  194. package/dist/config.d.cts.map +0 -1
  195. package/dist/config.d.mts.map +0 -1
  196. package/dist/config.mjs.map +0 -1
  197. package/dist/configWriter.cjs.map +0 -1
  198. package/dist/configWriter.d.cts.map +0 -1
  199. package/dist/configWriter.d.mts.map +0 -1
  200. package/dist/configWriter.mjs.map +0 -1
  201. package/dist/container.cjs +0 -2
  202. package/dist/container.cjs.map +0 -1
  203. package/dist/container.d.cts +0 -12
  204. package/dist/container.d.cts.map +0 -1
  205. package/dist/container.d.mts +0 -12
  206. package/dist/container.d.mts.map +0 -1
  207. package/dist/container.mjs +0 -2
  208. package/dist/container.mjs.map +0 -1
  209. package/dist/domains.cjs.map +0 -1
  210. package/dist/domains.d.cts +0 -77
  211. package/dist/domains.d.cts.map +0 -1
  212. package/dist/domains.d.mts.map +0 -1
  213. package/dist/domains.mjs.map +0 -1
  214. package/dist/extensions/doom.cjs +0 -2
  215. package/dist/extensions/doom.cjs.map +0 -1
  216. package/dist/extensions/doom.d.cts +0 -8
  217. package/dist/extensions/doom.d.cts.map +0 -1
  218. package/dist/extensions/doom.d.mts +0 -8
  219. package/dist/extensions/doom.d.mts.map +0 -1
  220. package/dist/extensions/doom.mjs +0 -2
  221. package/dist/extensions/doom.mjs.map +0 -1
  222. package/dist/extensions/pi.d.cts +0 -8
  223. package/dist/extensions/pi.d.cts.map +0 -1
  224. package/dist/harnessState.cjs.map +0 -1
  225. package/dist/harnessState.d.cts +0 -50
  226. package/dist/harnessState.d.cts.map +0 -1
  227. package/dist/harnessState.d.mts.map +0 -1
  228. package/dist/harnessState.mjs.map +0 -1
  229. package/dist/harnessStore.cjs.map +0 -1
  230. package/dist/harnessStore.d.cts.map +0 -1
  231. package/dist/harnessStore.d.mts.map +0 -1
  232. package/dist/harnessStore.mjs.map +0 -1
  233. package/dist/index.d.cts +0 -16
  234. package/dist/init.cjs.map +0 -1
  235. package/dist/init.d.cts +0 -33
  236. package/dist/init.d.cts.map +0 -1
  237. package/dist/init.d.mts.map +0 -1
  238. package/dist/init.mjs.map +0 -1
  239. package/dist/layeredConfig.cjs.map +0 -1
  240. package/dist/layeredConfig.d.cts.map +0 -1
  241. package/dist/layeredConfig.d.mts.map +0 -1
  242. package/dist/layeredConfig.mjs.map +0 -1
  243. package/dist/majorModes.cjs.map +0 -1
  244. package/dist/majorModes.d.cts +0 -75
  245. package/dist/majorModes.d.cts.map +0 -1
  246. package/dist/majorModes.d.mts.map +0 -1
  247. package/dist/majorModes.mjs.map +0 -1
  248. package/dist/pi.cjs +0 -1
  249. package/dist/pi.d.cts +0 -2
  250. package/dist/pi.d.mts +0 -2
  251. package/dist/pi.mjs +0 -1
  252. package/dist/piContext.cjs.map +0 -1
  253. package/dist/piContext.d.cts +0 -19
  254. package/dist/piContext.d.cts.map +0 -1
  255. package/dist/piContext.d.mts.map +0 -1
  256. package/dist/piContext.mjs.map +0 -1
  257. package/dist/profiles.cjs.map +0 -1
  258. package/dist/profiles.d.cts +0 -35
  259. package/dist/profiles.d.cts.map +0 -1
  260. package/dist/profiles.mjs.map +0 -1
  261. package/dist/types.cjs.map +0 -1
  262. package/dist/types.d.cts +0 -132
  263. package/dist/types.d.cts.map +0 -1
  264. package/dist/types.d.mts.map +0 -1
  265. package/dist/types.mjs.map +0 -1
  266. package/skills/doom-pi-config/SKILL.md +0 -26
  267. package/skills/doom-pi-config/agents/openai.yaml +0 -4
  268. package/skills/doom-pi-config/references/config-contract.md +0 -43
  269. /package/dist/{config/schema.cjs → configSchema.cjs} +0 -0
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @agimon-ai/doompi-config
2
2
 
3
- Shared Doom configuration loading and dependency injection.
3
+ Typed runtime configuration and immutable session context for
4
+ [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi).
4
5
 
5
- Loads and validates `.doom/`: major modes and their layers, domains, profiles, and the hook
6
- registry. Owns the resolved harness state file that every other reader derives from.
6
+ This package is fixed core in the DoomPi distribution. It loads `config.yaml`, validates supported
7
+ runtime settings, publishes the `doom/config` Cordis service, and maintains selection state shared
8
+ by the major-mode, domain, and profile transition flows.
7
9
 
8
- Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
9
- only the skills and tools you name. This package is composed by Doompi and is not usually
10
- installed on its own.
10
+ > **Alpha:** configuration contracts may change between releases.
11
+
12
+ ## Requirements
13
+
14
+ - Node.js 22.19.0 or newer
15
+ - Pi 0.85.0 as a peer dependency
11
16
 
12
17
  ## Install
13
18
 
@@ -15,21 +20,153 @@ installed on its own.
15
20
  npm install @agimon-ai/doompi-config
16
21
  ```
17
22
 
18
- ## How it loads
23
+ Do not add this package to a DoomPi layer. The distribution activates it before selectable packages
24
+ so the rest of the parent session can consume one resolved configuration snapshot.
25
+
26
+ ## Runtime configuration
27
+
28
+ DoomPi reads `~/.pi/.doom/config.yaml` first and `<repoRoot>/.doom/config.yaml` second. Merge policy
29
+ is field-specific because some settings are personal and others are repository policy:
30
+
31
+ | Setting | Effective source policy |
32
+ | ------------------- | -------------------------------------------------------------------- |
33
+ | `projectTrust` | Repository value, defaulting to `ask` |
34
+ | `editor` | Personal config only |
35
+ | `modes.planning` | Personal and repository fields merge, with repository values winning |
36
+ | `voice` | Personal and repository fields merge, with repository values winning |
37
+ | `voice.autoCapture` | Personal config only |
38
+ | `selection` | Per-axis merge, with repository values winning |
39
+
40
+ Supported root keys are `modes`, `projectTrust`, `editor`, `voice`, and `selection`. Unknown keys and
41
+ invalid values fail during loading.
42
+
43
+ ```yaml
44
+ projectTrust: ask
45
+ modes:
46
+ planning:
47
+ main:
48
+ model: openai/gpt-5.4
49
+ thinking: high
50
+ plansDirectory: .doom/plans
51
+ voice:
52
+ engine: auto
53
+ language: en
54
+ selection:
55
+ majorMode: copilot
56
+ domains: [default]
57
+ profile: reviewer
58
+ ```
59
+
60
+ The `selection` mapping names defaults. Definitions for those axes live in `modes.yaml`,
61
+ `domains.yaml`, and `profiles.yaml` and are documented by their owning package Help skills.
62
+
63
+ See the shipped [configuration contract](./src/prompts/doompi-author-config/references/config-contract.md) for
64
+ the complete runtime schema and additional configuration details.
65
+
66
+ ## Typed session context
67
+
68
+ `@agimon-ai/doompi-config/extensions/pi` joins the runner-scoped Cordis host and provides
69
+ `DOOM_CONFIG_SERVICE`. Consumers must access it under an owning service injection:
70
+
71
+ ```ts
72
+ import { DOOM_CONFIG_SERVICE, type DoomConfigContext, requireDoomConfigContext } from '@agimon-ai/doompi-config';
73
+ import type { Context } from '@deepseek-ai/cordis';
74
+
75
+ export function consumeDoomConfig(context: Context, consume: (snapshot: DoomConfigContext) => void): void {
76
+ context.inject([DOOM_CONFIG_SERVICE], (configContext) => {
77
+ consume(requireDoomConfigContext(configContext));
78
+ });
79
+ }
80
+ ```
81
+
82
+ The snapshot is deeply frozen. It contains validated `settings`, active `harness` state, an optional
83
+ `pendingSelection`, and `requiresRelaunch`. Do not mutate it or retain it across provider or session
84
+ replacement. The next session publishes a new snapshot.
85
+
86
+ ## Plugin structure and lifecycle
87
+
88
+ Pi and server entries live directly in `src/extensions`. HTTP request handling lives in routed `api/` files, package behavior in `src/services/{serviceName}`, and cached harness state in `src/models`. Public consumption modules remain flat under `src/exports`.
89
+
90
+ The Pi entry uses `definePiExtension` with a Config service contribution and a typed `session_start` event. The shared helper owns mounting and disposal. Config publishes its readiness coordinator synchronously, loads configuration asynchronously, and waits at the session-start barrier before later features run. Session replacement cancels the prior generation and removes its Config and MCP projection services. Help contributions follow their optional provider without restarting Config.
91
+
92
+ This session readiness barrier is separate from plugin `onStart`. Awaiting configuration during plugin mounting would block publication of the services that startup consumers need.
93
+
94
+ ## Selection transitions
95
+
96
+ Major-mode, domain, and profile packages resolve their own requested changes, then use the shared
97
+ transition coordinator. Config owns the persisted selection and transition journal records used to
98
+ distinguish active state from pending intent.
99
+
100
+ Transitions are serialized and compared with the active composition fingerprint. A compatible
101
+ change can use Pi reload. A launcher-owned composition change records pending intent for process
102
+ relaunch. A fresh session acknowledges a pending transition only when its exact target selection and
103
+ composition are active.
104
+
105
+ Consumers must not edit `snapshot.harness`, bypass the coordinator, or cache launcher environment
106
+ state at module scope.
107
+
108
+ ## Public API
109
+
110
+ The root and focused `/config` and `/piContext` exports provide runtime parsing, merge policy, and
111
+ session integration:
112
+
113
+ ```ts
114
+ import { loadDoomConfig } from '@agimon-ai/doompi-config';
115
+
116
+ const settings = loadDoomConfig(process.cwd());
117
+ ```
118
+
119
+ Pi-specific JSON settings are exposed through `/pi-config`; their types are exposed through `/config-schema`. The removed `/config/piConfig` and `/config/schema` paths have no aliases.
120
+
121
+ The package also exposes lower-level loaders used by the fixed axis packages. User-facing guidance
122
+ for defining modes, domains, and profiles is intentionally owned by those packages instead of this
123
+ runtime Config authoring skill.
124
+
125
+ ## Help
126
+
127
+ The package publishes `src/prompts/doompi-author-config`, links it from `llms.txt`, and registers
128
+ `doompi-author-config` with the `doom/help` Cordis service. The guidance is visible to the AI only
129
+ while the parent Help minor mode is active.
130
+
131
+ ## Remote session app
19
132
 
20
- Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
21
- `.doom/modes.yaml`: making it optional would only create broken configurations.
133
+ The remote-only `show_session` tool returns a read-only summary and links it to an MCP App.
134
+ Compatible hosts render the repository name, session identity, revision, profile, domains, layers,
135
+ and modes. Text-only clients receive the same information as readable text. `load_context` remains
136
+ separate and does not open a widget.
22
137
 
23
- Extension entry: `@agimon-ai/doompi-config/pi`
138
+ The component refreshes through the host's `tools/call` bridge. It receives no access token, does
139
+ not contact localhost or an external asset server, and does not include instructions, persona text,
140
+ or absolute repository paths. Conversation-routed connections still require host-supplied
141
+ conversation metadata on every tool call, including refresh. A host that cannot preserve that
142
+ metadata must use a dedicated-session connection; there is no fallback to a shared session.
24
143
 
25
- ## Notes
144
+ `build:mcp` first runs `build:app`. The browser SDK and HTML are bundled into
145
+ `generated/mcp-apps/session.ts`, then embedded in the published MCP facet. The `ui://` resource URI
146
+ contains a hash of the complete HTML. Do not edit generated assets or reuse an old URI for changed
147
+ content. Resource reads are authorized static prefetches and never create a child session.
26
148
 
27
- This is the package that defines the config contract. Changing the shape of `.doom/*.yaml`
28
- starts here.
149
+ Connect an MCP Apps host to the existing session MCP URL. After rebuilding and restarting the
150
+ server, refresh the host's tool catalog and invoke `show_session`. ChatGPT developer-mode
151
+ connections use the same endpoint. Public app submission, a stable production widget origin, and
152
+ upstream third-party widget forwarding are separate deployment work.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ pnpm build
158
+ pnpm typecheck
159
+ pnpm test
160
+ pnpm lint
161
+ pnpm test:app
162
+ ```
29
163
 
30
- ## Entry points
164
+ The browser tests require Playwright Chromium (`pnpm exec playwright install chromium`). They
165
+ exercise the packaged HTML in a sandboxed iframe using the standard SDK's `AppBridge`, including
166
+ refresh, theme changes, errors, text safety, and teardown. They do not substitute for testing the
167
+ connection inside ChatGPT itself.
31
168
 
32
- 18 subpaths are declared in the `exports` field of `package.json`.
169
+ Maintained by [Agimon](https://agimon.ai/about).
33
170
 
34
171
  ## License
35
172
 
@@ -0,0 +1,322 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
3
+ let node_crypto = require("node:crypto");
4
+ let node_fs = require("node:fs");
5
+ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
6
+ let node_http = require("node:http");
7
+ let node_net = require("node:net");
8
+ let node_path = require("node:path");
9
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
10
+ let _agimon_ai_doompi_core_mcpProjection = require("@agimon-ai/doompi-core/mcpProjection");
11
+ //#region src/services/agentPluginMcp/index.ts
12
+ const PRIVATE_FILE_MODE = 384;
13
+ const PRIVATE_DIRECTORY_MODE = 448;
14
+ const RESERVED_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["PLUGIN_ROOT", "PLUGIN_DATA"]);
15
+ const PROXY_SERVER_ALIASES = /* @__PURE__ */ new Set(["mcp-proxy", "agiflow-proxy"]);
16
+ function isObject(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+ function onlyKeys(value, allowed, label) {
20
+ const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
21
+ if (unexpected) throw new Error(`${label} contains unsupported field "${unexpected}"`);
22
+ }
23
+ function nonEmptyString(value, label) {
24
+ if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must be a non-empty string`);
25
+ return value;
26
+ }
27
+ function optionalStringArray(value, label) {
28
+ if (value === void 0) return void 0;
29
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array of strings`);
30
+ return value.map((item, index) => {
31
+ if (typeof item !== "string") throw new Error(`${label}[${index}] must be a string`);
32
+ return item;
33
+ });
34
+ }
35
+ function optionalStringRecord(value, label) {
36
+ if (value === void 0) return void 0;
37
+ if (!isObject(value)) throw new Error(`${label} must be an object of string values`);
38
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => {
39
+ if (typeof item !== "string") throw new Error(`${label}.${key} must be a string`);
40
+ return [key, item];
41
+ }));
42
+ }
43
+ function expandPluginPlaceholders(value, pluginRoot, pluginDataDirectory) {
44
+ return value.replace(/\$\{PLUGIN_(ROOT|DATA)\}/gu, (placeholder) => placeholder === "${PLUGIN_ROOT}" ? pluginRoot : pluginDataDirectory);
45
+ }
46
+ function isInside(root, candidate) {
47
+ const relative = node_path.default.relative(root, candidate);
48
+ return relative === "" || !relative.startsWith(`..${node_path.default.sep}`) && relative !== ".." && !node_path.default.isAbsolute(relative);
49
+ }
50
+ function realContainedPath(root, candidate, label) {
51
+ let realCandidate;
52
+ try {
53
+ realCandidate = node_fs.default.realpathSync(candidate);
54
+ } catch {
55
+ throw new Error(`${label} does not exist`);
56
+ }
57
+ if (!isInside(root, realCandidate)) throw new Error(`${label} escapes its allowed plugin directory`);
58
+ return realCandidate;
59
+ }
60
+ function normalizeCommand(command, pluginRoot) {
61
+ if (command.startsWith("./")) {
62
+ const resolved = realContainedPath(pluginRoot, node_path.default.resolve(pluginRoot, command), "command");
63
+ if (!node_fs.default.statSync(resolved).isFile()) throw new Error("command must resolve to a regular file");
64
+ try {
65
+ node_fs.default.accessSync(resolved, node_fs.default.constants.X_OK);
66
+ } catch {
67
+ throw new Error("command must resolve to an executable file");
68
+ }
69
+ return resolved;
70
+ }
71
+ if (command === "." || command === ".." || /[\s/\\]/u.test(command)) throw new Error("command must be a bare executable name or a ./ path inside the plugin");
72
+ return command;
73
+ }
74
+ function normalizeWorkingDirectory(declared, pluginRoot, pluginDataDirectory) {
75
+ if (declared === void 0) return pluginRoot;
76
+ const cwd = nonEmptyString(declared, "cwd");
77
+ let base;
78
+ let expanded;
79
+ if (cwd.startsWith("./")) {
80
+ base = pluginRoot;
81
+ expanded = node_path.default.resolve(pluginRoot, cwd);
82
+ } else if (cwd === "${PLUGIN_ROOT}" || cwd.startsWith("${PLUGIN_ROOT}/")) {
83
+ base = pluginRoot;
84
+ expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);
85
+ } else if (cwd === "${PLUGIN_DATA}" || cwd.startsWith("${PLUGIN_DATA}/")) {
86
+ base = pluginDataDirectory;
87
+ expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);
88
+ } else throw new Error("cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}");
89
+ const resolved = realContainedPath(base, node_path.default.resolve(expanded), "cwd");
90
+ if (!node_fs.default.statSync(resolved).isDirectory()) throw new Error("cwd must resolve to a directory");
91
+ return resolved;
92
+ }
93
+ function normalizeEnvironment(value, pluginRoot, pluginDataDirectory) {
94
+ const declared = optionalStringRecord(value, "env") ?? {};
95
+ for (const key of Object.keys(declared)) {
96
+ const reservedKey = process.platform === "win32" ? key.toUpperCase() : key;
97
+ if (RESERVED_ENVIRONMENT_KEYS.has(reservedKey)) throw new Error(`env key "${key}" is reserved by Agent Plugins`);
98
+ }
99
+ return {
100
+ ...Object.fromEntries(Object.entries(declared).map(([key, item]) => [key, expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory)])),
101
+ PLUGIN_ROOT: pluginRoot,
102
+ PLUGIN_DATA: pluginDataDirectory
103
+ };
104
+ }
105
+ function isLoopbackHost(hostname) {
106
+ const bareHostname = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
107
+ if (bareHostname.toLowerCase() === "localhost") return true;
108
+ const family = (0, node_net.isIP)(bareHostname);
109
+ if (family === 4) return bareHostname.split(".")[0] === "127";
110
+ return family === 6 && bareHostname === "::1";
111
+ }
112
+ function normalizeUrl(value) {
113
+ const raw = nonEmptyString(value, "url");
114
+ let parsed;
115
+ try {
116
+ parsed = new URL(raw);
117
+ } catch {
118
+ throw new Error("url must be an absolute HTTP(S) URL");
119
+ }
120
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("url must use http or https");
121
+ if (parsed.username || parsed.password) throw new Error("url must not contain user information");
122
+ if (parsed.hash) throw new Error("url must not contain a fragment");
123
+ if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname)) throw new Error("non-loopback MCP URLs must use https");
124
+ return raw;
125
+ }
126
+ function normalizeHeaders(value) {
127
+ const headers = optionalStringRecord(value, "headers");
128
+ if (!headers) return void 0;
129
+ const seen = /* @__PURE__ */ new Set();
130
+ for (const [name, item] of Object.entries(headers)) {
131
+ const canonical = name.toLowerCase();
132
+ if (seen.has(canonical)) throw new Error(`headers contain duplicate name "${name}"`);
133
+ seen.add(canonical);
134
+ try {
135
+ (0, node_http.validateHeaderName)(name);
136
+ (0, node_http.validateHeaderValue)(name, item);
137
+ } catch {
138
+ throw new Error(`header "${name}" is invalid`);
139
+ }
140
+ }
141
+ return headers;
142
+ }
143
+ function normalizeServer(raw, pluginRoot, pluginDataDirectory, name) {
144
+ if (!isObject(raw)) throw new Error("definition must be an object");
145
+ const type = nonEmptyString(raw.type, "type");
146
+ if (type === "stdio") {
147
+ onlyKeys(raw, [
148
+ "type",
149
+ "command",
150
+ "args",
151
+ "env",
152
+ "cwd"
153
+ ], `server "${name}"`);
154
+ const command = normalizeCommand(nonEmptyString(raw.command, "command"), pluginRoot);
155
+ const args = optionalStringArray(raw.args, "args")?.map((item) => expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory));
156
+ const env = normalizeEnvironment(raw.env, pluginRoot, pluginDataDirectory);
157
+ const cwd = normalizeWorkingDirectory(raw.cwd, pluginRoot, pluginDataDirectory);
158
+ const config = {
159
+ command,
160
+ ...args ? { args } : {},
161
+ env,
162
+ cwd
163
+ };
164
+ return {
165
+ internal: {
166
+ name,
167
+ transport: "stdio",
168
+ config
169
+ },
170
+ claude: {
171
+ type: "stdio",
172
+ ...config
173
+ }
174
+ };
175
+ }
176
+ if (type === "streamable-http" || type === "sse") {
177
+ onlyKeys(raw, [
178
+ "type",
179
+ "url",
180
+ "headers"
181
+ ], `server "${name}"`);
182
+ const url = normalizeUrl(raw.url);
183
+ const headers = normalizeHeaders(raw.headers);
184
+ const transport = type === "streamable-http" ? "http" : "sse";
185
+ const config = {
186
+ url,
187
+ ...headers ? { headers } : {}
188
+ };
189
+ return {
190
+ internal: {
191
+ name,
192
+ transport,
193
+ config
194
+ },
195
+ claude: {
196
+ type: transport,
197
+ ...config
198
+ }
199
+ };
200
+ }
201
+ throw new Error(`unsupported transport type "${type}"`);
202
+ }
203
+ function stagedInternalPath(source, contents, stagingDirectory) {
204
+ const digest = (0, node_crypto.createHash)("sha256").update(source.sourceId).update("\0").update(source.contentDigest).update("\0").update(contents).digest("hex").slice(0, 20);
205
+ return node_path.default.join(stagingDirectory, `agent-plugin-mcp-${digest}.internal.json`);
206
+ }
207
+ function writeStagedInternalConfig(target, contents, stagingDirectory) {
208
+ node_fs.default.mkdirSync(stagingDirectory, {
209
+ mode: PRIVATE_DIRECTORY_MODE,
210
+ recursive: true
211
+ });
212
+ node_fs.default.chmodSync(stagingDirectory, PRIVATE_DIRECTORY_MODE);
213
+ const temporaryPath = `${target}.${process.pid}-${(0, node_crypto.randomUUID)()}.tmp`;
214
+ try {
215
+ node_fs.default.writeFileSync(temporaryPath, contents, {
216
+ flag: "wx",
217
+ mode: PRIVATE_FILE_MODE
218
+ });
219
+ try {
220
+ node_fs.default.renameSync(temporaryPath, target);
221
+ } catch (error) {
222
+ const code = error.code;
223
+ if (code !== "EEXIST" && code !== "EPERM") throw error;
224
+ const stat = node_fs.default.lstatSync(target);
225
+ if (stat.isSymbolicLink() || !stat.isFile() || node_fs.default.readFileSync(target, "utf8") !== contents) throw new Error("content-addressed staging path is occupied by different content");
226
+ }
227
+ node_fs.default.chmodSync(target, PRIVATE_FILE_MODE);
228
+ } finally {
229
+ node_fs.default.rmSync(temporaryPath, { force: true });
230
+ }
231
+ }
232
+ function emptyResult(diagnostic) {
233
+ return {
234
+ claudeConfig: { mcpServers: {} },
235
+ serverNames: [],
236
+ droppedServers: [],
237
+ diagnostics: [diagnostic]
238
+ };
239
+ }
240
+ /**
241
+ * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.
242
+ *
243
+ * Server validation is isolated: one malformed server is diagnosed and skipped.
244
+ * Document/schema, source identity, or plugin-boundary failures reject the whole
245
+ * source. Only the two portable plugin placeholders are expanded; the staged
246
+ * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.
247
+ */
248
+ function normalizeAgentPluginMcpSource(source, options) {
249
+ const label = `Agent Plugin MCP source "${source.sourceId}"`;
250
+ try {
251
+ if (source.mcpSchemaUrl !== _agimon_ai_doompi_core_mcpProjection.AGENT_PLUGIN_MCP_SCHEMA_URL) throw new Error(`unsupported schema "${String(source.mcpSchemaUrl)}"`);
252
+ if (!node_path.default.isAbsolute(source.pluginRoot) || !node_path.default.isAbsolute(source.pluginDataDirectory)) throw new Error("pluginRoot and pluginDataDirectory must be absolute");
253
+ const pluginRoot = node_fs.default.realpathSync(source.pluginRoot);
254
+ try {
255
+ const dataStat = node_fs.default.lstatSync(source.pluginDataDirectory);
256
+ if (dataStat.isSymbolicLink() || !dataStat.isDirectory()) throw new Error("pluginDataDirectory must be a real directory");
257
+ } catch (error) {
258
+ if (error.code !== "ENOENT") throw error;
259
+ node_fs.default.mkdirSync(source.pluginDataDirectory, {
260
+ mode: PRIVATE_DIRECTORY_MODE,
261
+ recursive: true
262
+ });
263
+ }
264
+ node_fs.default.chmodSync(source.pluginDataDirectory, PRIVATE_DIRECTORY_MODE);
265
+ node_fs.default.accessSync(source.pluginDataDirectory, node_fs.default.constants.W_OK);
266
+ const pluginDataDirectory = node_fs.default.realpathSync(source.pluginDataDirectory);
267
+ const configPath = realContainedPath(pluginRoot, source.configPath, "mcp.json");
268
+ if (node_path.default.dirname(configPath) !== pluginRoot || node_path.default.basename(configPath) !== "mcp.json") throw new Error("portable plugin config must be root mcp.json");
269
+ const bytes = node_fs.default.readFileSync(configPath);
270
+ if ((0, node_crypto.createHash)("sha256").update(bytes).digest("hex") !== source.contentDigest) throw new Error("content digest does not match the projection");
271
+ const parsed = JSON.parse(bytes.toString("utf8"));
272
+ if (!isObject(parsed)) throw new Error("document must be an object");
273
+ onlyKeys(parsed, ["$schema", "mcpServers"], label);
274
+ if (parsed.$schema !== source.mcpSchemaUrl) throw new Error("$schema does not match the projected schema");
275
+ if (!isObject(parsed.mcpServers)) throw new Error("mcpServers must be an object");
276
+ const allowed = options.allowedServers;
277
+ const keepAll = !allowed || allowed.length === 0;
278
+ const stripProxyWrappers = options.stripProxyWrappers ?? true;
279
+ const internalServers = Object.create(null);
280
+ const claudeServers = Object.create(null);
281
+ const droppedServers = [];
282
+ const diagnostics = [];
283
+ for (const [name, rawServer] of Object.entries(parsed.mcpServers)) {
284
+ if (!name) {
285
+ diagnostics.push(`${label} contains an empty server name; that entry was skipped.`);
286
+ continue;
287
+ }
288
+ if (!keepAll && !allowed.includes(name) || stripProxyWrappers && PROXY_SERVER_ALIASES.has(name)) {
289
+ droppedServers.push(name);
290
+ continue;
291
+ }
292
+ try {
293
+ const normalized = normalizeServer(rawServer, pluginRoot, pluginDataDirectory, name);
294
+ internalServers[name] = normalized.internal;
295
+ claudeServers[name] = normalized.claude;
296
+ } catch (error) {
297
+ droppedServers.push(name);
298
+ diagnostics.push(`${label} skipped server "${name}": ${error instanceof Error ? error.message : String(error)}`);
299
+ }
300
+ }
301
+ const contents = `${JSON.stringify({ mcpServers: internalServers }, null, 2)}\n`;
302
+ const target = stagedInternalPath(source, contents, options.stagingDirectory);
303
+ writeStagedInternalConfig(target, contents, options.stagingDirectory);
304
+ return {
305
+ configSource: {
306
+ path: target,
307
+ format: "internal",
308
+ cacheKey: (0, node_crypto.createHash)("sha256").update("agent-plugin-v1\0").update(source.sourceId).update("\0").update(source.contentDigest).update("\0").update(configPath).update("\0").update(contents).digest("hex")
309
+ },
310
+ claudeConfig: { mcpServers: claudeServers },
311
+ serverNames: Object.keys(internalServers),
312
+ droppedServers,
313
+ diagnostics
314
+ };
315
+ } catch (error) {
316
+ return emptyResult(`${label} was disabled: ${error instanceof Error ? error.message : String(error)}`);
317
+ }
318
+ }
319
+ //#endregion
320
+ exports.normalizeAgentPluginMcpSource = normalizeAgentPluginMcpSource;
321
+
322
+ //# sourceMappingURL=agentPluginMcp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentPluginMcp.cjs","names":["path","fs","isIP","createHash","randomUUID","AGENT_PLUGIN_MCP_SCHEMA_URL"],"sources":["../src/services/agentPluginMcp/index.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport { validateHeaderName, validateHeaderValue } from 'node:http';\nimport { isIP } from 'node:net';\nimport path from 'node:path';\n\nimport {\n AGENT_PLUGIN_MCP_SCHEMA_URL,\n type DoomMcpAgentPluginProjectionSource,\n} from '@agimon-ai/doompi-core/mcpProjection';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst RESERVED_ENVIRONMENT_KEYS = new Set(['PLUGIN_ROOT', 'PLUGIN_DATA']);\nconst PROXY_SERVER_ALIASES = new Set(['mcp-proxy', 'agiflow-proxy']);\n\ntype JsonObject = Record<string, unknown>;\n\nexport interface AgentPluginMcpConfigSource {\n path: string;\n format: 'internal';\n cacheKey: string;\n}\n\nexport interface NormalizedAgentPluginMcpSource {\n /** Owner-only, content-addressed layer that bypasses Claude env interpolation. */\n configSource?: AgentPluginMcpConfigSource;\n /** Claude-compatible definitions for an external merged config. */\n claudeConfig: { mcpServers: Record<string, JsonObject> };\n serverNames: string[];\n droppedServers: string[];\n diagnostics: string[];\n}\n\nexport interface NormalizeAgentPluginMcpOptions {\n stagingDirectory: string;\n /** Empty and absent lists both mean keep every server. */\n allowedServers?: readonly string[];\n /** Doom strips nested wrappers; external-only callers may opt out. */\n stripProxyWrappers?: boolean;\n}\n\ninterface NormalizedServer {\n internal: JsonObject;\n claude: JsonObject;\n}\n\nfunction isObject(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction onlyKeys(value: JsonObject, allowed: readonly string[], label: string): void {\n const unexpected = Object.keys(value).find((key) => !allowed.includes(key));\n if (unexpected) throw new Error(`${label} contains unsupported field \"${unexpected}\"`);\n}\n\nfunction nonEmptyString(value: unknown, label: string): string {\n if (typeof value !== 'string' || value.length === 0) throw new Error(`${label} must be a non-empty string`);\n return value;\n}\n\nfunction optionalStringArray(value: unknown, label: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) throw new Error(`${label} must be an array of strings`);\n return value.map((item, index) => {\n if (typeof item !== 'string') throw new Error(`${label}[${index}] must be a string`);\n return item;\n });\n}\n\nfunction optionalStringRecord(value: unknown, label: string): Record<string, string> | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`${label} must be an object of string values`);\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => {\n if (typeof item !== 'string') throw new Error(`${label}.${key} must be a string`);\n return [key, item];\n }),\n );\n}\n\nfunction expandPluginPlaceholders(value: string, pluginRoot: string, pluginDataDirectory: string): string {\n // The portable contract is a single, non-recursive replacement pass. An\n // unrecognised placeholder remains literal and mcp-proxy must not reinterpret it.\n return value.replace(/\\$\\{PLUGIN_(ROOT|DATA)\\}/gu, (placeholder) =>\n placeholder === '${PLUGIN_ROOT}' ? pluginRoot : pluginDataDirectory,\n );\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));\n}\n\nfunction realContainedPath(root: string, candidate: string, label: string): string {\n let realCandidate: string;\n try {\n realCandidate = fs.realpathSync(candidate);\n } catch {\n throw new Error(`${label} does not exist`);\n }\n if (!isInside(root, realCandidate)) throw new Error(`${label} escapes its allowed plugin directory`);\n return realCandidate;\n}\n\nfunction normalizeCommand(command: string, pluginRoot: string): string {\n if (command.startsWith('./')) {\n const resolved = realContainedPath(pluginRoot, path.resolve(pluginRoot, command), 'command');\n if (!fs.statSync(resolved).isFile()) throw new Error('command must resolve to a regular file');\n try {\n fs.accessSync(resolved, fs.constants.X_OK);\n } catch {\n throw new Error('command must resolve to an executable file');\n }\n return resolved;\n }\n if (command === '.' || command === '..' || /[\\s/\\\\]/u.test(command)) {\n throw new Error('command must be a bare executable name or a ./ path inside the plugin');\n }\n return command;\n}\n\nfunction normalizeWorkingDirectory(declared: unknown, pluginRoot: string, pluginDataDirectory: string): string {\n if (declared === undefined) return pluginRoot;\n const cwd = nonEmptyString(declared, 'cwd');\n let base: string;\n let expanded: string;\n if (cwd.startsWith('./')) {\n base = pluginRoot;\n expanded = path.resolve(pluginRoot, cwd);\n } else if (cwd === '${PLUGIN_ROOT}' || cwd.startsWith('${PLUGIN_ROOT}/')) {\n base = pluginRoot;\n expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);\n } else if (cwd === '${PLUGIN_DATA}' || cwd.startsWith('${PLUGIN_DATA}/')) {\n base = pluginDataDirectory;\n expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);\n } else {\n throw new Error('cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}');\n }\n const resolved = realContainedPath(base, path.resolve(expanded), 'cwd');\n if (!fs.statSync(resolved).isDirectory()) throw new Error('cwd must resolve to a directory');\n return resolved;\n}\n\nfunction normalizeEnvironment(value: unknown, pluginRoot: string, pluginDataDirectory: string): Record<string, string> {\n const declared = optionalStringRecord(value, 'env') ?? {};\n for (const key of Object.keys(declared)) {\n const reservedKey = process.platform === 'win32' ? key.toUpperCase() : key;\n if (RESERVED_ENVIRONMENT_KEYS.has(reservedKey)) {\n throw new Error(`env key \"${key}\" is reserved by Agent Plugins`);\n }\n }\n return {\n ...Object.fromEntries(\n Object.entries(declared).map(([key, item]) => [\n key,\n expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory),\n ]),\n ),\n PLUGIN_ROOT: pluginRoot,\n PLUGIN_DATA: pluginDataDirectory,\n };\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n const bareHostname = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n if (bareHostname.toLowerCase() === 'localhost') return true;\n const family = isIP(bareHostname);\n if (family === 4) return bareHostname.split('.')[0] === '127';\n return family === 6 && bareHostname === '::1';\n}\n\nfunction normalizeUrl(value: unknown): string {\n const raw = nonEmptyString(value, 'url');\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error('url must be an absolute HTTP(S) URL');\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error('url must use http or https');\n }\n if (parsed.username || parsed.password) throw new Error('url must not contain user information');\n if (parsed.hash) throw new Error('url must not contain a fragment');\n if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {\n throw new Error('non-loopback MCP URLs must use https');\n }\n return raw;\n}\n\nfunction normalizeHeaders(value: unknown): Record<string, string> | undefined {\n const headers = optionalStringRecord(value, 'headers');\n if (!headers) return undefined;\n const seen = new Set<string>();\n for (const [name, item] of Object.entries(headers)) {\n const canonical = name.toLowerCase();\n if (seen.has(canonical)) throw new Error(`headers contain duplicate name \"${name}\"`);\n seen.add(canonical);\n try {\n validateHeaderName(name);\n validateHeaderValue(name, item);\n } catch {\n throw new Error(`header \"${name}\" is invalid`);\n }\n }\n return headers;\n}\n\nfunction normalizeServer(\n raw: unknown,\n pluginRoot: string,\n pluginDataDirectory: string,\n name: string,\n): NormalizedServer {\n if (!isObject(raw)) throw new Error('definition must be an object');\n const type = nonEmptyString(raw.type, 'type');\n if (type === 'stdio') {\n onlyKeys(raw, ['type', 'command', 'args', 'env', 'cwd'], `server \"${name}\"`);\n const command = normalizeCommand(nonEmptyString(raw.command, 'command'), pluginRoot);\n const args = optionalStringArray(raw.args, 'args')?.map((item) =>\n expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory),\n );\n const env = normalizeEnvironment(raw.env, pluginRoot, pluginDataDirectory);\n const cwd = normalizeWorkingDirectory(raw.cwd, pluginRoot, pluginDataDirectory);\n const config = { command, ...(args ? { args } : {}), env, cwd };\n return {\n internal: { name, transport: 'stdio', config },\n claude: { type: 'stdio', ...config },\n };\n }\n\n if (type === 'streamable-http' || type === 'sse') {\n onlyKeys(raw, ['type', 'url', 'headers'], `server \"${name}\"`);\n const url = normalizeUrl(raw.url);\n const headers = normalizeHeaders(raw.headers);\n const transport = type === 'streamable-http' ? 'http' : 'sse';\n const config = { url, ...(headers ? { headers } : {}) };\n return {\n internal: { name, transport, config },\n claude: { type: transport, ...config },\n };\n }\n\n throw new Error(`unsupported transport type \"${type}\"`);\n}\n\nfunction stagedInternalPath(\n source: DoomMcpAgentPluginProjectionSource,\n contents: string,\n stagingDirectory: string,\n): string {\n const digest = createHash('sha256')\n .update(source.sourceId)\n .update('\\0')\n .update(source.contentDigest)\n .update('\\0')\n .update(contents)\n .digest('hex')\n .slice(0, 20);\n return path.join(stagingDirectory, `agent-plugin-mcp-${digest}.internal.json`);\n}\n\nfunction writeStagedInternalConfig(target: string, contents: string, stagingDirectory: string): void {\n fs.mkdirSync(stagingDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(stagingDirectory, PRIVATE_DIRECTORY_MODE);\n const temporaryPath = `${target}.${process.pid}-${randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporaryPath, contents, { flag: 'wx', mode: PRIVATE_FILE_MODE });\n try {\n fs.renameSync(temporaryPath, target);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== 'EEXIST' && code !== 'EPERM') throw error;\n const stat = fs.lstatSync(target);\n if (stat.isSymbolicLink() || !stat.isFile() || fs.readFileSync(target, 'utf8') !== contents) {\n throw new Error('content-addressed staging path is occupied by different content');\n }\n }\n fs.chmodSync(target, PRIVATE_FILE_MODE);\n } finally {\n fs.rmSync(temporaryPath, { force: true });\n }\n}\n\nfunction emptyResult(diagnostic: string): NormalizedAgentPluginMcpSource {\n return {\n claudeConfig: { mcpServers: {} },\n serverNames: [],\n droppedServers: [],\n diagnostics: [diagnostic],\n };\n}\n\n/**\n * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.\n *\n * Server validation is isolated: one malformed server is diagnosed and skipped.\n * Document/schema, source identity, or plugin-boundary failures reject the whole\n * source. Only the two portable plugin placeholders are expanded; the staged\n * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.\n */\nexport function normalizeAgentPluginMcpSource(\n source: DoomMcpAgentPluginProjectionSource,\n options: NormalizeAgentPluginMcpOptions,\n): NormalizedAgentPluginMcpSource {\n const label = `Agent Plugin MCP source \"${source.sourceId}\"`;\n try {\n if (source.mcpSchemaUrl !== AGENT_PLUGIN_MCP_SCHEMA_URL) {\n throw new Error(`unsupported schema \"${String(source.mcpSchemaUrl)}\"`);\n }\n if (!path.isAbsolute(source.pluginRoot) || !path.isAbsolute(source.pluginDataDirectory)) {\n throw new Error('pluginRoot and pluginDataDirectory must be absolute');\n }\n const pluginRoot = fs.realpathSync(source.pluginRoot);\n try {\n const dataStat = fs.lstatSync(source.pluginDataDirectory);\n if (dataStat.isSymbolicLink() || !dataStat.isDirectory()) {\n throw new Error('pluginDataDirectory must be a real directory');\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n fs.mkdirSync(source.pluginDataDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n }\n fs.chmodSync(source.pluginDataDirectory, PRIVATE_DIRECTORY_MODE);\n fs.accessSync(source.pluginDataDirectory, fs.constants.W_OK);\n const pluginDataDirectory = fs.realpathSync(source.pluginDataDirectory);\n const configPath = realContainedPath(pluginRoot, source.configPath, 'mcp.json');\n if (path.dirname(configPath) !== pluginRoot || path.basename(configPath) !== 'mcp.json') {\n throw new Error('portable plugin config must be root mcp.json');\n }\n\n const bytes = fs.readFileSync(configPath);\n const digest = createHash('sha256').update(bytes).digest('hex');\n if (digest !== source.contentDigest) throw new Error('content digest does not match the projection');\n const parsed: unknown = JSON.parse(bytes.toString('utf8'));\n if (!isObject(parsed)) throw new Error('document must be an object');\n onlyKeys(parsed, ['$schema', 'mcpServers'], label);\n if (parsed.$schema !== source.mcpSchemaUrl) throw new Error('$schema does not match the projected schema');\n if (!isObject(parsed.mcpServers)) throw new Error('mcpServers must be an object');\n\n const allowed = options.allowedServers;\n const keepAll = !allowed || allowed.length === 0;\n const stripProxyWrappers = options.stripProxyWrappers ?? true;\n const internalServers = Object.create(null) as Record<string, JsonObject>;\n const claudeServers = Object.create(null) as Record<string, JsonObject>;\n const droppedServers: string[] = [];\n const diagnostics: string[] = [];\n\n for (const [name, rawServer] of Object.entries(parsed.mcpServers)) {\n if (!name) {\n diagnostics.push(`${label} contains an empty server name; that entry was skipped.`);\n continue;\n }\n if ((!keepAll && !allowed.includes(name)) || (stripProxyWrappers && PROXY_SERVER_ALIASES.has(name))) {\n droppedServers.push(name);\n continue;\n }\n try {\n const normalized = normalizeServer(rawServer, pluginRoot, pluginDataDirectory, name);\n internalServers[name] = normalized.internal;\n claudeServers[name] = normalized.claude;\n } catch (error) {\n droppedServers.push(name);\n diagnostics.push(\n `${label} skipped server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n const internalConfig = { mcpServers: internalServers };\n const contents = `${JSON.stringify(internalConfig, null, 2)}\\n`;\n const target = stagedInternalPath(source, contents, options.stagingDirectory);\n writeStagedInternalConfig(target, contents, options.stagingDirectory);\n const cacheKey = createHash('sha256')\n .update('agent-plugin-v1\\0')\n .update(source.sourceId)\n .update('\\0')\n .update(source.contentDigest)\n .update('\\0')\n .update(configPath)\n .update('\\0')\n .update(contents)\n .digest('hex');\n\n return {\n configSource: { path: target, format: 'internal', cacheKey },\n claudeConfig: { mcpServers: claudeServers },\n serverNames: Object.keys(internalServers),\n droppedServers,\n diagnostics,\n };\n } catch (error) {\n return emptyResult(`${label} was disabled: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { DoomMcpAgentPluginProjectionSource } from "@agimon-ai/doompi-core/mcpProjection";
2
+ //#region src/services/agentPluginMcp/index.d.ts
3
+ type JsonObject = Record<string, unknown>;
4
+ interface AgentPluginMcpConfigSource {
5
+ path: string;
6
+ format: 'internal';
7
+ cacheKey: string;
8
+ }
9
+ interface NormalizedAgentPluginMcpSource {
10
+ /** Owner-only, content-addressed layer that bypasses Claude env interpolation. */
11
+ configSource?: AgentPluginMcpConfigSource;
12
+ /** Claude-compatible definitions for an external merged config. */
13
+ claudeConfig: {
14
+ mcpServers: Record<string, JsonObject>;
15
+ };
16
+ serverNames: string[];
17
+ droppedServers: string[];
18
+ diagnostics: string[];
19
+ }
20
+ interface NormalizeAgentPluginMcpOptions {
21
+ stagingDirectory: string;
22
+ /** Empty and absent lists both mean keep every server. */
23
+ allowedServers?: readonly string[];
24
+ /** Doom strips nested wrappers; external-only callers may opt out. */
25
+ stripProxyWrappers?: boolean;
26
+ }
27
+ /**
28
+ * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.
29
+ *
30
+ * Server validation is isolated: one malformed server is diagnosed and skipped.
31
+ * Document/schema, source identity, or plugin-boundary failures reject the whole
32
+ * source. Only the two portable plugin placeholders are expanded; the staged
33
+ * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.
34
+ */
35
+ export declare function normalizeAgentPluginMcpSource(source: DoomMcpAgentPluginProjectionSource, options: NormalizeAgentPluginMcpOptions): NormalizedAgentPluginMcpSource;
36
+ //#endregion
37
+ export type { AgentPluginMcpConfigSource, NormalizeAgentPluginMcpOptions, NormalizedAgentPluginMcpSource };
38
+ //# sourceMappingURL=agentPluginMcp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentPluginMcp.d.mts","names":[],"sources":["../src/services/agentPluginMcp/index.ts"],"mappings":";;KAgBK,aAAa;UAED;EACf;EACA;EACA;;UAGe;;EAEf,eAAe;;EAEf;IAAgB,YAAY,eAAe;;EAC3C;EACA;EACA;;UAGe;EACf;;EAEA;;EAEA;;;;;;;;;;wBAuQc,8BACd,QAAQ,oCACR,SAAS,iCACR"}