@agimon-ai/po-domain 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/apiContracts.cjs +17 -0
  4. package/dist/apiContracts.cjs.map +1 -0
  5. package/dist/apiContracts.d.mts +53 -0
  6. package/dist/apiContracts.d.mts.map +1 -0
  7. package/dist/apiContracts.mjs +13 -0
  8. package/dist/apiContracts.mjs.map +1 -0
  9. package/dist/apply.cjs +76 -0
  10. package/dist/apply.cjs.map +1 -0
  11. package/dist/apply.d.mts +23 -0
  12. package/dist/apply.d.mts.map +1 -0
  13. package/dist/apply.mjs +72 -0
  14. package/dist/apply.mjs.map +1 -0
  15. package/dist/domainText-Dm8k7p4z.mjs +97 -0
  16. package/dist/domainText-Dm8k7p4z.mjs.map +1 -0
  17. package/dist/domainText-UIRIz6-_.cjs +204 -0
  18. package/dist/domainText-UIRIz6-_.cjs.map +1 -0
  19. package/dist/extensions/mcp.cjs +26 -0
  20. package/dist/extensions/mcp.cjs.map +1 -0
  21. package/dist/extensions/mcp.d.mts +5 -0
  22. package/dist/extensions/mcp.d.mts.map +1 -0
  23. package/dist/extensions/mcp.mjs +21 -0
  24. package/dist/extensions/mcp.mjs.map +1 -0
  25. package/dist/extensions/server.cjs +229 -0
  26. package/dist/extensions/server.cjs.map +1 -0
  27. package/dist/extensions/server.d.mts +5 -0
  28. package/dist/extensions/server.d.mts.map +1 -0
  29. package/dist/extensions/server.mjs +221 -0
  30. package/dist/extensions/server.mjs.map +1 -0
  31. package/dist/extensions/web.mjs +26 -0
  32. package/dist/extensions/web.mjs.map +1 -0
  33. package/dist/index.cjs +19 -0
  34. package/dist/index.d.mts +84 -0
  35. package/dist/index.d.mts.map +1 -0
  36. package/dist/index.mjs +3 -0
  37. package/dist/mcp.cjs +32 -0
  38. package/dist/mcp.cjs.map +1 -0
  39. package/dist/mcp.d.mts +52 -0
  40. package/dist/mcp.d.mts.map +1 -0
  41. package/dist/mcp.mjs +19 -0
  42. package/dist/mcp.mjs.map +1 -0
  43. package/dist/mcpFilter-CZNmU_VF.cjs +189 -0
  44. package/dist/mcpFilter-CZNmU_VF.cjs.map +1 -0
  45. package/dist/mcpFilter-Cf-czy8O.mjs +151 -0
  46. package/dist/mcpFilter-Cf-czy8O.mjs.map +1 -0
  47. package/dist/pluginMaterializer-C8IZ1PSY.cjs +267 -0
  48. package/dist/pluginMaterializer-C8IZ1PSY.cjs.map +1 -0
  49. package/dist/pluginMaterializer-D9-oDF-b.mjs +259 -0
  50. package/dist/pluginMaterializer-D9-oDF-b.mjs.map +1 -0
  51. package/dist/plugins.cjs +3 -0
  52. package/dist/plugins.d.mts +14 -0
  53. package/dist/plugins.d.mts.map +1 -0
  54. package/dist/plugins.mjs +2 -0
  55. package/dist/resourceCollector-CRY5cmeY.mjs +773 -0
  56. package/dist/resourceCollector-CRY5cmeY.mjs.map +1 -0
  57. package/dist/resourceCollector-Cu-lp5hJ.cjs +837 -0
  58. package/dist/resourceCollector-Cu-lp5hJ.cjs.map +1 -0
  59. package/dist/resources-BEjxE3Zn.d.mts +66 -0
  60. package/dist/resources-BEjxE3Zn.d.mts.map +1 -0
  61. package/dist/resources.cjs +12 -0
  62. package/dist/resources.d.mts +43 -0
  63. package/dist/resources.d.mts.map +1 -0
  64. package/dist/resources.mjs +2 -0
  65. package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
  66. package/dist/toolNames-Ckp5VLMD.mjs +43 -0
  67. package/dist/toolNames-Ckp5VLMD.mjs.map +1 -0
  68. package/dist/toolNames-Cuu-u8r1.cjs +48 -0
  69. package/dist/toolNames-Cuu-u8r1.cjs.map +1 -0
  70. package/llms.txt +12 -0
  71. package/package.json +158 -0
  72. package/src/prompts/po-author-domain/SKILL.md +18 -0
  73. package/src/prompts/po-author-domain/references/domains-contract.md +165 -0
