@agimon-ai/po-config 0.0.0-alpha.2 → 0.0.0-alpha.21

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 (221) hide show
  1. package/LICENSE +149 -21
  2. package/README.md +11 -11
  3. package/dist/agentPluginMcp.cjs +313 -134
  4. package/dist/agentPluginMcp.cjs.map +1 -1
  5. package/dist/agentPluginMcp.d.mts +22 -4
  6. package/dist/agentPluginMcp.d.mts.map +1 -1
  7. package/dist/agentPluginMcp.mjs +312 -134
  8. package/dist/agentPluginMcp.mjs.map +1 -1
  9. package/dist/apiContracts.cjs.map +1 -1
  10. package/dist/apiContracts.mjs.map +1 -1
  11. package/dist/atomicJson-D3MHqnLW.cjs +59 -0
  12. package/dist/atomicJson-D3MHqnLW.cjs.map +1 -0
  13. package/dist/atomicJson-Dj3rlZbD.mjs +40 -0
  14. package/dist/atomicJson-Dj3rlZbD.mjs.map +1 -0
  15. package/dist/atomicJson.cjs +4 -2
  16. package/dist/atomicJson.d.mts +2 -2
  17. package/dist/atomicJson.mjs +3 -2
  18. package/dist/{index-BT29GZx5.d.mts → completion-Ba0TiBHa.d.mts} +58 -27
  19. package/dist/completion-Ba0TiBHa.d.mts.map +1 -0
  20. package/dist/config-BBvn_ZLP.mjs +1 -0
  21. package/dist/config-Cdybp9rT.cjs +155 -0
  22. package/dist/config-Cdybp9rT.cjs.map +1 -0
  23. package/dist/config-Cxc2xw54.mjs +112 -0
  24. package/dist/config-Cxc2xw54.mjs.map +1 -0
  25. package/dist/{config-D2HbBLzo.d.mts → config-pxC7FyUJ.d.mts} +33 -33
  26. package/dist/config-pxC7FyUJ.d.mts.map +1 -0
  27. package/dist/config.cjs +21 -17
  28. package/dist/config.d.mts +3 -3
  29. package/dist/config.mjs +4 -3
  30. package/dist/{configPolicy-Ce1QKDxa.cjs → configPolicy-CowlLhK1.cjs} +99 -99
  31. package/dist/configPolicy-CowlLhK1.cjs.map +1 -0
  32. package/dist/{configPolicy-DCQgF5-f.mjs → configPolicy-DueX0k6i.mjs} +84 -84
  33. package/dist/configPolicy-DueX0k6i.mjs.map +1 -0
  34. package/dist/configSchema.d.mts +2 -2
  35. package/dist/configWriter-DCASRzr-.mjs +180 -0
  36. package/dist/configWriter-DCASRzr-.mjs.map +1 -0
  37. package/dist/configWriter-DGO0gmXo.cjs +218 -0
  38. package/dist/configWriter-DGO0gmXo.cjs.map +1 -0
  39. package/dist/configWriter.cjs +7 -4
  40. package/dist/configWriter.d.mts +2 -2
  41. package/dist/configWriter.mjs +1 -2
  42. package/dist/core-B0Qg0Qq2.cjs +224 -0
  43. package/dist/core-B0Qg0Qq2.cjs.map +1 -0
  44. package/dist/core-DKWW9gkh.mjs +139 -0
  45. package/dist/core-DKWW9gkh.mjs.map +1 -0
  46. package/dist/{domains-DGCb7uMC.cjs → domains-CiDUsRiX.cjs} +473 -171
  47. package/dist/domains-CiDUsRiX.cjs.map +1 -0
  48. package/dist/{domains-BroCVHrS.mjs → domains-IlieGHaY.mjs} +437 -170
  49. package/dist/domains-IlieGHaY.mjs.map +1 -0
  50. package/dist/domains.cjs +9 -3
  51. package/dist/domains.d.mts +3 -3
  52. package/dist/domains.mjs +3 -3
  53. package/dist/extensions/mcp-ui.mjs +1 -1
  54. package/dist/extensions/mcp-ui.mjs.map +1 -1
  55. package/dist/extensions/mcp.cjs +3 -3
  56. package/dist/extensions/mcp.cjs.map +1 -1
  57. package/dist/extensions/mcp.d.mts +1 -1
  58. package/dist/extensions/mcp.mjs +2 -2
  59. package/dist/extensions/mcp.mjs.map +1 -1
  60. package/dist/extensions/server.cjs +55 -47
  61. package/dist/extensions/server.cjs.map +1 -1
  62. package/dist/extensions/server.d.mts +2 -2
  63. package/dist/extensions/server.d.mts.map +1 -1
  64. package/dist/extensions/server.mjs +56 -48
  65. package/dist/extensions/server.mjs.map +1 -1
  66. package/dist/{harnessState-Dn_S7fB9.mjs → harnessState-8lZda_8r.mjs} +2 -2
  67. package/dist/{harnessState-Dn_S7fB9.mjs.map → harnessState-8lZda_8r.mjs.map} +1 -1
  68. package/dist/{harnessState-CCyBeutB.cjs → harnessState-BrmH0lG5.cjs} +2 -2
  69. package/dist/{harnessState-CCyBeutB.cjs.map → harnessState-BrmH0lG5.cjs.map} +1 -1
  70. package/dist/harnessState.cjs +1 -1
  71. package/dist/harnessState.d.mts +1 -1
  72. package/dist/harnessState.mjs +1 -2
  73. package/dist/{harnessStore-CJSxdSkV.cjs → harnessStore-BinxGITh.cjs} +189 -81
  74. package/dist/harnessStore-BinxGITh.cjs.map +1 -0
  75. package/dist/{harnessStore-CltwLB7M.mjs → harnessStore-w8zHmJ_H.mjs} +160 -82
  76. package/dist/harnessStore-w8zHmJ_H.mjs.map +1 -0
  77. package/dist/harnessStore.cjs +6 -1
  78. package/dist/harnessStore.d.mts +2 -2
  79. package/dist/harnessStore.mjs +1 -2
  80. package/dist/index-7RuIAWX1.d.mts +63 -0
  81. package/dist/index-7RuIAWX1.d.mts.map +1 -0
  82. package/dist/{index-CkDDvVQt.d.mts → index-BNcG0voG.d.mts} +61 -16
  83. package/dist/index-BNcG0voG.d.mts.map +1 -0
  84. package/dist/{index-Dy_-m7gx.d.mts → index-BTOU4XiI.d.mts} +3 -3
  85. package/dist/{index-Dy_-m7gx.d.mts.map → index-BTOU4XiI.d.mts.map} +1 -1
  86. package/dist/index-COokUToB.d.mts +83 -0
  87. package/dist/index-COokUToB.d.mts.map +1 -0
  88. package/dist/index-D3vGEYup.d.mts +135 -0
  89. package/dist/index-D3vGEYup.d.mts.map +1 -0
  90. package/dist/{index-Ch7ptOkW.d.mts → index-DNEK9W6n.d.mts} +26 -16
  91. package/dist/index-DNEK9W6n.d.mts.map +1 -0
  92. package/dist/index-D_OtYtsl.d.mts +37 -0
  93. package/dist/index-D_OtYtsl.d.mts.map +1 -0
  94. package/dist/{index-DNk4q0Qm.d.mts → index-Nbj2iQWw.d.mts} +19 -25
  95. package/dist/index-Nbj2iQWw.d.mts.map +1 -0
  96. package/dist/{index-CEUc7m16.d.mts → index-dYgF03lE.d.mts} +82 -32
  97. package/dist/index-dYgF03lE.d.mts.map +1 -0
  98. package/dist/index-fTuf8tVH.d.mts +34 -0
  99. package/dist/index-fTuf8tVH.d.mts.map +1 -0
  100. package/dist/index-nVcEC1jO.d.mts +62 -0
  101. package/dist/index-nVcEC1jO.d.mts.map +1 -0
  102. package/dist/index.cjs +81 -59
  103. package/dist/index.d.mts +16 -15
  104. package/dist/index.mjs +17 -15
  105. package/dist/{init-Cd3Ki6Ub.cjs → init-BdZ-gqqk.cjs} +209 -59
  106. package/dist/init-BdZ-gqqk.cjs.map +1 -0
  107. package/dist/{init-DVzhiCUE.mjs → init-DG7MxGxC.mjs} +187 -48
  108. package/dist/init-DG7MxGxC.mjs.map +1 -0
  109. package/dist/init.cjs +8 -6
  110. package/dist/init.d.mts +2 -2
  111. package/dist/init.mjs +1 -2
  112. package/dist/layeredConfig-BbkoIEfM.mjs +95 -0
  113. package/dist/layeredConfig-BbkoIEfM.mjs.map +1 -0
  114. package/dist/layeredConfig-Bs18dXQG.cjs +127 -0
  115. package/dist/layeredConfig-Bs18dXQG.cjs.map +1 -0
  116. package/dist/layeredConfig.cjs +5 -4
  117. package/dist/layeredConfig.d.mts +7 -2
  118. package/dist/layeredConfig.d.mts.map +1 -0
  119. package/dist/layeredConfig.mjs +1 -2
  120. package/dist/{majorModes-D1Itd8SN.mjs → majorModes-B8Km83e1.mjs} +130 -23
  121. package/dist/majorModes-B8Km83e1.mjs.map +1 -0
  122. package/dist/{majorModes-DnNSH9ZK.cjs → majorModes-CLccNMZt.cjs} +147 -22
  123. package/dist/majorModes-CLccNMZt.cjs.map +1 -0
  124. package/dist/majorModes.cjs +4 -1
  125. package/dist/majorModes.d.mts +2 -2
  126. package/dist/majorModes.mjs +1 -2
  127. package/dist/{piConfig-qGyCbcVj.mjs → piConfig-Bq36PQFF.mjs} +104 -69
  128. package/dist/piConfig-Bq36PQFF.mjs.map +1 -0
  129. package/dist/{piConfig-Bx26HMb_.d.mts → piConfig-C9rGUqPe.d.mts} +3 -3
  130. package/dist/{piConfig-Bx26HMb_.d.mts.map → piConfig-C9rGUqPe.d.mts.map} +1 -1
  131. package/dist/{piConfig-CCBdZXQr.cjs → piConfig-cluCie5i.cjs} +122 -70
  132. package/dist/piConfig-cluCie5i.cjs.map +1 -0
  133. package/dist/piConfig.cjs +4 -1
  134. package/dist/piConfig.d.mts +2 -2
  135. package/dist/piConfig.mjs +1 -2
  136. package/dist/piContext.cjs +27 -16
  137. package/dist/piContext.d.mts +3 -3
  138. package/dist/piContext.mjs +1 -2
  139. package/dist/{profiles-DBcYg5nA.mjs → profiles-CHict0x-.mjs} +322 -101
  140. package/dist/profiles-CHict0x-.mjs.map +1 -0
  141. package/dist/{profiles-tpplXY4r.cjs → profiles-t2EUj0aB.cjs} +359 -102
  142. package/dist/profiles-t2EUj0aB.cjs.map +1 -0
  143. package/dist/profiles.cjs +7 -1
  144. package/dist/profiles.d.mts +27 -3
  145. package/dist/profiles.d.mts.map +1 -1
  146. package/dist/profiles.mjs +1 -2
  147. package/dist/selectionSwitch.cjs +88 -22
  148. package/dist/selectionSwitch.cjs.map +1 -1
  149. package/dist/selectionSwitch.d.mts +40 -18
  150. package/dist/selectionSwitch.d.mts.map +1 -1
  151. package/dist/selectionSwitch.mjs +85 -22
  152. package/dist/selectionSwitch.mjs.map +1 -1
  153. package/dist/{sessionConfig-DV5xTRRw.mjs → sessionConfig-DFQF618G.mjs} +48 -47
  154. package/dist/sessionConfig-DFQF618G.mjs.map +1 -0
  155. package/dist/{sessionConfig-Bf6xHWLd.cjs → sessionConfig-DeP6pplT.cjs} +74 -85
  156. package/dist/sessionConfig-DeP6pplT.cjs.map +1 -0
  157. package/dist/sync-BJ8gCP9s.mjs +29 -0
  158. package/dist/sync-BJ8gCP9s.mjs.map +1 -0
  159. package/dist/sync-CC_15kkB.d.mts +46 -0
  160. package/dist/sync-CC_15kkB.d.mts.map +1 -0
  161. package/dist/sync-Dlxz2msM.cjs +37 -0
  162. package/dist/sync-Dlxz2msM.cjs.map +1 -0
  163. package/dist/types.cjs +2 -2
  164. package/dist/types.d.mts +2 -2
  165. package/dist/types.mjs +1 -2
  166. package/llms.txt +1 -1
  167. package/package.json +16 -16
  168. package/src/prompts/po-author-config/SKILL.md +4 -4
  169. package/src/prompts/po-author-config/agents/openai.yaml +1 -1
  170. package/src/prompts/po-author-config/references/config-contract.md +5 -5
  171. package/dist/atomicJson-1W0WhuFF.mjs +0 -44
  172. package/dist/atomicJson-1W0WhuFF.mjs.map +0 -1
  173. package/dist/atomicJson-Dt2z_DXl.cjs +0 -52
  174. package/dist/atomicJson-Dt2z_DXl.cjs.map +0 -1
  175. package/dist/config-CfTb0_9G.cjs +0 -219
  176. package/dist/config-CfTb0_9G.cjs.map +0 -1
  177. package/dist/config-D2HbBLzo.d.mts.map +0 -1
  178. package/dist/config-DoQWSt1V.mjs +0 -162
  179. package/dist/config-DoQWSt1V.mjs.map +0 -1
  180. package/dist/config-NSHRd6na.mjs +0 -2
  181. package/dist/configPolicy-Ce1QKDxa.cjs.map +0 -1
  182. package/dist/configPolicy-DCQgF5-f.mjs.map +0 -1
  183. package/dist/configWriter-B7uO8MuN.cjs +0 -150
  184. package/dist/configWriter-B7uO8MuN.cjs.map +0 -1
  185. package/dist/configWriter-DUtLgOlD.mjs +0 -130
  186. package/dist/configWriter-DUtLgOlD.mjs.map +0 -1
  187. package/dist/domains-BroCVHrS.mjs.map +0 -1
  188. package/dist/domains-DGCb7uMC.cjs.map +0 -1
  189. package/dist/harnessStore-CJSxdSkV.cjs.map +0 -1
  190. package/dist/harnessStore-CltwLB7M.mjs.map +0 -1
  191. package/dist/index-78zvoS9s.d.mts +0 -35
  192. package/dist/index-78zvoS9s.d.mts.map +0 -1
  193. package/dist/index-BT29GZx5.d.mts.map +0 -1
  194. package/dist/index-C6GaYjq-.d.mts +0 -6
  195. package/dist/index-C6GaYjq-.d.mts.map +0 -1
  196. package/dist/index-CEUc7m16.d.mts.map +0 -1
  197. package/dist/index-CRcoVYXz.d.mts +0 -39
  198. package/dist/index-CRcoVYXz.d.mts.map +0 -1
  199. package/dist/index-CVlciCJF.d.mts +0 -40
  200. package/dist/index-CVlciCJF.d.mts.map +0 -1
  201. package/dist/index-Ch7ptOkW.d.mts.map +0 -1
  202. package/dist/index-CkDDvVQt.d.mts.map +0 -1
  203. package/dist/index-Cvdt-Y-i.d.mts +0 -66
  204. package/dist/index-Cvdt-Y-i.d.mts.map +0 -1
  205. package/dist/index-DNk4q0Qm.d.mts.map +0 -1
  206. package/dist/index-DdH0w56b.d.mts +0 -116
  207. package/dist/index-DdH0w56b.d.mts.map +0 -1
  208. package/dist/init-Cd3Ki6Ub.cjs.map +0 -1
  209. package/dist/init-DVzhiCUE.mjs.map +0 -1
  210. package/dist/layeredConfig-fukqN2EA.mjs +0 -67
  211. package/dist/layeredConfig-fukqN2EA.mjs.map +0 -1
  212. package/dist/layeredConfig-ol0274TN.cjs +0 -94
  213. package/dist/layeredConfig-ol0274TN.cjs.map +0 -1
  214. package/dist/majorModes-D1Itd8SN.mjs.map +0 -1
  215. package/dist/majorModes-DnNSH9ZK.cjs.map +0 -1
  216. package/dist/piConfig-CCBdZXQr.cjs.map +0 -1
  217. package/dist/piConfig-qGyCbcVj.mjs.map +0 -1
  218. package/dist/profiles-DBcYg5nA.mjs.map +0 -1
  219. package/dist/profiles-tpplXY4r.cjs.map +0 -1
  220. package/dist/sessionConfig-Bf6xHWLd.cjs.map +0 -1
  221. package/dist/sessionConfig-DV5xTRRw.mjs.map +0 -1
