@leo000001/codex-mcp 2.0.2 → 2.1.1
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.
- package/README.md +41 -1
- package/dist/index.js +347 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ MCP server that wraps [OpenAI Codex](https://github.com/openai/codex) `app-serve
|
|
|
19
19
|
## Prerequisites
|
|
20
20
|
|
|
21
21
|
- [Node.js](https://nodejs.org) >= 18
|
|
22
|
-
- [OpenAI Codex CLI](https://github.com/openai/codex) installed and configured (`codex` in PATH)
|
|
22
|
+
- [OpenAI Codex CLI](https://github.com/openai/codex) installed and configured (`codex` or `codex-internal` in PATH)
|
|
23
23
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
@@ -76,6 +76,46 @@ Or add to `~/.claude/settings.json`:
|
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Codex Executable Configuration
|
|
80
|
+
|
|
81
|
+
By default, codex-mcp auto-detects the Codex CLI by searching PATH for `codex`, then `codex-internal`. You can override this with environment variables:
|
|
82
|
+
|
|
83
|
+
| Variable | Description | Example |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `CODEX_MCP_DEFAULT_CODEX_COMMAND` | Bare command name (resolved from PATH) | `codex-internal` |
|
|
86
|
+
| `CODEX_MCP_DEFAULT_CODEX_PATH` | Absolute or relative filesystem path to the executable | `/usr/local/bin/codex-internal` |
|
|
87
|
+
|
|
88
|
+
- The two variables are **mutually exclusive** — setting both causes a startup error.
|
|
89
|
+
- When neither is set, codex-mcp tries `codex` then `codex-internal` on PATH automatically.
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Use codex-internal instead of codex
|
|
95
|
+
CODEX_MCP_DEFAULT_CODEX_COMMAND=codex-internal npx -y @leo000001/codex-mcp
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Use an explicit path
|
|
100
|
+
CODEX_MCP_DEFAULT_CODEX_PATH=/opt/codex/bin/codex npx -y @leo000001/codex-mcp
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
MCP client config with env override:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"codex": {
|
|
109
|
+
"command": "npx",
|
|
110
|
+
"args": ["-y", "@leo000001/codex-mcp"],
|
|
111
|
+
"env": {
|
|
112
|
+
"CODEX_MCP_DEFAULT_CODEX_COMMAND": "codex-internal"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
79
119
|
## STDIO Guard Modes
|
|
80
120
|
|
|
81
121
|
`codex-mcp` includes a startup preflight guard for stdout contamination risk.
|
package/dist/index.js
CHANGED
|
@@ -111,21 +111,38 @@ function resolveCodexInvocation(codexArgs, deps = {}) {
|
|
|
111
111
|
const readFile = deps.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
112
112
|
const pathApi = platform === "win32" ? path.win32 : path.posix;
|
|
113
113
|
const delimiter = platform === "win32" ? ";" : ":";
|
|
114
|
+
const codexCommand = deps.codexCommand ?? "codex";
|
|
115
|
+
const codexIsPath = deps.codexIsPath ?? false;
|
|
116
|
+
if (codexIsPath) {
|
|
117
|
+
if (platform === "win32" && (codexCommand.toLowerCase().endsWith(".cmd") || codexCommand.toLowerCase().endsWith(".bat"))) {
|
|
118
|
+
const comspec2 = env.ComSpec || env.COMSPEC || "cmd.exe";
|
|
119
|
+
return {
|
|
120
|
+
cmd: comspec2,
|
|
121
|
+
args: ["/d", "/s", "/c", codexCommand, ...codexArgs],
|
|
122
|
+
spawnedViaCmd: true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return { cmd: codexCommand, args: codexArgs, spawnedViaCmd: false };
|
|
126
|
+
}
|
|
114
127
|
if (platform !== "win32") {
|
|
115
|
-
return { cmd:
|
|
128
|
+
return { cmd: codexCommand, args: codexArgs, spawnedViaCmd: false };
|
|
116
129
|
}
|
|
117
|
-
const shim = findOnPath(
|
|
130
|
+
const shim = findOnPath(codexCommand, env, exists, pathApi, delimiter, [".exe", ".cmd", ".bat"]);
|
|
118
131
|
if (shim && shim.toLowerCase().endsWith(".exe")) {
|
|
119
132
|
return { cmd: shim, args: codexArgs, spawnedViaCmd: false };
|
|
120
133
|
}
|
|
121
134
|
if (shim && (shim.toLowerCase().endsWith(".cmd") || shim.toLowerCase().endsWith(".bat"))) {
|
|
122
|
-
const script = tryResolveNodeScriptFromShim(shim, exists, readFile, pathApi);
|
|
135
|
+
const script = tryResolveNodeScriptFromShim(shim, codexCommand, exists, readFile, pathApi);
|
|
123
136
|
if (script) {
|
|
124
137
|
return { cmd: process.execPath, args: [script, ...codexArgs], spawnedViaCmd: false };
|
|
125
138
|
}
|
|
126
139
|
}
|
|
127
140
|
const comspec = env.ComSpec || env.COMSPEC || "cmd.exe";
|
|
128
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
cmd: comspec,
|
|
143
|
+
args: ["/d", "/s", "/c", codexCommand, ...codexArgs],
|
|
144
|
+
spawnedViaCmd: true
|
|
145
|
+
};
|
|
129
146
|
}
|
|
130
147
|
function findOnPath(base, env, exists, pathApi, delimiter, exts) {
|
|
131
148
|
const pathEnv = env.PATH || env.Path || env.path || "";
|
|
@@ -146,7 +163,7 @@ function stripSurroundingQuotes(value) {
|
|
|
146
163
|
}
|
|
147
164
|
return value;
|
|
148
165
|
}
|
|
149
|
-
function tryResolveNodeScriptFromShim(shimPath, exists, readFile, pathApi) {
|
|
166
|
+
function tryResolveNodeScriptFromShim(shimPath, codexCommand, exists, readFile, pathApi) {
|
|
150
167
|
let contents;
|
|
151
168
|
try {
|
|
152
169
|
contents = readFile(shimPath);
|
|
@@ -161,7 +178,13 @@ function tryResolveNodeScriptFromShim(shimPath, exists, readFile, pathApi) {
|
|
|
161
178
|
matches.push(m[1]);
|
|
162
179
|
}
|
|
163
180
|
if (matches.length === 0) return void 0;
|
|
164
|
-
const
|
|
181
|
+
const escapedCommand = codexCommand.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
182
|
+
const baseNameRe = new RegExp(escapedCommand, "i");
|
|
183
|
+
const pathRe = new RegExp(
|
|
184
|
+
`@openai\\\\${escapedCommand}|\\\\${escapedCommand}\\\\|\\/${escapedCommand}\\/`,
|
|
185
|
+
"i"
|
|
186
|
+
);
|
|
187
|
+
const preferred = matches.find((m) => baseNameRe.test(pathApi.basename(m))) ?? matches.find((m) => pathRe.test(m)) ?? matches[matches.length - 1];
|
|
165
188
|
const shimDir = pathApi.dirname(shimPath);
|
|
166
189
|
const dp0 = shimDir.endsWith(pathApi.sep) ? shimDir : shimDir + pathApi.sep;
|
|
167
190
|
let resolved = preferred.replace(/%~dp0/gi, dp0).replace(/%dp0%/gi, dp0);
|
|
@@ -171,6 +194,139 @@ function tryResolveNodeScriptFromShim(shimPath, exists, readFile, pathApi) {
|
|
|
171
194
|
return abs;
|
|
172
195
|
}
|
|
173
196
|
|
|
197
|
+
// src/utils/codex-executable.ts
|
|
198
|
+
import { accessSync, constants, existsSync as existsSync2, statSync } from "fs";
|
|
199
|
+
import path2 from "path";
|
|
200
|
+
var CODEX_MCP_DEFAULT_CODEX_COMMAND = "CODEX_MCP_DEFAULT_CODEX_COMMAND";
|
|
201
|
+
var CODEX_MCP_DEFAULT_CODEX_PATH = "CODEX_MCP_DEFAULT_CODEX_PATH";
|
|
202
|
+
var AUTO_CODEX_COMMANDS = ["codex", "codex-internal"];
|
|
203
|
+
var _resolved;
|
|
204
|
+
var WINDOWS_SUPPORTED_EXTENSIONS = [".com", ".exe", ".bat", ".cmd"];
|
|
205
|
+
function stripSurroundingQuotes2(value) {
|
|
206
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
207
|
+
return value.slice(1, -1);
|
|
208
|
+
}
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
function normalizeMaybeQuotedToken(raw) {
|
|
212
|
+
return stripSurroundingQuotes2(raw.trim());
|
|
213
|
+
}
|
|
214
|
+
function normalizeWindowsExtension(value) {
|
|
215
|
+
const trimmed = stripSurroundingQuotes2(value.trim());
|
|
216
|
+
if (!trimmed) return void 0;
|
|
217
|
+
return trimmed.startsWith(".") ? trimmed.toLowerCase() : `.${trimmed.toLowerCase()}`;
|
|
218
|
+
}
|
|
219
|
+
function isExecutableFile(candidate) {
|
|
220
|
+
try {
|
|
221
|
+
const stat = statSync(candidate);
|
|
222
|
+
if (!stat.isFile()) return false;
|
|
223
|
+
if (process.platform === "win32") return true;
|
|
224
|
+
accessSync(candidate, constants.X_OK);
|
|
225
|
+
return true;
|
|
226
|
+
} catch {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function getPathEntries(env) {
|
|
231
|
+
const pathEnv = env.PATH || env.Path || env.path || "";
|
|
232
|
+
return pathEnv.split(process.platform === "win32" ? ";" : ":").map((entry) => stripSurroundingQuotes2(entry.trim())).filter(Boolean);
|
|
233
|
+
}
|
|
234
|
+
function getPathExtensions(env) {
|
|
235
|
+
if (process.platform !== "win32") return [""];
|
|
236
|
+
const configured = env.PATHEXT ?? env.Pathext ?? env.pathext ?? process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
|
|
237
|
+
const configuredExts = configured.split(";").map((entry) => normalizeWindowsExtension(entry)).filter((entry) => Boolean(entry));
|
|
238
|
+
const merged = configuredExts.filter(
|
|
239
|
+
(ext) => WINDOWS_SUPPORTED_EXTENSIONS.includes(ext)
|
|
240
|
+
);
|
|
241
|
+
for (const ext of WINDOWS_SUPPORTED_EXTENSIONS) {
|
|
242
|
+
if (!merged.includes(ext)) merged.push(ext);
|
|
243
|
+
}
|
|
244
|
+
return Array.from(new Set(merged));
|
|
245
|
+
}
|
|
246
|
+
function commandExistsOnPath(command, env) {
|
|
247
|
+
const dirs = getPathEntries(env);
|
|
248
|
+
const ext = process.platform === "win32" ? path2.extname(command) : "";
|
|
249
|
+
const names = process.platform === "win32" ? Array.from(
|
|
250
|
+
new Set(
|
|
251
|
+
ext ? [command] : [...getPathExtensions(env).map((suffix) => `${command}${suffix}`), command]
|
|
252
|
+
)
|
|
253
|
+
) : [command];
|
|
254
|
+
for (const dir of dirs) {
|
|
255
|
+
for (const name of names) {
|
|
256
|
+
const candidate = path2.join(dir, name);
|
|
257
|
+
if (isExecutableFile(candidate)) return true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
function looksLikePath(value) {
|
|
263
|
+
return value.includes("/") || value.includes("\\");
|
|
264
|
+
}
|
|
265
|
+
function resolveDefaultCodexExecutable(env = process.env) {
|
|
266
|
+
const envPathRaw = env[CODEX_MCP_DEFAULT_CODEX_PATH]?.trim();
|
|
267
|
+
const envCommandRaw = env[CODEX_MCP_DEFAULT_CODEX_COMMAND]?.trim();
|
|
268
|
+
const envPath = envPathRaw ? normalizeMaybeQuotedToken(envPathRaw) : void 0;
|
|
269
|
+
const envCommand = envCommandRaw ? normalizeMaybeQuotedToken(envCommandRaw) : void 0;
|
|
270
|
+
if (envPath && envCommand) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`Cannot set both ${CODEX_MCP_DEFAULT_CODEX_PATH} and ${CODEX_MCP_DEFAULT_CODEX_COMMAND}. Use one or the other.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (envPath) {
|
|
276
|
+
const resolvedPath = path2.resolve(envPath);
|
|
277
|
+
if (!existsSync2(resolvedPath)) {
|
|
278
|
+
throw new Error(`${CODEX_MCP_DEFAULT_CODEX_PATH}="${envPath}" \u2014 file does not exist.`);
|
|
279
|
+
}
|
|
280
|
+
if (!isExecutableFile(resolvedPath)) {
|
|
281
|
+
throw new Error(`${CODEX_MCP_DEFAULT_CODEX_PATH}="${envPath}" \u2014 not an executable file.`);
|
|
282
|
+
}
|
|
283
|
+
return { command: resolvedPath, isPath: true, source: "env_path" };
|
|
284
|
+
}
|
|
285
|
+
if (envCommand) {
|
|
286
|
+
if (looksLikePath(envCommand)) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`${CODEX_MCP_DEFAULT_CODEX_COMMAND}="${envCommand}" looks like a path. Use ${CODEX_MCP_DEFAULT_CODEX_PATH} for filesystem paths.`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (!commandExistsOnPath(envCommand, env)) {
|
|
292
|
+
throw new Error(`${CODEX_MCP_DEFAULT_CODEX_COMMAND}="${envCommand}" was not found in PATH.`);
|
|
293
|
+
}
|
|
294
|
+
return { command: envCommand, isPath: false, source: "env_command" };
|
|
295
|
+
}
|
|
296
|
+
for (const candidate of AUTO_CODEX_COMMANDS) {
|
|
297
|
+
if (commandExistsOnPath(candidate, env)) {
|
|
298
|
+
return { command: candidate, isPath: false, source: "auto_detect" };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return { command: "codex", isPath: false, source: "default" };
|
|
302
|
+
}
|
|
303
|
+
function getDefaultCodexExecutable() {
|
|
304
|
+
if (!_resolved) {
|
|
305
|
+
_resolved = resolveDefaultCodexExecutable();
|
|
306
|
+
}
|
|
307
|
+
return _resolved;
|
|
308
|
+
}
|
|
309
|
+
function checkDefaultCodexExecutableAvailability() {
|
|
310
|
+
const info = getDefaultCodexExecutable();
|
|
311
|
+
const label = info.isPath ? "path" : "command";
|
|
312
|
+
switch (info.source) {
|
|
313
|
+
case "env_path":
|
|
314
|
+
console.error(`[codex-executable] Using ${CODEX_MCP_DEFAULT_CODEX_PATH}: ${info.command}`);
|
|
315
|
+
break;
|
|
316
|
+
case "env_command":
|
|
317
|
+
console.error(`[codex-executable] Using ${CODEX_MCP_DEFAULT_CODEX_COMMAND}: ${info.command}`);
|
|
318
|
+
break;
|
|
319
|
+
case "auto_detect":
|
|
320
|
+
console.error(`[codex-executable] Auto-detected ${label}: ${info.command}`);
|
|
321
|
+
break;
|
|
322
|
+
case "default":
|
|
323
|
+
console.error(
|
|
324
|
+
`[codex-executable] No codex found on PATH; falling back to "${info.command}". Set ${CODEX_MCP_DEFAULT_CODEX_COMMAND} or ${CODEX_MCP_DEFAULT_CODEX_PATH} to configure.`
|
|
325
|
+
);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
174
330
|
// src/types.ts
|
|
175
331
|
var APPROVAL_POLICIES = ["untrusted", "on-failure", "on-request", "never"];
|
|
176
332
|
var SANDBOX_MODES = ["read-only", "workspace-write", "danger-full-access"];
|
|
@@ -191,6 +347,7 @@ var COMMAND_DECISIONS = [
|
|
|
191
347
|
"accept",
|
|
192
348
|
"acceptForSession",
|
|
193
349
|
"acceptWithExecpolicyAmendment",
|
|
350
|
+
"applyNetworkPolicyAmendment",
|
|
194
351
|
"decline",
|
|
195
352
|
"cancel"
|
|
196
353
|
];
|
|
@@ -199,6 +356,7 @@ var ALL_DECISIONS = [
|
|
|
199
356
|
"accept",
|
|
200
357
|
"acceptForSession",
|
|
201
358
|
"acceptWithExecpolicyAmendment",
|
|
359
|
+
"applyNetworkPolicyAmendment",
|
|
202
360
|
"decline",
|
|
203
361
|
"cancel"
|
|
204
362
|
];
|
|
@@ -233,7 +391,7 @@ var DEFAULT_TERMINAL_CLEANUP_MS = 5 * 60 * 1e3;
|
|
|
233
391
|
var CLEANUP_INTERVAL_MS = 6e4;
|
|
234
392
|
|
|
235
393
|
// src/app-server/client.ts
|
|
236
|
-
var CLIENT_VERSION = true ? "2.
|
|
394
|
+
var CLIENT_VERSION = true ? "2.1.1" : "0.0.0-dev";
|
|
237
395
|
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
238
396
|
var STARTUP_REQUEST_TIMEOUT = 9e4;
|
|
239
397
|
var MAX_WRITE_QUEUE_BYTES = 5 * 1024 * 1024;
|
|
@@ -262,7 +420,11 @@ var AppServerClient = class extends EventEmitter {
|
|
|
262
420
|
const args = buildAppServerArgs(opts);
|
|
263
421
|
const env = { ...process.env };
|
|
264
422
|
const stdio = ["pipe", "pipe", "pipe"];
|
|
265
|
-
const
|
|
423
|
+
const exe = getDefaultCodexExecutable();
|
|
424
|
+
const invocation = resolveCodexInvocation(args, {
|
|
425
|
+
codexCommand: exe.command,
|
|
426
|
+
codexIsPath: exe.isPath
|
|
427
|
+
});
|
|
266
428
|
this.spawnedViaCmd = invocation.spawnedViaCmd;
|
|
267
429
|
this.spawnedDetached = process.platform !== "win32";
|
|
268
430
|
const proc = spawn(invocation.cmd, invocation.args, {
|
|
@@ -622,16 +784,16 @@ var AppServerClient = class extends EventEmitter {
|
|
|
622
784
|
};
|
|
623
785
|
|
|
624
786
|
// src/utils/cwd.ts
|
|
625
|
-
import { existsSync as
|
|
626
|
-
import
|
|
787
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
788
|
+
import path3 from "path";
|
|
627
789
|
function resolveAndValidateCwd(inputCwd, baseCwd) {
|
|
628
790
|
const candidate = inputCwd ?? baseCwd;
|
|
629
|
-
const resolved =
|
|
630
|
-
if (!
|
|
791
|
+
const resolved = path3.isAbsolute(candidate) ? candidate : path3.resolve(baseCwd, candidate);
|
|
792
|
+
if (!existsSync3(resolved)) {
|
|
631
793
|
throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd does not exist: ${resolved}`);
|
|
632
794
|
}
|
|
633
795
|
try {
|
|
634
|
-
const stat =
|
|
796
|
+
const stat = statSync2(resolved);
|
|
635
797
|
if (!stat.isDirectory()) {
|
|
636
798
|
throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd is not a directory: ${resolved}`);
|
|
637
799
|
}
|
|
@@ -653,15 +815,15 @@ function redactPaths(message) {
|
|
|
653
815
|
}
|
|
654
816
|
|
|
655
817
|
// src/utils/files.ts
|
|
656
|
-
import { existsSync as
|
|
657
|
-
import
|
|
818
|
+
import { existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
819
|
+
import path4 from "path";
|
|
658
820
|
function resolveAndValidateFilePath(inputPath, baseDir, label = "path") {
|
|
659
|
-
const resolved =
|
|
660
|
-
if (!
|
|
821
|
+
const resolved = path4.isAbsolute(inputPath) ? inputPath : path4.resolve(baseDir, inputPath);
|
|
822
|
+
if (!existsSync4(resolved)) {
|
|
661
823
|
throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${label} does not exist: ${resolved}`);
|
|
662
824
|
}
|
|
663
825
|
try {
|
|
664
|
-
const stat =
|
|
826
|
+
const stat = statSync3(resolved);
|
|
665
827
|
if (!stat.isFile()) {
|
|
666
828
|
throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${label} is not a file: ${resolved}`);
|
|
667
829
|
}
|
|
@@ -1119,6 +1281,10 @@ var SessionManager = class {
|
|
|
1119
1281
|
approvalId: req.approvalId,
|
|
1120
1282
|
commandActions: req.commandActions,
|
|
1121
1283
|
proposedExecpolicyAmendment: req.proposedExecpolicyAmendment,
|
|
1284
|
+
availableDecisions: req.availableDecisions,
|
|
1285
|
+
proposedNetworkPolicyAmendments: req.proposedNetworkPolicyAmendments,
|
|
1286
|
+
additionalPermissions: req.additionalPermissions,
|
|
1287
|
+
networkApprovalContext: req.networkApprovalContext,
|
|
1122
1288
|
createdAt: req.createdAt
|
|
1123
1289
|
});
|
|
1124
1290
|
}
|
|
@@ -1220,6 +1386,17 @@ var SessionManager = class {
|
|
|
1220
1386
|
);
|
|
1221
1387
|
}
|
|
1222
1388
|
if (req.kind === "command") {
|
|
1389
|
+
const available = parseAvailableDecisionSet(req.availableDecisions);
|
|
1390
|
+
if (available && !available.has(decision)) {
|
|
1391
|
+
throw new Error(
|
|
1392
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Decision '${decision}' is not available for this approval prompt`
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
if (!available && decision === "applyNetworkPolicyAmendment") {
|
|
1396
|
+
throw new Error(
|
|
1397
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Decision '${decision}' is not supported by this Codex CLI version (missing availableDecisions)`
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1223
1400
|
if (!COMMAND_DECISIONS.includes(decision)) {
|
|
1224
1401
|
throw new Error(
|
|
1225
1402
|
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Invalid command decision '${decision}'`
|
|
@@ -1230,6 +1407,33 @@ var SessionManager = class {
|
|
|
1230
1407
|
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment required for acceptWithExecpolicyAmendment`
|
|
1231
1408
|
);
|
|
1232
1409
|
}
|
|
1410
|
+
if (decision !== "acceptWithExecpolicyAmendment" && extra?.execpolicy_amendment !== void 0) {
|
|
1411
|
+
throw new Error(
|
|
1412
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment is only valid for acceptWithExecpolicyAmendment`
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
if (decision === "applyNetworkPolicyAmendment") {
|
|
1416
|
+
const amendment = extra?.network_policy_amendment;
|
|
1417
|
+
if (!amendment) {
|
|
1418
|
+
throw new Error(
|
|
1419
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment required for applyNetworkPolicyAmendment`
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
if (amendment.action !== "allow" && amendment.action !== "deny") {
|
|
1423
|
+
throw new Error(
|
|
1424
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.action must be 'allow' or 'deny'`
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
if (!amendment.host) {
|
|
1428
|
+
throw new Error(
|
|
1429
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.host required for applyNetworkPolicyAmendment`
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
} else if (extra?.network_policy_amendment !== void 0) {
|
|
1433
|
+
throw new Error(
|
|
1434
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment is only valid for applyNetworkPolicyAmendment`
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1233
1437
|
} else if (req.kind === "fileChange") {
|
|
1234
1438
|
if (!FILE_CHANGE_DECISIONS.includes(decision)) {
|
|
1235
1439
|
throw new Error(
|
|
@@ -1243,7 +1447,10 @@ var SessionManager = class {
|
|
|
1243
1447
|
}
|
|
1244
1448
|
let response;
|
|
1245
1449
|
if (req.kind === "command") {
|
|
1246
|
-
response = buildCommandApprovalResponse(decision,
|
|
1450
|
+
response = buildCommandApprovalResponse(decision, {
|
|
1451
|
+
execpolicy_amendment: extra?.execpolicy_amendment,
|
|
1452
|
+
network_policy_amendment: extra?.network_policy_amendment
|
|
1453
|
+
});
|
|
1247
1454
|
} else if (req.kind === "fileChange") {
|
|
1248
1455
|
response = { decision };
|
|
1249
1456
|
}
|
|
@@ -1501,6 +1708,12 @@ var SessionManager = class {
|
|
|
1501
1708
|
const proposedExecpolicyAmendment = normalizeStringArrayOrNull(
|
|
1502
1709
|
approvalParams.proposedExecpolicyAmendment
|
|
1503
1710
|
);
|
|
1711
|
+
const availableDecisions = Array.isArray(approvalParams.availableDecisions) ? approvalParams.availableDecisions : null;
|
|
1712
|
+
const proposedNetworkPolicyAmendments = Array.isArray(
|
|
1713
|
+
approvalParams.proposedNetworkPolicyAmendments
|
|
1714
|
+
) ? approvalParams.proposedNetworkPolicyAmendments : null;
|
|
1715
|
+
const additionalPermissions = "additionalPermissions" in approvalParams ? approvalParams.additionalPermissions : void 0;
|
|
1716
|
+
const networkApprovalContext = "networkApprovalContext" in approvalParams ? approvalParams.networkApprovalContext : void 0;
|
|
1504
1717
|
const pending = {
|
|
1505
1718
|
requestId,
|
|
1506
1719
|
kind: "command",
|
|
@@ -1512,6 +1725,10 @@ var SessionManager = class {
|
|
|
1512
1725
|
approvalId,
|
|
1513
1726
|
commandActions,
|
|
1514
1727
|
proposedExecpolicyAmendment,
|
|
1728
|
+
availableDecisions,
|
|
1729
|
+
proposedNetworkPolicyAmendments,
|
|
1730
|
+
additionalPermissions,
|
|
1731
|
+
networkApprovalContext,
|
|
1515
1732
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1516
1733
|
resolved: false,
|
|
1517
1734
|
respond: (result) => client.respondToServer(id, result)
|
|
@@ -1559,7 +1776,11 @@ var SessionManager = class {
|
|
|
1559
1776
|
cwd: approvalParams.cwd,
|
|
1560
1777
|
reason,
|
|
1561
1778
|
commandActions,
|
|
1562
|
-
proposedExecpolicyAmendment
|
|
1779
|
+
proposedExecpolicyAmendment,
|
|
1780
|
+
availableDecisions,
|
|
1781
|
+
proposedNetworkPolicyAmendments,
|
|
1782
|
+
additionalPermissions,
|
|
1783
|
+
networkApprovalContext
|
|
1563
1784
|
},
|
|
1564
1785
|
true
|
|
1565
1786
|
);
|
|
@@ -1878,7 +2099,11 @@ function compactActionsForBudget(actions) {
|
|
|
1878
2099
|
itemId: action.itemId,
|
|
1879
2100
|
createdAt: action.createdAt,
|
|
1880
2101
|
commandActions: action.commandActions,
|
|
1881
|
-
proposedExecpolicyAmendment: action.proposedExecpolicyAmendment
|
|
2102
|
+
proposedExecpolicyAmendment: action.proposedExecpolicyAmendment,
|
|
2103
|
+
availableDecisions: action.availableDecisions,
|
|
2104
|
+
additionalPermissions: action.additionalPermissions,
|
|
2105
|
+
networkApprovalContext: action.networkApprovalContext,
|
|
2106
|
+
proposedNetworkPolicyAmendments: action.proposedNetworkPolicyAmendments
|
|
1882
2107
|
}));
|
|
1883
2108
|
}
|
|
1884
2109
|
function compactActionParamsForBudget(action) {
|
|
@@ -1911,7 +2136,11 @@ function compactActionsToMinimum(actions) {
|
|
|
1911
2136
|
itemId: first.itemId,
|
|
1912
2137
|
createdAt: first.createdAt,
|
|
1913
2138
|
commandActions: first.commandActions,
|
|
1914
|
-
proposedExecpolicyAmendment: first.proposedExecpolicyAmendment
|
|
2139
|
+
proposedExecpolicyAmendment: first.proposedExecpolicyAmendment,
|
|
2140
|
+
availableDecisions: first.availableDecisions,
|
|
2141
|
+
additionalPermissions: first.additionalPermissions,
|
|
2142
|
+
networkApprovalContext: first.networkApprovalContext,
|
|
2143
|
+
proposedNetworkPolicyAmendments: first.proposedNetworkPolicyAmendments
|
|
1915
2144
|
}
|
|
1916
2145
|
];
|
|
1917
2146
|
}
|
|
@@ -2135,8 +2364,9 @@ function toSensitiveInfo(session) {
|
|
|
2135
2364
|
config: session.config
|
|
2136
2365
|
};
|
|
2137
2366
|
}
|
|
2138
|
-
function buildCommandApprovalResponse(decision,
|
|
2367
|
+
function buildCommandApprovalResponse(decision, extra) {
|
|
2139
2368
|
if (decision === "acceptWithExecpolicyAmendment") {
|
|
2369
|
+
const execpolicy_amendment = extra?.execpolicy_amendment;
|
|
2140
2370
|
if (!execpolicy_amendment || execpolicy_amendment.length === 0) {
|
|
2141
2371
|
throw new Error(
|
|
2142
2372
|
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment required for acceptWithExecpolicyAmendment`
|
|
@@ -2150,11 +2380,41 @@ function buildCommandApprovalResponse(decision, execpolicy_amendment) {
|
|
|
2150
2380
|
}
|
|
2151
2381
|
};
|
|
2152
2382
|
}
|
|
2383
|
+
if (decision === "applyNetworkPolicyAmendment") {
|
|
2384
|
+
const amendment = extra?.network_policy_amendment;
|
|
2385
|
+
if (!amendment) {
|
|
2386
|
+
throw new Error(
|
|
2387
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment required for applyNetworkPolicyAmendment`
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
return {
|
|
2391
|
+
decision: {
|
|
2392
|
+
applyNetworkPolicyAmendment: {
|
|
2393
|
+
network_policy_amendment: amendment
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2153
2398
|
return { decision };
|
|
2154
2399
|
}
|
|
2155
2400
|
function isRecord(value) {
|
|
2156
2401
|
return typeof value === "object" && value !== null;
|
|
2157
2402
|
}
|
|
2403
|
+
function parseAvailableDecisionSet(available) {
|
|
2404
|
+
if (!Array.isArray(available) || available.length === 0) return null;
|
|
2405
|
+
const set = /* @__PURE__ */ new Set();
|
|
2406
|
+
for (const entry of available) {
|
|
2407
|
+
if (typeof entry === "string") {
|
|
2408
|
+
set.add(entry);
|
|
2409
|
+
continue;
|
|
2410
|
+
}
|
|
2411
|
+
if (isRecord(entry)) {
|
|
2412
|
+
if ("acceptWithExecpolicyAmendment" in entry) set.add("acceptWithExecpolicyAmendment");
|
|
2413
|
+
if ("applyNetworkPolicyAmendment" in entry) set.add("applyNetworkPolicyAmendment");
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
return set.size > 0 ? set : null;
|
|
2417
|
+
}
|
|
2158
2418
|
function extractThreadId(result) {
|
|
2159
2419
|
if (!isRecord(result)) {
|
|
2160
2420
|
throw new Error(`Error [${"INTERNAL" /* INTERNAL */}]: Invalid thread response: expected object`);
|
|
@@ -2272,9 +2532,9 @@ function executeCodexCheck(args, sessionManager) {
|
|
|
2272
2532
|
const pollOptions = args.pollOptions;
|
|
2273
2533
|
switch (args.action) {
|
|
2274
2534
|
case "poll": {
|
|
2275
|
-
if (args.requestId !== void 0 || args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.denyMessage !== void 0 || args.answers !== void 0) {
|
|
2535
|
+
if (args.requestId !== void 0 || args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.network_policy_amendment !== void 0 || args.denyMessage !== void 0 || args.answers !== void 0) {
|
|
2276
2536
|
return {
|
|
2277
|
-
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: requestId/decision/execpolicy_amendment/denyMessage/answers are only valid for respond_* actions`,
|
|
2537
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: requestId/decision/execpolicy_amendment/network_policy_amendment/denyMessage/answers are only valid for respond_* actions`,
|
|
2278
2538
|
isError: true
|
|
2279
2539
|
};
|
|
2280
2540
|
}
|
|
@@ -2310,6 +2570,31 @@ function executeCodexCheck(args, sessionManager) {
|
|
|
2310
2570
|
isError: true
|
|
2311
2571
|
};
|
|
2312
2572
|
}
|
|
2573
|
+
if (args.decision === "applyNetworkPolicyAmendment") {
|
|
2574
|
+
if (!args.network_policy_amendment) {
|
|
2575
|
+
return {
|
|
2576
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment required for applyNetworkPolicyAmendment`,
|
|
2577
|
+
isError: true
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
if (args.network_policy_amendment.action !== "allow" && args.network_policy_amendment.action !== "deny") {
|
|
2581
|
+
return {
|
|
2582
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.action must be 'allow' or 'deny'`,
|
|
2583
|
+
isError: true
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
if (!args.network_policy_amendment.host) {
|
|
2587
|
+
return {
|
|
2588
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.host required for applyNetworkPolicyAmendment`,
|
|
2589
|
+
isError: true
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
} else if (args.network_policy_amendment !== void 0) {
|
|
2593
|
+
return {
|
|
2594
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment is only valid with decision='applyNetworkPolicyAmendment'`,
|
|
2595
|
+
isError: true
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2313
2598
|
if (!ALL_DECISIONS.includes(args.decision)) {
|
|
2314
2599
|
return {
|
|
2315
2600
|
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown decision '${args.decision}'`,
|
|
@@ -2319,6 +2604,7 @@ function executeCodexCheck(args, sessionManager) {
|
|
|
2319
2604
|
try {
|
|
2320
2605
|
sessionManager.resolveApproval(args.sessionId, args.requestId, args.decision, {
|
|
2321
2606
|
execpolicy_amendment: args.execpolicy_amendment,
|
|
2607
|
+
network_policy_amendment: args.network_policy_amendment,
|
|
2322
2608
|
denyMessage: args.denyMessage
|
|
2323
2609
|
});
|
|
2324
2610
|
} catch (err) {
|
|
@@ -2338,9 +2624,9 @@ function executeCodexCheck(args, sessionManager) {
|
|
|
2338
2624
|
isError: true
|
|
2339
2625
|
};
|
|
2340
2626
|
}
|
|
2341
|
-
if (args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.denyMessage !== void 0) {
|
|
2627
|
+
if (args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.network_policy_amendment !== void 0 || args.denyMessage !== void 0) {
|
|
2342
2628
|
return {
|
|
2343
|
-
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision/execpolicy_amendment/denyMessage are only valid for respond_permission`,
|
|
2629
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision/execpolicy_amendment/network_policy_amendment/denyMessage are only valid for respond_permission`,
|
|
2344
2630
|
isError: true
|
|
2345
2631
|
};
|
|
2346
2632
|
}
|
|
@@ -2935,7 +3221,7 @@ function registerResources(server, deps) {
|
|
|
2935
3221
|
}
|
|
2936
3222
|
|
|
2937
3223
|
// src/server.ts
|
|
2938
|
-
var SERVER_VERSION = true ? "2.
|
|
3224
|
+
var SERVER_VERSION = true ? "2.1.1" : "0.0.0-dev";
|
|
2939
3225
|
function formatErrorMessage(err) {
|
|
2940
3226
|
const message = err instanceof Error ? err.message : String(err);
|
|
2941
3227
|
const m = /^Error \[([A-Z_]+)\]:\s*(.*)$/.exec(message);
|
|
@@ -3004,9 +3290,13 @@ function createServer(serverCwd) {
|
|
|
3004
3290
|
// respond_permission
|
|
3005
3291
|
requestId: z.string().optional().describe("Request ID from actions[]"),
|
|
3006
3292
|
decision: z.enum(ALL_DECISIONS).optional().describe(
|
|
3007
|
-
"Approval decision for respond_permission. acceptWithExecpolicyAmendment requires execpolicy_amendment."
|
|
3293
|
+
"Approval decision for respond_permission. acceptWithExecpolicyAmendment requires execpolicy_amendment; applyNetworkPolicyAmendment requires network_policy_amendment."
|
|
3008
3294
|
),
|
|
3009
3295
|
execpolicy_amendment: z.array(z.string()).optional().describe("For acceptWithExecpolicyAmendment only"),
|
|
3296
|
+
network_policy_amendment: z.object({
|
|
3297
|
+
action: z.enum(["allow", "deny"]),
|
|
3298
|
+
host: z.string().min(1)
|
|
3299
|
+
}).optional().describe("For applyNetworkPolicyAmendment only"),
|
|
3010
3300
|
denyMessage: z.string().optional().describe("Deny reason (not sent to agent)"),
|
|
3011
3301
|
// respond_user_input
|
|
3012
3302
|
answers: z.record(
|
|
@@ -3016,10 +3306,10 @@ function createServer(serverCwd) {
|
|
|
3016
3306
|
})
|
|
3017
3307
|
).optional().describe("question-id -> answers map (id from actions[] user_input request).")
|
|
3018
3308
|
}).superRefine((value, ctx) => {
|
|
3019
|
-
const addIssue = (
|
|
3309
|
+
const addIssue = (path5, message) => {
|
|
3020
3310
|
ctx.addIssue({
|
|
3021
3311
|
code: z.ZodIssueCode.custom,
|
|
3022
|
-
path: [
|
|
3312
|
+
path: [path5],
|
|
3023
3313
|
message
|
|
3024
3314
|
});
|
|
3025
3315
|
};
|
|
@@ -3043,6 +3333,12 @@ function createServer(serverCwd) {
|
|
|
3043
3333
|
"execpolicy_amendment is only allowed for action='respond_permission'."
|
|
3044
3334
|
);
|
|
3045
3335
|
}
|
|
3336
|
+
if (value.network_policy_amendment !== void 0) {
|
|
3337
|
+
addIssue(
|
|
3338
|
+
"network_policy_amendment",
|
|
3339
|
+
"network_policy_amendment is only allowed for action='respond_permission'."
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3046
3342
|
if (value.denyMessage !== void 0) {
|
|
3047
3343
|
addIssue("denyMessage", "denyMessage is only allowed for action='respond_permission'.");
|
|
3048
3344
|
}
|
|
@@ -3062,6 +3358,7 @@ function createServer(serverCwd) {
|
|
|
3062
3358
|
addIssue("answers", "answers is only allowed for action='respond_user_input'.");
|
|
3063
3359
|
}
|
|
3064
3360
|
const needsExecpolicy = value.decision === "acceptWithExecpolicyAmendment";
|
|
3361
|
+
const needsNetworkPolicy = value.decision === "applyNetworkPolicyAmendment";
|
|
3065
3362
|
if (needsExecpolicy && (!value.execpolicy_amendment || value.execpolicy_amendment.length === 0)) {
|
|
3066
3363
|
addIssue(
|
|
3067
3364
|
"execpolicy_amendment",
|
|
@@ -3074,6 +3371,18 @@ function createServer(serverCwd) {
|
|
|
3074
3371
|
"execpolicy_amendment is only allowed when decision='acceptWithExecpolicyAmendment'."
|
|
3075
3372
|
);
|
|
3076
3373
|
}
|
|
3374
|
+
if (needsNetworkPolicy && !value.network_policy_amendment) {
|
|
3375
|
+
addIssue(
|
|
3376
|
+
"network_policy_amendment",
|
|
3377
|
+
"network_policy_amendment is required when decision='applyNetworkPolicyAmendment'."
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
if (!needsNetworkPolicy && value.network_policy_amendment !== void 0) {
|
|
3381
|
+
addIssue(
|
|
3382
|
+
"network_policy_amendment",
|
|
3383
|
+
"network_policy_amendment is only allowed when decision='applyNetworkPolicyAmendment'."
|
|
3384
|
+
);
|
|
3385
|
+
}
|
|
3077
3386
|
break;
|
|
3078
3387
|
}
|
|
3079
3388
|
case "respond_user_input": {
|
|
@@ -3092,6 +3401,12 @@ function createServer(serverCwd) {
|
|
|
3092
3401
|
"execpolicy_amendment is only allowed for action='respond_permission'."
|
|
3093
3402
|
);
|
|
3094
3403
|
}
|
|
3404
|
+
if (value.network_policy_amendment !== void 0) {
|
|
3405
|
+
addIssue(
|
|
3406
|
+
"network_policy_amendment",
|
|
3407
|
+
"network_policy_amendment is only allowed for action='respond_permission'."
|
|
3408
|
+
);
|
|
3409
|
+
}
|
|
3095
3410
|
if (value.denyMessage !== void 0) {
|
|
3096
3411
|
addIssue("denyMessage", "denyMessage is only allowed for action='respond_permission'.");
|
|
3097
3412
|
}
|
|
@@ -3390,6 +3705,7 @@ async function main() {
|
|
|
3390
3705
|
"STDIO preflight failed in strict mode due to blocking stdout contamination risk"
|
|
3391
3706
|
);
|
|
3392
3707
|
}
|
|
3708
|
+
checkDefaultCodexExecutableAvailability();
|
|
3393
3709
|
const serverCwd = process.cwd();
|
|
3394
3710
|
const server = createServer(serverCwd);
|
|
3395
3711
|
const transport = new StdioServerTransport();
|