agencyx-cli 0.0.0-stage → 0.1.0-dev.4

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 (39) hide show
  1. package/dist/adapters/agent.js +73 -0
  2. package/dist/adapters/ambient.js +35 -0
  3. package/dist/adapters/instructions.js +102 -0
  4. package/dist/adapters/mcp.js +237 -0
  5. package/dist/adapters/permissions.js +42 -0
  6. package/dist/adapters/plugins.js +484 -0
  7. package/dist/agency/invocation.js +97 -0
  8. package/dist/agency/prepared-input.js +124 -0
  9. package/dist/bridge/execution.js +119 -0
  10. package/dist/bridge/main.js +26 -0
  11. package/dist/bridge/plan.js +268 -0
  12. package/dist/bridge/version.js +2 -0
  13. package/dist/cli-options.js +96 -0
  14. package/dist/cli.js +41 -0
  15. package/dist/codex/agents.js +72 -0
  16. package/dist/codex/app-server.js +116 -0
  17. package/dist/codex/inspection.js +217 -0
  18. package/dist/codex/mcp-readiness.js +45 -0
  19. package/dist/codex/native-plugins.js +109 -0
  20. package/dist/codex/options.js +170 -0
  21. package/dist/codex/plugin-hooks.js +93 -0
  22. package/dist/codex/rpc.js +170 -0
  23. package/dist/codex/session-proxy.js +258 -0
  24. package/dist/codex/skill-policy.js +69 -0
  25. package/dist/codex/skill-views.js +104 -0
  26. package/dist/codex/toml.js +46 -0
  27. package/dist/commands/codex.js +118 -0
  28. package/dist/commands/doctor.js +58 -0
  29. package/dist/commands/passthrough.js +14 -0
  30. package/dist/diagnostics/plan.js +87 -0
  31. package/dist/diagnostics.js +39 -0
  32. package/dist/model.js +2 -0
  33. package/dist/runtime/ipc.js +316 -0
  34. package/dist/runtime/process.js +286 -0
  35. package/dist/runtime/resolve-command.js +38 -0
  36. package/dist/runtime/session.js +107 -0
  37. package/dist/validation.js +31 -0
  38. package/package.json +41 -4
  39. package/README.md +0 -3