@@ -1,12 +1,19 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import fs from "node:fs";
3
- import { validateHeaderName, validateHeaderValue } from "node:http";
4
- import { isIP } from "node:net";
5
1
  import path from "node:path";
2
+ import { getOrThrow, nodeHost, writeFileAtomic } from "@agimon-ai/po-core/platform";
3
+ import { createHash, randomUUID } from "node:crypto";
6
4
  import { AGENT_PLUGIN_MCP_SCHEMA_URL } from "@agimon-ai/po-core/mcpProjection";
7
- //#region src/services/agentPluginMcp/index.ts
8
- const PRIVATE_FILE_MODE = 384;
9
- const PRIVATE_DIRECTORY_MODE = 448;
5
+ import fs from "node:fs";
6
+ //#region src/services/agentPluginMcp/normalize.ts
7
+ /** Pure Agent Plugin MCP normalisation shared by the async API and the deprecated Node-only sync shim. */
8
+ /** Same token grammar as node:http `validateHeaderName` (RFC 9110 tchar). */
9
+ const HTTP_TOKEN = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/u;
10
+ /** Same rejected characters as node:http `validateHeaderValue`. */
11
+ const INVALID_HEADER_VALUE_CHARACTER = /[^\t\x20-\x7e\x80-\xff]/u;
12
+ const IPV4_OCTET = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)";
13
+ const IPV4_ADDRESS = new RegExp(`^${IPV4_OCTET}(?:\\.${IPV4_OCTET}){3}$`, "u");
14
+ function isIPv4(value) {
15
+ return IPV4_ADDRESS.test(value);
16
+ }
10
17
  const RESERVED_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["PLUGIN_ROOT", "PLUGIN_DATA"]);
