@byok-sdk/client 0.10.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -6
- package/dist/adapters/claude/claude-adapter.d.ts +3 -2
- package/dist/adapters/claude/permission-mapping.d.ts +48 -1
- package/dist/adapters/claude/resolve-approval-mcp-bin.d.ts +3 -2
- package/dist/adapters/codex/permission-mapping.d.ts +16 -0
- package/dist/adapters/index.js +292 -23
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/mcp-tool-grants.d.ts +36 -0
- package/dist/adapters/pi/resolve-extensions.d.ts +4 -1
- package/dist/adapters/pi/subagents-policy-config.d.ts +7 -0
- package/dist/adapters/pi/subagents-policy-extension.js +137 -0
- package/dist/adapters/pi/subagents-policy-extension.js.map +1 -0
- package/dist/agent-home.d.ts +33 -0
- package/dist/agent-memory/index.d.ts +1 -1
- package/dist/agent-memory/index.js +12 -9
- package/dist/agent-memory/index.js.map +1 -1
- package/dist/bin/agent-memory-mcp-server.d.ts +2 -2
- package/dist/bin/agent-message-mcp-server.d.ts +0 -1
- package/dist/bin/byok-agent-memory-mcp.js +340 -7
- package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
- package/dist/bin/byok-agent-message-mcp.js +331 -7
- package/dist/bin/byok-agent-message-mcp.js.map +1 -1
- package/dist/bin/byok-agent-team-mcp.d.ts +2 -0
- package/dist/bin/byok-agent-team-mcp.js +765 -0
- package/dist/bin/byok-agent-team-mcp.js.map +1 -0
- package/dist/bin/byok-agent.js +16413 -14479
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js +312 -45
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/bin/commands/team.d.ts +15 -0
- package/dist/bin/sdk-reserved-helper-runners.d.ts +2 -0
- package/dist/bin/team-mcp-server.d.ts +27 -0
- package/dist/bin/team-tmux-view.d.ts +19 -0
- package/dist/daemon/agent-egress-controller.d.ts +4 -0
- package/dist/daemon/agent-message-mcp-preflight.d.ts +17 -0
- package/dist/daemon/auth-manager.d.ts +20 -0
- package/dist/daemon/blob-client.d.ts +24 -6
- package/dist/daemon/connection-manager.d.ts +7 -0
- package/dist/daemon/control-protocol.d.ts +38 -0
- package/dist/daemon/create-daemon.d.ts +9 -0
- package/dist/daemon/device-credential-store.d.ts +16 -0
- package/dist/daemon/long-poll-transport.d.ts +3 -0
- package/dist/daemon/mcp-tools-probe.d.ts +105 -0
- package/dist/daemon/replay-cursor.d.ts +9 -0
- package/dist/daemon/resolve-agent-memory-mcp-bin.d.ts +2 -1
- package/dist/daemon/resolve-agent-message-mcp-bin.d.ts +2 -1
- package/dist/daemon/task-runner.d.ts +22 -1
- package/dist/daemon/team-workspace.d.ts +202 -0
- package/dist/daemon/toolset-registry.d.ts +0 -2
- package/dist/daemon/url.d.ts +7 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.js +3611 -1364
- package/dist/index.js.map +1 -1
- package/dist/sdk-reserved-helper-host.d.ts +25 -0
- package/dist/sdk-reserved-mcp.d.ts +23 -0
- package/dist/types.d.ts +36 -0
- package/package.json +10 -5
package/dist/adapters/index.js
CHANGED
|
@@ -67,6 +67,7 @@ function freezeRuntimeAdapterDescriptor(descriptor) {
|
|
|
67
67
|
return Object.freeze({
|
|
68
68
|
id: descriptor.id,
|
|
69
69
|
supportsDispatchSelection: descriptor.supportsDispatchSelection === true,
|
|
70
|
+
requiresMcpToolsetToolObservation: descriptor.requiresMcpToolsetToolObservation === true,
|
|
70
71
|
capabilities: Object.freeze({
|
|
71
72
|
steer: descriptor.capabilities.steer === true,
|
|
72
73
|
resume: descriptor.capabilities.resume === true,
|
|
@@ -143,12 +144,21 @@ function resolvePiExtensions() {
|
|
|
143
144
|
const clientManifest = fileURLToPath(import.meta.resolve("@byok-sdk/client/package.json"));
|
|
144
145
|
return {
|
|
145
146
|
webAccess: fileURLToPath(import.meta.resolve("pi-web-access/index.ts")),
|
|
146
|
-
mcpAdapter: path5.join(path5.dirname(clientManifest), "dist", "adapters", "pi", "mcp-extension.js")
|
|
147
|
+
mcpAdapter: path5.join(path5.dirname(clientManifest), "dist", "adapters", "pi", "mcp-extension.js"),
|
|
148
|
+
subagentsPolicy: path5.join(path5.dirname(clientManifest), "dist", "adapters", "pi", "subagents-policy-extension.js"),
|
|
149
|
+
subagents: fileURLToPath(import.meta.resolve("pi-subagents")),
|
|
150
|
+
// rpiv-todo publishes TypeScript source without a JavaScript `main`, so
|
|
151
|
+
// select the single authoritative Pi entry declared by its package metadata.
|
|
152
|
+
todo: fileURLToPath(import.meta.resolve("@juicesharp/rpiv-todo/index.ts"))
|
|
147
153
|
};
|
|
148
154
|
}
|
|
149
155
|
|
|
156
|
+
// src/adapters/pi/subagents-policy-config.ts
|
|
157
|
+
var BYOK_PI_PERMISSION_MODE = "BYOK_PI_PERMISSION_MODE";
|
|
158
|
+
var BYOK_PI_READONLY_PARENT_TOOLS = ["subagent", "todo"];
|
|
159
|
+
|
|
150
160
|
// src/adapters/pi/permission-mapping.ts
|
|
151
|
-
var READONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
161
|
+
var READONLY_TOOLS = ["read", "grep", "find", "ls", ...BYOK_PI_READONLY_PARENT_TOOLS];
|
|
152
162
|
function mapPermissionPolicyToPiArgs(policy) {
|
|
153
163
|
if (policy.network === false) {
|
|
154
164
|
return {
|
|
@@ -970,6 +980,11 @@ var PiAdapter = class {
|
|
|
970
980
|
descriptor = freezeRuntimeAdapterDescriptor({
|
|
971
981
|
id: "pi",
|
|
972
982
|
supportsDispatchSelection: true,
|
|
983
|
+
// `requiresMcpToolsetToolObservation` is deliberately absent: pi projects
|
|
984
|
+
// MCP servers through its own adapter extension and grants their tools
|
|
985
|
+
// itself, so it never reads `mcpToolsetTools`. Declaring the requirement
|
|
986
|
+
// would make every pi-routed toolset offer wait on a `tools/list` probe of
|
|
987
|
+
// every projected server for an observation nothing consumes.
|
|
973
988
|
capabilities: {
|
|
974
989
|
steer: true,
|
|
975
990
|
resume: true,
|
|
@@ -1067,21 +1082,40 @@ var PiAdapter = class {
|
|
|
1067
1082
|
let mcpConfigDir;
|
|
1068
1083
|
let runtimeEnv = manifestSelection === void 0 ? startInput.env : withoutProviderCredentials(startInput.env);
|
|
1069
1084
|
const taskMcpServers = startInput.mcpServers ?? {};
|
|
1070
|
-
|
|
1071
|
-
|
|
1085
|
+
let mcpConfigPath;
|
|
1086
|
+
try {
|
|
1072
1087
|
mcpConfigDir = await promises.mkdtemp(path5.join(os.tmpdir(), "byok-pi-mcp-"));
|
|
1073
1088
|
await promises.chmod(mcpConfigDir, 448).catch(() => {
|
|
1074
1089
|
});
|
|
1075
|
-
|
|
1090
|
+
mcpConfigPath = path5.join(mcpConfigDir, "mcp-config.json");
|
|
1076
1091
|
await promises.writeFile(mcpConfigPath, JSON.stringify({ mcpServers: taskMcpServers }), { mode: 384 });
|
|
1077
|
-
|
|
1092
|
+
} catch (cause) {
|
|
1093
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1094
|
+
throw new RuntimeExecutionFailure({
|
|
1095
|
+
phase: "start",
|
|
1096
|
+
category: "infrastructure",
|
|
1097
|
+
retry: "retryable",
|
|
1098
|
+
reason: "pi task-scoped MCP configuration could not be created"
|
|
1099
|
+
}, { cause });
|
|
1078
1100
|
}
|
|
1101
|
+
runtimeEnv = {
|
|
1102
|
+
...runtimeEnv,
|
|
1103
|
+
[BYOK_PI_MCP_CONFIG_PATH]: mcpConfigPath,
|
|
1104
|
+
[BYOK_PI_PERMISSION_MODE]: input.policy.mode
|
|
1105
|
+
};
|
|
1079
1106
|
const piArgs = [
|
|
1080
1107
|
"--mode",
|
|
1081
1108
|
"rpc",
|
|
1082
1109
|
"--extension",
|
|
1083
1110
|
extensions.webAccess,
|
|
1084
|
-
|
|
1111
|
+
"--extension",
|
|
1112
|
+
extensions.mcpAdapter,
|
|
1113
|
+
"--extension",
|
|
1114
|
+
extensions.subagentsPolicy,
|
|
1115
|
+
"--extension",
|
|
1116
|
+
extensions.subagents,
|
|
1117
|
+
"--extension",
|
|
1118
|
+
extensions.todo,
|
|
1085
1119
|
...resumeSessionId ? ["--session", resumeSessionId] : [],
|
|
1086
1120
|
...mapping.args
|
|
1087
1121
|
];
|
|
@@ -1298,18 +1332,79 @@ function resolveClaudeBin() {
|
|
|
1298
1332
|
}
|
|
1299
1333
|
return { command: "claude", source: "path" };
|
|
1300
1334
|
}
|
|
1301
|
-
|
|
1335
|
+
|
|
1336
|
+
// src/sdk-reserved-mcp.ts
|
|
1337
|
+
var AGENT_MESSAGE_MCP_SERVER_NAME = "byokagentmessage";
|
|
1338
|
+
var AGENT_MESSAGE_TOOL_NAME = "send_agent_message";
|
|
1339
|
+
var AGENT_MEMORY_MCP_SERVER_NAME = "byokagentmemory";
|
|
1340
|
+
var AGENT_TEAM_MCP_SERVER_NAME = "byokagentteam";
|
|
1341
|
+
var APPROVAL_MCP_SERVER_NAME = "byokapproval";
|
|
1342
|
+
var RESERVED_MCP_SERVER_NAMES = Object.freeze([
|
|
1343
|
+
AGENT_MESSAGE_MCP_SERVER_NAME,
|
|
1344
|
+
AGENT_MEMORY_MCP_SERVER_NAME,
|
|
1345
|
+
APPROVAL_MCP_SERVER_NAME,
|
|
1346
|
+
AGENT_TEAM_MCP_SERVER_NAME
|
|
1347
|
+
]);
|
|
1348
|
+
function isReservedMcpServerName(name) {
|
|
1349
|
+
return RESERVED_MCP_SERVER_NAMES.includes(name);
|
|
1350
|
+
}
|
|
1351
|
+
var AGENT_MEMORY_RECALL_TOOL_NAME = "memory_recall";
|
|
1352
|
+
var AGENT_MEMORY_SAVE_TOOL_NAME = "memory_save";
|
|
1353
|
+
var APPROVAL_TOOL_NAME = "approval_prompt";
|
|
1354
|
+
|
|
1355
|
+
// src/sdk-reserved-helper-host.ts
|
|
1356
|
+
var BYOK_SDK_HELPER_SUBCOMMAND = "__byok_sdk_helper";
|
|
1357
|
+
var DIST_SCRIPT_BY_KIND = Object.freeze({
|
|
1358
|
+
"agent-message-mcp": "byok-agent-message-mcp.js",
|
|
1359
|
+
"agent-memory-mcp": "byok-agent-memory-mcp.js",
|
|
1360
|
+
"approval-mcp": "byok-approval-mcp.js",
|
|
1361
|
+
"agent-team-mcp": "byok-agent-team-mcp.js"
|
|
1362
|
+
});
|
|
1363
|
+
function assertExecutable(executable) {
|
|
1364
|
+
if (!path5.isAbsolute(executable) || /[\u0000\r\n]/u.test(executable)) {
|
|
1365
|
+
throw new Error("SdkHelperHostConfig.executable must be an absolute executable path");
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function resolveSdkReservedHelperBin(kind, host) {
|
|
1369
|
+
if (host !== void 0) {
|
|
1370
|
+
if (host.mode !== "self-executable") throw new Error(`unsupported SDK helper host mode: ${String(host.mode)}`);
|
|
1371
|
+
const executable = host.executable ?? process.execPath;
|
|
1372
|
+
assertExecutable(executable);
|
|
1373
|
+
return Object.freeze({
|
|
1374
|
+
command: executable,
|
|
1375
|
+
args: Object.freeze([BYOK_SDK_HELPER_SUBCOMMAND, kind]),
|
|
1376
|
+
source: "self-executable"
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
const script = path5.join(
|
|
1380
|
+
path5.dirname(fileURLToPath(import.meta.url)),
|
|
1381
|
+
"bin",
|
|
1382
|
+
DIST_SCRIPT_BY_KIND[kind]
|
|
1383
|
+
);
|
|
1384
|
+
return Object.freeze({
|
|
1385
|
+
command: process.execPath,
|
|
1386
|
+
args: Object.freeze([script]),
|
|
1387
|
+
source: "dist-script"
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// src/adapters/claude/resolve-approval-mcp-bin.ts
|
|
1392
|
+
function resolveApprovalMcpBin(host) {
|
|
1302
1393
|
const override = process.env.BYOK_APPROVAL_MCP_BIN;
|
|
1303
1394
|
if (override) {
|
|
1304
1395
|
return { command: override, args: [], source: "env" };
|
|
1305
1396
|
}
|
|
1306
|
-
const
|
|
1307
|
-
return {
|
|
1397
|
+
const resolved = resolveSdkReservedHelperBin("approval-mcp", host);
|
|
1398
|
+
return {
|
|
1399
|
+
command: resolved.command,
|
|
1400
|
+
args: [...resolved.args],
|
|
1401
|
+
source: resolved.source === "self-executable" ? "host" : "dist"
|
|
1402
|
+
};
|
|
1308
1403
|
}
|
|
1309
1404
|
|
|
1310
1405
|
// src/adapters/claude/permission-mapping.ts
|
|
1311
1406
|
var READONLY_TOOLS2 = ["Read", "Glob", "Grep"];
|
|
1312
|
-
function mapPermissionPolicyToClaudeArgs(policy) {
|
|
1407
|
+
function mapPermissionPolicyToClaudeArgs(policy, toolsetGrants = []) {
|
|
1313
1408
|
if (policy.network === false) {
|
|
1314
1409
|
return {
|
|
1315
1410
|
ok: false,
|
|
@@ -1335,7 +1430,10 @@ function mapPermissionPolicyToClaudeArgs(policy) {
|
|
|
1335
1430
|
if (policy.mode === "readonly") {
|
|
1336
1431
|
const base = policy.allowTools ? policy.allowTools.filter((tool) => READONLY_TOOLS2.includes(tool)) : [...READONLY_TOOLS2];
|
|
1337
1432
|
const effective = subtractDenied(base, denyTools);
|
|
1338
|
-
return {
|
|
1433
|
+
return {
|
|
1434
|
+
ok: true,
|
|
1435
|
+
args: ["--permission-mode", "default", "--tools", effective.join(","), ...allowedToolsArgs(toolsetGrants)]
|
|
1436
|
+
};
|
|
1339
1437
|
}
|
|
1340
1438
|
if (denyTools.length > 0) {
|
|
1341
1439
|
return {
|
|
@@ -1349,12 +1447,17 @@ function mapPermissionPolicyToClaudeArgs(policy) {
|
|
|
1349
1447
|
if (policy.allowTools && policy.allowTools.length > 0) {
|
|
1350
1448
|
args.push("--tools", policy.allowTools.join(","));
|
|
1351
1449
|
}
|
|
1450
|
+
if (policy.mode !== "plan") args.push(...allowedToolsArgs(toolsetGrants));
|
|
1352
1451
|
return { ok: true, args };
|
|
1353
1452
|
}
|
|
1354
1453
|
function subtractDenied(tools, denyTools) {
|
|
1355
1454
|
const denied = new Set(denyTools);
|
|
1356
1455
|
return tools.filter((tool) => !denied.has(tool));
|
|
1357
1456
|
}
|
|
1457
|
+
function allowedToolsArgs(toolsetGrants) {
|
|
1458
|
+
const identifiers = toolsetGrants.flatMap((grant) => grant.tools.map((tool) => `mcp__${grant.server}__${tool}`)).sort();
|
|
1459
|
+
return identifiers.length === 0 ? [] : ["--allowedTools", identifiers.join(",")];
|
|
1460
|
+
}
|
|
1358
1461
|
function createToolUseCorrelation() {
|
|
1359
1462
|
return { toolNameByUseId: /* @__PURE__ */ new Map() };
|
|
1360
1463
|
}
|
|
@@ -1759,10 +1862,62 @@ var ClaudeProcessClient = class {
|
|
|
1759
1862
|
this.eventQueue.end();
|
|
1760
1863
|
}
|
|
1761
1864
|
};
|
|
1762
|
-
var
|
|
1865
|
+
var GRANTABLE_TOOL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/u;
|
|
1866
|
+
var GRANTABLE_MCP_SERVER_NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/u;
|
|
1867
|
+
|
|
1868
|
+
// src/adapters/mcp-tool-grants.ts
|
|
1869
|
+
var PREGRANTED_RESERVED_MCP_TOOLS = Object.freeze({
|
|
1870
|
+
[AGENT_MESSAGE_MCP_SERVER_NAME]: Object.freeze([AGENT_MESSAGE_TOOL_NAME]),
|
|
1871
|
+
[AGENT_MEMORY_MCP_SERVER_NAME]: Object.freeze([
|
|
1872
|
+
AGENT_MEMORY_RECALL_TOOL_NAME,
|
|
1873
|
+
AGENT_MEMORY_SAVE_TOOL_NAME
|
|
1874
|
+
])
|
|
1875
|
+
});
|
|
1876
|
+
function resolveReservedMcpToolGrants(servers) {
|
|
1877
|
+
return Object.entries(PREGRANTED_RESERVED_MCP_TOOLS).filter(([server]) => Object.prototype.hasOwnProperty.call(servers ?? {}, server)).map(([server, tools]) => Object.freeze({ server, tools }));
|
|
1878
|
+
}
|
|
1879
|
+
function resolveMcpToolsetGrants(servers, observation) {
|
|
1880
|
+
const projected = Object.keys(servers ?? {}).filter((name) => !isReservedMcpServerName(name)).sort();
|
|
1881
|
+
const ungrantableServers = projected.filter((name) => !GRANTABLE_MCP_SERVER_NAME.test(name));
|
|
1882
|
+
if (ungrantableServers.length > 0) {
|
|
1883
|
+
return {
|
|
1884
|
+
ok: false,
|
|
1885
|
+
reason: `projected MCP toolset server name(s) [${ungrantableServers.join(", ")}] cannot be expressed as a runtime tool grant \u2014 refusing to start a task whose grants would be ambiguous`
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
const observed = observation ?? {};
|
|
1889
|
+
const unexpected = Object.keys(observed).filter((name) => !projected.includes(name)).sort();
|
|
1890
|
+
if (unexpected.length > 0) {
|
|
1891
|
+
return {
|
|
1892
|
+
ok: false,
|
|
1893
|
+
reason: `observed MCP tool names for server(s) [${unexpected.join(", ")}] that are not projected for this task \u2014 refusing to grant tools for a server the task was never given`
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
const grants = [];
|
|
1897
|
+
for (const server of projected) {
|
|
1898
|
+
const tools = observed[server];
|
|
1899
|
+
if (tools === void 0 || tools.length === 0) {
|
|
1900
|
+
return {
|
|
1901
|
+
ok: false,
|
|
1902
|
+
reason: `no tools/list observation for projected MCP toolset server "${server}" \u2014 refusing to start a task whose toolset tools cannot be granted`
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
const ungrantableTools = tools.filter((tool) => typeof tool !== "string" || !GRANTABLE_TOOL_NAME.test(tool));
|
|
1906
|
+
if (ungrantableTools.length > 0) {
|
|
1907
|
+
return {
|
|
1908
|
+
ok: false,
|
|
1909
|
+
reason: `MCP toolset server "${server}" reported tool name(s) [${ungrantableTools.map((tool) => JSON.stringify(tool)).join(", ")}] that cannot be expressed as a runtime tool grant`
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
grants.push({ server, tools: Object.freeze([...tools].sort()) });
|
|
1913
|
+
}
|
|
1914
|
+
return { ok: true, grants: Object.freeze(grants) };
|
|
1915
|
+
}
|
|
1916
|
+
function grantFingerprint(grants) {
|
|
1917
|
+
return grants.flatMap((grant) => grant.tools.map((tool) => `${grant.server} ${tool}`)).sort().join("\n");
|
|
1918
|
+
}
|
|
1763
1919
|
|
|
1764
1920
|
// src/adapters/claude/claude-adapter.ts
|
|
1765
|
-
var APPROVAL_MCP_SERVER_NAME = "byokapproval";
|
|
1766
1921
|
var execFileAsync2 = promisify(execFile);
|
|
1767
1922
|
var DETECT_TIMEOUT_MS2 = 5e3;
|
|
1768
1923
|
function errorMessage2(err) {
|
|
@@ -1787,6 +1942,10 @@ var ClaudeAdapter = class {
|
|
|
1787
1942
|
descriptor = freezeRuntimeAdapterDescriptor({
|
|
1788
1943
|
id: "claude",
|
|
1789
1944
|
supportsDispatchSelection: true,
|
|
1945
|
+
// `--allowedTools mcp__<server>__<tool>` names each projected toolset
|
|
1946
|
+
// tool explicitly, so this adapter cannot admit a projected server
|
|
1947
|
+
// without the daemon's own `tools/list` observation of it.
|
|
1948
|
+
requiresMcpToolsetToolObservation: true,
|
|
1790
1949
|
capabilities: {
|
|
1791
1950
|
steer: false,
|
|
1792
1951
|
resume: true,
|
|
@@ -1808,7 +1967,13 @@ var ClaudeAdapter = class {
|
|
|
1808
1967
|
}
|
|
1809
1968
|
}
|
|
1810
1969
|
async prepare(input) {
|
|
1811
|
-
const
|
|
1970
|
+
const toolsetGrants = resolveMcpToolsetGrants(input.mcpServers, input.mcpToolsetTools);
|
|
1971
|
+
if (!toolsetGrants.ok) return { kind: "reject", reason: `claude adapter cannot grant projected MCP toolset tools: ${toolsetGrants.reason}`, retryable: false };
|
|
1972
|
+
const reservedMemoryGrants = resolveReservedMcpToolGrants(input.mcpServers).filter((grant) => grant.server === AGENT_MEMORY_MCP_SERVER_NAME);
|
|
1973
|
+
const mapping = mapPermissionPolicyToClaudeArgs(input.policy, [
|
|
1974
|
+
...reservedMemoryGrants,
|
|
1975
|
+
...toolsetGrants.grants
|
|
1976
|
+
]);
|
|
1812
1977
|
if (!mapping.ok) return { kind: "reject", reason: mapping.reason ?? "policy rejected by claude adapter", retryable: false };
|
|
1813
1978
|
let modelId;
|
|
1814
1979
|
try {
|
|
@@ -1836,11 +2001,11 @@ var ClaudeAdapter = class {
|
|
|
1836
2001
|
return {
|
|
1837
2002
|
kind: "prepared",
|
|
1838
2003
|
operation: {
|
|
1839
|
-
start: (startInput) => this.startPrepared(startInput, mapping, modelId, bin, approvalMcpBin)
|
|
2004
|
+
start: (startInput) => this.startPrepared(startInput, mapping, modelId, bin, approvalMcpBin, toolsetGrants.grants)
|
|
1840
2005
|
}
|
|
1841
2006
|
};
|
|
1842
2007
|
}
|
|
1843
|
-
async startPrepared(startInput, initialMapping, modelId, bin, approvalMcpBin) {
|
|
2008
|
+
async startPrepared(startInput, initialMapping, modelId, bin, approvalMcpBin, preparedGrants) {
|
|
1844
2009
|
if (!initialMapping.ok) throw new RuntimeExecutionFailure({
|
|
1845
2010
|
phase: "start",
|
|
1846
2011
|
category: "authority",
|
|
@@ -1855,6 +2020,15 @@ var ClaudeAdapter = class {
|
|
|
1855
2020
|
reason: "prepared claude operation requires a resolved string instruction"
|
|
1856
2021
|
});
|
|
1857
2022
|
}
|
|
2023
|
+
const startGrants = resolveMcpToolsetGrants(startInput.mcpServers, startInput.mcpToolsetTools);
|
|
2024
|
+
if (!startGrants.ok || grantFingerprint(startGrants.grants) !== grantFingerprint(preparedGrants)) {
|
|
2025
|
+
throw new RuntimeExecutionFailure({
|
|
2026
|
+
phase: "start",
|
|
2027
|
+
category: "authority",
|
|
2028
|
+
retry: "non-retryable",
|
|
2029
|
+
reason: "prepared claude operation received different MCP toolset tool authority than it was admitted with"
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
1858
2032
|
const mapping = { ...initialMapping, args: [...initialMapping.args] };
|
|
1859
2033
|
let mcpConfigDir;
|
|
1860
2034
|
const taskMcpServers = startInput.mcpServers ?? {};
|
|
@@ -2539,6 +2713,8 @@ var CodexProcessRunner = class {
|
|
|
2539
2713
|
// src/adapters/codex/codex-adapter.ts
|
|
2540
2714
|
var execFileAsync3 = promisify(execFile);
|
|
2541
2715
|
var DETECT_TIMEOUT_MS3 = 5e3;
|
|
2716
|
+
var RESERVED_MCP_POLICY_PROBE_TIMEOUT_MS = 5e3;
|
|
2717
|
+
var MIN_CODEX_RESERVED_MCP_APPROVAL_VERSION = [0, 149, 0];
|
|
2542
2718
|
var CodexAdapter = class {
|
|
2543
2719
|
constructor(options = {}) {
|
|
2544
2720
|
this.options = options;
|
|
@@ -2547,6 +2723,11 @@ var CodexAdapter = class {
|
|
|
2547
2723
|
descriptor = freezeRuntimeAdapterDescriptor({
|
|
2548
2724
|
id: "codex",
|
|
2549
2725
|
supportsDispatchSelection: true,
|
|
2726
|
+
// `mcp_servers.<name>.enabled_tools` plus a per-tool `approval_mode`
|
|
2727
|
+
// names each projected toolset tool explicitly, so this adapter cannot
|
|
2728
|
+
// admit a projected server without the daemon's own `tools/list`
|
|
2729
|
+
// observation of it.
|
|
2730
|
+
requiresMcpToolsetToolObservation: true,
|
|
2550
2731
|
capabilities: {
|
|
2551
2732
|
steer: false,
|
|
2552
2733
|
resume: true,
|
|
@@ -2619,14 +2800,43 @@ ${withStreams.stderr ?? ""}`);
|
|
|
2619
2800
|
} catch (error) {
|
|
2620
2801
|
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
2621
2802
|
}
|
|
2803
|
+
const toolsetGrants = resolveMcpToolsetGrants(input.mcpServers, input.mcpToolsetTools);
|
|
2804
|
+
if (!toolsetGrants.ok) {
|
|
2805
|
+
return { kind: "reject", reason: `codex adapter cannot grant projected MCP toolset tools: ${toolsetGrants.reason}`, retryable: false };
|
|
2806
|
+
}
|
|
2807
|
+
const reservedGrants = resolveReservedMcpToolGrants(input.mcpServers);
|
|
2808
|
+
const allGrants = Object.freeze([...reservedGrants, ...toolsetGrants.grants]);
|
|
2809
|
+
if (allGrants.length > 0) {
|
|
2810
|
+
try {
|
|
2811
|
+
await requireCodexPerToolApprovalSupport(command);
|
|
2812
|
+
for (const grant of allGrants) {
|
|
2813
|
+
await probeCodexMcpToolApproval(command, grant.server, input.mcpServers[grant.server], grant.tools);
|
|
2814
|
+
}
|
|
2815
|
+
} catch (error) {
|
|
2816
|
+
return {
|
|
2817
|
+
kind: "reject",
|
|
2818
|
+
reason: `codex MCP tool approval preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2819
|
+
retryable: false
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2622
2823
|
return {
|
|
2623
2824
|
kind: "prepared",
|
|
2624
2825
|
operation: {
|
|
2625
|
-
start: (startInput) => this.startPrepared(startInput, mapping.args, modelId, command)
|
|
2826
|
+
start: (startInput) => this.startPrepared(startInput, mapping.args, modelId, command, toolsetGrants.grants, allGrants)
|
|
2626
2827
|
}
|
|
2627
2828
|
};
|
|
2628
2829
|
}
|
|
2629
|
-
async startPrepared(startInput, policyArgs, modelId, command) {
|
|
2830
|
+
async startPrepared(startInput, policyArgs, modelId, command, preparedToolsetGrants, preparedMcpGrants) {
|
|
2831
|
+
const startGrants = resolveMcpToolsetGrants(startInput.mcpServers, startInput.mcpToolsetTools);
|
|
2832
|
+
if (!startGrants.ok || grantFingerprint(startGrants.grants) !== grantFingerprint(preparedToolsetGrants)) {
|
|
2833
|
+
throw new RuntimeExecutionFailure({
|
|
2834
|
+
phase: "start",
|
|
2835
|
+
category: "authority",
|
|
2836
|
+
retry: "non-retryable",
|
|
2837
|
+
reason: "prepared codex operation received different MCP toolset tool authority than it was admitted with"
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2630
2840
|
if (typeof startInput.instruction !== "string") {
|
|
2631
2841
|
throw new RuntimeExecutionFailure({
|
|
2632
2842
|
phase: "start",
|
|
@@ -2678,12 +2888,13 @@ ${withStreams.stderr ?? ""}`);
|
|
|
2678
2888
|
reason: "prepared codex operation received a manifest with different runtime selection"
|
|
2679
2889
|
});
|
|
2680
2890
|
}
|
|
2891
|
+
const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, preparedMcpGrants);
|
|
2681
2892
|
const { sessionRef, runner } = await runCodexTurn({
|
|
2682
2893
|
command,
|
|
2683
2894
|
resumeRef: startInput.manifest.sessionRef,
|
|
2684
2895
|
instruction: startInput.instruction,
|
|
2685
2896
|
modelId: manifestModelId,
|
|
2686
|
-
policyArgs: [...policyArgs, ...
|
|
2897
|
+
policyArgs: [...policyArgs, ...mcpConfigArgs],
|
|
2687
2898
|
cwd: manifestCwd,
|
|
2688
2899
|
env: runtimeEnv,
|
|
2689
2900
|
spawnFn: this.options.spawnFn,
|
|
@@ -2706,15 +2917,55 @@ ${withStreams.stderr ?? ""}`);
|
|
|
2706
2917
|
initialRunner: runner,
|
|
2707
2918
|
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
2708
2919
|
modelId: manifestModelId,
|
|
2709
|
-
terminal
|
|
2920
|
+
terminal,
|
|
2921
|
+
mcpConfigArgs
|
|
2710
2922
|
});
|
|
2711
2923
|
}
|
|
2712
2924
|
resolveBin() {
|
|
2713
2925
|
return (this.options.resolveBin ?? resolveCodexBin)();
|
|
2714
2926
|
}
|
|
2715
2927
|
};
|
|
2716
|
-
function
|
|
2928
|
+
async function requireCodexPerToolApprovalSupport(command) {
|
|
2929
|
+
const versionResult = await execFileAsync3(command, ["--version"], { timeout: RESERVED_MCP_POLICY_PROBE_TIMEOUT_MS });
|
|
2930
|
+
const versionText = `${versionResult.stdout}
|
|
2931
|
+
${versionResult.stderr}`.trim();
|
|
2932
|
+
const versionMatch = /codex-cli\s+(\d+)\.(\d+)\.(\d+)/u.exec(versionText);
|
|
2933
|
+
if (versionMatch === null) throw new Error(`unrecognized Codex version: ${versionText || "(empty)"}`);
|
|
2934
|
+
const version = versionMatch.slice(1, 4).map(Number);
|
|
2935
|
+
for (let index = 0; index < MIN_CODEX_RESERVED_MCP_APPROVAL_VERSION.length; index += 1) {
|
|
2936
|
+
if (version[index] > MIN_CODEX_RESERVED_MCP_APPROVAL_VERSION[index]) break;
|
|
2937
|
+
if (version[index] < MIN_CODEX_RESERVED_MCP_APPROVAL_VERSION[index]) {
|
|
2938
|
+
throw new Error(`Codex ${version.slice(0, 3).join(".")} lacks the required per-MCP-tool approval contract`);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
async function probeCodexMcpToolApproval(command, name, server, tools) {
|
|
2943
|
+
const probeArgs = [
|
|
2944
|
+
"mcp",
|
|
2945
|
+
"get",
|
|
2946
|
+
name,
|
|
2947
|
+
"--json",
|
|
2948
|
+
"-c",
|
|
2949
|
+
`mcp_servers.${name}.command=${JSON.stringify(server.command)}`,
|
|
2950
|
+
...codexMcpToolApprovalArgs(name, tools)
|
|
2951
|
+
];
|
|
2952
|
+
const result = await execFileAsync3(command, probeArgs, { timeout: RESERVED_MCP_POLICY_PROBE_TIMEOUT_MS });
|
|
2953
|
+
const parsed = JSON.parse(result.stdout);
|
|
2954
|
+
const readBack = Array.isArray(parsed.enabled_tools) ? parsed.enabled_tools : void 0;
|
|
2955
|
+
if (parsed.name !== name || parsed.enabled !== true || readBack === void 0 || readBack.length !== tools.length || readBack.some((tool, index) => tool !== tools[index])) {
|
|
2956
|
+
throw new Error(`Codex did not read back the exact tool allowlist for MCP server "${name}"`);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
function codexMcpToolApprovalArgs(name, tools) {
|
|
2960
|
+
const args = ["-c", `mcp_servers.${name}.enabled_tools=${JSON.stringify([...tools])}`];
|
|
2961
|
+
for (const tool of tools) {
|
|
2962
|
+
args.push("-c", `mcp_servers.${name}.tools.${tool}.approval_mode="approve"`);
|
|
2963
|
+
}
|
|
2964
|
+
return args;
|
|
2965
|
+
}
|
|
2966
|
+
function codexMcpConfigArgs(servers, grants = []) {
|
|
2717
2967
|
if (servers === void 0 || Object.keys(servers).length === 0) return [];
|
|
2968
|
+
const grantedTools = new Map(grants.map((grant) => [grant.server, grant.tools]));
|
|
2718
2969
|
const args = ["--ignore-user-config"];
|
|
2719
2970
|
for (const [name, server] of Object.entries(servers).sort(([left], [right]) => left.localeCompare(right))) {
|
|
2720
2971
|
args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(server.command)}`);
|
|
@@ -2722,6 +2973,8 @@ function codexMcpConfigArgs(servers) {
|
|
|
2722
2973
|
for (const [key, value] of Object.entries(server.env ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
2723
2974
|
args.push("-c", `mcp_servers.${name}.env.${key}=${JSON.stringify(value)}`);
|
|
2724
2975
|
}
|
|
2976
|
+
const granted = grantedTools.get(name);
|
|
2977
|
+
if (granted !== void 0) args.push(...codexMcpToolApprovalArgs(name, granted));
|
|
2725
2978
|
}
|
|
2726
2979
|
return args;
|
|
2727
2980
|
}
|
|
@@ -2908,6 +3161,19 @@ var CodexSession = class {
|
|
|
2908
3161
|
recordUnmapped;
|
|
2909
3162
|
preparedGit;
|
|
2910
3163
|
modelId;
|
|
3164
|
+
/**
|
|
3165
|
+
* A resume spawns a BRAND NEW codex process, which inherits none of the
|
|
3166
|
+
* first turn's `-c` overrides — exactly the same reason `followUp()`
|
|
3167
|
+
* re-maps the permission policy on every call. Without replaying these
|
|
3168
|
+
* bytes, `--ignore-user-config` and every `mcp_servers.*` key (command,
|
|
3169
|
+
* args, env, `enabled_tools`, per-tool `approval_mode`) would silently
|
|
3170
|
+
* vanish after turn one: the reserved message server and any projected
|
|
3171
|
+
* toolset would stop existing, and any surviving tool call would be
|
|
3172
|
+
* refused outright under `approval_policy=never`. Frozen at start; never
|
|
3173
|
+
* recomputed from anything a later turn supplies, so a follow-up can never
|
|
3174
|
+
* widen this session's MCP authority.
|
|
3175
|
+
*/
|
|
3176
|
+
mcpConfigArgs;
|
|
2911
3177
|
terminal;
|
|
2912
3178
|
currentRunner;
|
|
2913
3179
|
ownedRunners = /* @__PURE__ */ new Set();
|
|
@@ -2925,6 +3191,7 @@ var CodexSession = class {
|
|
|
2925
3191
|
this.preparedGit = options.preparedGit;
|
|
2926
3192
|
this.modelId = options.modelId;
|
|
2927
3193
|
this.terminal = options.terminal;
|
|
3194
|
+
this.mcpConfigArgs = options.mcpConfigArgs;
|
|
2928
3195
|
this.currentRunner = options.initialRunner;
|
|
2929
3196
|
this.ownedRunners.add(options.initialRunner);
|
|
2930
3197
|
void this.forgetRunnerOnceClosed(options.initialRunner);
|
|
@@ -3033,7 +3300,9 @@ var CodexSession = class {
|
|
|
3033
3300
|
resumeRef,
|
|
3034
3301
|
instruction: task.instruction,
|
|
3035
3302
|
modelId,
|
|
3036
|
-
|
|
3303
|
+
// Freshly re-mapped policy for THIS turn, plus the start turn's
|
|
3304
|
+
// frozen MCP config replayed verbatim — see `mcpConfigArgs`.
|
|
3305
|
+
policyArgs: [...mapping.args, ...this.mcpConfigArgs],
|
|
3037
3306
|
cwd: this.workspaceDir,
|
|
3038
3307
|
env: withoutProviderCredentials(this.env),
|
|
3039
3308
|
spawnFn: this.spawnFn,
|