@gaunt-sloth/core 2.0.0-alpha.9 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gsloth.review.md +2 -0
- package/README.md +71 -20
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/configDiscovery.d.ts +79 -0
- package/dist/config/configDiscovery.js +80 -0
- package/dist/config/configDiscovery.js.map +1 -0
- package/dist/config/defaults.d.ts +20 -20
- package/dist/config/defaults.js +10 -8
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/filesystem-tools.d.ts +41 -0
- package/dist/config/filesystem-tools.js +56 -0
- package/dist/config/filesystem-tools.js.map +1 -0
- package/dist/config/loader.d.ts +171 -19
- package/dist/config/loader.js +1054 -144
- package/dist/config/loader.js.map +1 -1
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- package/dist/config/profiles.d.ts +68 -0
- package/dist/config/profiles.js +93 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providerKeys.d.ts +69 -0
- package/dist/config/providerKeys.js +69 -0
- package/dist/config/providerKeys.js.map +1 -0
- package/dist/config/schema.d.ts +2671 -138
- package/dist/config/schema.js +1361 -85
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +899 -111
- package/dist/config/shell-policy.js +800 -70
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +211 -0
- package/dist/config/tool-descriptions.js +272 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +352 -41
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +28 -1
- package/dist/constants.js +28 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +166 -11
- package/dist/core/GthAbstractAgent.js +484 -45
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +543 -57
- package/dist/core/GthAgentRunner.js +1494 -140
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +117 -2
- package/dist/core/GthLangChainAgent.js +602 -29
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/annotations.d.ts +122 -0
- package/dist/core/approvals/annotations.js +137 -0
- package/dist/core/approvals/annotations.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +216 -0
- package/dist/core/approvals/grants.js +469 -0
- package/dist/core/approvals/grants.js.map +1 -0
- package/dist/core/approvals/matcher.d.ts +202 -0
- package/dist/core/approvals/matcher.js +267 -0
- package/dist/core/approvals/matcher.js.map +1 -0
- package/dist/core/approvals/mcpSubjects.d.ts +40 -0
- package/dist/core/approvals/mcpSubjects.js +99 -0
- package/dist/core/approvals/mcpSubjects.js.map +1 -0
- package/dist/core/approvals/promptHeader.d.ts +28 -0
- package/dist/core/approvals/promptHeader.js +62 -0
- package/dist/core/approvals/promptHeader.js.map +1 -0
- package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
- package/dist/core/approvals/toolAnnotationSources.js +277 -0
- package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
- package/dist/core/approvals/toolHost.d.ts +46 -0
- package/dist/core/approvals/toolHost.js +108 -0
- package/dist/core/approvals/toolHost.js.map +1 -0
- package/dist/core/debugCapture.d.ts +74 -0
- package/dist/core/debugCapture.js +100 -0
- package/dist/core/debugCapture.js.map +1 -0
- package/dist/core/gthLeanAgentFactory.d.ts +4 -4
- package/dist/core/gthLeanAgentFactory.js +4 -4
- package/dist/core/launchBanner.d.ts +127 -0
- package/dist/core/launchBanner.js +414 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/modelLabel.d.ts +19 -0
- package/dist/core/modelLabel.js +26 -0
- package/dist/core/modelLabel.js.map +1 -0
- package/dist/core/plainToolIndication.d.ts +15 -0
- package/dist/core/plainToolIndication.js +174 -0
- package/dist/core/plainToolIndication.js.map +1 -0
- package/dist/core/reasoningBlocks.d.ts +65 -0
- package/dist/core/reasoningBlocks.js +103 -0
- package/dist/core/reasoningBlocks.js.map +1 -0
- package/dist/core/refusal.d.ts +53 -0
- package/dist/core/refusal.js +133 -0
- package/dist/core/refusal.js.map +1 -0
- package/dist/core/runHeader.d.ts +38 -0
- package/dist/core/runHeader.js +42 -0
- package/dist/core/runHeader.js.map +1 -0
- package/dist/core/runStats.d.ts +14 -3
- package/dist/core/runStats.js +48 -3
- package/dist/core/runStats.js.map +1 -1
- package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
- package/dist/core/shell/ShellCommandFailedError.js +3 -4
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
- package/dist/core/shell/abstention.d.ts +88 -0
- package/dist/core/shell/abstention.js +184 -0
- package/dist/core/shell/abstention.js.map +1 -0
- package/dist/core/shell/approvalCapture.d.ts +271 -0
- package/dist/core/shell/approvalCapture.js +108 -0
- package/dist/core/shell/approvalCapture.js.map +1 -0
- package/dist/core/shell/approvalStop.d.ts +123 -0
- package/dist/core/shell/approvalStop.js +269 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/escalationSeverity.d.ts +141 -0
- package/dist/core/shell/escalationSeverity.js +89 -0
- package/dist/core/shell/escalationSeverity.js.map +1 -0
- package/dist/core/shell/framing.d.ts +190 -0
- package/dist/core/shell/framing.js +633 -0
- package/dist/core/shell/framing.js.map +1 -0
- package/dist/core/shell/hardline.d.ts +103 -0
- package/dist/core/shell/hardline.js +780 -0
- package/dist/core/shell/hardline.js.map +1 -0
- package/dist/core/shell/negotiation.d.ts +328 -0
- package/dist/core/shell/negotiation.js +488 -0
- package/dist/core/shell/negotiation.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +44 -4
- package/dist/core/shell/normalize.js +61 -7
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +263 -0
- package/dist/core/shell/openWorld.js +1188 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +873 -0
- package/dist/core/shell/rater.js +1454 -0
- package/dist/core/shell/rater.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +41 -0
- package/dist/core/shell/raterModel.js +51 -0
- package/dist/core/shell/raterModel.js.map +1 -0
- package/dist/core/shell/raterVocabulary.d.ts +121 -0
- package/dist/core/shell/raterVocabulary.js +116 -0
- package/dist/core/shell/raterVocabulary.js.map +1 -0
- package/dist/core/shell/rejection.d.ts +69 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/toolCallRepair/grammar.d.ts +41 -0
- package/dist/core/toolCallRepair/grammar.js +116 -0
- package/dist/core/toolCallRepair/grammar.js.map +1 -0
- package/dist/core/toolCallRepair/index.d.ts +2 -0
- package/dist/core/toolCallRepair/index.js +7 -0
- package/dist/core/toolCallRepair/index.js.map +1 -0
- package/dist/core/toolCallRepair/payload.d.ts +36 -0
- package/dist/core/toolCallRepair/payload.js +341 -0
- package/dist/core/toolCallRepair/payload.js.map +1 -0
- package/dist/core/toolCallRepair/promote.d.ts +45 -0
- package/dist/core/toolCallRepair/promote.js +90 -0
- package/dist/core/toolCallRepair/promote.js.map +1 -0
- package/dist/core/toolDisplay.d.ts +123 -0
- package/dist/core/toolDisplay.js +451 -0
- package/dist/core/toolDisplay.js.map +1 -0
- package/dist/core/toolOutputChannel.d.ts +95 -0
- package/dist/core/toolOutputChannel.js +165 -0
- package/dist/core/toolOutputChannel.js.map +1 -0
- package/dist/core/types.d.ts +349 -16
- package/dist/core/types.js.map +1 -1
- package/dist/history/historyFormat.d.ts +12 -3
- package/dist/history/historyFormat.js +50 -8
- package/dist/history/historyFormat.js.map +1 -1
- package/dist/history/historyStore.d.ts +77 -0
- package/dist/history/historyStore.js +173 -6
- package/dist/history/historyStore.js.map +1 -1
- package/dist/history/recordSession.d.ts +10 -1
- package/dist/history/recordSession.js +27 -0
- package/dist/history/recordSession.js.map +1 -1
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/configurationPassthrough.d.ts +107 -0
- package/dist/providers/configurationPassthrough.js +148 -0
- package/dist/providers/configurationPassthrough.js.map +1 -0
- package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
- package/dist/providers/geminiSchemaSanitizer.js +347 -0
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +60 -0
- package/dist/providers/geminiThinking.js +92 -0
- package/dist/providers/geminiThinking.js.map +1 -0
- package/dist/providers/google-genai.js +18 -1
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.js +12 -0
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/huggingface.d.ts +25 -0
- package/dist/providers/huggingface.js +69 -0
- package/dist/providers/huggingface.js.map +1 -0
- package/dist/providers/modelCatalog.d.ts +109 -0
- package/dist/providers/modelCatalog.js +245 -0
- package/dist/providers/modelCatalog.js.map +1 -0
- package/dist/providers/modelDiscovery.d.ts +99 -5
- package/dist/providers/modelDiscovery.js +191 -35
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/ollama.d.ts +18 -4
- package/dist/providers/ollama.js +67 -37
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +34 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +26 -4
- package/dist/providers/openrouter.js +83 -26
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +19 -1
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.js +20 -0
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +105 -0
- package/dist/runtime/askStructured.js +120 -0
- package/dist/runtime/askStructured.js.map +1 -0
- package/dist/runtime/conversation.d.ts +64 -0
- package/dist/runtime/conversation.js +171 -0
- package/dist/runtime/conversation.js.map +1 -0
- package/dist/runtime/singleShot.d.ts +37 -6
- package/dist/runtime/singleShot.js +113 -67
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/runtime/structuredOutput.d.ts +104 -0
- package/dist/runtime/structuredOutput.js +393 -0
- package/dist/runtime/structuredOutput.js.map +1 -0
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/aiignoreUtils.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +95 -0
- package/dist/utils/consoleUtils.js +112 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/crashHandler.d.ts +87 -0
- package/dist/utils/crashHandler.js +128 -0
- package/dist/utils/crashHandler.js.map +1 -0
- package/dist/utils/debugDump.d.ts +134 -0
- package/dist/utils/debugDump.js +381 -0
- package/dist/utils/debugDump.js.map +1 -0
- package/dist/utils/debugUtils.d.ts +13 -4
- package/dist/utils/debugUtils.js +36 -13
- package/dist/utils/debugUtils.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +53 -0
- package/dist/utils/displayWidth.js +195 -0
- package/dist/utils/displayWidth.js.map +1 -0
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/globalConfigUtils.d.ts +14 -2
- package/dist/utils/globalConfigUtils.js +22 -4
- package/dist/utils/globalConfigUtils.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +39 -8
- package/dist/utils/llmUtils.js +76 -8
- package/dist/utils/llmUtils.js.map +1 -1
- package/dist/utils/redactSecrets.d.ts +63 -0
- package/dist/utils/redactSecrets.js +286 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +222 -0
- package/dist/utils/systemPromptNotes.js +338 -0
- package/dist/utils/systemPromptNotes.js.map +1 -0
- package/dist/utils/systemUtils.d.ts +18 -1
- package/dist/utils/systemUtils.js +38 -3
- package/dist/utils/systemUtils.js.map +1 -1
- package/dist/utils/toolMatching.d.ts +30 -0
- package/dist/utils/toolMatching.js +44 -0
- package/dist/utils/toolMatching.js.map +1 -0
- package/dist/utils/untrustedText.d.ts +86 -0
- package/dist/utils/untrustedText.js +101 -0
- package/dist/utils/untrustedText.js.map +1 -0
- package/package.json +21 -7
- package/schema/gsloth-config.schema.json +1921 -326
- package/dist/core/shell/allowlist.d.ts +0 -75
- package/dist/core/shell/allowlist.js +0 -187
- package/dist/core/shell/allowlist.js.map +0 -1
- package/dist/core/shell/judge.d.ts +0 -161
- package/dist/core/shell/judge.js +0 -261
- package/dist/core/shell/judge.js.map +0 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PendingToolInterrupt } from '#src/core/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The opening sentence of the approval prompt for one gated call, **without** any surface's own
|
|
4
|
+
* punctuation or styling.
|
|
5
|
+
*
|
|
6
|
+
* Three arms, one per subject kind:
|
|
7
|
+
*
|
|
8
|
+
* - `shell` — *The agent wants to run a shell command via `<tool>`*, unchanged, so the case that was
|
|
9
|
+
* already clear stays clear.
|
|
10
|
+
* - `mcpTool` — *The agent wants to call `<name>` on the MCP server `<server>`, via `<tool>`*.
|
|
11
|
+
* **Naming the server is a deliberate addition rather than a side effect**: the subject already
|
|
12
|
+
* carries it, and which server a call reaches is the one load-bearing fact the old prompt hid.
|
|
13
|
+
* - `tool` — *The agent wants to use the `<tool>` tool*.
|
|
14
|
+
*
|
|
15
|
+
* **An MCP call whose server would print as a blank falls to the `tool` arm.** The `mcpTool`
|
|
16
|
+
* sentence would otherwise read "on the MCP server , via …" — nothing where the decisive word goes.
|
|
17
|
+
* The `tool` arm names the full registered name instead, which still carries the visible `mcp__`
|
|
18
|
+
* namespace, and invents no prose claiming a server nothing could attribute.
|
|
19
|
+
*
|
|
20
|
+
* **A pending call with no subject also falls to the `tool` arm**, which is true of any gated call
|
|
21
|
+
* and false of none. The runner attaches a subject to every interrupt it hands the approval
|
|
22
|
+
* callback (asserted in `packages/core/spec/approvalPromptHeader.spec.ts`), so this is a floor
|
|
23
|
+
* rather than a path: a surface handed a hand-built interrupt gets the vague sentence, never a
|
|
24
|
+
* wrong one.
|
|
25
|
+
*
|
|
26
|
+
* @param pending The gated call — its registered name, and the subject the gate decided on.
|
|
27
|
+
*/
|
|
28
|
+
export declare function approvalPromptHeader(pending: Pick<PendingToolInterrupt, 'name' | 'subject'>): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { UNRESOLVED_MCP_SERVER } from '#src/core/approvals/mcpSubjects.js';
|
|
2
|
+
import { neutralizeToOneLine } from '#src/core/shell/framing.js';
|
|
3
|
+
/**
|
|
4
|
+
* An identifier as it may be interpolated into the prompt's **own** chrome line.
|
|
5
|
+
*
|
|
6
|
+
* A registered tool name is not always ours: an MCP server supplies the names in its tool listing,
|
|
7
|
+
* and the `mcpTool` arm additionally prints the server-supplied short name. This line sits at
|
|
8
|
+
* column 0 among the dialog's own rows, so a name carrying a newline would lay down a row that
|
|
9
|
+
* looks exactly like chrome and a carriage return would walk the cursor back over it — the
|
|
10
|
+
* [[TUI-C26]] forgery, reached through the one string that was never framed. `neutralizeToOneLine`
|
|
11
|
+
* is core's existing answer for text that must survive as a single safe line; an ordinary name
|
|
12
|
+
* passes through it byte for byte, so nothing legitimate changes shape.
|
|
13
|
+
*/
|
|
14
|
+
const identifier = (value) => neutralizeToOneLine(value);
|
|
15
|
+
/**
|
|
16
|
+
* The opening sentence of the approval prompt for one gated call, **without** any surface's own
|
|
17
|
+
* punctuation or styling.
|
|
18
|
+
*
|
|
19
|
+
* Three arms, one per subject kind:
|
|
20
|
+
*
|
|
21
|
+
* - `shell` — *The agent wants to run a shell command via `<tool>`*, unchanged, so the case that was
|
|
22
|
+
* already clear stays clear.
|
|
23
|
+
* - `mcpTool` — *The agent wants to call `<name>` on the MCP server `<server>`, via `<tool>`*.
|
|
24
|
+
* **Naming the server is a deliberate addition rather than a side effect**: the subject already
|
|
25
|
+
* carries it, and which server a call reaches is the one load-bearing fact the old prompt hid.
|
|
26
|
+
* - `tool` — *The agent wants to use the `<tool>` tool*.
|
|
27
|
+
*
|
|
28
|
+
* **An MCP call whose server would print as a blank falls to the `tool` arm.** The `mcpTool`
|
|
29
|
+
* sentence would otherwise read "on the MCP server , via …" — nothing where the decisive word goes.
|
|
30
|
+
* The `tool` arm names the full registered name instead, which still carries the visible `mcp__`
|
|
31
|
+
* namespace, and invents no prose claiming a server nothing could attribute.
|
|
32
|
+
*
|
|
33
|
+
* **A pending call with no subject also falls to the `tool` arm**, which is true of any gated call
|
|
34
|
+
* and false of none. The runner attaches a subject to every interrupt it hands the approval
|
|
35
|
+
* callback (asserted in `packages/core/spec/approvalPromptHeader.spec.ts`), so this is a floor
|
|
36
|
+
* rather than a path: a surface handed a hand-built interrupt gets the vague sentence, never a
|
|
37
|
+
* wrong one.
|
|
38
|
+
*
|
|
39
|
+
* @param pending The gated call — its registered name, and the subject the gate decided on.
|
|
40
|
+
*/
|
|
41
|
+
export function approvalPromptHeader(pending) {
|
|
42
|
+
const tool = identifier(pending.name);
|
|
43
|
+
const subject = pending.subject;
|
|
44
|
+
if (subject?.kind === 'shell') {
|
|
45
|
+
return `The agent wants to run a shell command via ${tool}`;
|
|
46
|
+
}
|
|
47
|
+
if (subject?.kind === 'mcpTool') {
|
|
48
|
+
const server = identifier(subject.server);
|
|
49
|
+
// **The guard is on the RENDERED server, not the raw one.** `UNRESOLVED_MCP_SERVER` is the
|
|
50
|
+
// empty string — the one identity a user cannot spell in config — but it is not the only value
|
|
51
|
+
// that reaches the screen as a blank: `neutralizeToOneLine` ends in a trim, so a server key of
|
|
52
|
+
// whitespace alone, which `z.string().min(1)` admits, is empty by the time it would be printed.
|
|
53
|
+
// Comparing the value before neutralisation lets exactly that key render the sentence with a
|
|
54
|
+
// hole in it, which is the output this fallback exists to prevent.
|
|
55
|
+
if (server !== UNRESOLVED_MCP_SERVER) {
|
|
56
|
+
const name = identifier(subject.name);
|
|
57
|
+
return `The agent wants to call ${name} on the MCP server ${server}, via ${tool}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return `The agent wants to use the ${tool} tool`;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=promptHeader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promptHeader.js","sourceRoot":"","sources":["../../../src/core/approvals/promptHeader.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAuD;IAEvD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,OAAO,GAAgC,OAAO,CAAC,OAAO,CAAC;IAC7D,IAAI,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;QAC9B,OAAO,8CAA8C,IAAI,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,OAAO,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC1C,2FAA2F;QAC3F,+FAA+F;QAC/F,+FAA+F;QAC/F,gGAAgG;QAChG,6FAA6F;QAC7F,mEAAmE;QACnE,IAAI,MAAM,KAAK,qBAAqB,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACtC,OAAO,2BAA2B,IAAI,sBAAsB,MAAM,SAAS,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC;IACD,OAAO,8BAA8B,IAAI,OAAO,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { DeclaredToolAnnotations } from '#src/core/approvals/annotations.js';
|
|
2
|
+
/**
|
|
3
|
+
* A built-in's authored hint set. **All four hints are stated**, never a subset: an omitted hint
|
|
4
|
+
* silently takes the fail-closed default, so a partial row would read as a considered judgment
|
|
5
|
+
* while actually being a gap. Requiring the whole set makes a forgotten hint a compile error.
|
|
6
|
+
*/
|
|
7
|
+
export type AuthoredToolAnnotations = Required<DeclaredToolAnnotations>;
|
|
8
|
+
/**
|
|
9
|
+
* §4.7 — what each of our own tools does, in the MCP `ToolAnnotations` vocabulary. Read **verbatim**
|
|
10
|
+
* through the trusted provenance (§4.7.1: we author these), so every value here has to be *true*
|
|
11
|
+
* rather than convenient.
|
|
12
|
+
*
|
|
13
|
+
* Keyed by the registered tool NAME, not by owner — gsloth's own `GthFileSystemToolkit` and
|
|
14
|
+
* `gth_*` tools, plus the conventional `ls`/`glob`/`grep` a graph builder or MCP server may
|
|
15
|
+
* register — because the conventions overlap on `read_file`/`write_file`/`edit_file` and one flat
|
|
16
|
+
* table by name serves them all, exactly as `BUILT_IN_TOOL_ACCESS` does.
|
|
17
|
+
*
|
|
18
|
+
* The judgments that carry security weight:
|
|
19
|
+
*
|
|
20
|
+
* - **A local read is `readOnlyHint: true`, `openWorldHint: false`.** It mutates nothing and reaches
|
|
21
|
+
* nothing off this machine.
|
|
22
|
+
* - **`gth_web_fetch` is `readOnlyHint: true` AND `openWorldHint: true`** — §4.7.3. It mutates
|
|
23
|
+
* nothing locally *and* reaches the network, and the two facts are independent. Reading it as a
|
|
24
|
+
* local read is precisely what §4.7.3 forbids: §4.6 floors a shell fetch before any model call,
|
|
25
|
+
* and "the same fetch reached through a tool instead of through `curl` must not be ungated", or
|
|
26
|
+
* the preflight is a rule about spelling rather than about fetching.
|
|
27
|
+
* - **Anything that writes, moves or deletes is not `readOnlyHint: true`**, and is
|
|
28
|
+
* `destructiveHint: true` wherever the call can destroy something that was already there —
|
|
29
|
+
* which `create_directory` cannot and `write_file`, `edit_file`, `move_file` and the two deletes
|
|
30
|
+
* can.
|
|
31
|
+
* - `idempotentHint` says whether repeating the identical call changes anything further. It has no
|
|
32
|
+
* built-in consumer (§4.7.2) and is recorded so the vocabulary round-trips and a user's own
|
|
33
|
+
* `hint` entry can reference it. MCP defines it, like `destructiveHint`, as meaningful only when
|
|
34
|
+
* `readOnlyHint` is false, so on a read row it is **moot and stated `true`**: a tool that changes
|
|
35
|
+
* nothing cannot change anything further on a second call, and a read row claiming otherwise
|
|
36
|
+
* would contradict itself.
|
|
37
|
+
*/
|
|
38
|
+
export declare const BUILT_IN_TOOL_ANNOTATIONS: Readonly<Record<string, AuthoredToolAnnotations>>;
|
|
39
|
+
/**
|
|
40
|
+
* §4.7.1 — the `builtIn` half of a `DeclaredToolAnnotationLookup`: what WE say one of our own tools
|
|
41
|
+
* does.
|
|
42
|
+
*
|
|
43
|
+
* **Keyed off {@link BUILT_IN_TOOL_ANNOTATIONS}, our own authored table, and never off the bound
|
|
44
|
+
* tool list.** The bound list is the merged set the graph is given, and it contains every MCP
|
|
45
|
+
* server's tools; a `builtIn` lookup implemented over it would read a third-party declaration
|
|
46
|
+
* through the trusted-verbatim path — the same "a gate any server can opt itself out of" hole as
|
|
47
|
+
* routing an MCP call to a `tool` subject, arriving by a different door. §4.7.1 puts it more
|
|
48
|
+
* strongly still: what decides trust is *who authored the annotation*, so even a tool running
|
|
49
|
+
* in-process whose annotations we did not write does not take this path.
|
|
50
|
+
*
|
|
51
|
+
* Returns a fresh object, so the table cannot be mutated through the lookup; `undefined` for
|
|
52
|
+
* anything not in it, which is fail-closed.
|
|
53
|
+
*/
|
|
54
|
+
export declare function builtInToolAnnotations(toolName: string): DeclaredToolAnnotations | undefined;
|
|
55
|
+
/** The slice of a registered tool this module reads. Keeps it free of any langchain type. */
|
|
56
|
+
export interface AnnotationDeclaringTool {
|
|
57
|
+
name?: string;
|
|
58
|
+
/** `unknown` on purpose: it is whatever the adapter attached, and it is network-derived. */
|
|
59
|
+
metadata?: unknown;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Read the four MCP hints a tool DECLARES, off the `metadata.annotations` the MCP adapter carries
|
|
63
|
+
* over verbatim from the server's `tools/list` response.
|
|
64
|
+
*
|
|
65
|
+
* Own-property reads of the four known hint names only, so nothing else in a server's payload can
|
|
66
|
+
* arrive as a hint. Values are passed through **unchanged**, including non-booleans: the one place
|
|
67
|
+
* that decides a string `"true"` is not a `true` is the trust computation
|
|
68
|
+
* (`createEffectiveToolAnnotationSource`), and duplicating that judgment here would give it two
|
|
69
|
+
* homes that can disagree.
|
|
70
|
+
*
|
|
71
|
+
* `undefined` when the payload declares none of the four — a server that says nothing about itself
|
|
72
|
+
* is the ordinary case and must land on fail-closed, not on an empty "it declared nothing" object.
|
|
73
|
+
*/
|
|
74
|
+
export declare function declaredToolAnnotationsFrom(metadata: unknown): DeclaredToolAnnotations | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* §4.7.1 — collect what the connected MCP servers declared, keyed by the **registered tool name**
|
|
77
|
+
* (`mcp__<server>__<tool>`).
|
|
78
|
+
*
|
|
79
|
+
* Keying by the registered name rather than by a parsed `(server, tool)` pair is what makes the
|
|
80
|
+
* lookup immune to the `__`-in-a-server-key hazard: the reader rebuilds the same string with
|
|
81
|
+
* `mcpToolRegisteredName` from a server key it has already resolved against the configured set, so
|
|
82
|
+
* nothing is ever split apart and re-joined differently.
|
|
83
|
+
*
|
|
84
|
+
* Only MCP-namespaced names are collected. Our own tools' annotations come from
|
|
85
|
+
* {@link BUILT_IN_TOOL_ANNOTATIONS} and must not be reachable through the per-hint-trusted MCP
|
|
86
|
+
* path, and a custom tool's own metadata is not ours to believe either (§4.7.1).
|
|
87
|
+
*/
|
|
88
|
+
export declare function collectDeclaredMcpToolAnnotations(tools: readonly AnnotationDeclaringTool[]): ReadonlyMap<string, DeclaredToolAnnotations>;
|
|
89
|
+
/**
|
|
90
|
+
* §4.7.1/§4.7.5 — the `mcp` half of a `DeclaredToolAnnotationLookup`, over what
|
|
91
|
+
* {@link collectDeclaredMcpToolAnnotations} recorded.
|
|
92
|
+
*
|
|
93
|
+
* **{@link UNRESOLVED_MCP_SERVER} is refused outright**, and that guard is structural rather than
|
|
94
|
+
* belt-and-braces. A call whose server could not be resolved has no identity to look a declaration
|
|
95
|
+
* up by; without the guard the lookup would fall back to *string arithmetic* on the sentinel and
|
|
96
|
+
* find a declaration in whatever entry happened to reconstruct to the same registered name.
|
|
97
|
+
* "Happened not to collide" is not a security property. Refusing the sentinel makes an
|
|
98
|
+
* unidentifiable call fail-closed for a reason that does not depend on the shape of anyone's tool
|
|
99
|
+
* names — and, because trust for an unnamed server would otherwise fall through to
|
|
100
|
+
* `approvals.mcp.defaults`, this is also what stops a permissive `defaults` from reaching it.
|
|
101
|
+
*
|
|
102
|
+
* There is one lookup rather than one per call site so the guard cannot be present in the runner
|
|
103
|
+
* and absent in the next consumer.
|
|
104
|
+
*/
|
|
105
|
+
export declare function mcpDeclaredAnnotationLookup(declared: ReadonlyMap<string, DeclaredToolAnnotations> | undefined): (server: string, toolName: string) => DeclaredToolAnnotations | undefined;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/approvals/toolAnnotationSources
|
|
3
|
+
*
|
|
4
|
+
* EXT-70 §4.7 — **where a declared annotation actually comes from**: our own authored hint sets for
|
|
5
|
+
* our own tools, and a server's `tools/list` declaration for an MCP tool. The trust that is then
|
|
6
|
+
* applied to them lives in `core/approvals/annotations.ts` and only there; this module supplies the
|
|
7
|
+
* raw claims and nothing else.
|
|
8
|
+
*
|
|
9
|
+
* ## One annotation-driven classifier, and no exempt list
|
|
10
|
+
*
|
|
11
|
+
* §4.7 is explicit that the vocabulary — `readOnlyHint`, `destructiveHint`, `idempotentHint`,
|
|
12
|
+
* `openWorldHint` — is shared by built-in, MCP and custom tools, with *"no bespoke exempt list"*.
|
|
13
|
+
* {@link BUILT_IN_TOOL_ANNOTATIONS} is therefore a statement of **what each tool does**, not a list
|
|
14
|
+
* of tools that get special treatment: nothing reads a name out of it to decide an outcome, and
|
|
15
|
+
* every consumer reads the four booleans. A table of names that skip the gate would be the thing
|
|
16
|
+
* this section removes.
|
|
17
|
+
*
|
|
18
|
+
* ## Absent means fail-closed, never "trusted, declaring nothing"
|
|
19
|
+
*
|
|
20
|
+
* A tool this module cannot answer for yields `undefined`, which
|
|
21
|
+
* `createEffectiveToolAnnotationSource` resolves to the MCP fail-closed defaults — a destructive,
|
|
22
|
+
* non-idempotent, open-world writer. That is why tools whose honest annotation set simply *is* the
|
|
23
|
+
* fail-closed default are deliberately absent below rather than spelled out: `run_shell_command`,
|
|
24
|
+
* the fixed dev-command tools, A2A agent tools and user-authored custom tools all reach arbitrary
|
|
25
|
+
* code or the network, and writing rows that restate the default would add entries no behaviour
|
|
26
|
+
* could distinguish from their absence.
|
|
27
|
+
*/
|
|
28
|
+
import { TOOL_ANNOTATION_HINTS } from '#src/config/shell-policy.js';
|
|
29
|
+
import { isMcpNamespacedToolName, mcpToolRegisteredName, UNRESOLVED_MCP_SERVER, } from '#src/core/approvals/mcpSubjects.js';
|
|
30
|
+
/** Build one authored set. Named arguments, because a row of four bare booleans is unreviewable. */
|
|
31
|
+
function authored(annotations) {
|
|
32
|
+
return Object.freeze(annotations);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads a file, a directory or file contents inside the working folder. Mutates nothing, so
|
|
36
|
+
* `destructiveHint` is moot and false; reaches nothing off this machine; and reading twice reads
|
|
37
|
+
* the same thing.
|
|
38
|
+
*/
|
|
39
|
+
const LOCAL_READ = authored({
|
|
40
|
+
readOnlyHint: true,
|
|
41
|
+
destructiveHint: false,
|
|
42
|
+
idempotentHint: true,
|
|
43
|
+
openWorldHint: false,
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* Leaves the working folder in a state its **arguments alone** determine — overwriting or removing
|
|
47
|
+
* whatever happened to be at the path — so it destroys, and repeating the identical call lands the
|
|
48
|
+
* same end state. Covers writing a whole file and deleting a file or a directory: what they have in
|
|
49
|
+
* common is that neither reads what is there first, and both discard it.
|
|
50
|
+
*/
|
|
51
|
+
const LOCAL_ABSOLUTE_CHANGE = authored({
|
|
52
|
+
readOnlyHint: false,
|
|
53
|
+
destructiveHint: true,
|
|
54
|
+
idempotentHint: true,
|
|
55
|
+
openWorldHint: false,
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Changes something in the working folder in a way that depends on what is there now: it destroys
|
|
59
|
+
* what it replaced, and repeating it either fails or lands a second, different time.
|
|
60
|
+
*/
|
|
61
|
+
const LOCAL_RELATIVE_CHANGE = authored({
|
|
62
|
+
readOnlyHint: false,
|
|
63
|
+
destructiveHint: true,
|
|
64
|
+
idempotentHint: false,
|
|
65
|
+
openWorldHint: false,
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Adds something to the working folder that was not there, destroying nothing, and settles into the
|
|
69
|
+
* same state when repeated.
|
|
70
|
+
*/
|
|
71
|
+
const LOCAL_ADDITIVE_CHANGE = authored({
|
|
72
|
+
readOnlyHint: false,
|
|
73
|
+
destructiveHint: false,
|
|
74
|
+
idempotentHint: true,
|
|
75
|
+
openWorldHint: false,
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* Writes state that belongs to **this session** — the agent's own plan, a surface rendered for the
|
|
79
|
+
* user — rather than to the working folder. Replacing such state destroys nothing outside the
|
|
80
|
+
* session, and re-sending the same state leaves the same state.
|
|
81
|
+
*
|
|
82
|
+
* Deliberately a separate constant from {@link LOCAL_ADDITIVE_CHANGE} despite carrying the same
|
|
83
|
+
* four values: the two describe different things, so a future correction to one must not silently
|
|
84
|
+
* move the other's tools.
|
|
85
|
+
*/
|
|
86
|
+
const SESSION_STATE_CHANGE = authored({
|
|
87
|
+
readOnlyHint: false,
|
|
88
|
+
destructiveHint: false,
|
|
89
|
+
idempotentHint: true,
|
|
90
|
+
openWorldHint: false,
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* §4.7 — what each of our own tools does, in the MCP `ToolAnnotations` vocabulary. Read **verbatim**
|
|
94
|
+
* through the trusted provenance (§4.7.1: we author these), so every value here has to be *true*
|
|
95
|
+
* rather than convenient.
|
|
96
|
+
*
|
|
97
|
+
* Keyed by the registered tool NAME, not by owner — gsloth's own `GthFileSystemToolkit` and
|
|
98
|
+
* `gth_*` tools, plus the conventional `ls`/`glob`/`grep` a graph builder or MCP server may
|
|
99
|
+
* register — because the conventions overlap on `read_file`/`write_file`/`edit_file` and one flat
|
|
100
|
+
* table by name serves them all, exactly as `BUILT_IN_TOOL_ACCESS` does.
|
|
101
|
+
*
|
|
102
|
+
* The judgments that carry security weight:
|
|
103
|
+
*
|
|
104
|
+
* - **A local read is `readOnlyHint: true`, `openWorldHint: false`.** It mutates nothing and reaches
|
|
105
|
+
* nothing off this machine.
|
|
106
|
+
* - **`gth_web_fetch` is `readOnlyHint: true` AND `openWorldHint: true`** — §4.7.3. It mutates
|
|
107
|
+
* nothing locally *and* reaches the network, and the two facts are independent. Reading it as a
|
|
108
|
+
* local read is precisely what §4.7.3 forbids: §4.6 floors a shell fetch before any model call,
|
|
109
|
+
* and "the same fetch reached through a tool instead of through `curl` must not be ungated", or
|
|
110
|
+
* the preflight is a rule about spelling rather than about fetching.
|
|
111
|
+
* - **Anything that writes, moves or deletes is not `readOnlyHint: true`**, and is
|
|
112
|
+
* `destructiveHint: true` wherever the call can destroy something that was already there —
|
|
113
|
+
* which `create_directory` cannot and `write_file`, `edit_file`, `move_file` and the two deletes
|
|
114
|
+
* can.
|
|
115
|
+
* - `idempotentHint` says whether repeating the identical call changes anything further. It has no
|
|
116
|
+
* built-in consumer (§4.7.2) and is recorded so the vocabulary round-trips and a user's own
|
|
117
|
+
* `hint` entry can reference it. MCP defines it, like `destructiveHint`, as meaningful only when
|
|
118
|
+
* `readOnlyHint` is false, so on a read row it is **moot and stated `true`**: a tool that changes
|
|
119
|
+
* nothing cannot change anything further on a second call, and a read row claiming otherwise
|
|
120
|
+
* would contradict itself.
|
|
121
|
+
*/
|
|
122
|
+
export const BUILT_IN_TOOL_ANNOTATIONS = Object.freeze({
|
|
123
|
+
// ---- Local reads (gsloth's GthFileSystemToolkit and gth_* tools, lean backend). -----------
|
|
124
|
+
read_file: LOCAL_READ,
|
|
125
|
+
read_multiple_files: LOCAL_READ,
|
|
126
|
+
gth_read_binary: LOCAL_READ,
|
|
127
|
+
list_directory: LOCAL_READ,
|
|
128
|
+
list_directory_with_sizes: LOCAL_READ,
|
|
129
|
+
directory_tree: LOCAL_READ,
|
|
130
|
+
search_files: LOCAL_READ,
|
|
131
|
+
get_file_info: LOCAL_READ,
|
|
132
|
+
// Reports which directories the file tools may touch. Reads configuration, changes nothing.
|
|
133
|
+
list_allowed_directories: LOCAL_READ,
|
|
134
|
+
gth_grep: LOCAL_READ,
|
|
135
|
+
// ---- Conventional local-read names a graph builder or MCP server may register. -----------
|
|
136
|
+
ls: LOCAL_READ,
|
|
137
|
+
glob: LOCAL_READ,
|
|
138
|
+
grep: LOCAL_READ,
|
|
139
|
+
// ---- Local writes. -----------------------------------------------------------------------
|
|
140
|
+
// Writes the whole file, so it destroys whatever was there; the same bytes twice leave the
|
|
141
|
+
// same file.
|
|
142
|
+
write_file: LOCAL_ABSOLUTE_CHANGE,
|
|
143
|
+
// A targeted edit destroys the text it replaces, and re-applying it either fails or lands a
|
|
144
|
+
// second time.
|
|
145
|
+
edit_file: LOCAL_RELATIVE_CHANGE,
|
|
146
|
+
// Creating a directory adds and removes nothing; the second call leaves the same tree.
|
|
147
|
+
create_directory: LOCAL_ADDITIVE_CHANGE,
|
|
148
|
+
// A move removes the source, and the second call no longer has one.
|
|
149
|
+
move_file: LOCAL_RELATIVE_CHANGE,
|
|
150
|
+
// Deletion destroys by definition; the second call finds nothing left to change.
|
|
151
|
+
delete_file: LOCAL_ABSOLUTE_CHANGE,
|
|
152
|
+
delete_directory: LOCAL_ABSOLUTE_CHANGE,
|
|
153
|
+
// ---- The open-world read (§4.7.3): read-only locally, and on the network. -----------------
|
|
154
|
+
gth_web_fetch: authored({
|
|
155
|
+
readOnlyHint: true,
|
|
156
|
+
destructiveHint: false,
|
|
157
|
+
idempotentHint: true,
|
|
158
|
+
openWorldHint: true,
|
|
159
|
+
}),
|
|
160
|
+
// ---- Tools that touch only this session's own surfaces. ----------------------------------
|
|
161
|
+
// Prints one line for the user and changes no state anywhere, so it is a read. `idempotentHint`
|
|
162
|
+
// is then MOOT for exactly the reason `destructiveHint` is — MCP defines both as meaningful
|
|
163
|
+
// only when `readOnlyHint` is false — and a moot hint states the value consistent with the
|
|
164
|
+
// read: a tool that changes nothing cannot change anything *further* when called again.
|
|
165
|
+
// Stating `false` here would have this row contradict itself.
|
|
166
|
+
gth_status_update: authored({
|
|
167
|
+
readOnlyHint: true,
|
|
168
|
+
destructiveHint: false,
|
|
169
|
+
idempotentHint: true,
|
|
170
|
+
openWorldHint: false,
|
|
171
|
+
}),
|
|
172
|
+
// Replaces the agent's own plan state: a write, destroying nothing outside the session, and
|
|
173
|
+
// re-sending the same checklist leaves the same checklist.
|
|
174
|
+
gth_checklist: SESSION_STATE_CHANGE,
|
|
175
|
+
// Renders a UI surface on the client — it changes what the user sees rather than nothing at
|
|
176
|
+
// all, and re-rendering the same surface is the same surface.
|
|
177
|
+
show_a2ui_surface: SESSION_STATE_CHANGE,
|
|
178
|
+
});
|
|
179
|
+
/**
|
|
180
|
+
* §4.7.1 — the `builtIn` half of a `DeclaredToolAnnotationLookup`: what WE say one of our own tools
|
|
181
|
+
* does.
|
|
182
|
+
*
|
|
183
|
+
* **Keyed off {@link BUILT_IN_TOOL_ANNOTATIONS}, our own authored table, and never off the bound
|
|
184
|
+
* tool list.** The bound list is the merged set the graph is given, and it contains every MCP
|
|
185
|
+
* server's tools; a `builtIn` lookup implemented over it would read a third-party declaration
|
|
186
|
+
* through the trusted-verbatim path — the same "a gate any server can opt itself out of" hole as
|
|
187
|
+
* routing an MCP call to a `tool` subject, arriving by a different door. §4.7.1 puts it more
|
|
188
|
+
* strongly still: what decides trust is *who authored the annotation*, so even a tool running
|
|
189
|
+
* in-process whose annotations we did not write does not take this path.
|
|
190
|
+
*
|
|
191
|
+
* Returns a fresh object, so the table cannot be mutated through the lookup; `undefined` for
|
|
192
|
+
* anything not in it, which is fail-closed.
|
|
193
|
+
*/
|
|
194
|
+
export function builtInToolAnnotations(toolName) {
|
|
195
|
+
if (!Object.hasOwn(BUILT_IN_TOOL_ANNOTATIONS, toolName))
|
|
196
|
+
return undefined;
|
|
197
|
+
return { ...BUILT_IN_TOOL_ANNOTATIONS[toolName] };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Read the four MCP hints a tool DECLARES, off the `metadata.annotations` the MCP adapter carries
|
|
201
|
+
* over verbatim from the server's `tools/list` response.
|
|
202
|
+
*
|
|
203
|
+
* Own-property reads of the four known hint names only, so nothing else in a server's payload can
|
|
204
|
+
* arrive as a hint. Values are passed through **unchanged**, including non-booleans: the one place
|
|
205
|
+
* that decides a string `"true"` is not a `true` is the trust computation
|
|
206
|
+
* (`createEffectiveToolAnnotationSource`), and duplicating that judgment here would give it two
|
|
207
|
+
* homes that can disagree.
|
|
208
|
+
*
|
|
209
|
+
* `undefined` when the payload declares none of the four — a server that says nothing about itself
|
|
210
|
+
* is the ordinary case and must land on fail-closed, not on an empty "it declared nothing" object.
|
|
211
|
+
*/
|
|
212
|
+
export function declaredToolAnnotationsFrom(metadata) {
|
|
213
|
+
if (!metadata || typeof metadata !== 'object')
|
|
214
|
+
return undefined;
|
|
215
|
+
const annotations = metadata.annotations;
|
|
216
|
+
if (!annotations || typeof annotations !== 'object')
|
|
217
|
+
return undefined;
|
|
218
|
+
const declared = annotations;
|
|
219
|
+
const result = {};
|
|
220
|
+
let stated = false;
|
|
221
|
+
for (const hint of TOOL_ANNOTATION_HINTS) {
|
|
222
|
+
if (!Object.hasOwn(declared, hint))
|
|
223
|
+
continue;
|
|
224
|
+
result[hint] = declared[hint];
|
|
225
|
+
stated = true;
|
|
226
|
+
}
|
|
227
|
+
return stated ? result : undefined;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* §4.7.1 — collect what the connected MCP servers declared, keyed by the **registered tool name**
|
|
231
|
+
* (`mcp__<server>__<tool>`).
|
|
232
|
+
*
|
|
233
|
+
* Keying by the registered name rather than by a parsed `(server, tool)` pair is what makes the
|
|
234
|
+
* lookup immune to the `__`-in-a-server-key hazard: the reader rebuilds the same string with
|
|
235
|
+
* `mcpToolRegisteredName` from a server key it has already resolved against the configured set, so
|
|
236
|
+
* nothing is ever split apart and re-joined differently.
|
|
237
|
+
*
|
|
238
|
+
* Only MCP-namespaced names are collected. Our own tools' annotations come from
|
|
239
|
+
* {@link BUILT_IN_TOOL_ANNOTATIONS} and must not be reachable through the per-hint-trusted MCP
|
|
240
|
+
* path, and a custom tool's own metadata is not ours to believe either (§4.7.1).
|
|
241
|
+
*/
|
|
242
|
+
export function collectDeclaredMcpToolAnnotations(tools) {
|
|
243
|
+
const collected = new Map();
|
|
244
|
+
for (const tool of tools) {
|
|
245
|
+
const name = tool?.name;
|
|
246
|
+
if (typeof name !== 'string' || !isMcpNamespacedToolName(name))
|
|
247
|
+
continue;
|
|
248
|
+
const declared = declaredToolAnnotationsFrom(tool.metadata);
|
|
249
|
+
if (declared)
|
|
250
|
+
collected.set(name, declared);
|
|
251
|
+
}
|
|
252
|
+
return collected;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* §4.7.1/§4.7.5 — the `mcp` half of a `DeclaredToolAnnotationLookup`, over what
|
|
256
|
+
* {@link collectDeclaredMcpToolAnnotations} recorded.
|
|
257
|
+
*
|
|
258
|
+
* **{@link UNRESOLVED_MCP_SERVER} is refused outright**, and that guard is structural rather than
|
|
259
|
+
* belt-and-braces. A call whose server could not be resolved has no identity to look a declaration
|
|
260
|
+
* up by; without the guard the lookup would fall back to *string arithmetic* on the sentinel and
|
|
261
|
+
* find a declaration in whatever entry happened to reconstruct to the same registered name.
|
|
262
|
+
* "Happened not to collide" is not a security property. Refusing the sentinel makes an
|
|
263
|
+
* unidentifiable call fail-closed for a reason that does not depend on the shape of anyone's tool
|
|
264
|
+
* names — and, because trust for an unnamed server would otherwise fall through to
|
|
265
|
+
* `approvals.mcp.defaults`, this is also what stops a permissive `defaults` from reaching it.
|
|
266
|
+
*
|
|
267
|
+
* There is one lookup rather than one per call site so the guard cannot be present in the runner
|
|
268
|
+
* and absent in the next consumer.
|
|
269
|
+
*/
|
|
270
|
+
export function mcpDeclaredAnnotationLookup(declared) {
|
|
271
|
+
return (server, toolName) => {
|
|
272
|
+
if (server === UNRESOLVED_MCP_SERVER)
|
|
273
|
+
return undefined;
|
|
274
|
+
return declared?.get(mcpToolRegisteredName(server, toolName));
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=toolAnnotationSources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolAnnotationSources.js","sourceRoot":"","sources":["../../../src/core/approvals/toolAnnotationSources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/approvals/toolHost
|
|
3
|
+
*
|
|
4
|
+
* EXT-70 (spec §4.7.4) — **the host a tool call reaches**, read off the call's own arguments.
|
|
5
|
+
*
|
|
6
|
+
* §4.7.4's bound on a sticky tool grant needs one thing the tool name does not carry: *which
|
|
7
|
+
* counterparty this call talks to*. A grant that recorded only the tool would be every host,
|
|
8
|
+
* forever — the hole §4.6 spent a whole preflight closing, reached through a tool instead of
|
|
9
|
+
* `curl`. A host is not "the full argument signature"; it is the one argument both
|
|
10
|
+
* security-relevant and stable enough across calls for the grant to stay useful.
|
|
11
|
+
*
|
|
12
|
+
* ## Why this is not a second implementation of §4.6's scanner
|
|
13
|
+
*
|
|
14
|
+
* `core/shell/openWorld.ts` answers a different question over a different input: *"does this shell
|
|
15
|
+
* word name a counterparty at all"*, over a tokenized command line, returning a boolean plus the
|
|
16
|
+
* literals it found for a human-readable reason. This answers *"which host, exactly"* over a
|
|
17
|
+
* **structured tool argument**, because the answer here is stored in a config entry and later
|
|
18
|
+
* compared for equality — so it must be a single normalized string or nothing.
|
|
19
|
+
*
|
|
20
|
+
* Structured arguments admit the precise instrument the shell path cannot use: `URL` parsing. A
|
|
21
|
+
* value either parses as a URL with a host or it does not, with no tokenization, no quoting and no
|
|
22
|
+
* heuristics about operand position.
|
|
23
|
+
*
|
|
24
|
+
* ## The COUNT is the answer, which is why nothing here returns a single host
|
|
25
|
+
*
|
|
26
|
+
* A grant's `host` is one exact string, so only a call naming **exactly one** host has a host to
|
|
27
|
+
* record — but the decision needs the count and not just that one host, because the two ways of
|
|
28
|
+
* having no single host are opposite outcomes: a call naming **no** host gets a tool-only grant
|
|
29
|
+
* (§6's *always approve `mcp__jira__create_issue`*, where no host is involved), while a call naming
|
|
30
|
+
* **several** gets no grant at all, since the menu may not display one bound and store another.
|
|
31
|
+
* A convenience that collapsed both to "no host" would hand a caller the one distinction this
|
|
32
|
+
* module exists to preserve, already discarded — so {@link toolCallHosts} is the whole surface, and
|
|
33
|
+
* a caller that wants *the* host reads `hosts.length === 1 ? hosts[0] : …` at the point where it
|
|
34
|
+
* also decides what the other two cases mean.
|
|
35
|
+
*
|
|
36
|
+
* A value this module fails to recognize as a host costs a re-prompt or a broader grant than the
|
|
37
|
+
* call deserved, never a narrower one that silently fails to match.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Every distinct host named anywhere in a tool call's arguments, in the order first seen.
|
|
41
|
+
*
|
|
42
|
+
* Own enumerable properties only, so nothing inherited can contribute a host, and arrays are walked
|
|
43
|
+
* as their elements. This is what the approvals decision reads, because it needs the COUNT: a call
|
|
44
|
+
* naming no host and a call naming several are the same absent host and not the same grant.
|
|
45
|
+
*/
|
|
46
|
+
export declare function toolCallHosts(args: unknown): string[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/approvals/toolHost
|
|
3
|
+
*
|
|
4
|
+
* EXT-70 (spec §4.7.4) — **the host a tool call reaches**, read off the call's own arguments.
|
|
5
|
+
*
|
|
6
|
+
* §4.7.4's bound on a sticky tool grant needs one thing the tool name does not carry: *which
|
|
7
|
+
* counterparty this call talks to*. A grant that recorded only the tool would be every host,
|
|
8
|
+
* forever — the hole §4.6 spent a whole preflight closing, reached through a tool instead of
|
|
9
|
+
* `curl`. A host is not "the full argument signature"; it is the one argument both
|
|
10
|
+
* security-relevant and stable enough across calls for the grant to stay useful.
|
|
11
|
+
*
|
|
12
|
+
* ## Why this is not a second implementation of §4.6's scanner
|
|
13
|
+
*
|
|
14
|
+
* `core/shell/openWorld.ts` answers a different question over a different input: *"does this shell
|
|
15
|
+
* word name a counterparty at all"*, over a tokenized command line, returning a boolean plus the
|
|
16
|
+
* literals it found for a human-readable reason. This answers *"which host, exactly"* over a
|
|
17
|
+
* **structured tool argument**, because the answer here is stored in a config entry and later
|
|
18
|
+
* compared for equality — so it must be a single normalized string or nothing.
|
|
19
|
+
*
|
|
20
|
+
* Structured arguments admit the precise instrument the shell path cannot use: `URL` parsing. A
|
|
21
|
+
* value either parses as a URL with a host or it does not, with no tokenization, no quoting and no
|
|
22
|
+
* heuristics about operand position.
|
|
23
|
+
*
|
|
24
|
+
* ## The COUNT is the answer, which is why nothing here returns a single host
|
|
25
|
+
*
|
|
26
|
+
* A grant's `host` is one exact string, so only a call naming **exactly one** host has a host to
|
|
27
|
+
* record — but the decision needs the count and not just that one host, because the two ways of
|
|
28
|
+
* having no single host are opposite outcomes: a call naming **no** host gets a tool-only grant
|
|
29
|
+
* (§6's *always approve `mcp__jira__create_issue`*, where no host is involved), while a call naming
|
|
30
|
+
* **several** gets no grant at all, since the menu may not display one bound and store another.
|
|
31
|
+
* A convenience that collapsed both to "no host" would hand a caller the one distinction this
|
|
32
|
+
* module exists to preserve, already discarded — so {@link toolCallHosts} is the whole surface, and
|
|
33
|
+
* a caller that wants *the* host reads `hosts.length === 1 ? hosts[0] : …` at the point where it
|
|
34
|
+
* also decides what the other two cases mean.
|
|
35
|
+
*
|
|
36
|
+
* A value this module fails to recognize as a host costs a re-prompt or a broader grant than the
|
|
37
|
+
* call deserved, never a narrower one that silently fails to match.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* How deep into a nested argument object a host may hide. Tool arguments are shallow by
|
|
41
|
+
* construction (a JSON Schema the model fills in), so this is a budget rather than a limit anyone
|
|
42
|
+
* should expect to reach.
|
|
43
|
+
*/
|
|
44
|
+
const MAX_DEPTH = 4;
|
|
45
|
+
/** How many values are examined in total, so a pathological argument object cannot spin. */
|
|
46
|
+
const MAX_VALUES = 256;
|
|
47
|
+
/**
|
|
48
|
+
* Values longer than this are not candidate URLs. A URL that reaches this length is not something a
|
|
49
|
+
* grant should be keyed on, and the cap bounds the work regardless of what a model emitted.
|
|
50
|
+
*/
|
|
51
|
+
const MAX_VALUE_LENGTH = 4096;
|
|
52
|
+
/**
|
|
53
|
+
* The host a single string names, or `undefined`.
|
|
54
|
+
*
|
|
55
|
+
* **A non-empty `hostname` is the whole test**, and it is what makes the schemes that name no
|
|
56
|
+
* counterparty drop out without a list to maintain: `file:///etc/passwd`, `data:text/plain,x` and
|
|
57
|
+
* `mailto:someone@example.com` all parse, and all have an empty hostname. Lower-cased because host
|
|
58
|
+
* names are case-insensitive and the stored value is compared for equality; the port is
|
|
59
|
+
* deliberately left out, since §3.1's example is a bare host and a grant that dissolved when a
|
|
60
|
+
* server moved port would be a grant on the wrong thing.
|
|
61
|
+
*/
|
|
62
|
+
function hostOf(value) {
|
|
63
|
+
if (value.length === 0 || value.length > MAX_VALUE_LENGTH)
|
|
64
|
+
return undefined;
|
|
65
|
+
let parsed;
|
|
66
|
+
try {
|
|
67
|
+
parsed = new URL(value);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
return parsed.hostname.length > 0 ? parsed.hostname.toLowerCase() : undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Every distinct host named anywhere in a tool call's arguments, in the order first seen.
|
|
76
|
+
*
|
|
77
|
+
* Own enumerable properties only, so nothing inherited can contribute a host, and arrays are walked
|
|
78
|
+
* as their elements. This is what the approvals decision reads, because it needs the COUNT: a call
|
|
79
|
+
* naming no host and a call naming several are the same absent host and not the same grant.
|
|
80
|
+
*/
|
|
81
|
+
export function toolCallHosts(args) {
|
|
82
|
+
const hosts = [];
|
|
83
|
+
let budget = MAX_VALUES;
|
|
84
|
+
const walk = (value, depth) => {
|
|
85
|
+
if (budget <= 0 || depth > MAX_DEPTH)
|
|
86
|
+
return;
|
|
87
|
+
budget -= 1;
|
|
88
|
+
if (typeof value === 'string') {
|
|
89
|
+
const host = hostOf(value);
|
|
90
|
+
if (host !== undefined && !hosts.includes(host))
|
|
91
|
+
hosts.push(host);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
for (const item of value)
|
|
96
|
+
walk(item, depth + 1);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (value && typeof value === 'object') {
|
|
100
|
+
for (const key of Object.keys(value)) {
|
|
101
|
+
walk(value[key], depth + 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
walk(args, 0);
|
|
106
|
+
return hosts;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=toolHost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolHost.js","sourceRoot":"","sources":["../../../src/core/approvals/toolHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;;GAIG;AACH,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,4FAA4F;AAC5F,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB;;;GAGG;AACH,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B;;;;;;;;;GASG;AACH,SAAS,MAAM,CAAC,KAAa;IAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,gBAAgB;QAAE,OAAO,SAAS,CAAC;IAC5E,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAChF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,GAAG,UAAU,CAAC;IAExB,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,KAAa,EAAQ,EAAE;QACnD,IAAI,MAAM,IAAI,CAAC,IAAI,KAAK,GAAG,SAAS;YAAE,OAAO;QAC7C,MAAM,IAAI,CAAC,CAAC;QACZ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YAC3B,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClE,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,EAAE,CAAC;gBAChE,IAAI,CAAE,KAAiC,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACd,OAAO,KAAK,CAAC;AACf,CAAC"}
|