@@ -0,0 +1,73 @@
1
+ import { readTextFile } from "../agency/prepared-input.js";
2
+ import { fail } from "../diagnostics.js";
3
+ import { assertKnownFields, isRecord, nonEmptyString, stringArray, } from "../validation.js";
4
+ import { markdownDocument } from "./instructions.js";
5
+ export async function adaptPrimaryAgent(file, effectiveConfig, toolsPolicy = "strict") {
6
+ const { metadata, body } = markdownDocument(await readTextFile(file), file);
7
+ assertKnownFields(metadata, [
8
+ "name",
9
+ "description",
10
+ "model",
11
+ "tools",
12
+ "mcp-servers",
13
+ "mcpServers",
14
+ "agency",
15
+ ], "primary agent");
16
+ if (metadata.tools !== undefined) {
17
+ const tools = typeof metadata.tools === "string"
18
+ ? metadata.tools.split(",").map((item) => item.trim())
19
+ : stringArray(metadata.tools, "agent.tools");
20
+ if (toolsPolicy !== "inherit" && !(tools.length === 1 && tools[0] === "*"))
21
+ fail("AGENT_TOOLS_UNSUPPORTED", "compatibility", "A restricted Copilot built-in/tool allow-list cannot be enforced by instructions. This agent requires a verified native tool policy.", { path: file, field: "tools" });
22
+ // Strict mode keeps explicit empty lists from widening access.
23
+ }
24
+ if (metadata.model !== undefined && metadata.model !== effectiveConfig.model)
25
+ fail("AGENT_MODEL_UNSUPPORTED", "compatibility", "The agent model requirement does not match the effective Codex model. No model-name substitution is performed.", { path: file, field: "model" });
26
+ for (const field of ["mcp-servers", "mcpServers"]) {
27
+ if (metadata[field] !== undefined &&
28
+ (!isRecord(metadata[field]) || Object.keys(metadata[field]).length))
29
+ fail("AGENT_MCP_UNVERIFIED", "compatibility", "The rendered agent still declares MCP behavior that was not proven to be in the Agency final configuration.", { path: file, field });
30
+ }
31
+ if (metadata.agency !== undefined) {
32
+ if (!isRecord(metadata.agency))
33
+ fail("AGENT_METADATA_INVALID", "configuration", "Agency agent metadata must be an object.", { path: file }, 5);
34
+ assertKnownFields(metadata.agency, ["inputs", "extra_args", "env", "description", "engine", "agent_type"], "agent.agency");
35
+ for (const field of ["extra_args", "env"]) {
36
+ const value = metadata.agency[field];
37
+ if (value !== undefined &&
38
+ value !== null &&
39
+ !(Array.isArray(value) && value.length === 0) &&
40
+ !(isRecord(value) && Object.keys(value).length === 0))
41
+ fail("AGENT_CONTRACT_UNVERIFIED", "compatibility", "The rendered agent contains execution metadata without a verified equivalent.", { path: file, field: `agency.${field}` });
42
+ }
43
+ if (metadata.agency.engine !== undefined &&
44
+ metadata.agency.engine !== "codex")
45
+ fail("AGENT_ENGINE_UNSUPPORTED", "compatibility", "The agent declares a different required execution engine.", { path: file, field: "agency.engine" });
46
+ }
47
+ if (!body.trim())
48
+ fail("AGENT_INSTRUCTIONS_EMPTY", "configuration", "The selected rendered agent has no instructions.", { path: file }, 5);
49
+ return body;
50
+ }
51
+ export async function readPluginAgent(file) {
52
+ const { metadata, body } = markdownDocument(await readTextFile(file), file);
53
+ assertKnownFields(metadata, ["name", "description", "model", "tools"], "plugin agent", file);
54
+ if (!body.trim())
55
+ fail("AGENT_INSTRUCTIONS_EMPTY", "configuration", "The plugin agent has no instructions.", { path: file }, 5);
56
+ const definition = {
57
+ name: nonEmptyString(metadata.name, "plugin agent name"),
58
+ description: nonEmptyString(metadata.description, "plugin agent description"),
59
+ instructions: body,
60
+ sourcePath: file,
61
+ };
62
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(definition.name))
63
+ fail("AGENT_NAME_UNSUPPORTED", "compatibility", "The plugin agent name contains unsupported characters.", { path: file, field: "name" });
64
+ if (metadata.tools !== undefined)
65
+ definition.declaredTools =
66
+ typeof metadata.tools === "string"
67
+ ? metadata.tools.split(",").map((tool) => tool.trim())
68
+ : stringArray(metadata.tools, "plugin agent tools");
69
+ if (metadata.model !== undefined)
70
+ definition.model = nonEmptyString(metadata.model, "plugin agent model");
71
+ return definition;
72
+ }
73
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1,35 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fail } from "../diagnostics.js";
4
+ async function exists(file) {
5
+ try {
6
+ await stat(file);
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ export async function validateAmbientCopilotContracts(prepared) {
14
+ for (const root of new Set([
15
+ prepared.repositoryRoot ?? prepared.cwd,
16
+ prepared.cwd,
17
+ ])) {
18
+ const hooks = path.join(root, ".github", "hooks");
19
+ if (await exists(hooks)) {
20
+ const entries = await readdir(hooks, { withFileTypes: true });
21
+ if (entries.some((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".json")))
22
+ fail("REPOSITORY_HOOKS_UNSUPPORTED", "compatibility", "The repository declares Copilot hooks whose execution and blocking semantics have not been adapted. They cannot be omitted from this launch.", { path: hooks });
23
+ }
24
+ if (prepared.extraInstructionRoots.some((directory) => path.resolve(directory) === path.resolve(root)))
25
+ continue;
26
+ const global = path.join(root, ".github", "copilot-instructions.md");
27
+ const instructions = path.join(root, ".github", "instructions");
28
+ if ((await exists(global)) ||
29
+ ((await exists(instructions)) &&
30
+ (await readdir(instructions)).some((name) => name.endsWith(".instructions.md")))) {
31
+ fail("REPOSITORY_INSTRUCTIONS_UNVERIFIED", "compatibility", "Copilot repository instructions require an explicit Agency instruction source and a verified scope/trust mapping. They are not silently omitted or elevated into developer instructions.", { path: root });
32
+ }
33
+ }
34
+ }
35
+ //# sourceMappingURL=ambient.js.map
@@ -0,0 +1,102 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { parseDocument } from "yaml";
4
+ import { readTextFile } from "../agency/prepared-input.js";
5
+ import { fail } from "../diagnostics.js";
6
+ import { assertKnownFields, isRecord } from "../validation.js";
7
+ export function markdownDocument(text, file) {
8
+ if (!/^---\r?\n/.test(text))
9
+ return { metadata: {}, body: text };
10
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/.exec(text);
11
+ if (!match)
12
+ fail("FRONTMATTER_INVALID", "configuration", "Markdown frontmatter is not terminated.", { path: file }, 5);
13
+ const document = parseDocument(match[1], { uniqueKeys: true });
14
+ if (document.errors.length)
15
+ fail("FRONTMATTER_INVALID", "configuration", "Markdown frontmatter must contain valid YAML with unique keys.", { path: file }, 5);
16
+ let metadata;
17
+ try {
18
+ metadata = document.toJS({ maxAliasCount: 20 });
19
+ }
20
+ catch {
21
+ fail("FRONTMATTER_INVALID", "configuration", "Markdown frontmatter contains excessive YAML aliases.", { path: file }, 5);
22
+ }
23
+ if (metadata !== null && !isRecord(metadata))
24
+ fail("FRONTMATTER_INVALID", "configuration", "Markdown frontmatter must be an object.", { path: file }, 5);
25
+ return { metadata: metadata ?? {}, body: match[2] };
26
+ }
27
+ export async function collectInstructions(roots) {
28
+ const collected = [];
29
+ const seen = new Set();
30
+ for (const root of roots) {
31
+ let metadata;
32
+ try {
33
+ metadata = await stat(root);
34
+ }
35
+ catch {
36
+ fail("INSTRUCTIONS_UNREADABLE", "configuration", "An Agency instruction root cannot be read.", { path: root }, 5);
37
+ }
38
+ if (!metadata.isDirectory())
39
+ fail("INSTRUCTIONS_UNREADABLE", "configuration", "An Agency instruction root must be a directory.", { path: root }, 5);
40
+ const files = [];
41
+ const global = path.join(root, ".github", "copilot-instructions.md");
42
+ try {
43
+ if ((await stat(global)).isFile())
44
+ files.push(global);
45
+ }
46
+ catch {
47
+ /* Optional global instruction file. */
48
+ }
49
+ await collectInstructionFiles(path.join(root, ".github", "instructions"), files);
50
+ for (const file of files.sort()) {
51
+ if (seen.has(file))
52
+ continue;
53
+ seen.add(file);
54
+ const { metadata, body } = markdownDocument(await readTextFile(file), file);
55
+ assertKnownFields(metadata, ["applyTo", "description", "name"], "Agency instructions");
56
+ if (metadata.applyTo !== undefined &&
57
+ metadata.applyTo !== "**" &&
58
+ metadata.applyTo !== "**/*")
59
+ fail("INSTRUCTION_SCOPE_UNSUPPORTED", "compatibility", "Scoped Copilot instructions cannot be made unconditional Codex developer instructions.", { path: file, field: "applyTo" });
60
+ if (body.trim())
61
+ collected.push(body);
62
+ }
63
+ }
64
+ return collected;
65
+ }
66
+ async function collectInstructionFiles(directory, result, depth = 0) {
67
+ if (depth > 10 || result.length > 1_000)
68
+ fail("INSTRUCTIONS_LIMIT", "configuration", "Instruction discovery exceeded its supported limit.", { path: directory }, 5);
69
+ let entries;
70
+ try {
71
+ entries = await readdir(directory, { withFileTypes: true });
72
+ }
73
+ catch (error) {
74
+ if (isRecord(error) && error.code === "ENOENT")
75
+ return;
76
+ throw error;
77
+ }
78
+ for (const entry of entries) {
79
+ const file = path.join(directory, entry.name);
80
+ if (entry.isSymbolicLink())
81
+ fail("INSTRUCTION_PATH_UNSUPPORTED", "compatibility", "Symbolic instruction entries require an explicit verified source path.", { path: file });
82
+ if (entry.isDirectory())
83
+ await collectInstructionFiles(file, result, depth + 1);
84
+ else if (entry.isFile() && entry.name.endsWith(".instructions.md"))
85
+ result.push(file);
86
+ }
87
+ }
88
+ export function combineInstructions(original, additions, aliases) {
89
+ if (original !== undefined &&
90
+ original !== null &&
91
+ typeof original !== "string")
92
+ fail("CODEX_CONFIG_INVALID", "configuration", "Codex developer_instructions must be a string.", {}, 5);
93
+ const sections = [
94
+ typeof original === "string" ? original : "",
95
+ ...additions,
96
+ ].filter((part) => part.trim());
97
+ const names = Object.entries(aliases);
98
+ if (names.length)
99
+ sections.push(`Agency MCP server aliases for this session:\n${names.map(([from, to]) => `- ${JSON.stringify(from)} is exposed as ${JSON.stringify(to)}.`).join("\n")}\nUse the actual Codex tool catalog for tool names.`);
100
+ return sections.length ? sections.join("\n\n") : undefined;
101
+ }
102
+ //# sourceMappingURL=instructions.js.map
@@ -0,0 +1,237 @@
1
+ import path from "node:path";
2
+ import { fail } from "../diagnostics.js";
3
+ import { assertKnownFields, isRecord, nonEmptyString, record, stringArray, } from "../validation.js";
4
+ const FIELDS = [
5
+ "type",
6
+ "command",
7
+ "args",
8
+ "cwd",
9
+ "url",
10
+ "headers",
11
+ "env",
12
+ "description",
13
+ "tools",
14
+ "timeout",
15
+ "oauthClientId",
16
+ "oauthPublicClient",
17
+ "oauthGrantType",
18
+ "enabled_tools",
19
+ "disabled_tools",
20
+ "enabled",
21
+ "env_vars",
22
+ "startup_timeout_sec",
23
+ "tool_timeout_sec",
24
+ ];
25
+ export function parseMcpDocument(value) {
26
+ const document = record(value, "MCP configuration");
27
+ assertKnownFields(document, ["mcpServers"], "MCP configuration");
28
+ return record(document.mcpServers, "mcpServers");
29
+ }
30
+ export function convertMcpServer(name, value, cwd) {
31
+ nonEmptyString(name, "MCP server name");
32
+ const source = record(value, name);
33
+ assertKnownFields(source, FIELDS, name);
34
+ for (const field of [
35
+ "oauthClientId",
36
+ "oauthPublicClient",
37
+ "oauthGrantType",
38
+ ]) {
39
+ if (source[field] !== undefined && source[field] !== null)
40
+ fail("MCP_AUTH_UNSUPPORTED", "compatibility", "Copilot-specific OAuth settings must be handled by an Agency proxy.", { component: name, field });
41
+ }
42
+ for (const field of ["headers", "env"]) {
43
+ if (source[field] !== undefined &&
44
+ Object.keys(record(source[field], `${name}.${field}`)).length > 0) {
45
+ fail("MCP_AUTH_UNSUPPORTED", "compatibility", "Literal headers and environment values are not copied into generated command-line configuration. Use an Agency-managed proxy or existing environment references.", { component: name, field });
46
+ }
47
+ }
48
+ const result = {};
49
+ if (source.command !== undefined && source.url !== undefined)
50
+ fail("MCP_TRANSPORT_INVALID", "configuration", "A server cannot declare both command and URL transports.", { component: name }, 5);
51
+ const transport = source.type ?? (source.url !== undefined ? "http" : "stdio");
52
+ if (transport === "stdio" || transport === "local") {
53
+ result.command = nonEmptyString(source.command, `${name}.command`);
54
+ result.args =
55
+ source.args === undefined ? [] : stringArray(source.args, `${name}.args`);
56
+ if (result.args.some((arg) => /(?:^|[-_])(?:token|secret|password|authorization|credential)(?:=|$)/i.test(arg))) {
57
+ fail("MCP_AUTH_UNSUPPORTED", "compatibility", "Authentication arguments must remain inside an Agency-managed proxy.", { component: name, field: "args" });
58
+ }
59
+ result.cwd =
60
+ source.cwd === undefined
61
+ ? cwd
62
+ : path.resolve(cwd, nonEmptyString(source.cwd, `${name}.cwd`));
63
+ if (source.env_vars !== undefined)
64
+ result.env_vars = stringArray(source.env_vars, `${name}.env_vars`);
65
+ }
66
+ else if (transport === "http") {
67
+ let url;
68
+ try {
69
+ url = new URL(nonEmptyString(source.url, `${name}.url`));
70
+ }
71
+ catch {
72
+ fail("MCP_TRANSPORT_INVALID", "configuration", "Expected an absolute HTTP or HTTPS URL.", { component: name, field: "url" }, 5);
73
+ }
74
+ if (!["http:", "https:"].includes(url.protocol))
75
+ fail("MCP_TRANSPORT_UNSUPPORTED", "compatibility", "Only Streamable HTTP and stdio transports are supported.", { component: name });
76
+ if (url.username || url.password || url.search || url.hash)
77
+ fail("MCP_AUTH_UNSUPPORTED", "compatibility", "Credential-bearing or query-bearing URLs are not copied into generated command lines. Use an Agency-managed proxy.", { component: name, field: "url" });
78
+ if (source.args !== undefined ||
79
+ source.cwd !== undefined ||
80
+ source.env_vars !== undefined)
81
+ fail("MCP_TRANSPORT_INVALID", "configuration", "An HTTP server cannot declare stdio-only settings.", { component: name }, 5);
82
+ result.url = source.url;
83
+ }
84
+ else
85
+ fail("MCP_TRANSPORT_UNSUPPORTED", "compatibility", "The requested transport has no verified Codex mapping. SSE is not relabeled as Streamable HTTP.", { component: name, field: "type" });
86
+ if (source.tools !== undefined && source.enabled_tools !== undefined)
87
+ fail("MCP_TOOLS_INVALID", "configuration", "Use one tool allow-list representation.", { component: name }, 5);
88
+ if (source.tools !== undefined) {
89
+ const tools = stringArray(source.tools, `${name}.tools`);
90
+ if (!(tools.length === 1 && tools[0] === "*")) {
91
+ if (tools.some((tool) => /[*?]/.test(tool)))
92
+ fail("MCP_TOOLS_UNSUPPORTED", "compatibility", "Only a single all-tools wildcard or exact tool names have a verified mapping.", { component: name, field: "tools" });
93
+ result.enabled_tools = [...new Set(tools)];
94
+ }
95
+ }
96
+ for (const field of ["enabled_tools", "disabled_tools"]) {
97
+ if (source[field] !== undefined) {
98
+ const tools = stringArray(source[field], `${name}.${field}`);
99
+ if (tools.some((tool) => /[*?]/.test(tool)))
100
+ fail("MCP_TOOLS_UNSUPPORTED", "compatibility", "Native tool filters require exact tool names.", { component: name, field });
101
+ result[field] = [...new Set(tools)];
102
+ }
103
+ }
104
+ if (source.timeout !== undefined && source.tool_timeout_sec !== undefined)
105
+ fail("MCP_TIMEOUT_INVALID", "configuration", "The timeout cannot be specified in two units.", { component: name }, 5);
106
+ for (const field of ["timeout", "startup_timeout_sec", "tool_timeout_sec"]) {
107
+ const number = source[field];
108
+ if (number === undefined)
109
+ continue;
110
+ if (typeof number !== "number" || !Number.isFinite(number) || number <= 0)
111
+ fail("MCP_TIMEOUT_INVALID", "configuration", "Timeouts must be positive finite numbers.", { component: name, field }, 5);
112
+ result[field === "timeout" ? "tool_timeout_sec" : field] =
113
+ field === "timeout" ? number / 1_000 : number;
114
+ }
115
+ if (source.enabled !== undefined) {
116
+ if (typeof source.enabled !== "boolean")
117
+ fail("CONFIG_INVALID", "configuration", "enabled must be a boolean.", { component: name }, 5);
118
+ result.enabled = source.enabled;
119
+ }
120
+ return result;
121
+ }
122
+ function sorted(value) {
123
+ if (Array.isArray(value))
124
+ return value.map(sorted);
125
+ if (isRecord(value))
126
+ return Object.fromEntries(Object.keys(value)
127
+ .sort()
128
+ .filter((key) => value[key] !== null && value[key] !== undefined)
129
+ .map((key) => [key, sorted(value[key])]));
130
+ return value;
131
+ }
132
+ export function equivalentMcp(left, right, cwd) {
133
+ const normalize = (source) => {
134
+ const result = { ...source };
135
+ for (const [key, value] of Object.entries(result))
136
+ if (value === null || value === undefined)
137
+ delete result[key];
138
+ const defaults = {
139
+ enabled: true,
140
+ required: false,
141
+ env: {},
142
+ env_vars: [],
143
+ disabled_tools: [],
144
+ startup_timeout_sec: 10,
145
+ tool_timeout_sec: 60,
146
+ environment_id: "local",
147
+ };
148
+ if (result.command !== undefined) {
149
+ defaults.args = [];
150
+ defaults.cwd = cwd;
151
+ }
152
+ for (const [key, defaultValue] of Object.entries(defaults))
153
+ if (JSON.stringify(sorted(result[key])) ===
154
+ JSON.stringify(sorted(defaultValue)))
155
+ delete result[key];
156
+ if (Array.isArray(result.enabled_tools))
157
+ result.enabled_tools = [...result.enabled_tools].sort();
158
+ return sorted(result);
159
+ };
160
+ return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
161
+ }
162
+ export function planMcps(incoming, existing, profileMode, policy, cwd) {
163
+ if (profileMode === "unknown")
164
+ fail("PROFILE_ISOLATION_UNKNOWN", "compatibility", "The effective Agency profile mode could not be established. Select --profile or --profile-only explicitly.");
165
+ const entries = Object.create(null);
166
+ const plan = {
167
+ overrides: { mcp_servers: entries },
168
+ names: [],
169
+ reused: [],
170
+ disabled: [],
171
+ aliases: Object.create(null),
172
+ findings: [],
173
+ expected: Object.create(null),
174
+ };
175
+ const occupied = new Set([
176
+ ...Object.keys(existing),
177
+ ...Object.keys(incoming),
178
+ ]);
179
+ for (const [name, raw] of Object.entries(incoming)) {
180
+ const converted = convertMcpServer(name, raw, cwd);
181
+ if (converted.enabled !== false)
182
+ converted.required = true;
183
+ const previous = Object.hasOwn(existing, name) ? existing[name] : undefined;
184
+ if (previous !== undefined) {
185
+ const previousDefinition = record(previous, name);
186
+ const comparison = converted.required === true
187
+ ? { ...previousDefinition, required: true }
188
+ : previousDefinition;
189
+ if (equivalentMcp(comparison, converted, cwd)) {
190
+ if (converted.required === true && previousDefinition.required !== true)
191
+ entries[name] = { required: true };
192
+ plan.reused.push(name);
193
+ plan.names.push(name);
194
+ plan.expected[name] = converted;
195
+ continue;
196
+ }
197
+ if (policy !== "prefer-agency")
198
+ fail("MCP_CONFLICT", "compatibility", "An existing Codex server differs from the Agency definition. Use --mcp-conflict=prefer-agency to replace it for this session.", { component: name });
199
+ // A new name avoids Codex deep-merging transport or auth fields from the old entry.
200
+ let alias = `agencyx_${name}`;
201
+ let suffix = 2;
202
+ while (occupied.has(alias))
203
+ alias = `agencyx_${name}_${suffix++}`;
204
+ occupied.add(alias);
205
+ entries[name] = { enabled: false };
206
+ entries[alias] = converted;
207
+ plan.aliases[name] = alias;
208
+ plan.disabled.push(name);
209
+ plan.names.push(alias);
210
+ plan.expected[alias] = converted;
211
+ }
212
+ else {
213
+ entries[name] = converted;
214
+ plan.names.push(name);
215
+ plan.expected[name] = converted;
216
+ }
217
+ }
218
+ if (profileMode === "profile-only") {
219
+ for (const [name, value] of Object.entries(existing)) {
220
+ if (!Object.hasOwn(incoming, name) &&
221
+ record(value, name).enabled !== false) {
222
+ entries[name] = { enabled: false };
223
+ plan.disabled.push(name);
224
+ }
225
+ }
226
+ }
227
+ for (const name of plan.names)
228
+ plan.findings.push({
229
+ component: `mcp:${name}`,
230
+ state: plan.reused.includes(name) ? "native" : "adapted",
231
+ reason: plan.reused.includes(name)
232
+ ? "The complete effective definition is equivalent and will be reused."
233
+ : "Agency prepared the connection; Codex performs native MCP discovery and execution.",
234
+ });
235
+ return plan;
236
+ }
237
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,42 @@
1
+ import path from "node:path";
2
+ import { fail } from "../diagnostics.js";
3
+ import { isRecord } from "../validation.js";
4
+ export function validateReadGrants(directories, config, requirements) {
5
+ if (!directories.length)
6
+ return [];
7
+ const profile = config.default_permissions;
8
+ const knownProfile = profile === undefined ||
9
+ profile === null ||
10
+ [
11
+ ":read-only",
12
+ ":workspace",
13
+ ":workspace-write",
14
+ ":danger-full-access",
15
+ ":unrestricted",
16
+ ].includes(String(profile));
17
+ const knownSandbox = config.sandbox_mode === undefined ||
18
+ config.sandbox_mode === null ||
19
+ ["read-only", "workspace-write", "danger-full-access"].includes(String(config.sandbox_mode));
20
+ if (!knownProfile ||
21
+ !knownSandbox ||
22
+ config.sandbox_permissions !== undefined ||
23
+ (requirements &&
24
+ (requirements.permissions !== undefined ||
25
+ requirements.filesystem !== undefined))) {
26
+ fail("READ_PERMISSION_UNVERIFIED", "compatibility", "Agency supplied read-directory grants, but the effective Codex filesystem policy cannot be proven to provide the same access. No writable directories were added.");
27
+ }
28
+ if (isRecord(config.permissions) &&
29
+ Object.keys(config.permissions).length &&
30
+ profile &&
31
+ !String(profile).startsWith(":"))
32
+ fail("READ_PERMISSION_UNVERIFIED", "compatibility", "A custom permissions profile requires a verified filesystem mapping.");
33
+ return [
34
+ ...new Set(directories.map((directory) => path.resolve(directory))),
35
+ ].map((directory) => ({
36
+ component: "read-directory",
37
+ state: "adapted",
38
+ path: directory,
39
+ reason: "The native Codex policy already permits reads. No write permission is granted by the wrapper.",
40
+ }));
41
+ }
42
+ //# sourceMappingURL=permissions.js.map