@cjhyy/code-shell-core 0.6.0-rc.9 → 0.7.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/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/automation/scheduler.d.ts +13 -7
- package/dist/automation/scheduler.js +116 -37
- package/dist/capability-control/service.d.ts +2 -0
- package/dist/capability-control/service.js +4 -2
- package/dist/cc-orchestrator/agent-adapter.d.ts +4 -0
- package/dist/cc-orchestrator/agent-adapter.js +11 -1
- package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
- package/dist/cc-orchestrator/codex-session-history.js +65 -5
- package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
- package/dist/cc-orchestrator/cwd-normalize.js +19 -0
- package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
- package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
- package/dist/cc-orchestrator/external-agent-changes.js +22 -5
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +265 -50
- package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
- package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
- package/dist/cc-orchestrator/session-history.d.ts +35 -0
- package/dist/cc-orchestrator/session-history.js +98 -15
- package/dist/cli/agent-server-stdio.js +9 -2
- package/dist/context/compaction.d.ts +8 -1
- package/dist/context/compaction.js +49 -4
- package/dist/context/manager.d.ts +16 -2
- package/dist/context/manager.js +103 -19
- package/dist/credentials/access.d.ts +57 -0
- package/dist/credentials/access.js +185 -0
- package/dist/credentials/index.d.ts +3 -1
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +30 -11
- package/dist/credentials/oauth.d.ts +20 -0
- package/dist/credentials/oauth.js +114 -0
- package/dist/credentials/store.d.ts +1 -0
- package/dist/credentials/store.js +3 -1
- package/dist/credentials/types.d.ts +49 -3
- package/dist/credentials/use-credential-tool.d.ts +9 -1
- package/dist/credentials/use-credential-tool.js +58 -45
- package/dist/engine/engine.d.ts +23 -3
- package/dist/engine/engine.js +380 -255
- package/dist/engine/goal.d.ts +17 -0
- package/dist/engine/goal.js +16 -6
- package/dist/engine/image-policy.d.ts +6 -0
- package/dist/engine/image-policy.js +17 -6
- package/dist/engine/input-attachments.d.ts +13 -0
- package/dist/engine/input-attachments.js +398 -0
- package/dist/engine/model-facade.d.ts +5 -2
- package/dist/engine/model-facade.js +4 -4
- package/dist/engine/parse-task.d.ts +10 -0
- package/dist/engine/parse-task.js +5 -0
- package/dist/engine/run-image-input.d.ts +22 -0
- package/dist/engine/run-image-input.js +195 -0
- package/dist/engine/steer-queue.d.ts +3 -1
- package/dist/engine/steer-queue.js +10 -2
- package/dist/engine/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +37 -2
- package/dist/engine/turn-loop.js +228 -43
- package/dist/engine/types.d.ts +8 -0
- package/dist/git/worktree/crud.d.ts +69 -0
- package/dist/git/worktree/crud.js +206 -0
- package/dist/git/worktree/diff.d.ts +14 -0
- package/dist/git/worktree/diff.js +82 -0
- package/dist/git/worktree/git-exec.d.ts +7 -0
- package/dist/git/worktree/git-exec.js +51 -0
- package/dist/git/worktree/index.d.ts +5 -0
- package/dist/git/worktree/index.js +5 -0
- package/dist/git/worktree/query.d.ts +42 -0
- package/dist/git/worktree/query.js +121 -0
- package/dist/git/worktree/slug.d.ts +11 -0
- package/dist/git/worktree/slug.js +58 -0
- package/dist/git/worktree.d.ts +1 -84
- package/dist/git/worktree.js +5 -230
- package/dist/hooks/goal-stop-hook.d.ts +33 -1
- package/dist/hooks/goal-stop-hook.js +202 -34
- package/dist/index.d.ts +31 -28
- package/dist/index.js +27 -25
- package/dist/logging/logger.js +6 -6
- package/dist/logging/sanitize-messages.d.ts +10 -2
- package/dist/logging/sanitize-messages.js +21 -6
- package/dist/plugins/installer/checkUpdate.d.ts +4 -1
- package/dist/plugins/installer/checkUpdate.js +4 -2
- package/dist/plugins/installer/install.js +2 -0
- package/dist/plugins/installer/installFromSource.js +9 -1
- package/dist/plugins/installer/parseSource.d.ts +4 -1
- package/dist/plugins/installer/parseSource.js +28 -10
- package/dist/plugins/installer/sourcePath.d.ts +9 -0
- package/dist/plugins/installer/sourcePath.js +50 -0
- package/dist/plugins/installer/update.d.ts +4 -1
- package/dist/plugins/installer/update.js +5 -3
- package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
- package/dist/plugins/parseMarketplaceInput.js +4 -3
- package/dist/plugins/pluginInstaller.js +24 -22
- package/dist/preset/index.d.ts +1 -0
- package/dist/preset/index.js +30 -13
- package/dist/prompt/sections/base.md +1 -1
- package/dist/protocol/chat-session-manager.d.ts +2 -0
- package/dist/protocol/chat-session-manager.js +38 -2
- package/dist/protocol/chat-session.d.ts +3 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/client.d.ts +9 -4
- package/dist/protocol/client.js +18 -1
- package/dist/protocol/server.d.ts +30 -0
- package/dist/protocol/server.js +247 -47
- package/dist/protocol/types.d.ts +49 -0
- package/dist/protocol/types.js +6 -0
- package/dist/run/FileRunStore.js +10 -1
- package/dist/run/Heartbeat.js +12 -0
- package/dist/run/RunApprovalBackend.d.ts +3 -0
- package/dist/run/RunApprovalBackend.js +41 -6
- package/dist/run/RunLock.js +2 -0
- package/dist/run/RunManager.d.ts +2 -0
- package/dist/run/RunManager.js +64 -24
- package/dist/run/ids.d.ts +2 -0
- package/dist/run/ids.js +23 -0
- package/dist/runtime/background-shell.d.ts +1 -0
- package/dist/runtime/background-shell.js +23 -13
- package/dist/runtime/spawn-common.js +10 -0
- package/dist/services/auto-dream.d.ts +15 -4
- package/dist/services/auto-dream.js +20 -20
- package/dist/services/dream-consolidation.d.ts +2 -3
- package/dist/services/dream-consolidation.js +65 -15
- package/dist/services/extract-memories.d.ts +14 -5
- package/dist/services/extract-memories.js +20 -3
- package/dist/services/global-dream-promotion.d.ts +23 -0
- package/dist/services/global-dream-promotion.js +112 -0
- package/dist/services/memory-orchestrator.js +347 -34
- package/dist/session/memory.d.ts +61 -18
- package/dist/session/memory.js +342 -79
- package/dist/session/session-manager.d.ts +35 -1
- package/dist/session/session-manager.js +224 -4
- package/dist/session/transcript.d.ts +1 -1
- package/dist/session/transcript.js +17 -5
- package/dist/settings/manager.d.ts +1 -0
- package/dist/settings/manager.js +87 -37
- package/dist/settings/schema-export.d.ts +2 -3
- package/dist/settings/schema-export.js +2 -3
- package/dist/settings/schema.d.ts +21 -0
- package/dist/settings/schema.js +12 -0
- package/dist/skills/scanner.d.ts +3 -2
- package/dist/skills/scanner.js +12 -9
- package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
- package/dist/tool-system/builtin/agent-notifications.js +19 -7
- package/dist/tool-system/builtin/background-jobs.d.ts +35 -6
- package/dist/tool-system/builtin/background-jobs.js +116 -7
- package/dist/tool-system/builtin/background-work.d.ts +17 -18
- package/dist/tool-system/builtin/background-work.js +51 -5
- package/dist/tool-system/builtin/config.d.ts +2 -1
- package/dist/tool-system/builtin/config.js +16 -11
- package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
- package/dist/tool-system/builtin/cron-list.definition.js +6 -0
- package/dist/tool-system/builtin/cron.d.ts +1 -2
- package/dist/tool-system/builtin/cron.js +9 -7
- package/dist/tool-system/builtin/drive-claude-code.d.ts +23 -2
- package/dist/tool-system/builtin/drive-claude-code.js +589 -48
- package/dist/tool-system/builtin/edit.js +5 -2
- package/dist/tool-system/builtin/generate-video.d.ts +1 -0
- package/dist/tool-system/builtin/generate-video.js +13 -4
- package/dist/tool-system/builtin/index.d.ts +8 -3
- package/dist/tool-system/builtin/index.js +50 -22
- package/dist/tool-system/builtin/lsp.d.ts +2 -1
- package/dist/tool-system/builtin/lsp.js +6 -3
- package/dist/tool-system/builtin/memory.js +40 -8
- package/dist/tool-system/builtin/notebook-edit.js +5 -2
- package/dist/tool-system/builtin/powershell.d.ts +5 -2
- package/dist/tool-system/builtin/powershell.js +11 -7
- package/dist/tool-system/builtin/read.js +118 -6
- package/dist/tool-system/builtin/sleep.d.ts +1 -2
- package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
- package/dist/tool-system/builtin/sleep.definition.js +28 -0
- package/dist/tool-system/builtin/sleep.js +1 -22
- package/dist/tool-system/builtin/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/view-image.js +109 -19
- package/dist/tool-system/builtin/worktree.d.ts +4 -4
- package/dist/tool-system/builtin/worktree.js +297 -74
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +41 -1
- package/dist/tool-system/executor.d.ts +1 -5
- package/dist/tool-system/executor.js +94 -115
- package/dist/tool-system/mcp-manager.d.ts +31 -6
- package/dist/tool-system/mcp-manager.js +193 -78
- package/dist/tool-system/path-policy.d.ts +2 -0
- package/dist/tool-system/path-policy.js +41 -12
- package/dist/tool-system/permission.d.ts +28 -7
- package/dist/tool-system/permission.js +130 -49
- package/dist/tool-system/registry.js +11 -4
- package/dist/tool-system/tool-result-redaction.d.ts +7 -0
- package/dist/tool-system/tool-result-redaction.js +48 -0
- package/dist/tool-system/workspace-bridge.d.ts +11 -0
- package/dist/tool-system/workspace-bridge.js +1 -0
- package/dist/types.d.ts +78 -11
- package/dist/utils/toolDisplay.js +1 -1
- package/package.json +4 -3
|
@@ -7,11 +7,13 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
7
7
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8
8
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
9
9
|
import { logger } from "../logging/logger.js";
|
|
10
|
-
import {
|
|
10
|
+
import { getCredentialAccess } from "../credentials/access.js";
|
|
11
|
+
import { buildOAuthRefreshRequest, oauthCredentialStatus, parseOAuthCredentialSecret, } from "../credentials/oauth.js";
|
|
11
12
|
import { ENV_ALLOWLIST } from "../runtime/spawn-common.js";
|
|
12
|
-
import {
|
|
13
|
+
import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
|
|
13
14
|
import { join } from "node:path";
|
|
14
15
|
import { diagnoseMcpStdioMissingCommand, previewPath } from "./mcp-stdio-diagnostics.js";
|
|
16
|
+
import { codeShellHome } from "../session/session-manager.js";
|
|
15
17
|
/**
|
|
16
18
|
* Read a required secret from `process.env` by NAME (Codex-style env-secret
|
|
17
19
|
* handling — the value is never persisted in MCP config). A referenced env var
|
|
@@ -72,14 +74,14 @@ export function buildStdioEnv(serverName, config) {
|
|
|
72
74
|
* base; env-sourced secrets (`bearerTokenEnvVar`, `envHeaders`) layer on top
|
|
73
75
|
* and win on conflict. Pure + exported for unit testing.
|
|
74
76
|
*/
|
|
75
|
-
export function buildHttpHeaders(serverName, config, resolveCredential) {
|
|
77
|
+
export function buildHttpHeaders(serverName, config, resolveCredential, options = {}) {
|
|
76
78
|
const headers = { ...(config.headers ?? {}) };
|
|
77
79
|
if (config.credentialRef) {
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
+
const credential = normalizeResolvedMcpCredential(resolveCredential?.(config.credentialRef));
|
|
81
|
+
if (!credential || credential.secret === "") {
|
|
80
82
|
throw new Error(`MCP server "${serverName}": credential "${config.credentialRef}" not found or empty`);
|
|
81
83
|
}
|
|
82
|
-
headers["Authorization"] = `Bearer ${
|
|
84
|
+
headers["Authorization"] = `Bearer ${bearerTokenFromMcpCredential(serverName, config.credentialRef, credential, options)}`;
|
|
83
85
|
}
|
|
84
86
|
else if (config.bearerTokenEnvVar) {
|
|
85
87
|
headers["Authorization"] = `Bearer ${readRequiredEnv(serverName, "bearerTokenEnvVar", config.bearerTokenEnvVar)}`;
|
|
@@ -89,6 +91,68 @@ export function buildHttpHeaders(serverName, config, resolveCredential) {
|
|
|
89
91
|
}
|
|
90
92
|
return headers;
|
|
91
93
|
}
|
|
94
|
+
function normalizeResolvedMcpCredential(value) {
|
|
95
|
+
if (typeof value === "string")
|
|
96
|
+
return { secret: value };
|
|
97
|
+
if (!value)
|
|
98
|
+
return undefined;
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
export function bearerTokenFromMcpCredential(serverName, credentialId, credential, options = {}) {
|
|
102
|
+
if (credential.type === undefined)
|
|
103
|
+
return credential.secret;
|
|
104
|
+
if (credential.type !== "oauth") {
|
|
105
|
+
if (credential.type !== "token" && credential.type !== "link") {
|
|
106
|
+
throw new Error(`MCP server "${serverName}": credential "${credentialId}" type "${credential.type}" cannot be used as a bearer token`);
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(credential.secret);
|
|
110
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
111
|
+
throw new Error(`MCP server "${serverName}": credential "${credentialId}" resolved to a structured secret that does not match token metadata; refusing bearer injection`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (err instanceof SyntaxError)
|
|
116
|
+
return credential.secret;
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
return credential.secret;
|
|
120
|
+
}
|
|
121
|
+
const secret = parseOAuthCredentialSecret(credential.secret);
|
|
122
|
+
const status = oauthCredentialStatus(secret, {
|
|
123
|
+
now: options.now,
|
|
124
|
+
skewMs: options.oauthRefreshSkewMs,
|
|
125
|
+
});
|
|
126
|
+
if (status.state === "expired") {
|
|
127
|
+
const refresh = buildOAuthRefreshRequest(credentialId, secret);
|
|
128
|
+
const refreshHint = refresh
|
|
129
|
+
? "refresh data is present, but automatic OAuth refresh is reserved and not wired yet"
|
|
130
|
+
: "missing refreshToken or tokenEndpoint for automatic refresh";
|
|
131
|
+
throw new Error(`MCP server "${serverName}": oauth credential "${credentialId}" access token expired; ${refreshHint}`);
|
|
132
|
+
}
|
|
133
|
+
return secret.accessToken;
|
|
134
|
+
}
|
|
135
|
+
export async function buildHttpHeadersWithCredentialAccess(serverName, config, access = getCredentialAccess(), options = {}) {
|
|
136
|
+
if (!config.credentialRef)
|
|
137
|
+
return buildHttpHeaders(serverName, config, undefined, options);
|
|
138
|
+
if (!access.resolveValue) {
|
|
139
|
+
throw new Error(`MCP server "${serverName}": credential "${config.credentialRef}" not found or empty`);
|
|
140
|
+
}
|
|
141
|
+
const meta = access.resolveMeta?.(undefined, config.credentialRef, "full");
|
|
142
|
+
if (!meta) {
|
|
143
|
+
throw new Error(`MCP server "${serverName}": credential "${config.credentialRef}" metadata is unavailable; refusing bearer injection`);
|
|
144
|
+
}
|
|
145
|
+
if (meta.type !== "token" && meta.type !== "link" && meta.type !== "oauth") {
|
|
146
|
+
throw new Error(`MCP server "${serverName}": credential "${config.credentialRef}" type "${meta.type}" cannot be used as a bearer token`);
|
|
147
|
+
}
|
|
148
|
+
const secret = await access.resolveValue({
|
|
149
|
+
cwd: undefined,
|
|
150
|
+
id: config.credentialRef,
|
|
151
|
+
scope: "full",
|
|
152
|
+
purpose: "mcp",
|
|
153
|
+
});
|
|
154
|
+
return buildHttpHeaders(serverName, config, (id) => id === config.credentialRef ? { secret, type: meta?.type, label: meta?.label } : undefined, options);
|
|
155
|
+
}
|
|
92
156
|
/**
|
|
93
157
|
* Infer the transport when the config doesn't name one: a url-only entry is
|
|
94
158
|
* HTTP, everything else stdio. This is the CC `.mcp.json` convention —
|
|
@@ -118,7 +182,29 @@ export function inferTransportType(config) {
|
|
|
118
182
|
* spill itself is bounded by the byte budget below; this cap is just
|
|
119
183
|
* to keep ls(~/.code-shell/mcp_images) tractable.
|
|
120
184
|
*/
|
|
185
|
+
const MAX_MCP_IMAGE_SPILLS_PER_TOOL = 50;
|
|
121
186
|
const MAX_MCP_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
187
|
+
function spillTimestamp(filename, prefix) {
|
|
188
|
+
const suffix = filename.slice(prefix.length);
|
|
189
|
+
const dot = suffix.lastIndexOf(".");
|
|
190
|
+
const raw = dot === -1 ? suffix : suffix.slice(0, dot);
|
|
191
|
+
const timestamp = Number(raw);
|
|
192
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
193
|
+
}
|
|
194
|
+
async function pruneMcpImageSpills(baseDir, prefix) {
|
|
195
|
+
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
196
|
+
const spills = entries
|
|
197
|
+
.filter((entry) => entry.isFile() && entry.name.startsWith(prefix))
|
|
198
|
+
.map((entry) => ({
|
|
199
|
+
name: entry.name,
|
|
200
|
+
timestamp: spillTimestamp(entry.name, prefix),
|
|
201
|
+
}))
|
|
202
|
+
.sort((a, b) => a.timestamp - b.timestamp || a.name.localeCompare(b.name));
|
|
203
|
+
const excess = spills.length - MAX_MCP_IMAGE_SPILLS_PER_TOOL;
|
|
204
|
+
if (excess <= 0)
|
|
205
|
+
return;
|
|
206
|
+
await Promise.all(spills.slice(0, excess).map((entry) => unlink(join(baseDir, entry.name)).catch(() => { })));
|
|
207
|
+
}
|
|
122
208
|
/**
|
|
123
209
|
* Persist an MCP-returned image to disk and return the textual
|
|
124
210
|
* reference the LLM should see. Images larger than
|
|
@@ -130,9 +216,7 @@ const MAX_MCP_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
|
130
216
|
* [mcp-image] server=playwright tool=screenshot saved=/abs/path.png (123 KB)
|
|
131
217
|
*/
|
|
132
218
|
export async function spillMcpImage(serverName, toolName, base64, mimeType, opts) {
|
|
133
|
-
const
|
|
134
|
-
const baseDir = opts?.baseDir ??
|
|
135
|
-
(home ? join(home, ".code-shell", "mcp_images") : join("/tmp", "code-shell-mcp-images"));
|
|
219
|
+
const baseDir = opts?.baseDir ?? join(codeShellHome(), "mcp_images");
|
|
136
220
|
const now = opts?.now ?? Date.now;
|
|
137
221
|
const decodedBytes = Math.floor((base64.length * 3) / 4);
|
|
138
222
|
if (decodedBytes > MAX_MCP_IMAGE_BYTES) {
|
|
@@ -147,11 +231,13 @@ export async function spillMcpImage(serverName, toolName, base64, mimeType, opts
|
|
|
147
231
|
: "png";
|
|
148
232
|
const safeServer = serverName.replace(/[^\w.-]+/g, "_");
|
|
149
233
|
const safeTool = toolName.replace(/[^\w.-]+/g, "_");
|
|
150
|
-
const
|
|
234
|
+
const prefix = `${safeServer}-${safeTool}-`;
|
|
235
|
+
const filename = `${prefix}${now()}.${ext}`;
|
|
151
236
|
const filePath = join(baseDir, filename);
|
|
152
237
|
try {
|
|
153
238
|
await mkdir(baseDir, { recursive: true });
|
|
154
239
|
await writeFile(filePath, Buffer.from(base64, "base64"));
|
|
240
|
+
await pruneMcpImageSpills(baseDir, prefix);
|
|
155
241
|
}
|
|
156
242
|
catch (err) {
|
|
157
243
|
logger.warn("mcp.image_spill_failed", {
|
|
@@ -205,7 +291,10 @@ export function buildRegisteredTool(serverName, tool) {
|
|
|
205
291
|
return {
|
|
206
292
|
name: toOpenAIToolName(`mcp_${serverName}_${tool.name}`),
|
|
207
293
|
description: `[${serverName}] ${tool.description ?? tool.name}`,
|
|
208
|
-
inputSchema: tool.inputSchema ?? {
|
|
294
|
+
inputSchema: tool.inputSchema ?? {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {},
|
|
297
|
+
},
|
|
209
298
|
source: "mcp",
|
|
210
299
|
serverName,
|
|
211
300
|
permissionDefault: "ask",
|
|
@@ -247,13 +336,16 @@ export class MCPManager {
|
|
|
247
336
|
* Connect to all configured MCP servers and register their tools.
|
|
248
337
|
*/
|
|
249
338
|
async connectAll(servers, owner) {
|
|
339
|
+
const enabledNames = this.enabledServerNames(servers);
|
|
250
340
|
// Register this owner's desired set up front (see reconcile's shared-pool
|
|
251
341
|
// note) so a later reconcile from ANOTHER session can't disconnect servers
|
|
252
342
|
// this session connected at run start.
|
|
253
343
|
if (owner !== undefined) {
|
|
254
|
-
this.desiredByOwner.set(owner,
|
|
255
|
-
|
|
256
|
-
|
|
344
|
+
this.desiredByOwner.set(owner, enabledNames);
|
|
345
|
+
this.desiredServerNames = this.unionDesired() ?? new Set();
|
|
346
|
+
}
|
|
347
|
+
else if (this.desiredByOwner.size === 0) {
|
|
348
|
+
this.desiredServerNames = enabledNames;
|
|
257
349
|
}
|
|
258
350
|
// Codex-style toggle: skip servers explicitly disabled in settings.
|
|
259
351
|
// Only the literal `false` disables — absent / true / any other value
|
|
@@ -279,26 +371,42 @@ export class MCPManager {
|
|
|
279
371
|
}
|
|
280
372
|
}
|
|
281
373
|
async reconcile(servers, owner) {
|
|
282
|
-
const enabledNames =
|
|
283
|
-
.filter(([, config]) => config.enabled !== false)
|
|
284
|
-
.map(([name]) => name));
|
|
285
|
-
this.desiredServerNames = enabledNames;
|
|
374
|
+
const enabledNames = this.enabledServerNames(servers);
|
|
286
375
|
// Shared-pool semantics: this ONE pool serves every session in the worker,
|
|
287
376
|
// and sessions in different projects legitimately want DIFFERENT server
|
|
288
377
|
// sets (per-project capabilityOverrides). Disconnect only servers that NO
|
|
289
378
|
// registered owner wants — otherwise the per-session hot-reload patches
|
|
290
379
|
// would thrash each other's connections (last reconcile wins, killing a
|
|
291
|
-
// server another project's session is using). Owners are engines;
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
// correctness regardless.
|
|
380
|
+
// server another project's session is using). Owners are engines; closed
|
|
381
|
+
// sessions call unregisterOwner() so their desired sets stop retaining idle
|
|
382
|
+
// connections.
|
|
295
383
|
if (owner !== undefined)
|
|
296
384
|
this.desiredByOwner.set(owner, enabledNames);
|
|
297
385
|
const union = this.unionDesired() ?? enabledNames;
|
|
386
|
+
this.desiredServerNames = union;
|
|
298
387
|
const stale = this.listServers().filter((name) => !union.has(name));
|
|
299
388
|
await Promise.all(stale.map((name) => this.disconnect(name)));
|
|
300
389
|
await this.connectAll(servers, owner);
|
|
301
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Remove one owner from the shared desired-server pool and disconnect servers
|
|
393
|
+
* that no remaining owner wants. This is called when a session-owned Engine
|
|
394
|
+
* is closed; without it project-scoped MCP servers can stay connected until
|
|
395
|
+
* worker shutdown.
|
|
396
|
+
*/
|
|
397
|
+
async unregisterOwner(owner) {
|
|
398
|
+
if (!this.desiredByOwner.delete(owner))
|
|
399
|
+
return;
|
|
400
|
+
const desired = this.unionDesired() ?? new Set();
|
|
401
|
+
this.desiredServerNames = desired;
|
|
402
|
+
const stale = this.listServers().filter((name) => !desired.has(name));
|
|
403
|
+
await Promise.all(stale.map((name) => this.disconnect(name)));
|
|
404
|
+
}
|
|
405
|
+
enabledServerNames(servers) {
|
|
406
|
+
return new Set(Object.entries(servers)
|
|
407
|
+
.filter(([, config]) => config.enabled !== false)
|
|
408
|
+
.map(([name]) => name));
|
|
409
|
+
}
|
|
302
410
|
/** Union of every registered owner's desired set; null when none registered. */
|
|
303
411
|
unionDesired() {
|
|
304
412
|
if (this.desiredByOwner.size === 0)
|
|
@@ -361,12 +469,10 @@ export class MCPManager {
|
|
|
361
469
|
if (!config.url) {
|
|
362
470
|
throw new Error(`MCP server "${name}": url is required for ${transportType} transport`);
|
|
363
471
|
}
|
|
364
|
-
// credentialRef resolves against
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
const credStore = new CredentialStore(undefined);
|
|
369
|
-
const headers = buildHttpHeaders(name, config, (id) => credStore.resolve(id)?.secret);
|
|
472
|
+
// credentialRef resolves against user-scope credential access. The shared
|
|
473
|
+
// MCPManager pool has no per-session cwd, and MCP-referenced credentials
|
|
474
|
+
// (e.g. a Figma token) remain user-global by design.
|
|
475
|
+
const headers = await buildHttpHeadersWithCredentialAccess(name, config);
|
|
370
476
|
transport = new StreamableHTTPClientTransport(new URL(config.url), {
|
|
371
477
|
requestInit: Object.keys(headers).length ? { headers } : undefined,
|
|
372
478
|
});
|
|
@@ -429,59 +535,65 @@ export class MCPManager {
|
|
|
429
535
|
* Discover tools from an MCP server and register them.
|
|
430
536
|
*/
|
|
431
537
|
async discoverTools(serverName, client) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
538
|
+
try {
|
|
539
|
+
const result = await client.listTools();
|
|
540
|
+
for (const tool of result.tools) {
|
|
541
|
+
const registered = buildRegisteredTool(serverName, tool);
|
|
542
|
+
// Register with an executor that calls the MCP server
|
|
543
|
+
this.toolRegistry.registerTool(registered, async (args, ctx) => {
|
|
544
|
+
const callResult = await client.callTool({
|
|
545
|
+
name: tool.name,
|
|
546
|
+
arguments: stripInternalToolArgs(args),
|
|
547
|
+
}, undefined, ctx?.signal ? { signal: ctx.signal } : undefined);
|
|
548
|
+
// Extract text + image content from the result. Image blobs
|
|
549
|
+
// are spilled to ~/.code-shell/mcp_images/ so they don't bloat
|
|
550
|
+
// the LLM message tree — same pattern as the GenerateImage
|
|
551
|
+
// tool — and the model sees a textual reference it can Read
|
|
552
|
+
// on a later turn if it needs the pixels. This sidesteps the
|
|
553
|
+
// "MCP returned a 5MB screenshot → token budget exploded"
|
|
554
|
+
// failure mode that bit Codex (issue #11845); we never put
|
|
555
|
+
// raw base64 image data in the result text. See
|
|
556
|
+
// TODO-week.md #9c + docs/research-cc-vs-codex-image-handling.md §B.
|
|
557
|
+
const parts = [];
|
|
558
|
+
if (Array.isArray(callResult.content)) {
|
|
559
|
+
for (const item of callResult.content) {
|
|
560
|
+
if (typeof item === "string") {
|
|
561
|
+
parts.push(item);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (typeof item !== "object" || item === null)
|
|
565
|
+
continue;
|
|
566
|
+
if ("text" in item) {
|
|
567
|
+
parts.push(String(item.text));
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (item.type === "image") {
|
|
571
|
+
const block = item;
|
|
572
|
+
if (typeof block.data === "string" && block.data.length > 0) {
|
|
573
|
+
const note = await spillMcpImage(serverName, tool.name, block.data, block.mimeType ?? "image/png");
|
|
574
|
+
parts.push(note);
|
|
575
|
+
}
|
|
468
576
|
}
|
|
469
577
|
}
|
|
470
578
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
579
|
+
const body = parts.join("\n") || "(no output)";
|
|
580
|
+
// Trust boundary: MCP output is external content. Wrap it so the
|
|
581
|
+
// model sees an explicit reminder that the body comes from an
|
|
582
|
+
// untrusted server and any instructions inside are content, not
|
|
583
|
+
// commands. The marker is intentionally short so it doesn't bloat
|
|
584
|
+
// every tool result, but distinct enough that prompt-injected
|
|
585
|
+
// strings can't fake their way out.
|
|
586
|
+
return wrapMcpOutput(serverName, tool.name, body);
|
|
587
|
+
});
|
|
588
|
+
const set = this.registeredToolsByServer.get(serverName) ?? new Set();
|
|
589
|
+
set.add(registered.name);
|
|
590
|
+
this.registeredToolsByServer.set(serverName, set);
|
|
591
|
+
logger.info("mcp.tool_registered", { server: serverName, tool: registered.name });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch (err) {
|
|
595
|
+
await this.disconnect(serverName);
|
|
596
|
+
throw err;
|
|
485
597
|
}
|
|
486
598
|
}
|
|
487
599
|
/**
|
|
@@ -489,6 +601,8 @@ export class MCPManager {
|
|
|
489
601
|
*/
|
|
490
602
|
async disconnectAll() {
|
|
491
603
|
await Promise.all([...this.connections.keys()].map((name) => this.disconnect(name)));
|
|
604
|
+
this.desiredByOwner.clear();
|
|
605
|
+
this.desiredServerNames = null;
|
|
492
606
|
}
|
|
493
607
|
async disconnect(name) {
|
|
494
608
|
const conn = this.connections.get(name);
|
|
@@ -562,6 +676,7 @@ export class MCPManager {
|
|
|
562
676
|
uri: r.uri,
|
|
563
677
|
name: r.name ?? r.uri,
|
|
564
678
|
description: r.description,
|
|
679
|
+
serverName: name,
|
|
565
680
|
});
|
|
566
681
|
}
|
|
567
682
|
}
|
|
@@ -52,6 +52,8 @@ export interface ClassifyOptions {
|
|
|
52
52
|
export type PathApprovalScope = "once" | "session" | "project";
|
|
53
53
|
/** Test seam: clear the in-memory session grants. */
|
|
54
54
|
export declare function _resetSessionPathGrants(): void;
|
|
55
|
+
export declare function openSessionPathApprovals(sessionId: string): void;
|
|
56
|
+
export declare function clearSessionPathApprovals(sessionId: string): void;
|
|
55
57
|
/**
|
|
56
58
|
* Classify a file path against the workspace + sensitive-path policy.
|
|
57
59
|
*
|
|
@@ -105,6 +105,10 @@ function expandTilde(p) {
|
|
|
105
105
|
}
|
|
106
106
|
/** sessionId → set of approved directory grants (prefix + operation). */
|
|
107
107
|
const sessionPathGrants = new Map();
|
|
108
|
+
/** Session ids explicitly closed by ChatSessionManager; late approvals must not recreate grants. */
|
|
109
|
+
const closedPathApprovalSessions = new Set();
|
|
110
|
+
/** Per-session prompt chains for enforcePathPolicyWithApproval (see comment there). */
|
|
111
|
+
const askChains = new Map();
|
|
108
112
|
/** True if a grant for `grantOp` authorizes an operation of `wantOp`. */
|
|
109
113
|
function grantCoversOp(grantOp, wantOp) {
|
|
110
114
|
// write ⊇ {read, write}; read ⊇ {read}.
|
|
@@ -198,6 +202,8 @@ function storedOp(g) {
|
|
|
198
202
|
function recordPathApproval(scope, resolved, operation, cwd, sessionId) {
|
|
199
203
|
if (scope === "once")
|
|
200
204
|
return;
|
|
205
|
+
if (sessionId && closedPathApprovalSessions.has(sessionId))
|
|
206
|
+
return;
|
|
201
207
|
const prefix = dirPrefix(dirname(resolved));
|
|
202
208
|
if (scope === "session") {
|
|
203
209
|
if (!sessionId)
|
|
@@ -271,6 +277,16 @@ function recordPathApproval(scope, resolved, operation, cwd, sessionId) {
|
|
|
271
277
|
/** Test seam: clear the in-memory session grants. */
|
|
272
278
|
export function _resetSessionPathGrants() {
|
|
273
279
|
sessionPathGrants.clear();
|
|
280
|
+
closedPathApprovalSessions.clear();
|
|
281
|
+
askChains.clear();
|
|
282
|
+
}
|
|
283
|
+
export function openSessionPathApprovals(sessionId) {
|
|
284
|
+
closedPathApprovalSessions.delete(sessionId);
|
|
285
|
+
}
|
|
286
|
+
export function clearSessionPathApprovals(sessionId) {
|
|
287
|
+
sessionPathGrants.delete(sessionId);
|
|
288
|
+
closedPathApprovalSessions.add(sessionId);
|
|
289
|
+
askChains.delete(sessionId);
|
|
274
290
|
}
|
|
275
291
|
/**
|
|
276
292
|
* Best-effort resolution. realpath fails when the path doesn't exist yet —
|
|
@@ -356,6 +372,12 @@ function isSafeCodeShellDiagnosticRead(resolved) {
|
|
|
356
372
|
}
|
|
357
373
|
return false;
|
|
358
374
|
}
|
|
375
|
+
function isNoRepoAttachmentRead(resolved, operation) {
|
|
376
|
+
if (operation !== "read")
|
|
377
|
+
return false;
|
|
378
|
+
const root = join(homedir(), ".code-shell", "no-repo", ".code-shell", "attachments");
|
|
379
|
+
return isInsideDir(resolved, root);
|
|
380
|
+
}
|
|
359
381
|
/**
|
|
360
382
|
* Classify a file path against the workspace + sensitive-path policy.
|
|
361
383
|
*
|
|
@@ -396,6 +418,13 @@ export function classifyPath(rawPath, opts) {
|
|
|
396
418
|
// Sensitive: write is always denied, read always asks. Workspace placement
|
|
397
419
|
// doesn't soften the rule — an `.env` in the project still asks on read.
|
|
398
420
|
if (sensitiveLabel) {
|
|
421
|
+
if (isNoRepoAttachmentRead(resolved, opts.operation)) {
|
|
422
|
+
return {
|
|
423
|
+
decision: "allow",
|
|
424
|
+
reason: "no-repo attachment read",
|
|
425
|
+
resolvedPath: resolved,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
399
428
|
if (opts.operation === "read" && isSafeCodeShellDiagnosticRead(resolved)) {
|
|
400
429
|
return {
|
|
401
430
|
decision: "allow",
|
|
@@ -461,8 +490,8 @@ export function enforcePathPolicy(filePath, operation, workspaceRoot) {
|
|
|
461
490
|
}
|
|
462
491
|
// ask — MVP refuses with explanatory message until askUser plumbing
|
|
463
492
|
// lands. The conservative bias matches the plan's leaning answer for Q1.
|
|
464
|
-
return `Error: path requires approval — ${c.reason}. Path: ${c.resolvedPath}. ` +
|
|
465
|
-
`Set CODESHELL_PATH_POLICY=off to disable enforcement during a rollback
|
|
493
|
+
return (`Error: path requires approval — ${c.reason}. Path: ${c.resolvedPath}. ` +
|
|
494
|
+
`Set CODESHELL_PATH_POLICY=off to disable enforcement during a rollback.`);
|
|
466
495
|
}
|
|
467
496
|
export async function enforcePathPolicyWithApproval(filePath, operation, ctx) {
|
|
468
497
|
if (ctx?.cwd === undefined)
|
|
@@ -481,8 +510,8 @@ export async function enforcePathPolicyWithApproval(filePath, operation, ctx) {
|
|
|
481
510
|
return `Error: blocked by path policy — ${c.reason}. Path: ${c.resolvedPath}`;
|
|
482
511
|
}
|
|
483
512
|
if (operation === "write" && ctx.planMode) {
|
|
484
|
-
return `Error: blocked by path policy — ${c.reason}. Path: ${c.resolvedPath}. ` +
|
|
485
|
-
`Plan mode does not allow file writes
|
|
513
|
+
return (`Error: blocked by path policy — ${c.reason}. Path: ${c.resolvedPath}. ` +
|
|
514
|
+
`Plan mode does not allow file writes.`);
|
|
486
515
|
}
|
|
487
516
|
// Already approved this directory for this OPERATION (this session or
|
|
488
517
|
// persisted for the project)? Proceed without re-prompting — this is the fix
|
|
@@ -491,8 +520,8 @@ export async function enforcePathPolicyWithApproval(filePath, operation, ctx) {
|
|
|
491
520
|
if (isPathPreApproved(c.resolvedPath, operation, ctx.cwd, ctx.sessionId))
|
|
492
521
|
return null;
|
|
493
522
|
if (!ctx.askUser) {
|
|
494
|
-
return `Error: path requires approval — ${c.reason}. Path: ${c.resolvedPath}. ` +
|
|
495
|
-
`No interactive approval UI is available in this run
|
|
523
|
+
return (`Error: path requires approval — ${c.reason}. Path: ${c.resolvedPath}. ` +
|
|
524
|
+
`No interactive approval UI is available in this run.`);
|
|
496
525
|
}
|
|
497
526
|
// Serialize concurrent asks (per session) and RE-CHECK grants when our turn
|
|
498
527
|
// comes. Parallel tools hitting the same not-yet-approved directory all pass
|
|
@@ -502,7 +531,8 @@ export async function enforcePathPolicyWithApproval(filePath, operation, ctx) {
|
|
|
502
531
|
const chainKey = ctx.sessionId ?? "__global__";
|
|
503
532
|
const prevTurn = askChains.get(chainKey) ?? Promise.resolve();
|
|
504
533
|
let release;
|
|
505
|
-
|
|
534
|
+
const currentTurn = new Promise((r) => (release = r));
|
|
535
|
+
askChains.set(chainKey, currentTurn);
|
|
506
536
|
try {
|
|
507
537
|
await prevTurn;
|
|
508
538
|
if (isPathPreApproved(c.resolvedPath, operation, ctx.cwd, ctx.sessionId))
|
|
@@ -511,6 +541,9 @@ export async function enforcePathPolicyWithApproval(filePath, operation, ctx) {
|
|
|
511
541
|
}
|
|
512
542
|
finally {
|
|
513
543
|
release();
|
|
544
|
+
if (askChains.get(chainKey) === currentTurn) {
|
|
545
|
+
askChains.delete(chainKey);
|
|
546
|
+
}
|
|
514
547
|
}
|
|
515
548
|
}
|
|
516
549
|
/** The actual interactive ask — split out so the serialized section reads flat.
|
|
@@ -521,9 +554,7 @@ async function promptForPathApproval(c, operation, ctx) {
|
|
|
521
554
|
// "工作区外路径" header was misleading for sensitive-path asks.
|
|
522
555
|
const isSensitive = c.reason.startsWith("sensitive");
|
|
523
556
|
const what = operation === "read" ? "读取" : "写入";
|
|
524
|
-
const title = isSensitive
|
|
525
|
-
? `工具想${what}敏感文件`
|
|
526
|
-
: `工具想${what}工作区外路径`;
|
|
557
|
+
const title = isSensitive ? `工具想${what}敏感文件` : `工具想${what}工作区外路径`;
|
|
527
558
|
const header = isSensitive ? "敏感文件权限" : "路径权限";
|
|
528
559
|
// Scope options carry remembered grants for the directory of this path, so
|
|
529
560
|
// the same folder isn't re-prompted. Labels are matched by exact string
|
|
@@ -566,8 +597,6 @@ async function promptForPathApproval(c, operation, ctx) {
|
|
|
566
597
|
}
|
|
567
598
|
return `Error: path approval denied by user — ${c.reason}. Path: ${c.resolvedPath}`;
|
|
568
599
|
}
|
|
569
|
-
/** Per-session prompt chains for enforcePathPolicyWithApproval (see comment there). */
|
|
570
|
-
const askChains = new Map();
|
|
571
600
|
/**
|
|
572
601
|
* Internal: reset the "disabled warning" latch. Tests flip the env var
|
|
573
602
|
* between cases and need each one to be able to re-trigger the warning.
|
|
@@ -21,6 +21,17 @@ export declare class AutoApprovalBackend implements ApprovalBackend {
|
|
|
21
21
|
requestApproval(req: ApprovalRequest): Promise<ApprovalResult>;
|
|
22
22
|
private isSafeOperation;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Closed-session tombstones are a bounded replay guard. A prompt that started
|
|
26
|
+
* before close is still protected by the state-object identity check in
|
|
27
|
+
* isActiveSessionState(): clearSession deletes the captured state, so the late
|
|
28
|
+
* result cannot write into a new bucket even if an old tombstone has aged out.
|
|
29
|
+
* The tombstone only blocks fresh post-close calls carrying a recently closed
|
|
30
|
+
* sessionId from creating an empty bucket. 4096 is 256x the default
|
|
31
|
+
* ChatSessionManager.maxSessions (16), which covers normal late-result bursts
|
|
32
|
+
* while keeping long-lived processes from retaining every historical id.
|
|
33
|
+
*/
|
|
34
|
+
export declare const CLOSED_SESSION_TOMBSTONE_LIMIT = 4096;
|
|
24
35
|
/**
|
|
25
36
|
* Interactive approval backend — prompts the user via a callback.
|
|
26
37
|
*
|
|
@@ -34,13 +45,11 @@ export declare class AutoApprovalBackend implements ApprovalBackend {
|
|
|
34
45
|
* the next time the user opens the project.
|
|
35
46
|
*/
|
|
36
47
|
export declare class InteractiveApprovalBackend implements ApprovalBackend {
|
|
37
|
-
private
|
|
38
|
-
private
|
|
48
|
+
private sessionStateById;
|
|
49
|
+
private closedSessionIds;
|
|
39
50
|
private promptFn;
|
|
40
|
-
private
|
|
41
|
-
private
|
|
42
|
-
private onProjectRules;
|
|
43
|
-
private promptTurn;
|
|
51
|
+
private legacyPromptTurn;
|
|
52
|
+
private legacyContext;
|
|
44
53
|
setPromptFn(fn: (request: ApprovalRequest) => Promise<ApprovalResult>): void;
|
|
45
54
|
/**
|
|
46
55
|
* Has someone installed a real prompt callback? Engine consults this
|
|
@@ -59,8 +68,18 @@ export declare class InteractiveApprovalBackend implements ApprovalBackend {
|
|
|
59
68
|
* earlier approvals.
|
|
60
69
|
*/
|
|
61
70
|
setOnProjectRules(fn: (rules: PermissionRule[]) => void): void;
|
|
71
|
+
setSessionContext(sessionId: string, context: {
|
|
72
|
+
cwd: string;
|
|
73
|
+
onProjectRules: (rules: PermissionRule[]) => void;
|
|
74
|
+
}): void;
|
|
75
|
+
openSession(sessionId: string): void;
|
|
76
|
+
clearSession(sessionId: string): void;
|
|
77
|
+
private rememberClosedSession;
|
|
78
|
+
private makeSessionState;
|
|
79
|
+
private getSessionState;
|
|
80
|
+
private isActiveSessionState;
|
|
62
81
|
requestApproval(req: ApprovalRequest): Promise<ApprovalResult>;
|
|
63
|
-
/** Session-rule lookup — operation-scoped (see
|
|
82
|
+
/** Session-rule lookup — operation-scoped (see sessionStateById doc). Deny
|
|
64
83
|
* wins over allow if both somehow match (conservative). Null = no rule. */
|
|
65
84
|
private checkSessionRules;
|
|
66
85
|
/** The actual interactive ask + rule recording (runs inside the prompt turn). */
|
|
@@ -89,6 +108,8 @@ export declare function pathRuleArgsPattern(absPath: string, scope: "file" | "di
|
|
|
89
108
|
*/
|
|
90
109
|
export declare function ruleMatches(rule: PermissionRule, toolName: string, args: Record<string, unknown>): boolean;
|
|
91
110
|
export declare function getInteractiveApprovalBackend(): InteractiveApprovalBackend;
|
|
111
|
+
export declare function openInteractiveApprovalSession(sessionId: string): void;
|
|
112
|
+
export declare function clearInteractiveApprovalSession(sessionId: string): void;
|
|
92
113
|
export declare function setInteractiveApprovalFn(fn: (request: ApprovalRequest) => Promise<ApprovalResult>): void;
|
|
93
114
|
type BashSafetyLevel = "safe-read" | "safe-write" | "unsafe" | "dangerous";
|
|
94
115
|
/**
|