@@ -0,0 +1,189 @@
1
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
2
+ let node_fs = require("node:fs");
3
+ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
4
+ let node_path = require("node:path");
5
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
6
+ let _agimon_ai_po_config_domains = require("@agimon-ai/po-config/domains");
7
+ let yaml = require("yaml");
8
+ //#region src/services/mcpFilter/index.ts
9
+ /** Server name in .mcp.json whose upstreams are declared in mcp-config.yaml. */
10
+ const PROXY_SERVER_NAME = "agiflow-proxy";
11
+ const PROXY_CONFIG_FILE = "mcp-config.yaml";
12
+ const CONFIG_ARGUMENT = "--config";
13
+ /**
14
+ * Unions the mcp allowlists of the selected domains.
15
+ *
16
+ * Returns undefined when any selected domain omits `mcp`, which means "give me
17
+ * everything". Filtering only kicks in once every selected domain has opted in,
18
+ * so a domain that has not been migrated yet cannot silently lose its tools.
19
+ */
20
+ function resolveMcpAllowlist(repoRoot, domainNames) {
21
+ const manifest = (0, _agimon_ai_po_config_domains.loadDomains)(repoRoot);
22
+ const expanded = (0, _agimon_ai_po_config_domains.expandDomainNames)(manifest, domainNames);
23
+ if (expanded.length === 0) return void 0;
24
+ const servers = /* @__PURE__ */ new Set();
25
+ const proxy = /* @__PURE__ */ new Set();
26
+ for (const name of expanded) {
27
+ const allowlist = manifest.domains[name]?.mcp;
28
+ if (!allowlist) return void 0;
29
+ for (const server of allowlist.servers ?? []) servers.add(server);
30
+ for (const upstream of allowlist.proxy ?? []) proxy.add(upstream);
31
+ }
32
+ return {
33
+ servers: [...servers],
34
+ proxy: [...proxy]
35
+ };
36
+ }
37
+ /** Drops servers that are not on the allowlist. An empty list means keep all. */
38
+ function filterMcpServers(config, allowed) {
39
+ if (!allowed || allowed.length === 0) return config;
40
+ const servers = config.mcpServers ?? {};
41
+ const kept = {};
42
+ for (const [name, definition] of Object.entries(servers)) if (allowed.includes(name)) kept[name] = definition;
43
+ return {
44
+ ...config,
45
+ mcpServers: kept
46
+ };
47
+ }
48
+ /**
49
+ * Rewrites the proxy's upstream list, keeping only allowed servers.
50
+ *
51
+ * Entries are removed rather than marked disabled so a filtered upstream cannot
52
+ * be reached even if the proxy ignores the disabled flag. Comments in the
53
+ * source file are not preserved; the result is a per-run temporary file.
54
+ */
55
+ function filterProxyConfig(yamlText, allowed) {
56
+ if (!allowed || allowed.length === 0) return yamlText;
57
+ const config = (0, yaml.parse)(yamlText) ?? {};
58
+ const servers = config.mcpServers ?? {};
59
+ const kept = {};
60
+ const dropped = [];
61
+ for (const [name, definition] of Object.entries(servers)) if (allowed.includes(name)) kept[name] = definition;
62
+ else dropped.push(name);
63
+ return `${[
64
+ "# Generated by po for this run. Do not edit.",
65
+ `# Upstreams kept: ${Object.keys(kept).join(", ") || "(none)"}`,
66
+ `# Upstreams removed by the domain allowlist: ${dropped.join(", ") || "(none)"}`,
67
+ ""
68
+ ].join("\n")}${(0, yaml.stringify)({
69
+ ...config,
70
+ mcpServers: kept
71
+ })}`;
72
+ }
73
+ async function copyFileIfPresent(source, target) {
74
+ try {
75
+ await node_fs.default.promises.copyFile(source, target);
76
+ return true;
77
+ } catch (error) {
78
+ if (error.code === "ENOENT") return false;
79
+ throw error;
80
+ }
81
+ }
82
+ /**
83
+ * Copies a generated MCP config out of the run directory so another frontend
84
+ * can use it, keeping the proxy's --config pointing at the copy.
85
+ *
86
+ * The run directory is deleted when the harness exits, so emitting for
87
+ * claude.sh or codex.sh has to relocate both files together.
88
+ */
89
+ async function persistMcpConfig(sourceConfigPath, temporaryDirectory, targetDirectory) {
90
+ await node_fs.default.promises.mkdir(targetDirectory, { recursive: true });
91
+ const config = JSON.parse(await node_fs.default.promises.readFile(sourceConfigPath, "utf8"));
92
+ const servers = config.mcpServers ?? {};
93
+ const proxy = servers[PROXY_SERVER_NAME];
94
+ const generatedProxyConfig = node_path.default.join(temporaryDirectory, PROXY_CONFIG_FILE);
95
+ if (proxy) {
96
+ const args = [...proxy.args ?? []];
97
+ const configIndex = args.indexOf(CONFIG_ARGUMENT);
98
+ if (configIndex !== -1) {
99
+ const targetProxyConfig = node_path.default.join(targetDirectory, PROXY_CONFIG_FILE);
100
+ if (await copyFileIfPresent(generatedProxyConfig, targetProxyConfig)) {
101
+ args[configIndex + 1] = targetProxyConfig;
102
+ servers[PROXY_SERVER_NAME] = {
103
+ ...proxy,
104
+ args
105
+ };
106
+ }
107
+ }
108
+ }
109
+ const targetPath = node_path.default.join(targetDirectory, "mcp.json");
110
+ await node_fs.default.promises.writeFile(targetPath, `${JSON.stringify({
111
+ ...config,
112
+ mcpServers: servers
113
+ }, null, 2)}\n`, { mode: 384 });
114
+ return targetPath;
115
+ }
116
+ /**
117
+ * Applies the allowlist to a merged MCP config, writing a filtered proxy config
118
+ * into the run directory and repointing the proxy at it when needed.
119
+ */
120
+ async function applyMcpAllowlist(config, allowlist, repoRoot, temporaryDirectory) {
121
+ if (!allowlist) return config;
122
+ const filtered = filterMcpServers(config, allowlist.servers);
123
+ const servers = filtered.mcpServers ?? {};
124
+ const proxy = servers[PROXY_SERVER_NAME];
125
+ if (!proxy || !allowlist.proxy || allowlist.proxy.length === 0) return filtered;
126
+ const args = [...proxy.args ?? []];
127
+ const configIndex = args.indexOf(CONFIG_ARGUMENT);
128
+ if (configIndex === -1 || !args[configIndex + 1]) return filtered;
129
+ const sourcePath = node_path.default.resolve(repoRoot, args[configIndex + 1]);
130
+ let sourceConfig;
131
+ try {
132
+ sourceConfig = await node_fs.default.promises.readFile(sourcePath, "utf8");
133
+ } catch (error) {
134
+ if (error.code === "ENOENT") return filtered;
135
+ throw error;
136
+ }
137
+ const targetPath = node_path.default.join(temporaryDirectory, PROXY_CONFIG_FILE);
138
+ await node_fs.default.promises.writeFile(targetPath, filterProxyConfig(sourceConfig, allowlist.proxy), { mode: 384 });
139
+ args[configIndex + 1] = targetPath;
140
+ return {
141
+ ...filtered,
142
+ mcpServers: {
143
+ ...servers,
144
+ [PROXY_SERVER_NAME]: {
145
+ ...proxy,
146
+ args
147
+ }
148
+ }
149
+ };
150
+ }
151
+ //#endregion
152
+ Object.defineProperty(exports, "PROXY_SERVER_NAME", {
153
+ enumerable: true,
154
+ get: function() {
155
+ return PROXY_SERVER_NAME;
156
+ }
157
+ });
158
+ Object.defineProperty(exports, "applyMcpAllowlist", {
159
+ enumerable: true,
160
+ get: function() {
161
+ return applyMcpAllowlist;
162
+ }
163
+ });
164
+ Object.defineProperty(exports, "filterMcpServers", {
165
+ enumerable: true,
166
+ get: function() {
167
+ return filterMcpServers;
168
+ }
169
+ });
170
+ Object.defineProperty(exports, "filterProxyConfig", {
171
+ enumerable: true,
172
+ get: function() {
173
+ return filterProxyConfig;
174
+ }
175
+ });
176
+ Object.defineProperty(exports, "persistMcpConfig", {
177
+ enumerable: true,
178
+ get: function() {
179
+ return persistMcpConfig;
180
+ }
181
+ });
182
+ Object.defineProperty(exports, "resolveMcpAllowlist", {
183
+ enumerable: true,
184
+ get: function() {
185
+ return resolveMcpAllowlist;
186
+ }
187
+ });
188
+
189
+ //# sourceMappingURL=mcpFilter-CZNmU_VF.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpFilter-CZNmU_VF.cjs","names":["loadDomains","expandDomainNames","parseYaml","stringifyYaml","fs","path"],"sources":["../src/services/mcpFilter/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { type DomainMcpAllowlist, expandDomainNames, loadDomains } from '@agimon-ai/po-config/domains';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport type { JsonObject } from '../../types/resources';\n\n/** Server name in .mcp.json whose upstreams are declared in mcp-config.yaml. */\nexport const PROXY_SERVER_NAME = 'agiflow-proxy';\nconst PROXY_CONFIG_FILE = 'mcp-config.yaml';\nconst CONFIG_ARGUMENT = '--config';\n\n/**\n * Unions the mcp allowlists of the selected domains.\n *\n * Returns undefined when any selected domain omits `mcp`, which means \"give me\n * everything\". Filtering only kicks in once every selected domain has opted in,\n * so a domain that has not been migrated yet cannot silently lose its tools.\n */\nexport function resolveMcpAllowlist(repoRoot: string, domainNames: string[]): DomainMcpAllowlist | undefined {\n const manifest = loadDomains(repoRoot);\n const expanded = expandDomainNames(manifest, domainNames);\n if (expanded.length === 0) return undefined;\n\n const servers = new Set<string>();\n const proxy = new Set<string>();\n for (const name of expanded) {\n const allowlist = manifest.domains[name]?.mcp;\n if (!allowlist) return undefined;\n for (const server of allowlist.servers ?? []) servers.add(server);\n for (const upstream of allowlist.proxy ?? []) proxy.add(upstream);\n }\n return { servers: [...servers], proxy: [...proxy] };\n}\n\n/** Drops servers that are not on the allowlist. An empty list means keep all. */\nexport function filterMcpServers(config: JsonObject, allowed?: string[]): JsonObject {\n if (!allowed || allowed.length === 0) return config;\n const servers = (config.mcpServers ?? {}) as Record<string, unknown>;\n const kept: Record<string, unknown> = {};\n for (const [name, definition] of Object.entries(servers)) {\n if (allowed.includes(name)) kept[name] = definition;\n }\n return { ...config, mcpServers: kept };\n}\n\n/**\n * Rewrites the proxy's upstream list, keeping only allowed servers.\n *\n * Entries are removed rather than marked disabled so a filtered upstream cannot\n * be reached even if the proxy ignores the disabled flag. Comments in the\n * source file are not preserved; the result is a per-run temporary file.\n */\nexport function filterProxyConfig(yamlText: string, allowed?: string[]): string {\n if (!allowed || allowed.length === 0) return yamlText;\n const config = (parseYaml(yamlText) ?? {}) as JsonObject;\n const servers = (config.mcpServers ?? {}) as Record<string, unknown>;\n const kept: Record<string, unknown> = {};\n const dropped: string[] = [];\n for (const [name, definition] of Object.entries(servers)) {\n if (allowed.includes(name)) kept[name] = definition;\n else dropped.push(name);\n }\n const header = [\n '# Generated by po for this run. Do not edit.',\n `# Upstreams kept: ${Object.keys(kept).join(', ') || '(none)'}`,\n `# Upstreams removed by the domain allowlist: ${dropped.join(', ') || '(none)'}`,\n '',\n ].join('\\n');\n return `${header}${stringifyYaml({ ...config, mcpServers: kept })}`;\n}\n\nasync function copyFileIfPresent(source: string, target: string): Promise<boolean> {\n try {\n await fs.promises.copyFile(source, target);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;\n throw error;\n }\n}\n\n/**\n * Copies a generated MCP config out of the run directory so another frontend\n * can use it, keeping the proxy's --config pointing at the copy.\n *\n * The run directory is deleted when the harness exits, so emitting for\n * claude.sh or codex.sh has to relocate both files together.\n */\nexport async function persistMcpConfig(\n sourceConfigPath: string,\n temporaryDirectory: string,\n targetDirectory: string,\n): Promise<string> {\n await fs.promises.mkdir(targetDirectory, { recursive: true });\n const config = JSON.parse(await fs.promises.readFile(sourceConfigPath, 'utf8')) as JsonObject;\n const servers = (config.mcpServers ?? {}) as Record<string, JsonObject>;\n const proxy = servers[PROXY_SERVER_NAME];\n const generatedProxyConfig = path.join(temporaryDirectory, PROXY_CONFIG_FILE);\n\n if (proxy) {\n const args = [...((proxy.args ?? []) as string[])];\n const configIndex = args.indexOf(CONFIG_ARGUMENT);\n if (configIndex !== -1) {\n const targetProxyConfig = path.join(targetDirectory, PROXY_CONFIG_FILE);\n if (await copyFileIfPresent(generatedProxyConfig, targetProxyConfig)) {\n args[configIndex + 1] = targetProxyConfig;\n servers[PROXY_SERVER_NAME] = { ...proxy, args };\n }\n }\n }\n\n const targetPath = path.join(targetDirectory, 'mcp.json');\n await fs.promises.writeFile(targetPath, `${JSON.stringify({ ...config, mcpServers: servers }, null, 2)}\\n`, {\n mode: 0o600,\n });\n return targetPath;\n}\n\n/**\n * Applies the allowlist to a merged MCP config, writing a filtered proxy config\n * into the run directory and repointing the proxy at it when needed.\n */\nexport async function applyMcpAllowlist(\n config: JsonObject,\n allowlist: DomainMcpAllowlist | undefined,\n repoRoot: string,\n temporaryDirectory: string,\n): Promise<JsonObject> {\n if (!allowlist) return config;\n const filtered = filterMcpServers(config, allowlist.servers);\n const servers = (filtered.mcpServers ?? {}) as Record<string, JsonObject>;\n const proxy = servers[PROXY_SERVER_NAME];\n if (!proxy || !allowlist.proxy || allowlist.proxy.length === 0) return filtered;\n\n const args = [...((proxy.args ?? []) as string[])];\n const configIndex = args.indexOf(CONFIG_ARGUMENT);\n if (configIndex === -1 || !args[configIndex + 1]) return filtered;\n\n const sourcePath = path.resolve(repoRoot, args[configIndex + 1]);\n let sourceConfig: string;\n try {\n sourceConfig = await fs.promises.readFile(sourcePath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return filtered;\n throw error;\n }\n const targetPath = path.join(temporaryDirectory, PROXY_CONFIG_FILE);\n await fs.promises.writeFile(targetPath, filterProxyConfig(sourceConfig, allowlist.proxy), { mode: 0o600 });\n args[configIndex + 1] = targetPath;\n return { ...filtered, mcpServers: { ...servers, [PROXY_SERVER_NAME]: { ...proxy, args } } };\n}\n"],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { expandDomainNames, loadDomains } from "@agimon-ai/po-config/domains";
4
+ import { parse, stringify } from "yaml";
5
+ //#region src/services/mcpFilter/index.ts
6
+ /** Server name in .mcp.json whose upstreams are declared in mcp-config.yaml. */
7
+ const PROXY_SERVER_NAME = "agiflow-proxy";
8
+ const PROXY_CONFIG_FILE = "mcp-config.yaml";
9
+ const CONFIG_ARGUMENT = "--config";
10
+ /**
11
+ * Unions the mcp allowlists of the selected domains.
12
+ *
13
+ * Returns undefined when any selected domain omits `mcp`, which means "give me
14
+ * everything". Filtering only kicks in once every selected domain has opted in,
15
+ * so a domain that has not been migrated yet cannot silently lose its tools.
16
+ */
17
+ function resolveMcpAllowlist(repoRoot, domainNames) {
18
+ const manifest = loadDomains(repoRoot);
19
+ const expanded = expandDomainNames(manifest, domainNames);
20
+ if (expanded.length === 0) return void 0;
21
+ const servers = /* @__PURE__ */ new Set();
22
+ const proxy = /* @__PURE__ */ new Set();
23
+ for (const name of expanded) {
24
+ const allowlist = manifest.domains[name]?.mcp;
25
+ if (!allowlist) return void 0;
26
+ for (const server of allowlist.servers ?? []) servers.add(server);
27
+ for (const upstream of allowlist.proxy ?? []) proxy.add(upstream);
28
+ }
29
+ return {
30
+ servers: [...servers],
31
+ proxy: [...proxy]
32
+ };
33
+ }
34
+ /** Drops servers that are not on the allowlist. An empty list means keep all. */
35
+ function filterMcpServers(config, allowed) {
36
+ if (!allowed || allowed.length === 0) return config;
37
+ const servers = config.mcpServers ?? {};
38
+ const kept = {};
39
+ for (const [name, definition] of Object.entries(servers)) if (allowed.includes(name)) kept[name] = definition;
40
+ return {
41
+ ...config,
42
+ mcpServers: kept
43
+ };
44
+ }
45
+ /**
46
+ * Rewrites the proxy's upstream list, keeping only allowed servers.
47
+ *
48
+ * Entries are removed rather than marked disabled so a filtered upstream cannot
49
+ * be reached even if the proxy ignores the disabled flag. Comments in the
50
+ * source file are not preserved; the result is a per-run temporary file.
51
+ */
52
+ function filterProxyConfig(yamlText, allowed) {
53
+ if (!allowed || allowed.length === 0) return yamlText;
54
+ const config = parse(yamlText) ?? {};
55
+ const servers = config.mcpServers ?? {};
56
+ const kept = {};
57
+ const dropped = [];
58
+ for (const [name, definition] of Object.entries(servers)) if (allowed.includes(name)) kept[name] = definition;
59
+ else dropped.push(name);
60
+ return `${[
61
+ "# Generated by po for this run. Do not edit.",
62
+ `# Upstreams kept: ${Object.keys(kept).join(", ") || "(none)"}`,
63
+ `# Upstreams removed by the domain allowlist: ${dropped.join(", ") || "(none)"}`,
64
+ ""
65
+ ].join("\n")}${stringify({
66
+ ...config,
67
+ mcpServers: kept
68
+ })}`;
69
+ }
70
+ async function copyFileIfPresent(source, target) {
71
+ try {
72
+ await fs.promises.copyFile(source, target);
73
+ return true;
74
+ } catch (error) {
75
+ if (error.code === "ENOENT") return false;
76
+ throw error;
77
+ }
78
+ }
79
+ /**
80
+ * Copies a generated MCP config out of the run directory so another frontend
81
+ * can use it, keeping the proxy's --config pointing at the copy.
82
+ *
83
+ * The run directory is deleted when the harness exits, so emitting for
84
+ * claude.sh or codex.sh has to relocate both files together.
85
+ */
86
+ async function persistMcpConfig(sourceConfigPath, temporaryDirectory, targetDirectory) {
87
+ await fs.promises.mkdir(targetDirectory, { recursive: true });
88
+ const config = JSON.parse(await fs.promises.readFile(sourceConfigPath, "utf8"));
89
+ const servers = config.mcpServers ?? {};
90
+ const proxy = servers[PROXY_SERVER_NAME];
91
+ const generatedProxyConfig = path.join(temporaryDirectory, PROXY_CONFIG_FILE);
92
+ if (proxy) {
93
+ const args = [...proxy.args ?? []];
94
+ const configIndex = args.indexOf(CONFIG_ARGUMENT);
95
+ if (configIndex !== -1) {
96
+ const targetProxyConfig = path.join(targetDirectory, PROXY_CONFIG_FILE);
97
+ if (await copyFileIfPresent(generatedProxyConfig, targetProxyConfig)) {
98
+ args[configIndex + 1] = targetProxyConfig;
99
+ servers[PROXY_SERVER_NAME] = {
100
+ ...proxy,
101
+ args
102
+ };
103
+ }
104
+ }
105
+ }
106
+ const targetPath = path.join(targetDirectory, "mcp.json");
107
+ await fs.promises.writeFile(targetPath, `${JSON.stringify({
108
+ ...config,
109
+ mcpServers: servers
110
+ }, null, 2)}\n`, { mode: 384 });
111
+ return targetPath;
112
+ }
113
+ /**
114
+ * Applies the allowlist to a merged MCP config, writing a filtered proxy config
115
+ * into the run directory and repointing the proxy at it when needed.
116
+ */
117
+ async function applyMcpAllowlist(config, allowlist, repoRoot, temporaryDirectory) {
118
+ if (!allowlist) return config;
119
+ const filtered = filterMcpServers(config, allowlist.servers);
120
+ const servers = filtered.mcpServers ?? {};
121
+ const proxy = servers[PROXY_SERVER_NAME];
122
+ if (!proxy || !allowlist.proxy || allowlist.proxy.length === 0) return filtered;
123
+ const args = [...proxy.args ?? []];
124
+ const configIndex = args.indexOf(CONFIG_ARGUMENT);
125
+ if (configIndex === -1 || !args[configIndex + 1]) return filtered;
126
+ const sourcePath = path.resolve(repoRoot, args[configIndex + 1]);
127
+ let sourceConfig;
128
+ try {
129
+ sourceConfig = await fs.promises.readFile(sourcePath, "utf8");
130
+ } catch (error) {
131
+ if (error.code === "ENOENT") return filtered;
132
+ throw error;
133
+ }
134
+ const targetPath = path.join(temporaryDirectory, PROXY_CONFIG_FILE);
135
+ await fs.promises.writeFile(targetPath, filterProxyConfig(sourceConfig, allowlist.proxy), { mode: 384 });
136
+ args[configIndex + 1] = targetPath;
137
+ return {
138
+ ...filtered,
139
+ mcpServers: {
140
+ ...servers,
141
+ [PROXY_SERVER_NAME]: {
142
+ ...proxy,
143
+ args
144
+ }
145
+ }
146
+ };
147
+ }
148
+ //#endregion
149
+ export { persistMcpConfig as a, filterProxyConfig as i, applyMcpAllowlist as n, resolveMcpAllowlist as o, filterMcpServers as r, PROXY_SERVER_NAME as t };
150
+
151
+ //# sourceMappingURL=mcpFilter-Cf-czy8O.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpFilter-Cf-czy8O.mjs","names":["parseYaml","stringifyYaml"],"sources":["../src/services/mcpFilter/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { type DomainMcpAllowlist, expandDomainNames, loadDomains } from '@agimon-ai/po-config/domains';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport type { JsonObject } from '../../types/resources';\n\n/** Server name in .mcp.json whose upstreams are declared in mcp-config.yaml. */\nexport const PROXY_SERVER_NAME = 'agiflow-proxy';\nconst PROXY_CONFIG_FILE = 'mcp-config.yaml';\nconst CONFIG_ARGUMENT = '--config';\n\n/**\n * Unions the mcp allowlists of the selected domains.\n *\n * Returns undefined when any selected domain omits `mcp`, which means \"give me\n * everything\". Filtering only kicks in once every selected domain has opted in,\n * so a domain that has not been migrated yet cannot silently lose its tools.\n */\nexport function resolveMcpAllowlist(repoRoot: string, domainNames: string[]): DomainMcpAllowlist | undefined {\n const manifest = loadDomains(repoRoot);\n const expanded = expandDomainNames(manifest, domainNames);\n if (expanded.length === 0) return undefined;\n\n const servers = new Set<string>();\n const proxy = new Set<string>();\n for (const name of expanded) {\n const allowlist = manifest.domains[name]?.mcp;\n if (!allowlist) return undefined;\n for (const server of allowlist.servers ?? []) servers.add(server);\n for (const upstream of allowlist.proxy ?? []) proxy.add(upstream);\n }\n return { servers: [...servers], proxy: [...proxy] };\n}\n\n/** Drops servers that are not on the allowlist. An empty list means keep all. */\nexport function filterMcpServers(config: JsonObject, allowed?: string[]): JsonObject {\n if (!allowed || allowed.length === 0) return config;\n const servers = (config.mcpServers ?? {}) as Record<string, unknown>;\n const kept: Record<string, unknown> = {};\n for (const [name, definition] of Object.entries(servers)) {\n if (allowed.includes(name)) kept[name] = definition;\n }\n return { ...config, mcpServers: kept };\n}\n\n/**\n * Rewrites the proxy's upstream list, keeping only allowed servers.\n *\n * Entries are removed rather than marked disabled so a filtered upstream cannot\n * be reached even if the proxy ignores the disabled flag. Comments in the\n * source file are not preserved; the result is a per-run temporary file.\n */\nexport function filterProxyConfig(yamlText: string, allowed?: string[]): string {\n if (!allowed || allowed.length === 0) return yamlText;\n const config = (parseYaml(yamlText) ?? {}) as JsonObject;\n const servers = (config.mcpServers ?? {}) as Record<string, unknown>;\n const kept: Record<string, unknown> = {};\n const dropped: string[] = [];\n for (const [name, definition] of Object.entries(servers)) {\n if (allowed.includes(name)) kept[name] = definition;\n else dropped.push(name);\n }\n const header = [\n '# Generated by po for this run. Do not edit.',\n `# Upstreams kept: ${Object.keys(kept).join(', ') || '(none)'}`,\n `# Upstreams removed by the domain allowlist: ${dropped.join(', ') || '(none)'}`,\n '',\n ].join('\\n');\n return `${header}${stringifyYaml({ ...config, mcpServers: kept })}`;\n}\n\nasync function copyFileIfPresent(source: string, target: string): Promise<boolean> {\n try {\n await fs.promises.copyFile(source, target);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;\n throw error;\n }\n}\n\n/**\n * Copies a generated MCP config out of the run directory so another frontend\n * can use it, keeping the proxy's --config pointing at the copy.\n *\n * The run directory is deleted when the harness exits, so emitting for\n * claude.sh or codex.sh has to relocate both files together.\n */\nexport async function persistMcpConfig(\n sourceConfigPath: string,\n temporaryDirectory: string,\n targetDirectory: string,\n): Promise<string> {\n await fs.promises.mkdir(targetDirectory, { recursive: true });\n const config = JSON.parse(await fs.promises.readFile(sourceConfigPath, 'utf8')) as JsonObject;\n const servers = (config.mcpServers ?? {}) as Record<string, JsonObject>;\n const proxy = servers[PROXY_SERVER_NAME];\n const generatedProxyConfig = path.join(temporaryDirectory, PROXY_CONFIG_FILE);\n\n if (proxy) {\n const args = [...((proxy.args ?? []) as string[])];\n const configIndex = args.indexOf(CONFIG_ARGUMENT);\n if (configIndex !== -1) {\n const targetProxyConfig = path.join(targetDirectory, PROXY_CONFIG_FILE);\n if (await copyFileIfPresent(generatedProxyConfig, targetProxyConfig)) {\n args[configIndex + 1] = targetProxyConfig;\n servers[PROXY_SERVER_NAME] = { ...proxy, args };\n }\n }\n }\n\n const targetPath = path.join(targetDirectory, 'mcp.json');\n await fs.promises.writeFile(targetPath, `${JSON.stringify({ ...config, mcpServers: servers }, null, 2)}\\n`, {\n mode: 0o600,\n });\n return targetPath;\n}\n\n/**\n * Applies the allowlist to a merged MCP config, writing a filtered proxy config\n * into the run directory and repointing the proxy at it when needed.\n */\nexport async function applyMcpAllowlist(\n config: JsonObject,\n allowlist: DomainMcpAllowlist | undefined,\n repoRoot: string,\n temporaryDirectory: string,\n): Promise<JsonObject> {\n if (!allowlist) return config;\n const filtered = filterMcpServers(config, allowlist.servers);\n const servers = (filtered.mcpServers ?? {}) as Record<string, JsonObject>;\n const proxy = servers[PROXY_SERVER_NAME];\n if (!proxy || !allowlist.proxy || allowlist.proxy.length === 0) return filtered;\n\n const args = [...((proxy.args ?? []) as string[])];\n const configIndex = args.indexOf(CONFIG_ARGUMENT);\n if (configIndex === -1 || !args[configIndex + 1]) return filtered;\n\n const sourcePath = path.resolve(repoRoot, args[configIndex + 1]);\n let sourceConfig: string;\n try {\n sourceConfig = await fs.promises.readFile(sourcePath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return filtered;\n throw error;\n }\n const targetPath = path.join(temporaryDirectory, PROXY_CONFIG_FILE);\n await fs.promises.writeFile(targetPath, filterProxyConfig(sourceConfig, allowlist.proxy), { mode: 0o600 });\n args[configIndex + 1] = targetPath;\n return { ...filtered, mcpServers: { ...servers, [PROXY_SERVER_NAME]: { ...proxy, args } } };\n}\n"],"mappings":";;;;;;AASA,MAAa,oBAAoB;AACjC,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;;;;;;;;AASxB,SAAgB,oBAAoB,UAAkB,aAAuD;CAC3G,MAAM,WAAW,YAAY,QAAQ;CACrC,MAAM,WAAW,kBAAkB,UAAU,WAAW;CACxD,IAAI,SAAS,WAAW,GAAG,OAAO,KAAA;CAElC,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,QAAQ,UAAU;EAC3B,MAAM,YAAY,SAAS,QAAQ,KAAK,EAAE;EAC1C,IAAI,CAAC,WAAW,OAAO,KAAA;EACvB,KAAK,MAAM,UAAU,UAAU,WAAW,CAAC,GAAG,QAAQ,IAAI,MAAM;EAChE,KAAK,MAAM,YAAY,UAAU,SAAS,CAAC,GAAG,MAAM,IAAI,QAAQ;CAClE;CACA,OAAO;EAAE,SAAS,CAAC,GAAG,OAAO;EAAG,OAAO,CAAC,GAAG,KAAK;CAAE;AACpD;;AAGA,SAAgB,iBAAiB,QAAoB,SAAgC;CACnF,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO;CAC7C,MAAM,UAAW,OAAO,cAAc,CAAC;CACvC,MAAM,OAAgC,CAAC;CACvC,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,OAAO,GACrD,IAAI,QAAQ,SAAS,IAAI,GAAG,KAAK,QAAQ;CAE3C,OAAO;EAAE,GAAG;EAAQ,YAAY;CAAK;AACvC;;;;;;;;AASA,SAAgB,kBAAkB,UAAkB,SAA4B;CAC9E,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO;CAC7C,MAAM,SAAUA,MAAU,QAAQ,KAAK,CAAC;CACxC,MAAM,UAAW,OAAO,cAAc,CAAC;CACvC,MAAM,OAAgC,CAAC;CACvC,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,OAAO,GACrD,IAAI,QAAQ,SAAS,IAAI,GAAG,KAAK,QAAQ;MACpC,QAAQ,KAAK,IAAI;CAQxB,OAAO,GANQ;EACb;EACA,qBAAqB,OAAO,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI,KAAK;EACrD,gDAAgD,QAAQ,KAAK,IAAI,KAAK;EACtE;CACF,CAAC,CAAC,KAAK,IACQ,IAAIC,UAAc;EAAE,GAAG;EAAQ,YAAY;CAAK,CAAC;AAClE;AAEA,eAAe,kBAAkB,QAAgB,QAAkC;CACjF,IAAI;EACF,MAAM,GAAG,SAAS,SAAS,QAAQ,MAAM;EACzC,OAAO;CACT,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO;EAC/D,MAAM;CACR;AACF;;;;;;;;AASA,eAAsB,iBACpB,kBACA,oBACA,iBACiB;CACjB,MAAM,GAAG,SAAS,MAAM,iBAAiB,EAAE,WAAW,KAAK,CAAC;CAC5D,MAAM,SAAS,KAAK,MAAM,MAAM,GAAG,SAAS,SAAS,kBAAkB,MAAM,CAAC;CAC9E,MAAM,UAAW,OAAO,cAAc,CAAC;CACvC,MAAM,QAAQ,QAAQ;CACtB,MAAM,uBAAuB,KAAK,KAAK,oBAAoB,iBAAiB;CAE5E,IAAI,OAAO;EACT,MAAM,OAAO,CAAC,GAAK,MAAM,QAAQ,CAAC,CAAe;EACjD,MAAM,cAAc,KAAK,QAAQ,eAAe;EAChD,IAAI,gBAAgB,IAAI;GACtB,MAAM,oBAAoB,KAAK,KAAK,iBAAiB,iBAAiB;GACtE,IAAI,MAAM,kBAAkB,sBAAsB,iBAAiB,GAAG;IACpE,KAAK,cAAc,KAAK;IACxB,QAAQ,qBAAqB;KAAE,GAAG;KAAO;IAAK;GAChD;EACF;CACF;CAEA,MAAM,aAAa,KAAK,KAAK,iBAAiB,UAAU;CACxD,MAAM,GAAG,SAAS,UAAU,YAAY,GAAG,KAAK,UAAU;EAAE,GAAG;EAAQ,YAAY;CAAQ,GAAG,MAAM,CAAC,EAAE,KAAK,EAC1G,MAAM,IACR,CAAC;CACD,OAAO;AACT;;;;;AAMA,eAAsB,kBACpB,QACA,WACA,UACA,oBACqB;CACrB,IAAI,CAAC,WAAW,OAAO;CACvB,MAAM,WAAW,iBAAiB,QAAQ,UAAU,OAAO;CAC3D,MAAM,UAAW,SAAS,cAAc,CAAC;CACzC,MAAM,QAAQ,QAAQ;CACtB,IAAI,CAAC,SAAS,CAAC,UAAU,SAAS,UAAU,MAAM,WAAW,GAAG,OAAO;CAEvE,MAAM,OAAO,CAAC,GAAK,MAAM,QAAQ,CAAC,CAAe;CACjD,MAAM,cAAc,KAAK,QAAQ,eAAe;CAChD,IAAI,gBAAgB,MAAM,CAAC,KAAK,cAAc,IAAI,OAAO;CAEzD,MAAM,aAAa,KAAK,QAAQ,UAAU,KAAK,cAAc,EAAE;CAC/D,IAAI;CACJ,IAAI;EACF,eAAe,MAAM,GAAG,SAAS,SAAS,YAAY,MAAM;CAC9D,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO;EAC/D,MAAM;CACR;CACA,MAAM,aAAa,KAAK,KAAK,oBAAoB,iBAAiB;CAClE,MAAM,GAAG,SAAS,UAAU,YAAY,kBAAkB,cAAc,UAAU,KAAK,GAAG,EAAE,MAAM,IAAM,CAAC;CACzG,KAAK,cAAc,KAAK;CACxB,OAAO;EAAE,GAAG;EAAU,YAAY;GAAE,GAAG;IAAU,oBAAoB;IAAE,GAAG;IAAO;GAAK;EAAE;CAAE;AAC5F"}
@@ -0,0 +1,267 @@
1
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
2
+ let node_fs = require("node:fs");
3
+ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
4
+ let node_path = require("node:path");
5
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
6
+ let _agimon_ai_po_config_domains = require("@agimon-ai/po-config/domains");
7
+ let node_child_process = require("node:child_process");
8
+ let _agimon_ai_po_core_piSettings = require("@agimon-ai/po-core/piSettings");
9
+ //#region src/services/pluginMaterializer/index.ts
10
+ const AGENT_PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
11
+ const INSTALL_MARKER = ".po-plugin.json";
12
+ const INSTALL_MARKER_VERSION = 1;
13
+ const OUTPUT_LIMIT = 65536;
14
+ /** The staging project that declares the one npm plugin being installed. */
15
+ const STAGING_PACKAGE_NAME = "po-plugin-staging";
16
+ const LATEST_TAG = "latest";
17
+ function appendOutput(current, chunk) {
18
+ const next = current + chunk.toString("utf8");
19
+ return next.length <= OUTPUT_LIMIT ? next : next.slice(-65536);
20
+ }
21
+ function runCommand(command, args, options = {}) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = (0, node_child_process.spawn)(command, args, {
24
+ cwd: options.cwd,
25
+ env: options.environment ?? process.env,
26
+ stdio: [
27
+ "ignore",
28
+ "pipe",
29
+ "pipe"
30
+ ]
31
+ });
32
+ let stdout = "";
33
+ let stderr = "";
34
+ child.stdout.on("data", (chunk) => {
35
+ stdout = appendOutput(stdout, chunk);
36
+ });
37
+ child.stderr.on("data", (chunk) => {
38
+ stderr = appendOutput(stderr, chunk);
39
+ });
40
+ child.once("error", (error) => {
41
+ reject(new Error(`Failed to run ${command}: ${error.message}`, { cause: error }));
42
+ });
43
+ child.once("close", (code, signal) => {
44
+ if (code === 0) {
45
+ resolve(stdout.trim());
46
+ return;
47
+ }
48
+ const outcome = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
49
+ const details = stderr.trim() || stdout.trim();
50
+ reject(/* @__PURE__ */ new Error(`${command} ${args.join(" ")} failed with ${outcome}${details ? `\n${details}` : ""}`));
51
+ });
52
+ });
53
+ }
54
+ function assertContained(root, candidate, label) {
55
+ const relative = node_path.default.relative(root, candidate);
56
+ if (relative === "" || !relative.startsWith("..") && !node_path.default.isAbsolute(relative)) return;
57
+ throw new Error(`${label} resolves outside ${root}`);
58
+ }
59
+ async function materializeGitSource(source, workspace) {
60
+ const checkout = node_path.default.join(workspace, "checkout");
61
+ const pluginRoot = source.path ? node_path.default.resolve(checkout, source.path) : checkout;
62
+ if (source.path) assertContained(checkout, pluginRoot, "Git plugin path");
63
+ const cloneArgs = ["clone", "--filter=blob:none"];
64
+ if (source.path) cloneArgs.push("--sparse", "--no-checkout");
65
+ cloneArgs.push(source.url, checkout);
66
+ const environment = {
67
+ ...process.env,
68
+ GIT_TERMINAL_PROMPT: "0"
69
+ };
70
+ await runCommand("git", cloneArgs, { environment });
71
+ if (source.path) await runCommand("git", [
72
+ "sparse-checkout",
73
+ "set",
74
+ "--no-cone",
75
+ "--",
76
+ source.path
77
+ ], {
78
+ cwd: checkout,
79
+ environment
80
+ });
81
+ if (source.sha) {
82
+ await runCommand("git", ["checkout", source.sha], {
83
+ cwd: checkout,
84
+ environment
85
+ });
86
+ const checkedOutSha = await runCommand("git", ["rev-parse", "HEAD"], {
87
+ cwd: checkout,
88
+ environment
89
+ });
90
+ if (checkedOutSha.toLowerCase() !== source.sha.toLowerCase()) throw new Error(`Checked out Git SHA ${checkedOutSha} does not match requested SHA ${source.sha}`);
91
+ } else if (source.ref) await runCommand("git", ["checkout", source.ref], {
92
+ cwd: checkout,
93
+ environment
94
+ });
95
+ else if (source.path) await runCommand("git", ["checkout"], {
96
+ cwd: checkout,
97
+ environment
98
+ });
99
+ return pluginRoot;
100
+ }
101
+ /**
102
+ * Installs an npm plugin into the staging workspace with upm: a one-dependency
103
+ * project, never a spawned package manager. upm runs no lifecycle scripts, and
104
+ * its store is Po's shared `<po home>/store`.
105
+ */
106
+ async function materializeNpmSource(source, workspace) {
107
+ const prefix = node_path.default.join(workspace, "npm");
108
+ await node_fs.default.promises.mkdir(prefix, { recursive: true });
109
+ await node_fs.default.promises.writeFile(node_path.default.join(prefix, "package.json"), `${JSON.stringify({
110
+ name: STAGING_PACKAGE_NAME,
111
+ private: true,
112
+ dependencies: { [source.package]: source.version ?? LATEST_TAG }
113
+ }, null, 2)}\n`);
114
+ const { installRuntimePackages } = await import("@agimon-ai/po-core/runtimePackages");
115
+ await installRuntimePackages({
116
+ dir: prefix,
117
+ poHome: (0, _agimon_ai_po_core_piSettings.poHomeDirectory)(),
118
+ ...source.registry ? { registry: source.registry } : {}
119
+ });
120
+ const pluginRoot = node_path.default.resolve(prefix, "node_modules", ...source.package.split("/"));
121
+ assertContained(node_path.default.join(prefix, "node_modules"), pluginRoot, "npm plugin package");
122
+ return node_fs.default.promises.realpath(pluginRoot);
123
+ }
124
+ const DEFAULT_MATERIALIZERS = {
125
+ git: materializeGitSource,
126
+ npm: materializeNpmSource
127
+ };
128
+ async function pathType(target) {
129
+ try {
130
+ return (await node_fs.default.promises.lstat(target)).isDirectory() ? "directory" : "other";
131
+ } catch (error) {
132
+ if (error.code === "ENOENT") return "missing";
133
+ throw error;
134
+ }
135
+ }
136
+ function optionalManifestString(manifest, key) {
137
+ const value = manifest[key];
138
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
139
+ }
140
+ function validatePluginManifest(pluginRoot, manifestPath) {
141
+ try {
142
+ const manifest = JSON.parse(node_fs.default.readFileSync(manifestPath, "utf8"));
143
+ if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) throw new Error("manifest must contain a JSON object");
144
+ const record = manifest;
145
+ const agentPluginSchema = manifestPath === node_path.default.join(pluginRoot, "plugin.json") && record.$schema === AGENT_PLUGIN_SCHEMA ? AGENT_PLUGIN_SCHEMA : void 0;
146
+ const name = optionalManifestString(record, "name");
147
+ const version = optionalManifestString(record, "version");
148
+ const description = optionalManifestString(record, "description");
149
+ return {
150
+ skillDiscovery: agentPluginSchema ? "direct-children" : "recursive",
151
+ manifest: {
152
+ path: manifestPath,
153
+ ...name ? { name } : {},
154
+ ...version ? { version } : {},
155
+ ...description ? { description } : {},
156
+ ...agentPluginSchema ? { agentPluginSchema } : {}
157
+ }
158
+ };
159
+ } catch (error) {
160
+ throw new Error(`Invalid plugin manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
161
+ }
162
+ }
163
+ async function inspectInstallation(entry) {
164
+ const type = await pathType(entry.directory);
165
+ if (type === "missing") return false;
166
+ if (type !== "directory") throw new Error(`Remote plugin cache path is not a directory: ${entry.directory}`);
167
+ if (!entry.source || !(0, _agimon_ai_po_config_domains.isRemotePluginSource)(entry.source)) throw new Error(`Remote plugin entry is missing its source: ${entry.name ?? entry.directory}`);
168
+ let marker;
169
+ try {
170
+ marker = JSON.parse(await node_fs.default.promises.readFile(node_path.default.join(entry.directory, INSTALL_MARKER), "utf8"));
171
+ } catch (error) {
172
+ throw new Error(`Remote plugin cache is not managed by Po: ${entry.directory}`, { cause: error });
173
+ }
174
+ if (marker.version !== INSTALL_MARKER_VERSION || JSON.stringify(marker.source) !== JSON.stringify(entry.source)) throw new Error(`Remote plugin cache metadata does not match ${entry.name ?? entry.directory}: ${entry.directory}`);
175
+ const manifestPath = (0, _agimon_ai_po_config_domains.findPluginManifestPath)(entry.directory);
176
+ if (!manifestPath) throw new Error(`Remote plugin cache is missing a supported plugin manifest: ${entry.directory}`);
177
+ return validatePluginManifest(entry.directory, manifestPath);
178
+ }
179
+ async function copyPluginSource(sourceRoot, destination) {
180
+ if (await pathType(sourceRoot) !== "directory") throw new Error(`Materialized plugin source is not a directory: ${sourceRoot}`);
181
+ await node_fs.default.promises.cp(sourceRoot, destination, {
182
+ recursive: true,
183
+ filter: (candidate) => node_path.default.basename(candidate) !== ".git"
184
+ });
185
+ }
186
+ function isRenameRace(error) {
187
+ const code = error.code;
188
+ return code === "EEXIST" || code === "ENOTEMPTY";
189
+ }
190
+ async function installRemotePlugin(entry, materializers) {
191
+ if (!entry.source || !(0, _agimon_ai_po_config_domains.isRemotePluginSource)(entry.source)) return entry;
192
+ const existingManifest = await inspectInstallation(entry);
193
+ if (existingManifest) return {
194
+ ...entry,
195
+ ...existingManifest
196
+ };
197
+ const parent = node_path.default.dirname(entry.directory);
198
+ await node_fs.default.promises.mkdir(parent, {
199
+ recursive: true,
200
+ mode: 448
201
+ });
202
+ if (await pathType(parent) !== "directory") throw new Error(`Remote plugin cache root is not a directory: ${parent}`);
203
+ await node_fs.default.promises.chmod(parent, 448);
204
+ const workspace = await node_fs.default.promises.mkdtemp(node_path.default.join(parent, ".plugin-staging-"));
205
+ const installation = node_path.default.join(workspace, "installation");
206
+ try {
207
+ await copyPluginSource(entry.source.type === "git" ? await materializers.git(entry.source, workspace) : await materializers.npm(entry.source, workspace), installation);
208
+ const manifestPath = (0, _agimon_ai_po_config_domains.findPluginManifestPath)(installation);
209
+ if (!manifestPath) throw new Error(`Remote plugin ${entry.name ?? entry.directory} does not contain a supported plugin manifest`);
210
+ validatePluginManifest(installation, manifestPath);
211
+ const markerPath = node_path.default.join(installation, INSTALL_MARKER);
212
+ if (await pathType(markerPath) !== "missing") throw new Error(`Remote plugin contains reserved cache metadata path: ${INSTALL_MARKER}`);
213
+ const marker = {
214
+ version: INSTALL_MARKER_VERSION,
215
+ source: entry.source
216
+ };
217
+ await node_fs.default.promises.writeFile(markerPath, `${JSON.stringify(marker, null, 2)}\n`, {
218
+ flag: "wx",
219
+ mode: 384
220
+ });
221
+ try {
222
+ await node_fs.default.promises.rename(installation, entry.directory);
223
+ } catch (error) {
224
+ if (!isRenameRace(error)) throw error;
225
+ await node_fs.default.promises.rm(installation, {
226
+ recursive: true,
227
+ force: true
228
+ });
229
+ const racedManifest = await inspectInstallation(entry);
230
+ if (!racedManifest) throw new Error(`Remote plugin installation race left no cache: ${entry.directory}`);
231
+ return {
232
+ ...entry,
233
+ ...racedManifest
234
+ };
235
+ }
236
+ const installedManifest = await inspectInstallation(entry);
237
+ if (!installedManifest) throw new Error(`Remote plugin installation left no cache: ${entry.directory}`);
238
+ return {
239
+ ...entry,
240
+ ...installedManifest
241
+ };
242
+ } catch (error) {
243
+ throw new Error(`Failed to install remote plugin ${entry.name ?? entry.directory}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
244
+ } finally {
245
+ await node_fs.default.promises.rm(workspace, {
246
+ recursive: true,
247
+ force: true
248
+ });
249
+ }
250
+ }
251
+ /** Downloads selected remote plugins once into their content-addressed cache directories. */
252
+ async function materializePluginEntries(entries, options = {}) {
253
+ const materializers = {
254
+ git: options.materializers?.git ?? DEFAULT_MATERIALIZERS.git,
255
+ npm: options.materializers?.npm ?? DEFAULT_MATERIALIZERS.npm
256
+ };
257
+ return Promise.all(entries.map((entry) => installRemotePlugin(entry, materializers)));
258
+ }
259
+ //#endregion
260
+ Object.defineProperty(exports, "materializePluginEntries", {
261
+ enumerable: true,
262
+ get: function() {
263
+ return materializePluginEntries;
264
+ }
265
+ });
266
+
267
+ //# sourceMappingURL=pluginMaterializer-C8IZ1PSY.cjs.map