@cjhyy/code-shell-core 0.6.0-rc.16 → 0.6.0-rc.18
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/cc-orchestrator/agent-adapter.d.ts +2 -0
- package/dist/cc-orchestrator/agent-adapter.js +7 -1
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +102 -51
- package/dist/cli/agent-server-stdio.js +2 -0
- package/dist/credentials/access.d.ts +56 -0
- package/dist/credentials/access.js +183 -0
- package/dist/credentials/index.d.ts +1 -0
- package/dist/credentials/index.js +1 -0
- package/dist/credentials/inject-credential-tool.js +5 -5
- package/dist/credentials/use-credential-tool.d.ts +8 -1
- package/dist/credentials/use-credential-tool.js +55 -45
- package/dist/engine/engine.d.ts +3 -0
- package/dist/engine/engine.js +40 -13
- 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 +255 -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/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +4 -0
- package/dist/engine/turn-loop.js +106 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/logging/sanitize-messages.d.ts +10 -2
- package/dist/logging/sanitize-messages.js +21 -6
- package/dist/preset/index.js +10 -9
- package/dist/protocol/chat-session-manager.d.ts +1 -0
- package/dist/protocol/chat-session-manager.js +18 -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 +12 -0
- package/dist/protocol/server.js +116 -38
- package/dist/protocol/types.d.ts +37 -0
- package/dist/runtime/spawn-common.js +10 -0
- package/dist/tool-system/builtin/drive-claude-code.d.ts +1 -0
- package/dist/tool-system/builtin/drive-claude-code.js +137 -18
- package/dist/tool-system/builtin/index.d.ts +8 -3
- package/dist/tool-system/builtin/index.js +15 -13
- 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 +114 -5
- package/dist/tool-system/builtin/view-image.js +9 -0
- 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 +2 -0
- package/dist/tool-system/mcp-manager.js +23 -8
- package/dist/tool-system/path-policy.js +13 -0
- 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/types.d.ts +23 -4
- package/package.json +4 -3
|
@@ -14,22 +14,33 @@ import { parsePatch } from "./builtin/apply-patch/parser.js";
|
|
|
14
14
|
import { BUILTIN_TOOL_GUARDS } from "./builtin/index.js";
|
|
15
15
|
import { COMPLETE_GOAL_TOOL_NAME } from "./builtin/complete-goal.js";
|
|
16
16
|
import { CANCEL_GOAL_TOOL_NAME } from "./builtin/cancel-goal.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
//
|
|
24
|
-
//
|
|
17
|
+
import { toolResultDisplayText } from "./tool-result-redaction.js";
|
|
18
|
+
const PERMISSION_DECISION_RANK = {
|
|
19
|
+
allow: 0,
|
|
20
|
+
ask: 1,
|
|
21
|
+
deny: 2,
|
|
22
|
+
};
|
|
23
|
+
// Permission hooks may only keep or tighten the current decision. The merge
|
|
24
|
+
// order is deny > ask > allow, so classifier deny/rules stay hard-deny even if
|
|
25
|
+
// a hook asks the user for confirmation.
|
|
25
26
|
function clampHookDecision(classifier, hook) {
|
|
26
27
|
if (!hook)
|
|
27
28
|
return { decision: classifier, rejectedUpgrade: false };
|
|
28
|
-
if (hook
|
|
29
|
+
if (PERMISSION_DECISION_RANK[hook] < PERMISSION_DECISION_RANK[classifier]) {
|
|
29
30
|
return { decision: classifier, rejectedUpgrade: true };
|
|
30
31
|
}
|
|
31
32
|
return { decision: hook, rejectedUpgrade: false };
|
|
32
33
|
}
|
|
34
|
+
function permissionAskReason(preHookMessages, permissionHookMessages) {
|
|
35
|
+
const parts = [];
|
|
36
|
+
if (preHookMessages?.length) {
|
|
37
|
+
parts.push(`pre_tool_use:\n${preHookMessages.join("\n")}`);
|
|
38
|
+
}
|
|
39
|
+
if (permissionHookMessages?.length) {
|
|
40
|
+
parts.push(`on_permission_check:\n${permissionHookMessages.join("\n")}`);
|
|
41
|
+
}
|
|
42
|
+
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
43
|
+
}
|
|
33
44
|
export class ToolExecutor {
|
|
34
45
|
registry;
|
|
35
46
|
permission;
|
|
@@ -132,7 +143,9 @@ export class ToolExecutor {
|
|
|
132
143
|
};
|
|
133
144
|
}
|
|
134
145
|
const visibilityGuard = BUILTIN_TOOL_GUARDS.get(call.toolName);
|
|
135
|
-
if (visibilityGuard &&
|
|
146
|
+
if (visibilityGuard &&
|
|
147
|
+
this.toolCtx?.toolVisibility &&
|
|
148
|
+
!visibilityGuard(this.toolCtx.toolVisibility)) {
|
|
136
149
|
return {
|
|
137
150
|
id: call.id,
|
|
138
151
|
toolName: call.toolName,
|
|
@@ -276,41 +289,16 @@ export class ToolExecutor {
|
|
|
276
289
|
isError: true,
|
|
277
290
|
};
|
|
278
291
|
}
|
|
279
|
-
// A1 hardening: pre_tool_use can no longer pre-approve a tool via
|
|
280
|
-
// `decision === "allow"`. Hooks may relax `allow` and may force
|
|
281
|
-
// `ask`/`deny`, but they cannot promote a `deny`/`ask` decision to
|
|
282
|
-
// `allow`. The only paths to `allow` are the classifier (rules,
|
|
283
|
-
// safe-read, allowlist) and the user (interactive approval).
|
|
284
|
-
if (hookResult.decision === "allow") {
|
|
285
|
-
this.log.info("permission.hook_upgrade_rejected", {
|
|
286
|
-
cat: "permission",
|
|
287
|
-
tool: call.toolName,
|
|
288
|
-
site: "pre_tool_use",
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
// pre_tool_use can request interactive confirmation via
|
|
292
|
-
// decision: "ask". We invoke the same handleAsk path the classifier
|
|
293
|
-
// uses, but feed the hook's messages so the user sees the handler's
|
|
294
|
-
// reasoning. If the user approves, we skip the classifier below —
|
|
295
|
-
// the hook and the user together have decided.
|
|
296
|
-
if (hookResult.decision === "ask") {
|
|
297
|
-
const reason = hookResult.messages?.join("\n") ?? undefined;
|
|
298
|
-
const approved = await this.permission.handleAsk(call.toolName, call.args, reason, { sessionId: this.toolCtx?.sessionId });
|
|
299
|
-
if (!approved) {
|
|
300
|
-
return {
|
|
301
|
-
id: call.id,
|
|
302
|
-
toolName: call.toolName,
|
|
303
|
-
error: `Tool call denied by user (pre_tool_use ask).`,
|
|
304
|
-
isError: true,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
292
|
// 0.7. Investigation guard — block redundant reads and tag soft reminders
|
|
309
293
|
// onto results. See investigation-guard.ts for the rules; they enforce the
|
|
310
294
|
// soft prompt guidance in coding.md ("never re-read", "3-call budget").
|
|
311
295
|
const guardDecision = this.guard?.preToolCheck(call);
|
|
312
296
|
if (guardDecision?.block) {
|
|
313
|
-
this.log.info("guard.block", {
|
|
297
|
+
this.log.info("guard.block", {
|
|
298
|
+
cat: "guard",
|
|
299
|
+
tool: call.toolName,
|
|
300
|
+
reason: guardDecision.block.slice(0, 200),
|
|
301
|
+
});
|
|
314
302
|
return {
|
|
315
303
|
id: call.id,
|
|
316
304
|
toolName: call.toolName,
|
|
@@ -318,68 +306,77 @@ export class ToolExecutor {
|
|
|
318
306
|
isError: true,
|
|
319
307
|
};
|
|
320
308
|
}
|
|
321
|
-
// 1. Permission check
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
309
|
+
// 1. Permission check. `pre_tool_use: ask` only adds a stricter ask
|
|
310
|
+
// requirement; user approval for that ask cannot bypass classifier deny
|
|
311
|
+
// rules. Final merge order is deny > ask > allow, and at most one prompt
|
|
312
|
+
// is shown for the merged ask decision.
|
|
313
|
+
const classifierDecision = this.permission.classify(call.toolName, call.args);
|
|
314
|
+
this.log.info("permission.classify", {
|
|
315
|
+
cat: "permission",
|
|
316
|
+
tool: call.toolName,
|
|
317
|
+
decision: classifierDecision,
|
|
318
|
+
mode: this.permission.getMode(),
|
|
319
|
+
});
|
|
320
|
+
// on_permission_check hook: lets handlers audit and tighten the decision
|
|
321
|
+
// (e.g. `allow → ask/deny`). Attempts to relax classifier/pre-hook deny or
|
|
322
|
+
// ask are rejected by clampHookDecision.
|
|
323
|
+
const permHook = await this.hooks.emit("on_permission_check", {
|
|
324
|
+
toolName: call.toolName,
|
|
325
|
+
args: call.args,
|
|
326
|
+
toolCallId: call.id,
|
|
327
|
+
classifierDecision,
|
|
328
|
+
});
|
|
329
|
+
const preClamped = clampHookDecision(classifierDecision, hookResult.decision);
|
|
330
|
+
let decision = preClamped.decision;
|
|
331
|
+
if (preClamped.rejectedUpgrade) {
|
|
332
|
+
this.log.info("permission.hook_upgrade_rejected", {
|
|
333
|
+
cat: "permission",
|
|
334
|
+
tool: call.toolName,
|
|
335
|
+
site: "pre_tool_use",
|
|
336
|
+
attempted: hookResult.decision,
|
|
337
|
+
classifier: classifierDecision,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const permClamped = clampHookDecision(decision, permHook.decision);
|
|
341
|
+
decision = permClamped.decision;
|
|
342
|
+
if (permClamped.rejectedUpgrade) {
|
|
343
|
+
this.log.info("permission.hook_upgrade_rejected", {
|
|
344
|
+
cat: "permission",
|
|
345
|
+
tool: call.toolName,
|
|
346
|
+
site: "on_permission_check",
|
|
347
|
+
attempted: permHook.decision,
|
|
348
|
+
classifier: classifierDecision,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (decision !== classifierDecision) {
|
|
352
|
+
this.log.info("permission.hook_override", {
|
|
326
353
|
cat: "permission",
|
|
327
354
|
tool: call.toolName,
|
|
328
|
-
|
|
329
|
-
|
|
355
|
+
from: classifierDecision,
|
|
356
|
+
to: decision,
|
|
330
357
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const permHook = await this.hooks.emit("on_permission_check", {
|
|
358
|
+
}
|
|
359
|
+
if (decision === "deny") {
|
|
360
|
+
return {
|
|
361
|
+
id: call.id,
|
|
336
362
|
toolName: call.toolName,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
363
|
+
error: `Permission denied for tool: ${call.toolName}`,
|
|
364
|
+
isError: true,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (decision === "ask") {
|
|
368
|
+
const reason = permissionAskReason(hookResult.decision === "ask" ? hookResult.messages : undefined, permHook.decision === "ask" ? permHook.messages : undefined);
|
|
369
|
+
const approved = await this.permission.handleAsk(call.toolName, call.args, reason, {
|
|
370
|
+
sessionId: this.toolCtx?.sessionId,
|
|
340
371
|
});
|
|
341
|
-
|
|
342
|
-
const clamped = clampHookDecision(classifierDecision, permHook.decision);
|
|
343
|
-
const decision = clamped.decision;
|
|
344
|
-
if (clamped.rejectedUpgrade) {
|
|
345
|
-
this.log.info("permission.hook_upgrade_rejected", {
|
|
346
|
-
cat: "permission",
|
|
347
|
-
tool: call.toolName,
|
|
348
|
-
site: "on_permission_check",
|
|
349
|
-
attempted: permHook.decision,
|
|
350
|
-
classifier: classifierDecision,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
if (decision !== classifierDecision) {
|
|
354
|
-
this.log.info("permission.hook_override", {
|
|
355
|
-
cat: "permission",
|
|
356
|
-
tool: call.toolName,
|
|
357
|
-
from: classifierDecision,
|
|
358
|
-
to: decision,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
if (decision === "deny") {
|
|
372
|
+
if (!approved) {
|
|
362
373
|
return {
|
|
363
374
|
id: call.id,
|
|
364
375
|
toolName: call.toolName,
|
|
365
|
-
error: `Permission denied for tool: ${call.toolName}`,
|
|
376
|
+
error: `Permission denied by user for tool: ${call.toolName}`,
|
|
366
377
|
isError: true,
|
|
367
378
|
};
|
|
368
379
|
}
|
|
369
|
-
if (decision === "ask") {
|
|
370
|
-
const reason = permHook.messages?.join("\n");
|
|
371
|
-
const approved = await this.permission.handleAsk(call.toolName, call.args, reason, {
|
|
372
|
-
sessionId: this.toolCtx?.sessionId,
|
|
373
|
-
});
|
|
374
|
-
if (!approved) {
|
|
375
|
-
return {
|
|
376
|
-
id: call.id,
|
|
377
|
-
toolName: call.toolName,
|
|
378
|
-
error: `Permission denied by user for tool: ${call.toolName}`,
|
|
379
|
-
isError: true,
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
380
|
}
|
|
384
381
|
// 2. Pre-tool hook
|
|
385
382
|
await this.hooks.emit("on_tool_start", {
|
|
@@ -415,7 +412,7 @@ export class ToolExecutor {
|
|
|
415
412
|
toolName: call.toolName,
|
|
416
413
|
ok: false,
|
|
417
414
|
durationMs: Date.now() - toolStartedAt,
|
|
418
|
-
error: err instanceof Error ? err.stack ?? err.message : String(err),
|
|
415
|
+
error: err instanceof Error ? (err.stack ?? err.message) : String(err),
|
|
419
416
|
});
|
|
420
417
|
// A model calling a tool that isn't in the registry (hallucinated name,
|
|
421
418
|
// or a builtin missing from the active preset's whitelist) must not kill
|
|
@@ -437,7 +434,8 @@ export class ToolExecutor {
|
|
|
437
434
|
if (guardDecision?.prepend && !result.error && result.result) {
|
|
438
435
|
result.result = `${guardDecision.prepend}\n${result.result}`;
|
|
439
436
|
}
|
|
440
|
-
const
|
|
437
|
+
const observerResult = toolResultDisplayText(result);
|
|
438
|
+
const payload = observerResult ?? result.error ?? "";
|
|
441
439
|
span.end({
|
|
442
440
|
ok: !result.error,
|
|
443
441
|
chars: payload.length,
|
|
@@ -451,21 +449,21 @@ export class ToolExecutor {
|
|
|
451
449
|
toolName: call.toolName,
|
|
452
450
|
ok: !result.error,
|
|
453
451
|
durationMs: Date.now() - toolStartedAt,
|
|
454
|
-
output: result.error ? undefined :
|
|
452
|
+
output: result.error ? undefined : observerResult,
|
|
455
453
|
error: result.error,
|
|
456
454
|
});
|
|
457
455
|
// 4. Post-tool hook
|
|
458
456
|
await this.hooks.emit("on_tool_end", {
|
|
459
457
|
toolName: call.toolName,
|
|
460
458
|
toolCallId: call.id,
|
|
461
|
-
result:
|
|
459
|
+
result: observerResult,
|
|
462
460
|
error: result.error,
|
|
463
461
|
});
|
|
464
462
|
// 5. Post-tool-use hook (after execution, can observe/modify result)
|
|
465
463
|
const postHook = await this.hooks.emit("post_tool_use", {
|
|
466
464
|
toolName: call.toolName,
|
|
467
465
|
toolCallId: call.id,
|
|
468
|
-
result:
|
|
466
|
+
result: observerResult,
|
|
469
467
|
error: result.error,
|
|
470
468
|
});
|
|
471
469
|
// Append handler-supplied context (linter output, type-check result,
|
|
@@ -574,23 +572,4 @@ export class ToolExecutor {
|
|
|
574
572
|
return `Error: ${err.message}`;
|
|
575
573
|
}
|
|
576
574
|
}
|
|
577
|
-
/**
|
|
578
|
-
* Convert tool results into Message entries for the transcript.
|
|
579
|
-
*/
|
|
580
|
-
resultsToMessages(toolCalls, results) {
|
|
581
|
-
const blocks = [];
|
|
582
|
-
for (const result of results) {
|
|
583
|
-
blocks.push({
|
|
584
|
-
type: "tool_result",
|
|
585
|
-
tool_use_id: result.id,
|
|
586
|
-
content: result.error ? `Error: ${result.error}` : (result.result ?? "(no output)"),
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
return [
|
|
590
|
-
{
|
|
591
|
-
role: "user",
|
|
592
|
-
content: blocks,
|
|
593
|
-
},
|
|
594
|
-
];
|
|
595
|
-
}
|
|
596
575
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js";
|
|
7
7
|
import type { MCPServerConfig, RegisteredTool } from "../types.js";
|
|
8
8
|
import { ToolRegistry } from "./registry.js";
|
|
9
|
+
import { type CredentialAccess } from "../credentials/access.js";
|
|
9
10
|
interface MCPResourceInfo {
|
|
10
11
|
uri: string;
|
|
11
12
|
name: string;
|
|
@@ -34,6 +35,7 @@ export declare function buildStdioEnv(serverName: string, config: MCPServerConfi
|
|
|
34
35
|
* and win on conflict. Pure + exported for unit testing.
|
|
35
36
|
*/
|
|
36
37
|
export declare function buildHttpHeaders(serverName: string, config: MCPServerConfig, resolveCredential?: (id: string) => string | undefined): Record<string, string>;
|
|
38
|
+
export declare function buildHttpHeadersWithCredentialAccess(serverName: string, config: MCPServerConfig, access?: Pick<CredentialAccess, "resolveValue">): Promise<Record<string, string>>;
|
|
37
39
|
/**
|
|
38
40
|
* Infer the transport when the config doesn't name one: a url-only entry is
|
|
39
41
|
* HTTP, everything else stdio. This is the CC `.mcp.json` convention —
|
|
@@ -7,7 +7,7 @@ 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
11
|
import { ENV_ALLOWLIST } from "../runtime/spawn-common.js";
|
|
12
12
|
import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
|
|
13
13
|
import { join } from "node:path";
|
|
@@ -90,6 +90,20 @@ export function buildHttpHeaders(serverName, config, resolveCredential) {
|
|
|
90
90
|
}
|
|
91
91
|
return headers;
|
|
92
92
|
}
|
|
93
|
+
export async function buildHttpHeadersWithCredentialAccess(serverName, config, access = getCredentialAccess()) {
|
|
94
|
+
if (!config.credentialRef)
|
|
95
|
+
return buildHttpHeaders(serverName, config);
|
|
96
|
+
if (!access.resolveValue) {
|
|
97
|
+
throw new Error(`MCP server "${serverName}": credential "${config.credentialRef}" not found or empty`);
|
|
98
|
+
}
|
|
99
|
+
const secret = await access.resolveValue({
|
|
100
|
+
cwd: undefined,
|
|
101
|
+
id: config.credentialRef,
|
|
102
|
+
scope: "full",
|
|
103
|
+
purpose: "mcp",
|
|
104
|
+
});
|
|
105
|
+
return buildHttpHeaders(serverName, config, (id) => id === config.credentialRef ? secret : undefined);
|
|
106
|
+
}
|
|
93
107
|
/**
|
|
94
108
|
* Infer the transport when the config doesn't name one: a url-only entry is
|
|
95
109
|
* HTTP, everything else stdio. This is the CC `.mcp.json` convention —
|
|
@@ -228,7 +242,10 @@ export function buildRegisteredTool(serverName, tool) {
|
|
|
228
242
|
return {
|
|
229
243
|
name: toOpenAIToolName(`mcp_${serverName}_${tool.name}`),
|
|
230
244
|
description: `[${serverName}] ${tool.description ?? tool.name}`,
|
|
231
|
-
inputSchema: tool.inputSchema ?? {
|
|
245
|
+
inputSchema: tool.inputSchema ?? {
|
|
246
|
+
type: "object",
|
|
247
|
+
properties: {},
|
|
248
|
+
},
|
|
232
249
|
source: "mcp",
|
|
233
250
|
serverName,
|
|
234
251
|
permissionDefault: "ask",
|
|
@@ -403,12 +420,10 @@ export class MCPManager {
|
|
|
403
420
|
if (!config.url) {
|
|
404
421
|
throw new Error(`MCP server "${name}": url is required for ${transportType} transport`);
|
|
405
422
|
}
|
|
406
|
-
// credentialRef resolves against
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
const credStore = new CredentialStore(undefined);
|
|
411
|
-
const headers = buildHttpHeaders(name, config, (id) => credStore.resolve(id)?.secret);
|
|
423
|
+
// credentialRef resolves against user-scope credential access. The shared
|
|
424
|
+
// MCPManager pool has no per-session cwd, and MCP-referenced credentials
|
|
425
|
+
// (e.g. a Figma token) remain user-global by design.
|
|
426
|
+
const headers = await buildHttpHeadersWithCredentialAccess(name, config);
|
|
412
427
|
transport = new StreamableHTTPClientTransport(new URL(config.url), {
|
|
413
428
|
requestInit: Object.keys(headers).length ? { headers } : undefined,
|
|
414
429
|
});
|
|
@@ -372,6 +372,12 @@ function isSafeCodeShellDiagnosticRead(resolved) {
|
|
|
372
372
|
}
|
|
373
373
|
return false;
|
|
374
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
|
+
}
|
|
375
381
|
/**
|
|
376
382
|
* Classify a file path against the workspace + sensitive-path policy.
|
|
377
383
|
*
|
|
@@ -412,6 +418,13 @@ export function classifyPath(rawPath, opts) {
|
|
|
412
418
|
// Sensitive: write is always denied, read always asks. Workspace placement
|
|
413
419
|
// doesn't soften the rule — an `.env` in the project still asks on read.
|
|
414
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
|
+
}
|
|
415
428
|
if (opts.operation === "read" && isSafeCodeShellDiagnosticRead(resolved)) {
|
|
416
429
|
return {
|
|
417
430
|
decision: "allow",
|
|
@@ -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
|
/**
|