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

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