11
18
  const PROXY_SERVER_ALIASES = /* @__PURE__ */ new Set(["mcp-proxy", "agiflow-proxy"]);
12
19
  function isObject(value) {
@@ -43,49 +50,6 @@ function isInside(root, candidate) {
43
50
  const relative = path.relative(root, candidate);
44
51
  return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
45
52
  }
46
- function realContainedPath(root, candidate, label) {
47
- let realCandidate;
48
- try {
49
- realCandidate = fs.realpathSync(candidate);
50
- } catch {
51
- throw new Error(`${label} does not exist`);
52
- }
53
- if (!isInside(root, realCandidate)) throw new Error(`${label} escapes its allowed plugin directory`);
54
- return realCandidate;
55
- }
56
- function normalizeCommand(command, pluginRoot) {
57
- if (command.startsWith("./")) {
58
- const resolved = realContainedPath(pluginRoot, path.resolve(pluginRoot, command), "command");
59
- if (!fs.statSync(resolved).isFile()) throw new Error("command must resolve to a regular file");
60
- try {
61
- fs.accessSync(resolved, fs.constants.X_OK);
62
- } catch {
63
- throw new Error("command must resolve to an executable file");
64
- }
65
- return resolved;
66
- }
67
- if (command === "." || command === ".." || /[\s/\\]/u.test(command)) throw new Error("command must be a bare executable name or a ./ path inside the plugin");
68
- return command;
69
- }
70
- function normalizeWorkingDirectory(declared, pluginRoot, pluginDataDirectory) {
71
- if (declared === void 0) return pluginRoot;
72
- const cwd = nonEmptyString(declared, "cwd");
73
- let base;
74
- let expanded;
75
- if (cwd.startsWith("./")) {
76
- base = pluginRoot;
77
- expanded = path.resolve(pluginRoot, cwd);
78
- } else if (cwd === "${PLUGIN_ROOT}" || cwd.startsWith("${PLUGIN_ROOT}/")) {
79
- base = pluginRoot;
80
- expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);
81
- } else if (cwd === "${PLUGIN_DATA}" || cwd.startsWith("${PLUGIN_DATA}/")) {
82
- base = pluginDataDirectory;
83
- expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);
84
- } else throw new Error("cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}");
85
- const resolved = realContainedPath(base, path.resolve(expanded), "cwd");
86
- if (!fs.statSync(resolved).isDirectory()) throw new Error("cwd must resolve to a directory");
87
- return resolved;
88
- }
89
53
  function normalizeEnvironment(value, pluginRoot, pluginDataDirectory) {
90
54
  const declared = optionalStringRecord(value, "env") ?? {};
91
55
  for (const key of Object.keys(declared)) {
@@ -101,9 +65,8 @@ function normalizeEnvironment(value, pluginRoot, pluginDataDirectory) {
101
65
  function isLoopbackHost(hostname) {
102
66
  const bareHostname = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
103
67
  if (bareHostname.toLowerCase() === "localhost") return true;
104
- const family = isIP(bareHostname);
105
- if (family === 4) return bareHostname.split(".")[0] === "127";
106
- return family === 6 && bareHostname === "::1";
68
+ if (isIPv4(bareHostname)) return bareHostname.split(".")[0] === "127";
69
+ return bareHostname === "::1";
107
70
  }
108
71
  function normalizeUrl(value) {
109
72
  const raw = nonEmptyString(value, "url");
@@ -127,48 +90,89 @@ function normalizeHeaders(value) {
127
90
  const canonical = name.toLowerCase();
128
91
  if (seen.has(canonical)) throw new Error(`headers contain duplicate name "${name}"`);
129
92
  seen.add(canonical);
130
- try {
131
- validateHeaderName(name);
132
- validateHeaderValue(name, item);
133
- } catch {
134
- throw new Error(`header "${name}" is invalid`);
135
- }
93
+ if (!HTTP_TOKEN.test(name) || INVALID_HEADER_VALUE_CHARACTER.test(item)) throw new Error(`header "${name}" is invalid`);
136
94
  }
137
95
  return headers;
138
96
  }
139
- function normalizeServer(raw, pluginRoot, pluginDataDirectory, name) {
97
+ function stagedInternalPath(source, contents, stagingDirectory) {
98
+ const digest = createHash("sha256").update(source.sourceId).update("\0").update(source.contentDigest).update("\0").update(contents).digest("hex").slice(0, 20);
99
+ return path.join(stagingDirectory, `agent-plugin-mcp-${digest}.internal.json`);
100
+ }
101
+ function emptyResult(diagnostic) {
102
+ return {
103
+ claudeConfig: { mcpServers: {} },
104
+ serverNames: [],
105
+ droppedServers: [],
106
+ diagnostics: [diagnostic]
107
+ };
108
+ }
109
+ function sourceLabel(source) {
110
+ return `Agent Plugin MCP source "${source.sourceId}"`;
111
+ }
112
+ /** Checks the projection identity before any file is touched. */
113
+ function assertProjectionSource(source) {
114
+ if (source.mcpSchemaUrl !== AGENT_PLUGIN_MCP_SCHEMA_URL) throw new Error(`unsupported schema "${String(source.mcpSchemaUrl)}"`);
115
+ if (!path.isAbsolute(source.pluginRoot) || !path.isAbsolute(source.pluginDataDirectory)) throw new Error("pluginRoot and pluginDataDirectory must be absolute");
116
+ }
117
+ function assertRootConfigPath(configPath, pluginRoot) {
118
+ if (path.dirname(configPath) !== pluginRoot || path.basename(configPath) !== "mcp.json") throw new Error("portable plugin config must be root mcp.json");
119
+ }
120
+ /** Verifies the digest and document shape of the raw mcp.json bytes; returns its server map. */
121
+ function parseMcpDocument(source, bytes, label) {
122
+ if (createHash("sha256").update(bytes).digest("hex") !== source.contentDigest) throw new Error("content digest does not match the projection");
123
+ const parsed = JSON.parse(new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes));
124
+ if (!isObject(parsed)) throw new Error("document must be an object");
125
+ onlyKeys(parsed, ["$schema", "mcpServers"], label);
126
+ if (parsed.$schema !== source.mcpSchemaUrl) throw new Error("$schema does not match the projected schema");
127
+ if (!isObject(parsed.mcpServers)) throw new Error("mcpServers must be an object");
128
+ return parsed.mcpServers;
129
+ }
130
+ /** Whether a declared server is skipped (empty name), dropped by the allowlist or wrapper rule, or normalised. */
131
+ function serverDisposition(name, options) {
132
+ if (!name) return "empty";
133
+ const allowed = options.allowedServers;
134
+ const keepAll = !allowed || allowed.length === 0;
135
+ const stripProxyWrappers = options.stripProxyWrappers ?? true;
136
+ if (!keepAll && !allowed.includes(name) || stripProxyWrappers && PROXY_SERVER_ALIASES.has(name)) return "dropped";
137
+ return "keep";
138
+ }
139
+ function emptyServerDiagnostic(label) {
140
+ return `${label} contains an empty server name; that entry was skipped.`;
141
+ }
142
+ function skippedServerDiagnostic(label, name, error) {
143
+ return `${label} skipped server "${name}": ${error instanceof Error ? error.message : String(error)}`;
144
+ }
145
+ function disabledResult(label, error) {
146
+ return emptyResult(`${label} was disabled: ${error instanceof Error ? error.message : String(error)}`);
147
+ }
148
+ /** The declared transport of one server; stdio declarations are also checked for unsupported fields. */
149
+ function serverType(raw, name) {
140
150
  if (!isObject(raw)) throw new Error("definition must be an object");
141
151
  const type = nonEmptyString(raw.type, "type");
142
- if (type === "stdio") {
143
- onlyKeys(raw, [
144
- "type",
145
- "command",
146
- "args",
147
- "env",
148
- "cwd"
149
- ], `server "${name}"`);
150
- const command = normalizeCommand(nonEmptyString(raw.command, "command"), pluginRoot);
151
- const args = optionalStringArray(raw.args, "args")?.map((item) => expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory));
152
- const env = normalizeEnvironment(raw.env, pluginRoot, pluginDataDirectory);
153
- const cwd = normalizeWorkingDirectory(raw.cwd, pluginRoot, pluginDataDirectory);
154
- const config = {
155
- command,
156
- ...args ? { args } : {},
157
- env,
158
- cwd
159
- };
160
- return {
161
- internal: {
162
- name,
163
- transport: "stdio",
164
- config
165
- },
166
- claude: {
167
- type: "stdio",
168
- ...config
169
- }
170
- };
171
- }
152
+ if (type === "stdio") onlyKeys(raw, [
153
+ "type",
154
+ "command",
155
+ "args",
156
+ "env",
157
+ "cwd"
158
+ ], `server "${name}"`);
159
+ return type;
160
+ }
161
+ function stdioServer(name, config) {
162
+ return {
163
+ internal: {
164
+ name,
165
+ transport: "stdio",
166
+ config
167
+ },
168
+ claude: {
169
+ type: "stdio",
170
+ ...config
171
+ }
172
+ };
173
+ }
174
+ /** A streamable-http or sse server; any other type is unsupported. */
175
+ function remoteServer(raw, name, type) {
172
176
  if (type === "streamable-http" || type === "sse") {
173
177
  onlyKeys(raw, [
174
178
  "type",
@@ -196,21 +200,93 @@ function normalizeServer(raw, pluginRoot, pluginDataDirectory, name) {
196
200
  }
197
201
  throw new Error(`unsupported transport type "${type}"`);
198
202
  }
199
- function stagedInternalPath(source, contents, stagingDirectory) {
200
- const digest = createHash("sha256").update(source.sourceId).update("\0").update(source.contentDigest).update("\0").update(contents).digest("hex").slice(0, 20);
201
- return path.join(stagingDirectory, `agent-plugin-mcp-${digest}.internal.json`);
203
+ function stagedContents(internalServers) {
204
+ return `${JSON.stringify({ mcpServers: internalServers }, null, 2)}\n`;
205
+ }
206
+ function stagedCacheKey(source, configPath, contents) {
207
+ return 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");
202
208
  }
203
- function writeStagedInternalConfig(target, contents, stagingDirectory) {
209
+ /** The containment base and expanded path of a declared cwd, or undefined when the plugin root is the default. */
210
+ function workingDirectoryCandidate(declared, pluginRoot, pluginDataDirectory) {
211
+ if (declared === void 0) return void 0;
212
+ const cwd = nonEmptyString(declared, "cwd");
213
+ if (cwd.startsWith("./")) return {
214
+ base: pluginRoot,
215
+ candidate: path.resolve(pluginRoot, cwd)
216
+ };
217
+ const expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);
218
+ if (cwd === "${PLUGIN_ROOT}" || cwd.startsWith("${PLUGIN_ROOT}/")) return {
219
+ base: pluginRoot,
220
+ candidate: path.resolve(expanded)
221
+ };
222
+ if (cwd === "${PLUGIN_DATA}" || cwd.startsWith("${PLUGIN_DATA}/")) return {
223
+ base: pluginDataDirectory,
224
+ candidate: path.resolve(expanded)
225
+ };
226
+ throw new Error("cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}");
227
+ }
228
+ /** Rejects a command that is neither a bare executable name nor a ./ path; true when it must be resolved. */
229
+ function isPluginRelativeCommand(command) {
230
+ if (command.startsWith("./")) return true;
231
+ if (command === "." || command === ".." || /[\s/\\]/u.test(command)) throw new Error("command must be a bare executable name or a ./ path inside the plugin");
232
+ return false;
233
+ }
234
+ //#endregion
235
+ //#region src/services/agentPluginMcp/sync.ts
236
+ function realContainedPath$1(root, candidate, label) {
237
+ let realCandidate;
238
+ try {
239
+ realCandidate = fs.realpathSync(candidate);
240
+ } catch {
241
+ throw new Error(`${label} does not exist`);
242
+ }
243
+ if (!isInside(root, realCandidate)) throw new Error(`${label} escapes its allowed plugin directory`);
244
+ return realCandidate;
245
+ }
246
+ function normalizeCommand$1(command, pluginRoot) {
247
+ if (!isPluginRelativeCommand(command)) return command;
248
+ const resolved = realContainedPath$1(pluginRoot, path.resolve(pluginRoot, command), "command");
249
+ if (!fs.statSync(resolved).isFile()) throw new Error("command must resolve to a regular file");
250
+ try {
251
+ fs.accessSync(resolved, fs.constants.X_OK);
252
+ } catch {
253
+ throw new Error("command must resolve to an executable file");
254
+ }
255
+ return resolved;
256
+ }
257
+ function normalizeWorkingDirectory$1(declared, pluginRoot, pluginDataDirectory) {
258
+ const declaredCwd = workingDirectoryCandidate(declared, pluginRoot, pluginDataDirectory);
259
+ if (!declaredCwd) return pluginRoot;
260
+ const resolved = realContainedPath$1(declaredCwd.base, declaredCwd.candidate, "cwd");
261
+ if (!fs.statSync(resolved).isDirectory()) throw new Error("cwd must resolve to a directory");
262
+ return resolved;
263
+ }
264
+ function normalizeServer$1(raw, pluginRoot, pluginDataDirectory, name) {
265
+ const type = serverType(raw, name);
266
+ const declaration = raw;
267
+ if (type !== "stdio") return remoteServer(declaration, name, type);
268
+ const command = normalizeCommand$1(nonEmptyString(declaration.command, "command"), pluginRoot);
269
+ const args = optionalStringArray(declaration.args, "args")?.map((item) => expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory));
270
+ const env = normalizeEnvironment(declaration.env, pluginRoot, pluginDataDirectory);
271
+ const cwd = normalizeWorkingDirectory$1(declaration.cwd, pluginRoot, pluginDataDirectory);
272
+ return stdioServer(name, {
273
+ command,
274
+ ...args ? { args } : {},
275
+ env,
276
+ cwd
277
+ });
278
+ }
279
+ function writeStagedInternalConfig$1(target, contents, stagingDirectory) {
204
280
  fs.mkdirSync(stagingDirectory, {
205
- mode: PRIVATE_DIRECTORY_MODE,
281
+ mode: 448,
206
282
  recursive: true
207
283
  });
208
- fs.chmodSync(stagingDirectory, PRIVATE_DIRECTORY_MODE);
284
+ fs.chmodSync(stagingDirectory, 448);
209
285
  const temporaryPath = `${target}.${process.pid}-${randomUUID()}.tmp`;
210
286
  try {
211
287
  fs.writeFileSync(temporaryPath, contents, {
212
288
  flag: "wx",
213
- mode: PRIVATE_FILE_MODE
289
+ mode: 384
214
290
  });
215
291
  try {
216
292
  fs.renameSync(temporaryPath, target);
@@ -220,19 +296,11 @@ function writeStagedInternalConfig(target, contents, stagingDirectory) {
220
296
  const stat = fs.lstatSync(target);
221
297
  if (stat.isSymbolicLink() || !stat.isFile() || fs.readFileSync(target, "utf8") !== contents) throw new Error("content-addressed staging path is occupied by different content");
222
298
  }
223
- fs.chmodSync(target, PRIVATE_FILE_MODE);
299
+ fs.chmodSync(target, 384);
224
300
  } finally {
225
301
  fs.rmSync(temporaryPath, { force: true });
226
302
  }
227
303
  }
228
- function emptyResult(diagnostic) {
229
- return {
230
- claudeConfig: { mcpServers: {} },
231
- serverNames: [],
232
- droppedServers: [],
233
- diagnostics: [diagnostic]
234
- };
235
- }
236
304
  /**
237
305
  * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.
238
306
  *
@@ -240,12 +308,13 @@ function emptyResult(diagnostic) {
240
308
  * Document/schema, source identity, or plugin-boundary failures reject the whole
241
309
  * source. Only the two portable plugin placeholders are expanded; the staged
242
310
  * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.
311
+ *
312
+ * @deprecated Node-only sync shim; use normalizeAgentPluginMcpSourceAsync. Removed in M2.
243
313
  */
244
314
  function normalizeAgentPluginMcpSource(source, options) {
245
- const label = `Agent Plugin MCP source "${source.sourceId}"`;
315
+ const label = sourceLabel(source);
246
316
  try {
247
- if (source.mcpSchemaUrl !== AGENT_PLUGIN_MCP_SCHEMA_URL) throw new Error(`unsupported schema "${String(source.mcpSchemaUrl)}"`);
248
- if (!path.isAbsolute(source.pluginRoot) || !path.isAbsolute(source.pluginDataDirectory)) throw new Error("pluginRoot and pluginDataDirectory must be absolute");
317
+ assertProjectionSource(source);
249
318
  const pluginRoot = fs.realpathSync(source.pluginRoot);
250
319
  try {
251
320
  const dataStat = fs.lstatSync(source.pluginDataDirectory);
@@ -253,55 +322,164 @@ function normalizeAgentPluginMcpSource(source, options) {
253
322
  } catch (error) {
254
323
  if (error.code !== "ENOENT") throw error;
255
324
  fs.mkdirSync(source.pluginDataDirectory, {
256
- mode: PRIVATE_DIRECTORY_MODE,
325
+ mode: 448,
257
326
  recursive: true
258
327
  });
259
328
  }
260
- fs.chmodSync(source.pluginDataDirectory, PRIVATE_DIRECTORY_MODE);
329
+ fs.chmodSync(source.pluginDataDirectory, 448);
261
330
  fs.accessSync(source.pluginDataDirectory, fs.constants.W_OK);
262
331
  const pluginDataDirectory = fs.realpathSync(source.pluginDataDirectory);
263
- const configPath = realContainedPath(pluginRoot, source.configPath, "mcp.json");
264
- if (path.dirname(configPath) !== pluginRoot || path.basename(configPath) !== "mcp.json") throw new Error("portable plugin config must be root mcp.json");
265
- const bytes = fs.readFileSync(configPath);
266
- if (createHash("sha256").update(bytes).digest("hex") !== source.contentDigest) throw new Error("content digest does not match the projection");
267
- const parsed = JSON.parse(bytes.toString("utf8"));
268
- if (!isObject(parsed)) throw new Error("document must be an object");
269
- onlyKeys(parsed, ["$schema", "mcpServers"], label);
270
- if (parsed.$schema !== source.mcpSchemaUrl) throw new Error("$schema does not match the projected schema");
271
- if (!isObject(parsed.mcpServers)) throw new Error("mcpServers must be an object");
272
- const allowed = options.allowedServers;
273
- const keepAll = !allowed || allowed.length === 0;
274
- const stripProxyWrappers = options.stripProxyWrappers ?? true;
332
+ const configPath = realContainedPath$1(pluginRoot, source.configPath, "mcp.json");
333
+ assertRootConfigPath(configPath, pluginRoot);
334
+ const servers = parseMcpDocument(source, fs.readFileSync(configPath), label);
335
+ const internalServers = Object.create(null);
336
+ const claudeServers = Object.create(null);
337
+ const droppedServers = [];
338
+ const diagnostics = [];
339
+ for (const [name, rawServer] of Object.entries(servers)) {
340
+ const disposition = serverDisposition(name, options);
341
+ if (disposition === "empty") {
342
+ diagnostics.push(emptyServerDiagnostic(label));
343
+ continue;
344
+ }
345
+ if (disposition === "dropped") {
346
+ droppedServers.push(name);
347
+ continue;
348
+ }
349
+ try {
350
+ const normalized = normalizeServer$1(rawServer, pluginRoot, pluginDataDirectory, name);
351
+ internalServers[name] = normalized.internal;
352
+ claudeServers[name] = normalized.claude;
353
+ } catch (error) {
354
+ droppedServers.push(name);
355
+ diagnostics.push(skippedServerDiagnostic(label, name, error));
356
+ }
357
+ }
358
+ const contents = stagedContents(internalServers);
359
+ const target = stagedInternalPath(source, contents, options.stagingDirectory);
360
+ writeStagedInternalConfig$1(target, contents, options.stagingDirectory);
361
+ return {
362
+ configSource: {
363
+ path: target,
364
+ format: "internal",
365
+ cacheKey: stagedCacheKey(source, configPath, contents)
366
+ },
367
+ claudeConfig: { mcpServers: claudeServers },
368
+ serverNames: Object.keys(internalServers),
369
+ droppedServers,
370
+ diagnostics
371
+ };
372
+ } catch (error) {
373
+ return disabledResult(label, error);
374
+ }
375
+ }
376
+ //#endregion
377
+ //#region src/services/agentPluginMcp/index.ts
378
+ async function realContainedPath(fs, root, candidate, label, context) {
379
+ const real = await fs.canonicalPath(candidate, context);
380
+ if (!real.ok) throw new Error(`${label} does not exist`);
381
+ if (!isInside(root, real.value)) throw new Error(`${label} escapes its allowed plugin directory`);
382
+ return real.value;
383
+ }
384
+ async function normalizeCommand(fs, command, pluginRoot, context) {
385
+ if (!isPluginRelativeCommand(command)) return command;
386
+ const resolved = await realContainedPath(fs, pluginRoot, path.resolve(pluginRoot, command), "command", context);
387
+ if (getOrThrow(await fs.fileInfo(resolved, context)).kind !== "file") throw new Error("command must resolve to a regular file");
388
+ const mode = await nodeHost.fileMode(resolved);
389
+ if (mode !== void 0 && (mode & 73) === 0) throw new Error("command must resolve to an executable file");
390
+ return resolved;
391
+ }
392
+ async function normalizeWorkingDirectory(fs, declared, pluginRoot, pluginDataDirectory, context) {
393
+ const declaredCwd = workingDirectoryCandidate(declared, pluginRoot, pluginDataDirectory);
394
+ if (!declaredCwd) return pluginRoot;
395
+ const resolved = await realContainedPath(fs, declaredCwd.base, declaredCwd.candidate, "cwd", context);
396
+ if (getOrThrow(await fs.fileInfo(resolved, context)).kind !== "directory") throw new Error("cwd must resolve to a directory");
397
+ return resolved;
398
+ }
399
+ async function normalizeServer(fs, raw, pluginRoot, pluginDataDirectory, name, context) {
400
+ const type = serverType(raw, name);
401
+ const declaration = raw;
402
+ if (type !== "stdio") return remoteServer(declaration, name, type);
403
+ const command = await normalizeCommand(fs, nonEmptyString(declaration.command, "command"), pluginRoot, context);
404
+ const args = optionalStringArray(declaration.args, "args")?.map((item) => expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory));
405
+ const env = normalizeEnvironment(declaration.env, pluginRoot, pluginDataDirectory);
406
+ const cwd = await normalizeWorkingDirectory(fs, declaration.cwd, pluginRoot, pluginDataDirectory, context);
407
+ return stdioServer(name, {
408
+ command,
409
+ ...args ? { args } : {},
410
+ env,
411
+ cwd
412
+ });
413
+ }
414
+ /** Owner-only plugin data directory; a symbolic link or non-directory rejects the source. */
415
+ async function preparePluginDataDirectory(fs, directory, context) {
416
+ const info = await fs.fileInfo(directory, context);
417
+ if (info.ok) {
418
+ if (info.value.kind !== "directory") throw new Error("pluginDataDirectory must be a real directory");
419
+ } else if (info.error.code === "not_found") getOrThrow(await fs.createDir(directory, { recursive: true }, context));
420
+ else throw info.error;
421
+ await nodeHost.setMode(directory, 448);
422
+ return getOrThrow(await fs.canonicalPath(directory, context));
423
+ }
424
+ /**
425
+ * Content-addressed and owner-only. The name hashes the contents, so replacing an
426
+ * existing staged file through a uniquely named temporary file never changes what it holds.
427
+ */
428
+ async function writeStagedInternalConfig(fs, target, contents, stagingDirectory, context) {
429
+ getOrThrow(await fs.createDir(stagingDirectory, { recursive: true }, context));
430
+ await nodeHost.setMode(stagingDirectory, 448);
431
+ await writeFileAtomic(fs, target, contents, context);
432
+ await nodeHost.setMode(target, 384);
433
+ }
434
+ /**
435
+ * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer,
436
+ * reading and writing through the injected file system.
437
+ *
438
+ * Server validation is isolated: one malformed server is diagnosed and skipped.
439
+ * Document/schema, source identity, or plugin-boundary failures reject the whole
440
+ * source. Only the two portable plugin placeholders are expanded; the staged
441
+ * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.
442
+ */
443
+ async function normalizeAgentPluginMcpSourceAsync(fs, source, options, context) {
444
+ const label = sourceLabel(source);
445
+ try {
446
+ assertProjectionSource(source);
447
+ const pluginRoot = getOrThrow(await fs.canonicalPath(source.pluginRoot, context));
448
+ const pluginDataDirectory = await preparePluginDataDirectory(fs, source.pluginDataDirectory, context);
449
+ const configPath = await realContainedPath(fs, pluginRoot, source.configPath, "mcp.json", context);
450
+ assertRootConfigPath(configPath, pluginRoot);
451
+ const servers = parseMcpDocument(source, getOrThrow(await fs.readBinaryFile(configPath, context)), label);
275
452
  const internalServers = Object.create(null);
276
453
  const claudeServers = Object.create(null);
277
454
  const droppedServers = [];
278
455
  const diagnostics = [];
279
- for (const [name, rawServer] of Object.entries(parsed.mcpServers)) {
280
- if (!name) {
281
- diagnostics.push(`${label} contains an empty server name; that entry was skipped.`);
456
+ for (const [name, rawServer] of Object.entries(servers)) {
457
+ const disposition = serverDisposition(name, options);
458
+ if (disposition === "empty") {
459
+ diagnostics.push(emptyServerDiagnostic(label));
282
460
  continue;
283
461
  }
284
- if (!keepAll && !allowed.includes(name) || stripProxyWrappers && PROXY_SERVER_ALIASES.has(name)) {
462
+ if (disposition === "dropped") {
285
463
  droppedServers.push(name);
286
464
  continue;
287
465
  }
288
466
  try {
289
- const normalized = normalizeServer(rawServer, pluginRoot, pluginDataDirectory, name);
467
+ const normalized = await normalizeServer(fs, rawServer, pluginRoot, pluginDataDirectory, name, context);
290
468
  internalServers[name] = normalized.internal;
291
469
  claudeServers[name] = normalized.claude;
292
470
  } catch (error) {
293
471
  droppedServers.push(name);
294
- diagnostics.push(`${label} skipped server "${name}": ${error instanceof Error ? error.message : String(error)}`);
472
+ diagnostics.push(skippedServerDiagnostic(label, name, error));
295
473
  }
296
474
  }
297
- const contents = `${JSON.stringify({ mcpServers: internalServers }, null, 2)}\n`;
475
+ const contents = stagedContents(internalServers);
298
476
  const target = stagedInternalPath(source, contents, options.stagingDirectory);
299
- writeStagedInternalConfig(target, contents, options.stagingDirectory);
477
+ await writeStagedInternalConfig(fs, target, contents, options.stagingDirectory, context);
300
478
  return {
301
479
  configSource: {
302
480
  path: target,
303
481
  format: "internal",
304
- cacheKey: 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")
482
+ cacheKey: stagedCacheKey(source, configPath, contents)
305
483
  },
306
484
  claudeConfig: { mcpServers: claudeServers },
307
485
  serverNames: Object.keys(internalServers),
@@ -309,10 +487,10 @@ function normalizeAgentPluginMcpSource(source, options) {
309
487
  diagnostics
310
488
  };
311
489
  } catch (error) {
312
- return emptyResult(`${label} was disabled: ${error instanceof Error ? error.message : String(error)}`);
490
+ return disabledResult(label, error);
313
491
  }
314
492
  }
315
493
  //#endregion
316
- export { normalizeAgentPluginMcpSource };
494
+ export { normalizeAgentPluginMcpSource, normalizeAgentPluginMcpSourceAsync };
317
495
 
318
496
  //# sourceMappingURL=agentPluginMcp.mjs.map