jorgex-stack 1.9.57 → 1.9.59

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.
@@ -0,0 +1,711 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createLocalCapabilityReport,
4
+ detectCodex,
5
+ hasManagedMarkdownSection,
6
+ hasTomlChildSection,
7
+ hasTomlRootKey,
8
+ headerName,
9
+ isCanonicalMcpServerEnabled,
10
+ loadCanonicalDefaults,
11
+ multilineStringMask,
12
+ readTextIfExists,
13
+ readTomlSection,
14
+ registerOfficialSetupVerifier,
15
+ removeNativeHooks,
16
+ removeSystemPromptSections,
17
+ removeTomlRootKeyIfExact,
18
+ removeTomlSection,
19
+ resolveAgentModel,
20
+ upsertNativeHooks,
21
+ upsertTomlRootKeyIfMissing,
22
+ upsertTomlSection
23
+ } from "./chunk-KB5IQEKL.js";
24
+ import {
25
+ HOME,
26
+ samePath,
27
+ stackRoot
28
+ } from "./chunk-NQZVJFV2.js";
29
+
30
+ // src/adapters/codex.ts
31
+ import path from "path";
32
+ import fs from "fs";
33
+ function tomlString(value) {
34
+ return JSON.stringify(value);
35
+ }
36
+ function readMcpConfig(file) {
37
+ try {
38
+ return fs.readFileSync(file, "utf8");
39
+ } catch (error) {
40
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
41
+ if (code === "ENOENT") return null;
42
+ throw new Error(`Codex: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
43
+ }
44
+ }
45
+ var PRIMARY_MODEL = '"gpt-5.6-sol"';
46
+ var PRIMARY_CONTEXT_WINDOW = "872000";
47
+ var PRIMARY_MODEL_FIELD = "model";
48
+ var PRIMARY_CONTEXT_FIELD = "model_context_window";
49
+ function tomlMultiline(value) {
50
+ if (value.includes("'''")) return JSON.stringify(value);
51
+ return `'''
52
+ ${value.replace(/\r\n/g, "\n").trim()}
53
+ '''`;
54
+ }
55
+ function stdioMcpSection(server) {
56
+ const args = (server.args ?? []).map(tomlString).join(", ");
57
+ return `command = ${tomlString(server.command)}
58
+ args = [${args}]`;
59
+ }
60
+ function isManagedOptionalStdioServer(server, section) {
61
+ return server.optional === true && server.transport === "stdio" && section?.trim() === stdioMcpSection(server);
62
+ }
63
+ function hasActiveEngramPlugin(configDir) {
64
+ const config = readTextIfExists(path.join(configDir, "config.toml"));
65
+ if (config === null) return false;
66
+ const match = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
67
+ return match !== null && !/enabled\s*=\s*false/.test(match[1]);
68
+ }
69
+ function hasEngramProtocol(configDir) {
70
+ if (hasActiveEngramPlugin(configDir)) return true;
71
+ if (fs.existsSync(path.join(configDir, "engram-instructions.md"))) return true;
72
+ const config = readTextIfExists(path.join(configDir, "config.toml"));
73
+ return config !== null && /engram-instructions\.md/.test(config);
74
+ }
75
+ var CODEX_JSON_STRING = String.raw`"(?:\\.|[^"\\\r\n])*"`;
76
+ var CODEX_JSON_VALUE = String.raw`(?:${CODEX_JSON_STRING}|-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?|true|false)`;
77
+ var CODEX_JSON_STRING_ARRAY = String.raw`\[(?:\s*${CODEX_JSON_STRING}(?:\s*,\s*${CODEX_JSON_STRING})*\s*)?\]`;
78
+ var CODEX_KEY = String.raw`(?:[A-Za-z0-9_-]+|${CODEX_JSON_STRING})`;
79
+ var CODEX_ASSIGNMENT = new RegExp(String.raw`^\s*(${CODEX_KEY})\s*=\s*(${CODEX_JSON_STRING_ARRAY}|${CODEX_JSON_VALUE})\s*(?:#.*)?$`);
80
+ var CODEX_HEADER = new RegExp(String.raw`^\[\s*(${CODEX_KEY}(?:\s*\.\s*${CODEX_KEY})*)\s*\]\s*(?:#.*)?$`);
81
+ function tomlAssignment(section, key) {
82
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83
+ const keyPattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
84
+ const lines = section.split(/\r?\n/);
85
+ const mask = multilineStringMask(lines);
86
+ for (const [index, line] of lines.entries()) {
87
+ if (mask[index] || !keyPattern.test(line)) continue;
88
+ const match = CODEX_ASSIGNMENT.exec(line);
89
+ if (match) return { present: true, raw: match[2] };
90
+ const equals = line.indexOf("=");
91
+ const raw = equals === -1 ? void 0 : line.slice(equals + 1).trim().replace(/\s+#.*$/, "");
92
+ return { present: true, ...raw?.startsWith("'") && raw.endsWith("'") ? { raw } : {} };
93
+ }
94
+ return { present: false };
95
+ }
96
+ function parseTomlString(raw) {
97
+ if (raw === void 0) return void 0;
98
+ const value = raw.trim();
99
+ if (value.startsWith('"')) {
100
+ try {
101
+ const parsed = JSON.parse(value);
102
+ return typeof parsed === "string" ? parsed : void 0;
103
+ } catch {
104
+ return void 0;
105
+ }
106
+ }
107
+ if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
108
+ return value.slice(1, -1);
109
+ }
110
+ return void 0;
111
+ }
112
+ function context7HttpSection(server) {
113
+ const body = [`url = ${tomlString(server.url)}`];
114
+ const envHeaders = [];
115
+ const literalHeaders = [];
116
+ for (const [key, raw] of Object.entries(server.headers ?? {})) {
117
+ const envRef = /^\$\{(\w+)\}$/.exec(raw);
118
+ if (envRef) envHeaders.push(`${tomlString(key)} = ${tomlString(envRef[1])}`);
119
+ else literalHeaders.push(`${tomlString(key)} = ${tomlString(raw)}`);
120
+ }
121
+ if (literalHeaders.length > 0) body.push(`http_headers = { ${literalHeaders.join(", ")} }`);
122
+ if (envHeaders.length > 0) body.push(`env_http_headers = { ${envHeaders.join(", ")} }`);
123
+ return body.join("\n");
124
+ }
125
+ function isCompatibleContext7Server(server, section) {
126
+ if (server.transport !== "http" || typeof server.url !== "string" || section === null) return false;
127
+ const url = tomlAssignment(section, "url");
128
+ const command = tomlAssignment(section, "command");
129
+ const type = tomlAssignment(section, "type");
130
+ const enabled = tomlAssignment(section, "enabled");
131
+ const declaredType = type.present ? parseTomlString(type.raw) : "http";
132
+ return url.present && parseTomlString(url.raw) === server.url && !command.present && declaredType === "http" && (!enabled.present || enabled.raw?.trim() === "true");
133
+ }
134
+ function isCanonicalContext7Server(server, section, config) {
135
+ return isCompatibleContext7Server(server, section) && section.trim() === context7HttpSection(server) && !hasTomlChildSection(config, "mcp_servers.context7");
136
+ }
137
+ function assertCompatibleContext7(server, section, config) {
138
+ if (section === null && hasTomlChildSection(config, "mcp_servers.context7") || section !== null && !isCompatibleContext7Server(server, section)) {
139
+ throw new Error("Codex: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint, tipo o estado nativo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
140
+ }
141
+ }
142
+ var CODEX_PERMISSION_HEADERS = {
143
+ base: "permissions.jorgex-read-anywhere",
144
+ filesystem: "permissions.jorgex-read-anywhere.filesystem",
145
+ workspaceRoots: 'permissions.jorgex-read-anywhere.filesystem.":workspace_roots"'
146
+ };
147
+ var CODEX_PERMISSION_PROFILE = {
148
+ base: [["extends", ":workspace"]],
149
+ filesystem: [
150
+ [":root", "read"],
151
+ ["~/.ssh/**", "deny"],
152
+ ["~/.aws/credentials", "deny"],
153
+ ["~/.npmrc", "deny"],
154
+ ["~/.git-credentials", "deny"]
155
+ ],
156
+ workspaceRoots: [
157
+ [".", "write"],
158
+ ["*.env", "deny"],
159
+ ["*.env.*", "deny"],
160
+ [".ssh/**", "deny"],
161
+ [".aws/credentials", "deny"],
162
+ [".npmrc", "deny"],
163
+ [".git-credentials", "deny"],
164
+ ["**/id_rsa", "deny"],
165
+ ["**/id_ed25519", "deny"],
166
+ ["**/*.pem", "deny"],
167
+ ["**/*.key", "deny"]
168
+ ]
169
+ };
170
+ var CODEX_PERMISSION_SECTIONS = [
171
+ { header: CODEX_PERMISSION_HEADERS.base, entries: CODEX_PERMISSION_PROFILE.base, quoteKeys: false },
172
+ { header: CODEX_PERMISSION_HEADERS.filesystem, entries: CODEX_PERMISSION_PROFILE.filesystem, quoteKeys: true },
173
+ { header: CODEX_PERMISSION_HEADERS.workspaceRoots, entries: CODEX_PERMISSION_PROFILE.workspaceRoots, quoteKeys: true }
174
+ ];
175
+ function renderCodexPermissionEntries(entries, quoteKeys) {
176
+ return entries.map(([key, value]) => `${quoteKeys ? JSON.stringify(key) : key} = ${JSON.stringify(value)}`).join("\n");
177
+ }
178
+ var CODEX_STALE_PERMISSIONS_WARNING = "Codex: permission profile differs from the stack default and was left untouched; re-run with --upgrade-permissions to replace it (a backup is created first), or edit it by hand. Overwriting discards your own permission changes, including any extra hardenings.";
179
+ function codexNormalizedHeader(header) {
180
+ return headerName(`[${header}]`) ?? header;
181
+ }
182
+ function readCodexRootValue(config, key) {
183
+ const lines = config.replace(/\r\n/g, "\n").split("\n");
184
+ const mask = multilineStringMask(lines);
185
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
186
+ const keyPattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
187
+ for (const [index, line] of lines.entries()) {
188
+ if (mask[index]) continue;
189
+ if (line.trim().startsWith("[")) break;
190
+ if (!keyPattern.test(line)) continue;
191
+ const match = CODEX_ASSIGNMENT.exec(line.trim());
192
+ return match === null ? void 0 : parseTomlString(match[2]);
193
+ }
194
+ return void 0;
195
+ }
196
+ function setCodexRootKey(config, key, value) {
197
+ const eol = config.includes("\r\n") ? "\r\n" : "\n";
198
+ const lines = config.replace(/\r\n/g, "\n").split("\n");
199
+ const mask = multilineStringMask(lines);
200
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
201
+ const keyPattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
202
+ const assignment = new RegExp(
203
+ `^(\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=\\s*)(${CODEX_JSON_STRING_ARRAY}|${CODEX_JSON_VALUE})(\\s*(?:#.*)?)$`
204
+ );
205
+ for (const [index, line] of lines.entries()) {
206
+ if (mask[index]) continue;
207
+ if (line.trim().startsWith("[")) break;
208
+ if (!keyPattern.test(line)) continue;
209
+ const match = assignment.exec(line);
210
+ lines[index] = match === null ? `${key} = ${value}` : `${match[1]}${value}${match[3]}`;
211
+ return lines.join(eol);
212
+ }
213
+ return upsertTomlRootKeyIfMissing(config, key, value);
214
+ }
215
+ function isCodexPermissionBlockCurrent(config, defaults) {
216
+ const expectedApproval = defaults["approval_policy"];
217
+ const expectedDefault = defaults["default_permissions"];
218
+ if (typeof expectedApproval !== "string" || typeof expectedDefault !== "string") return true;
219
+ if (readCodexRootValue(config, "approval_policy") !== expectedApproval) return false;
220
+ if (readCodexRootValue(config, "default_permissions") !== expectedDefault) return false;
221
+ return CODEX_PERMISSION_SECTIONS.every(
222
+ ({ header, entries, quoteKeys }) => (readTomlSection(config, codexNormalizedHeader(header)) ?? "").trim() === renderCodexPermissionEntries(entries, quoteKeys).trim()
223
+ );
224
+ }
225
+ function reseedCodexPermissionBlock(config, defaults) {
226
+ let out = setCodexRootKey(config, "approval_policy", tomlString(String(defaults["approval_policy"])));
227
+ out = setCodexRootKey(out, "default_permissions", tomlString(String(defaults["default_permissions"])));
228
+ for (const { header, entries, quoteKeys } of CODEX_PERMISSION_SECTIONS) {
229
+ out = upsertTomlSection(out, header, renderCodexPermissionEntries(entries, quoteKeys));
230
+ }
231
+ return out;
232
+ }
233
+ function parseCodexKey(raw) {
234
+ if (!raw.startsWith('"')) return raw;
235
+ try {
236
+ const value = JSON.parse(raw);
237
+ return typeof value === "string" ? value : null;
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function parseCodexValue(raw) {
243
+ try {
244
+ const value = JSON.parse(raw);
245
+ return Array.isArray(value) && value.every((item) => typeof item === "string") || !Array.isArray(value) && (value === null || typeof value !== "object") ? value : void 0;
246
+ } catch {
247
+ return void 0;
248
+ }
249
+ }
250
+ function parseCodexHeader(line) {
251
+ const match = CODEX_HEADER.exec(line.trim());
252
+ if (match === null) return null;
253
+ for (const quoted of match[1].match(/"(?:\\.|[^"\\\r\n])*"/g) ?? []) {
254
+ if (parseCodexKey(quoted) === null) return null;
255
+ }
256
+ return match[1].replace(/\s*\.\s*/g, ".");
257
+ }
258
+ function scanCodexToml(config) {
259
+ if (config.includes("'''") || config.includes('"""')) return null;
260
+ const root = /* @__PURE__ */ new Map();
261
+ const sections = /* @__PURE__ */ new Map();
262
+ const headers = /* @__PURE__ */ new Set();
263
+ const keys = /* @__PURE__ */ new Set();
264
+ let section = null;
265
+ for (const line of config.replace(/\r\n/g, "\n").split("\n")) {
266
+ const trimmed = line.trim();
267
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
268
+ if (trimmed.startsWith("[")) {
269
+ const header = parseCodexHeader(trimmed);
270
+ if (header === null || headers.has(header)) return null;
271
+ headers.add(header);
272
+ if (header.startsWith(`${CODEX_PERMISSION_HEADERS.base}.`) && !CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === header)) {
273
+ return null;
274
+ }
275
+ section = header;
276
+ if (CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === header)) sections.set(header, /* @__PURE__ */ new Map());
277
+ continue;
278
+ }
279
+ const relevant = section === null || CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === section);
280
+ const match = CODEX_ASSIGNMENT.exec(trimmed);
281
+ if (!relevant && match === null) continue;
282
+ if (match === null) return null;
283
+ const key = parseCodexKey(match[1]);
284
+ const value = parseCodexValue(match[2]);
285
+ if (key === null || value === void 0) return null;
286
+ if (section === null && (key === "profile" || key === "sandbox_mode")) return null;
287
+ const scope = section ?? "<root>";
288
+ const declaration = `${scope}\0${key}`;
289
+ if (keys.has(declaration)) return null;
290
+ keys.add(declaration);
291
+ if (section === null) root.set(key, value);
292
+ else if (sections.has(section)) sections.get(section).set(key, value);
293
+ }
294
+ return { root, sections };
295
+ }
296
+ function hasCanonicalCodexPermissionProfile(scan) {
297
+ for (const { header, entries } of CODEX_PERMISSION_SECTIONS) {
298
+ const actual = scan.sections.get(header);
299
+ if (actual === void 0 || actual.size !== entries.length) return false;
300
+ for (const [key, expected] of entries) {
301
+ if (actual.get(key) !== expected) return false;
302
+ }
303
+ }
304
+ return true;
305
+ }
306
+ function hasCodexManualApproval(configDir) {
307
+ const config = readTextIfExists(path.join(configDir, "config.toml"));
308
+ if (config === null) return false;
309
+ const scan = scanCodexToml(config);
310
+ if (scan === null) return false;
311
+ try {
312
+ const defaults = loadCanonicalDefaults(stackRoot())["codex"];
313
+ const expectedApproval = defaults?.["approval_policy"];
314
+ const expectedPermissions = defaults?.["default_permissions"];
315
+ return typeof expectedApproval === "string" && typeof expectedPermissions === "string" && scan.root.get("approval_policy") === expectedApproval && scan.root.get("default_permissions") === expectedPermissions && hasCanonicalCodexPermissionProfile(scan);
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+ var codexAdapter = {
321
+ id: "codex",
322
+ name: "Codex CLI",
323
+ detect: detectCodex,
324
+ reportCapabilities(configDir) {
325
+ const prompt = readTextIfExists(path.join(configDir, "AGENTS.md"));
326
+ return createLocalCapabilityReport("codex", [
327
+ ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
328
+ id: "policy-guidance",
329
+ state: "prompt-only",
330
+ reason: "The managed policy prompt is advisory and cannot enforce the policy",
331
+ evidence: { source: "jorgex-stack-system-prompt", version: "1" }
332
+ }] : [],
333
+ ...hasCodexManualApproval(configDir) ? [{
334
+ id: "tool-approval",
335
+ state: "manual",
336
+ reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
337
+ evidence: { source: "jorgex-stack-codex-approval-policy", version: "1" }
338
+ }] : [],
339
+ {
340
+ id: "external-verification",
341
+ state: "unavailable",
342
+ reason: "External verification is available only through the external verifier"
343
+ }
344
+ ]);
345
+ },
346
+ injectEngramProtocol(ctx) {
347
+ return !hasEngramProtocol(ctx.configDir);
348
+ },
349
+ paths(configDir) {
350
+ const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path.join(HOME, ".codex"));
351
+ const agentsHome = isRealConfigDir ? HOME : path.dirname(configDir);
352
+ const skillsDir = path.join(agentsHome, ".agents", "skills");
353
+ return {
354
+ systemPromptFile: path.join(configDir, "AGENTS.md"),
355
+ agentsDir: path.join(configDir, "agents"),
356
+ skillsDir,
357
+ // Los custom prompts de Codex están deprecados: los commands se
358
+ // instalan como skills (renderCommand produce <nombre>/SKILL.md).
359
+ commandsDir: skillsDir,
360
+ pluginsDir: null,
361
+ scriptsDir: path.join(configDir, "scripts"),
362
+ outputStylesDir: null,
363
+ profilesDir: configDir
364
+ };
365
+ },
366
+ renderAgent(agent, models) {
367
+ const tierModel = resolveAgentModel(models, agent.name, agent.tier);
368
+ if (agent.mode === "primary") {
369
+ const profileLines = [
370
+ "# Managed by jorgex-stack \u2014 modo orquestador del agente principal.",
371
+ `# Uso: codex --profile ${agent.name}`,
372
+ `developer_instructions = ${tomlMultiline(agent.body)}`
373
+ ];
374
+ return [{ file: `${agent.name}.config.toml`, content: profileLines.join("\n") + "\n", kind: "profile" }];
375
+ }
376
+ const lines = [
377
+ `name = ${tomlString(agent.name)}`,
378
+ `description = ${tomlString(agent.description)}`
379
+ ];
380
+ if (tierModel.model !== "default") lines.push(`model = ${tomlString(tierModel.model)}`);
381
+ if (tierModel.variant) lines.push(`model_reasoning_effort = ${tomlString(tierModel.variant)}`);
382
+ lines.push(`sandbox_mode = ${tomlString(agent.readonly ? "read-only" : "workspace-write")}`);
383
+ lines.push(`developer_instructions = ${tomlMultiline(agent.body)}`);
384
+ return [{ file: `${agent.name}.toml`, content: lines.join("\n") + "\n", kind: "agent" }];
385
+ },
386
+ renderCommand(file, content) {
387
+ const name = file.replace(/\.md$/, "");
388
+ let description = name;
389
+ let body = content;
390
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(content);
391
+ if (fm) {
392
+ const match = /(?:^|\n)description:\s*(.+)/.exec(fm[1]);
393
+ if (match) description = match[1].trim();
394
+ body = content.slice(fm[0].length);
395
+ }
396
+ body = body.replace(/\{\{input\}\}/g, "the user's request in this conversation");
397
+ return {
398
+ file: `${name}/SKILL.md`,
399
+ content: `---
400
+ name: ${name}
401
+ description: ${JSON.stringify(description)}
402
+ ---
403
+ ${body}`
404
+ };
405
+ },
406
+ planHooks(canonical, ctx) {
407
+ const actions = [];
408
+ const { scriptsDir } = this.paths(ctx.configDir);
409
+ const hooksFile = path.join(ctx.configDir, "hooks.json");
410
+ const content = upsertNativeHooks(
411
+ readTextIfExists(hooksFile),
412
+ canonical,
413
+ scriptsDir,
414
+ (matcher) => matcher.split("|").some((p) => p.trim() === "Bash") ? "shell" : matcher
415
+ );
416
+ actions.push({ kind: "write", target: hooksFile, content });
417
+ ctx.warnings.push(
418
+ "Codex: los hooks no-managed requieren aprobaci\xF3n manual \u2014 ejecuta /hooks dentro de codex para activarlos."
419
+ );
420
+ const scriptsSource = path.join(ctx.stackDir, "scripts");
421
+ if (fs.existsSync(scriptsSource)) {
422
+ for (const f of fs.readdirSync(scriptsSource)) {
423
+ actions.push({ kind: "copy", source: path.join(scriptsSource, f), target: path.join(scriptsDir, f) });
424
+ }
425
+ }
426
+ return actions;
427
+ },
428
+ planMainConfig(canonical, ctx) {
429
+ const file = path.join(ctx.configDir, "config.toml");
430
+ const original = readMcpConfig(file);
431
+ const contentSource = original === null || original.trim() === "" ? null : original;
432
+ let content = contentSource;
433
+ const mcpOwnership = [];
434
+ const primaryModelOwnership = [];
435
+ const context7 = canonical.servers.context7;
436
+ if (context7 !== void 0) {
437
+ assertCompatibleContext7(context7, readTomlSection(contentSource, "mcp_servers.context7"), contentSource);
438
+ }
439
+ if (contentSource === null) {
440
+ const defaults = loadCanonicalDefaults(ctx.stackDir)["codex"] ?? {};
441
+ for (const [key, value] of Object.entries(defaults)) {
442
+ const line = `${key} = ${tomlString(String(value))}
443
+ `;
444
+ content = content === null ? line : line + content;
445
+ }
446
+ ctx.warnings.push(
447
+ "Codex: fresh config enables read-anywhere via the jorgex-read-anywhere permission profile; broad local reads can expose secrets not covered by deny rules."
448
+ );
449
+ content = upsertTomlSection(
450
+ content,
451
+ CODEX_PERMISSION_HEADERS.base,
452
+ renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.base, false)
453
+ );
454
+ content = upsertTomlSection(
455
+ content,
456
+ CODEX_PERMISSION_HEADERS.filesystem,
457
+ renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.filesystem, true)
458
+ );
459
+ content += [
460
+ `
461
+ [${CODEX_PERMISSION_HEADERS.workspaceRoots}]`,
462
+ renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.workspaceRoots, true),
463
+ ""
464
+ ].join("\n");
465
+ }
466
+ if (contentSource !== null) {
467
+ const codexDefaults = loadCanonicalDefaults(ctx.stackDir)["codex"];
468
+ if (codexDefaults !== void 0 && !isCodexPermissionBlockCurrent(contentSource, codexDefaults)) {
469
+ if (ctx.upgradePermissions === true) {
470
+ content = reseedCodexPermissionBlock(content, codexDefaults);
471
+ } else {
472
+ ctx.warnings.push(CODEX_STALE_PERMISSIONS_WARNING);
473
+ }
474
+ }
475
+ }
476
+ if (!hasTomlRootKey(content, PRIMARY_MODEL_FIELD)) {
477
+ content = upsertTomlRootKeyIfMissing(content, PRIMARY_MODEL_FIELD, PRIMARY_MODEL);
478
+ if (ctx.ownedPrimaryModelFields?.has(PRIMARY_MODEL_FIELD) !== true) {
479
+ primaryModelOwnership.push({ field: PRIMARY_MODEL_FIELD, owned: true });
480
+ }
481
+ }
482
+ if (!hasTomlRootKey(content, PRIMARY_CONTEXT_FIELD)) {
483
+ content = upsertTomlRootKeyIfMissing(content, PRIMARY_CONTEXT_FIELD, PRIMARY_CONTEXT_WINDOW);
484
+ if (ctx.ownedPrimaryModelFields?.has(PRIMARY_CONTEXT_FIELD) !== true) {
485
+ primaryModelOwnership.push({ field: PRIMARY_CONTEXT_FIELD, owned: true });
486
+ }
487
+ }
488
+ for (const [name, server] of Object.entries(canonical.servers)) {
489
+ if (name === "engram" && hasActiveEngramPlugin(ctx.configDir)) {
490
+ if (ctx.ownedMcpServers?.has(name) === true) {
491
+ mcpOwnership.push({ server: name, owned: false });
492
+ }
493
+ ctx.warnings.push(
494
+ "Codex: plugin oficial de Engram activo \u2014 sus hooks y skill no incluyen el MCP; el setup registra un MCP user separado que Stack no posee ni muta."
495
+ );
496
+ continue;
497
+ }
498
+ const section = `mcp_servers.${name}`;
499
+ const existing = readTomlSection(content, section);
500
+ const owned = ctx.ownedMcpServers?.has(name) === true;
501
+ if (name === "context7" && existing !== null) {
502
+ if (owned && !isCanonicalContext7Server(server, existing, content)) {
503
+ mcpOwnership.push({ server: name, owned: false });
504
+ }
505
+ continue;
506
+ }
507
+ if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
508
+ if (owned) {
509
+ if (isManagedOptionalStdioServer(server, existing)) content = removeTomlSection(content, section);
510
+ mcpOwnership.push({ server: name, owned: false });
511
+ }
512
+ continue;
513
+ }
514
+ if (server.optional && existing !== null) {
515
+ if (!owned || !isManagedOptionalStdioServer(server, existing)) {
516
+ if (owned) mcpOwnership.push({ server: name, owned: false });
517
+ ctx.warnings.push(`Codex: MCP opcional '${name}' ya pertenece a la configuraci\xF3n del usuario; se conserva.`);
518
+ continue;
519
+ }
520
+ }
521
+ if (server.transport === "stdio") {
522
+ if (server.command === "{{ENGRAM_BIN}}" && ctx.engramBin === null) {
523
+ ctx.warnings.push(
524
+ "Engram no detectado: el MCP 'engram' no se registra. Inst\xE1lalo (github.com/Gentleman-Programming/engram) y re-ejecuta sync."
525
+ );
526
+ continue;
527
+ }
528
+ const command = server.command === "{{ENGRAM_BIN}}" ? ctx.engramBin : server.command;
529
+ content = upsertTomlSection(content, section, `command = ${tomlString(command)}
530
+ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
531
+ if (server.optional && existing === null && !owned) mcpOwnership.push({ server: name, owned: true });
532
+ } else {
533
+ const previousSection = readTomlSection(content, section);
534
+ const prevUsesEnvHeaders = previousSection?.includes("env_http_headers") ?? false;
535
+ const literalPairs = [];
536
+ const refPairs = [];
537
+ for (const [key, raw] of Object.entries(server.headers ?? {})) {
538
+ const envRef = /^\$\{(\w+)\}$/.exec(raw);
539
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
540
+ const previousValue = !prevUsesEnvHeaders && previousSection !== null ? new RegExp(`(?:^|[{,]\\s*)"?${escaped}"?\\s*=\\s*"([^"]*)"`, "m").exec(previousSection)?.[1] : void 0;
541
+ if (previousValue) literalPairs.push(`${tomlString(key)} = ${tomlString(previousValue)}`);
542
+ else refPairs.push(`${tomlString(key)} = ${tomlString(envRef ? envRef[1] : raw)}`);
543
+ }
544
+ const body = [`url = ${tomlString(server.url)}`];
545
+ if (literalPairs.length > 0) body.push(`http_headers = { ${literalPairs.join(", ")} }`);
546
+ if (refPairs.length > 0) body.push(`env_http_headers = { ${refPairs.join(", ")} }`);
547
+ content = upsertTomlSection(content, section, body.join("\n"));
548
+ if (name === "context7" && existing === null && !owned) {
549
+ mcpOwnership.push({ server: name, owned: true });
550
+ }
551
+ }
552
+ }
553
+ if (content === null) return [];
554
+ if (!content.endsWith("\n")) content += "\n";
555
+ return [{
556
+ kind: "write",
557
+ target: file,
558
+ content,
559
+ ...mcpOwnership.length > 0 ? { mcpOwnership } : {},
560
+ ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
561
+ }];
562
+ },
563
+ planUnmerge(mcp, hooks, ctx) {
564
+ const actions = [];
565
+ const { systemPromptFile } = this.paths(ctx.configDir);
566
+ const prompt = readTextIfExists(systemPromptFile);
567
+ if (prompt !== null) {
568
+ const content = removeSystemPromptSections(prompt);
569
+ actions.push({ kind: "write", target: systemPromptFile, content });
570
+ }
571
+ const configFile = path.join(ctx.configDir, "config.toml");
572
+ const config = readMcpConfig(configFile);
573
+ if (config !== null) {
574
+ let content = config;
575
+ const primaryModelOwnership = [];
576
+ if (ctx.ownedPrimaryModelFields?.has(PRIMARY_MODEL_FIELD) === true) {
577
+ content = removeTomlRootKeyIfExact(content, PRIMARY_MODEL_FIELD, PRIMARY_MODEL);
578
+ primaryModelOwnership.push({ field: PRIMARY_MODEL_FIELD, owned: false });
579
+ }
580
+ if (ctx.ownedPrimaryModelFields?.has(PRIMARY_CONTEXT_FIELD) === true) {
581
+ content = removeTomlRootKeyIfExact(content, PRIMARY_CONTEXT_FIELD, PRIMARY_CONTEXT_WINDOW);
582
+ primaryModelOwnership.push({ field: PRIMARY_CONTEXT_FIELD, owned: false });
583
+ }
584
+ const mcpOwnership = [];
585
+ for (const [name, server] of Object.entries(mcp.servers)) {
586
+ const section = `mcp_servers.${name}`;
587
+ if (name === "context7") {
588
+ const current = readTomlSection(content, section);
589
+ const canonical = isCanonicalContext7Server(server, current, content);
590
+ const owned = ctx.ownedMcpServers?.has(name) === true;
591
+ if (owned) {
592
+ if (canonical) {
593
+ content = removeTomlSection(content, section);
594
+ }
595
+ mcpOwnership.push({ server: name, owned: false });
596
+ }
597
+ continue;
598
+ }
599
+ if (name === "engram" && hasActiveEngramPlugin(ctx.configDir)) {
600
+ ctx.warnings.push(
601
+ "Codex: MCP 'engram' oficial (plugin) se conserva; usa el desinstalador oficial de Engram para retirarlo."
602
+ );
603
+ continue;
604
+ }
605
+ if (!server.optional) {
606
+ content = removeTomlSection(content, section);
607
+ continue;
608
+ }
609
+ if (ctx.ownedMcpServers?.has(name) === true) {
610
+ if (isManagedOptionalStdioServer(server, readTomlSection(content, section))) {
611
+ content = removeTomlSection(content, section);
612
+ }
613
+ mcpOwnership.push({ server: name, owned: false });
614
+ }
615
+ }
616
+ actions.push({
617
+ kind: "write",
618
+ target: configFile,
619
+ content,
620
+ ...mcpOwnership.length > 0 ? { mcpOwnership } : {},
621
+ ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
622
+ });
623
+ }
624
+ const hooksFile = path.join(ctx.configDir, "hooks.json");
625
+ const hooksJson = readTextIfExists(hooksFile);
626
+ if (hooksJson !== null) {
627
+ const content = removeNativeHooks(hooksJson, hooks);
628
+ if (content !== null) {
629
+ actions.push({ kind: "write", target: hooksFile, content: content.trim() === "{}" ? "" : content });
630
+ }
631
+ }
632
+ return actions;
633
+ }
634
+ };
635
+ function parseCodexEngramPluginRef(config) {
636
+ const header = /\[plugins\."engram@([^"]+)"\]/.exec(config);
637
+ if (header === null) return null;
638
+ const block = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
639
+ if (block !== null && /enabled\s*=\s*false/.test(block[1])) return null;
640
+ return header[1];
641
+ }
642
+ function isExactCodexEngramMcp(section, engramBin) {
643
+ if (section === null) return false;
644
+ const command = parseTomlString(tomlAssignment(section, "command").raw);
645
+ if (command !== engramBin) return false;
646
+ const argsRaw = tomlAssignment(section, "args").raw;
647
+ if (argsRaw === void 0) return false;
648
+ try {
649
+ const args = JSON.parse(argsRaw);
650
+ return Array.isArray(args) && args.length === 2 && args[0] === "mcp" && args[1] === "--tools=agent";
651
+ } catch {
652
+ return false;
653
+ }
654
+ }
655
+ function resolveCodexInstructionPath(config, configDir, key, fallback) {
656
+ const ref = config !== null ? readCodexRootValue(config, key) : void 0;
657
+ const rel = typeof ref === "string" && ref.trim() !== "" ? ref.trim() : fallback;
658
+ return path.isAbsolute(rel) ? rel : path.join(configDir, rel);
659
+ }
660
+ function isNonEmptyFile(file) {
661
+ try {
662
+ const stat = fs.statSync(file);
663
+ if (!stat.isFile() || stat.size === 0) return false;
664
+ return fs.readFileSync(file, "utf8").trim().length > 0;
665
+ } catch {
666
+ return false;
667
+ }
668
+ }
669
+ async function verifyOfficialSetup(args) {
670
+ const config = readTextIfExists(path.join(args.configDir, "config.toml"));
671
+ const pluginRef = config !== null ? parseCodexEngramPluginRef(config) : null;
672
+ const mcpSection = config !== null ? readTomlSection(config, "mcp_servers.engram") : null;
673
+ const hasMcp = mcpSection !== null && typeof args.engramBin === "string" && args.engramBin !== "" ? isExactCodexEngramMcp(mcpSection, args.engramBin) : false;
674
+ const mcpPresentButForeign = mcpSection !== null && !hasMcp;
675
+ const instructionsFile = resolveCodexInstructionPath(config, args.configDir, "model_instructions_file", "engram-instructions.md");
676
+ const compactFile = resolveCodexInstructionPath(
677
+ config,
678
+ args.configDir,
679
+ "experimental_compact_prompt_file",
680
+ "engram-compact-prompt.md"
681
+ );
682
+ const hasInstructions = isNonEmptyFile(instructionsFile);
683
+ const hasCompact = isNonEmptyFile(compactFile);
684
+ const passed = [];
685
+ const missing = [];
686
+ if (pluginRef !== null) passed.push("plugin");
687
+ else missing.push("plugin:missing");
688
+ if (hasMcp) passed.push("mcp");
689
+ else missing.push(mcpPresentButForeign ? "mcp:conflict" : "mcp:missing");
690
+ if (hasInstructions) passed.push("instructions");
691
+ else missing.push("instructions:missing");
692
+ if (hasCompact) passed.push("compact");
693
+ else missing.push("compact:missing");
694
+ if (missing.length === 0) {
695
+ return { ok: true, layers: passed, acceptedRef: pluginRef, duplicates: false };
696
+ }
697
+ const detail = mcpPresentButForeign ? `MCP 'engram' en conflicto (no apunta al binario oficial ${args.engramBin}); se preserva la config ajena sin reescribir.` : `Codex: setup oficial Engram incompleto (falta: ${missing.join(", ")}).`;
698
+ return {
699
+ ok: false,
700
+ layers: [...passed, ...missing],
701
+ ...pluginRef !== null ? { acceptedRef: pluginRef } : {},
702
+ duplicates: false,
703
+ reason: detail
704
+ };
705
+ }
706
+ registerOfficialSetupVerifier("codex", verifyOfficialSetup);
707
+
708
+ export {
709
+ codexAdapter,
710
+ verifyOfficialSetup
711
+ };