@namzu/sdk 26.0.0 → 27.0.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/CHANGELOG.md +123 -0
- package/dist/compaction/manager.d.ts +22 -0
- package/dist/compaction/manager.d.ts.map +1 -1
- package/dist/compaction/manager.js +31 -3
- package/dist/compaction/manager.js.map +1 -1
- package/dist/connector/mcp/adapter.d.ts +7 -1
- package/dist/connector/mcp/adapter.d.ts.map +1 -1
- package/dist/connector/mcp/adapter.js +12 -1
- package/dist/connector/mcp/adapter.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +1 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +3 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/prompt-adapter.d.ts +10 -4
- package/dist/connector/mcp/prompt-adapter.d.ts.map +1 -1
- package/dist/connector/mcp/prompt-adapter.js +10 -4
- package/dist/connector/mcp/prompt-adapter.js.map +1 -1
- package/dist/connector/mcp/server-stdio.d.ts +62 -0
- package/dist/connector/mcp/server-stdio.d.ts.map +1 -0
- package/dist/connector/mcp/server-stdio.js +121 -0
- package/dist/connector/mcp/server-stdio.js.map +1 -0
- package/dist/eval/experiment.d.ts +0 -6
- package/dist/eval/experiment.d.ts.map +1 -1
- package/dist/eval/experiment.js +28 -0
- package/dist/eval/experiment.js.map +1 -1
- package/dist/eval/index.d.ts +2 -0
- package/dist/eval/index.d.ts.map +1 -1
- package/dist/eval/index.js +1 -0
- package/dist/eval/index.js.map +1 -1
- package/dist/eval/types.d.ts +17 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/eval/uncertainty.d.ts +64 -0
- package/dist/eval/uncertainty.d.ts.map +1 -0
- package/dist/eval/uncertainty.js +92 -0
- package/dist/eval/uncertainty.js.map +1 -0
- package/dist/public-runtime.d.ts +1 -0
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -0
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +2 -1
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/session/workspace/git-worktree.d.ts +15 -0
- package/dist/session/workspace/git-worktree.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.js +55 -1
- package/dist/session/workspace/git-worktree.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +35 -0
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +12 -0
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +134 -6
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/trusted-read-only.d.ts +42 -0
- package/dist/tools/trusted-read-only.d.ts.map +1 -0
- package/dist/tools/trusted-read-only.js +51 -0
- package/dist/tools/trusted-read-only.js.map +1 -0
- package/dist/tools/untrusted-envelope.d.ts +22 -2
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +22 -2
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +23 -0
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +28 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/verification/rules.d.ts.map +1 -1
- package/dist/verification/rules.js +5 -1
- package/dist/verification/rules.js.map +1 -1
- package/package.json +1 -1
- package/src/compaction/manager.ts +39 -3
- package/src/connector/mcp/adapter.ts +11 -0
- package/src/connector/mcp/index.ts +3 -0
- package/src/connector/mcp/prompt-adapter.ts +10 -4
- package/src/connector/mcp/server-stdio.ts +137 -0
- package/src/eval/experiment.ts +30 -0
- package/src/eval/index.ts +2 -0
- package/src/eval/types.ts +17 -0
- package/src/eval/uncertainty.ts +124 -0
- package/src/public-runtime.ts +1 -0
- package/src/registry/tool/execute.ts +2 -1
- package/src/session/workspace/git-worktree.ts +55 -1
- package/src/tools/builtins/bash.ts +34 -0
- package/src/tools/coordinator/agent.ts +12 -0
- package/src/tools/coordinator/index.ts +144 -8
- package/src/tools/trusted-read-only.ts +52 -0
- package/src/tools/untrusted-envelope.ts +22 -2
- package/src/types/sandbox/index.ts +23 -0
- package/src/types/tool/index.ts +30 -0
- package/src/verification/rules.ts +5 -1
|
@@ -157,6 +157,73 @@ const askUserQuestionModelInputSchema: Record<string, unknown> = {
|
|
|
157
157
|
additionalProperties: false,
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/** One well-formed tag token: `<step>`, `</step>`, `<a href="…">`, `<br/>`. */
|
|
161
|
+
const TAG_TOKEN = /<\/?[A-Za-z][\w-]*(?:\s[^<>]*)?\/?>/g
|
|
162
|
+
const DESCRIPTION_BLOCK = /<description>([\s\S]*?)<\/description>/gi
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Remove every tag token, including the ones removing a tag creates.
|
|
166
|
+
*
|
|
167
|
+
* One pass is not enough and the reason is not obvious: deleting an inner
|
|
168
|
+
* tag can splice its neighbours into a new one. `<<step>step>` loses the
|
|
169
|
+
* inner `<step>` and the halves close up into `<step>` again, so a line
|
|
170
|
+
* that is nothing but markup comes back non-empty and is offered to a
|
|
171
|
+
* human as a step to approve — which is the exact outcome this whole path
|
|
172
|
+
* exists to prevent.
|
|
173
|
+
*
|
|
174
|
+
* Repeating to a fixed point terminates: every pass that changes the
|
|
175
|
+
* string removes at least one token and so strictly shortens it.
|
|
176
|
+
*
|
|
177
|
+
* Only ever used to ANSWER "is there anything here besides markup". The
|
|
178
|
+
* result is never shown to anyone, so this is a test rather than a
|
|
179
|
+
* sanitiser, and it does not have to defend against every way a tag can
|
|
180
|
+
* be spelled.
|
|
181
|
+
*/
|
|
182
|
+
function withoutTags(text: string): string {
|
|
183
|
+
let current = text
|
|
184
|
+
for (;;) {
|
|
185
|
+
const next = current.replace(TAG_TOKEN, '')
|
|
186
|
+
if (next === current) return current
|
|
187
|
+
current = next
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Peel tag wrappers off the ENDS of one line, and nowhere else — a step
|
|
193
|
+
* that legitimately says "wrap it in a <div>" keeps its sentence.
|
|
194
|
+
*/
|
|
195
|
+
function unwrapStepLine(line: string): string {
|
|
196
|
+
let text = line.trim()
|
|
197
|
+
for (;;) {
|
|
198
|
+
const next = text
|
|
199
|
+
.replace(/^<[A-Za-z][\w-]*(?:\s[^<>]*)?>\s*/, '')
|
|
200
|
+
.replace(/\s*<\/[A-Za-z][\w-]*>$/, '')
|
|
201
|
+
.trim()
|
|
202
|
+
if (next === text) break
|
|
203
|
+
text = next
|
|
204
|
+
}
|
|
205
|
+
return text
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A step list the model serialized instead of building.
|
|
210
|
+
*
|
|
211
|
+
* The line-splitting fallback below is the general case, and it had one
|
|
212
|
+
* shape badly wrong. A model that serializes this array tends to reach for
|
|
213
|
+
* MARKUP, not for prose:
|
|
214
|
+
*
|
|
215
|
+
* <steps>
|
|
216
|
+
* <step>
|
|
217
|
+
* <description>Convert the document to Word</description>
|
|
218
|
+
* </step>
|
|
219
|
+
* </steps>
|
|
220
|
+
*
|
|
221
|
+
* Split on newlines, that is seven "steps", five of which are tags. A host
|
|
222
|
+
* then numbered them in an approval card and asked a person to approve
|
|
223
|
+
* `</steps>` — reported from a real run. The descriptions the model named
|
|
224
|
+
* are right there, so read them; fall back to lines only when there are
|
|
225
|
+
* none, and drop the lines that carry no words at all.
|
|
226
|
+
*/
|
|
160
227
|
function normalizeApprovePlanSteps(value: unknown): unknown {
|
|
161
228
|
if (typeof value !== 'string') return value
|
|
162
229
|
|
|
@@ -171,19 +238,84 @@ function normalizeApprovePlanSteps(value: unknown): unknown {
|
|
|
171
238
|
}
|
|
172
239
|
}
|
|
173
240
|
|
|
241
|
+
const described = [...trimmed.matchAll(DESCRIPTION_BLOCK)]
|
|
242
|
+
.map((match) => (match[1] ?? '').trim())
|
|
243
|
+
.filter(Boolean)
|
|
244
|
+
if (described.length > 0) {
|
|
245
|
+
return described.map((description) => ({ description }))
|
|
246
|
+
}
|
|
247
|
+
|
|
174
248
|
const lines = trimmed
|
|
175
249
|
.split(/\r?\n+/)
|
|
176
250
|
.map((line) =>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
251
|
+
unwrapStepLine(
|
|
252
|
+
line
|
|
253
|
+
.trim()
|
|
254
|
+
.replace(/^(?:[-*•]|\d+[.)])\s*/, '')
|
|
255
|
+
.trim(),
|
|
256
|
+
),
|
|
181
257
|
)
|
|
182
|
-
.filter(
|
|
258
|
+
.filter((line) => line.length > 0 && withoutTags(line).trim().length > 0)
|
|
259
|
+
|
|
260
|
+
// Every line was markup: there is no plan in this string, and inventing
|
|
261
|
+
// one step reading `<steps>` is worse than saying so.
|
|
262
|
+
if (lines.length === 0) {
|
|
263
|
+
return withoutTags(unwrapStepLine(trimmed)).trim()
|
|
264
|
+
? [{ description: unwrapStepLine(trimmed) }]
|
|
265
|
+
: []
|
|
266
|
+
}
|
|
183
267
|
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
|
|
268
|
+
return lines.map((description) => ({ description }))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The single closed shape a capable provider constrains this call to —
|
|
273
|
+
* the same instrument `ask_user_question` carries, for the same failure.
|
|
274
|
+
*
|
|
275
|
+
* `steps` arriving as a STRING is what everything above exists to survive,
|
|
276
|
+
* and surviving it is not the same as preventing it: the normalizer can
|
|
277
|
+
* only guess at a structure the model already threw away. Advertising the
|
|
278
|
+
* closed shape turns the guess into a refusal at generation time.
|
|
279
|
+
*/
|
|
280
|
+
const approvePlanModelInputSchema: Record<string, unknown> = {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
title: {
|
|
284
|
+
type: 'string',
|
|
285
|
+
description: 'Short title for the plan (e.g. "TypeScript Security & Performance Review").',
|
|
286
|
+
},
|
|
287
|
+
summary: {
|
|
288
|
+
type: 'string',
|
|
289
|
+
description: '1-3 sentence summary of what you plan to do.',
|
|
290
|
+
},
|
|
291
|
+
steps: {
|
|
292
|
+
type: 'array',
|
|
293
|
+
description:
|
|
294
|
+
'A JSON array of ordered step objects. Never a string, and never markup — no <step> or <description> tags.',
|
|
295
|
+
items: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {
|
|
298
|
+
description: {
|
|
299
|
+
type: 'string',
|
|
300
|
+
description: 'What this step does, as one plain sentence a person can read.',
|
|
301
|
+
},
|
|
302
|
+
agent_id: {
|
|
303
|
+
type: 'string',
|
|
304
|
+
description: 'Which agent handles this; omit for steps you carry out yourself.',
|
|
305
|
+
},
|
|
306
|
+
depends_on: {
|
|
307
|
+
type: 'array',
|
|
308
|
+
items: { type: 'string' },
|
|
309
|
+
description: 'Descriptions of the steps that must finish before this one.',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
required: ['description'],
|
|
313
|
+
additionalProperties: false,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
required: ['title', 'summary', 'steps'],
|
|
318
|
+
additionalProperties: false,
|
|
187
319
|
}
|
|
188
320
|
|
|
189
321
|
/**
|
|
@@ -1013,6 +1145,10 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
1013
1145
|
.preprocess(normalizeApprovePlanSteps, z.array(approvePlanStepSchema))
|
|
1014
1146
|
.describe('Ordered list of planned steps'),
|
|
1015
1147
|
}),
|
|
1148
|
+
modelInputSchema: structuredClone(approvePlanModelInputSchema),
|
|
1149
|
+
enforceModelInput: true,
|
|
1150
|
+
validationErrorHint:
|
|
1151
|
+
'Required shape: {"title":"…","summary":"…","steps":[{"description":"One plain sentence"}]}. "steps" must be a JSON array of objects — never a string, and never markup such as <step> or <description>.',
|
|
1016
1152
|
category: 'custom',
|
|
1017
1153
|
permissions: [],
|
|
1018
1154
|
readOnly: true,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../types/tool/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* May this tool's read-only claim settle a gate on its own?
|
|
5
|
+
*
|
|
6
|
+
* A connected server declares whether its own tools are read-only, and
|
|
7
|
+
* that declaration decided whether a call was approved without asking. The
|
|
8
|
+
* thing being gated supplied the input to the gate.
|
|
9
|
+
*
|
|
10
|
+
* The wire itself calls these fields HINTS. Three separate consumers read
|
|
11
|
+
* them as facts, so a server setting `readOnlyHint: true` and
|
|
12
|
+
* `destructiveHint: false` controlled the whole predicate for its own
|
|
13
|
+
* tools — the kernel's `allow_read_only` rule, the operator prompt
|
|
14
|
+
* exemption, and the plan-mode pass.
|
|
15
|
+
*
|
|
16
|
+
* The estate floor this repository inherits already decided this: least
|
|
17
|
+
* privilege, default deny, and — on tool results and fetched content —
|
|
18
|
+
* data is not instructions, and untrusted content cannot escalate
|
|
19
|
+
* capabilities. A server's declaration about its own tools is untrusted
|
|
20
|
+
* content by that definition. This is that rule applied, not a new policy.
|
|
21
|
+
*
|
|
22
|
+
* **The asymmetry is the design.** A self-declaration may RAISE the
|
|
23
|
+
* requirement and never LOWER it:
|
|
24
|
+
*
|
|
25
|
+
* - `destructiveHint: true` from a server is believed. A server
|
|
26
|
+
* volunteering that its tool is dangerous moves toward caution, and
|
|
27
|
+
* disbelieving it buys nothing.
|
|
28
|
+
* - `readOnlyHint: true` from a server does not, on its own, settle a
|
|
29
|
+
* call as allowed or skip a prompt. That is the untrusted party
|
|
30
|
+
* opening its own gate.
|
|
31
|
+
*
|
|
32
|
+
* Trust for the second case comes from the operator, per server, and is
|
|
33
|
+
* recorded on the tool as `provenance.readOnlyHintTrusted`. Never a global
|
|
34
|
+
* switch: one flag meaning "trust annotations" hands every connected
|
|
35
|
+
* server the same reach, which is the hole restated.
|
|
36
|
+
*
|
|
37
|
+
* `isReadOnly` itself is left reporting faithfully what the server said.
|
|
38
|
+
* Provenance and policy are different questions, and collapsing them would
|
|
39
|
+
* corrupt the outbound re-export and the prompt's own destructive label in
|
|
40
|
+
* order to fix a gate.
|
|
41
|
+
*/
|
|
42
|
+
export function isTrustedReadOnly(tool: ToolDefinition | undefined, input: unknown): boolean {
|
|
43
|
+
if (!tool?.isReadOnly) return false
|
|
44
|
+
|
|
45
|
+
// No provenance means the tool is host-defined: it came from this
|
|
46
|
+
// process, from code the operator installed, and there is no untrusted
|
|
47
|
+
// party in the chain. Requiring an opt-in for a builtin would break
|
|
48
|
+
// every read-only exemption for no gain in trust.
|
|
49
|
+
if (tool.provenance && !tool.provenance.readOnlyHintTrusted) return false
|
|
50
|
+
|
|
51
|
+
return tool.isReadOnly(input)
|
|
52
|
+
}
|
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
* Framing for content the agent did not author and must not obey.
|
|
3
3
|
*
|
|
4
4
|
* An unlabelled block of text in a tool result reads exactly like the agent's
|
|
5
|
-
* own instructions.
|
|
6
|
-
* not filtering — it is saying plainly whose words these are and that they are
|
|
5
|
+
* own instructions. This says plainly whose words these are and that they are
|
|
7
6
|
* material rather than direction. That is the floor this estate already
|
|
8
7
|
* states: data is not instructions, and a tool result cannot escalate what an
|
|
9
8
|
* agent may do.
|
|
10
9
|
*
|
|
10
|
+
* **It marks provenance. It refuses nothing, and it does not stop an
|
|
11
|
+
* attacker who is trying.** This paragraph used to claim the framing was
|
|
12
|
+
* "the mitigation that survives contact with a real model", and that is
|
|
13
|
+
* measurably wrong. Nasr et al., "The Attacker Moves Second"
|
|
14
|
+
* (arXiv:2510.09023), broke twelve published defences at above 90% attack
|
|
15
|
+
* success once the attacker adapts; the majority had originally reported
|
|
16
|
+
* near-zero success. Delimiting specifically goes from as low as 1% under
|
|
17
|
+
* a static benchmark to above 95% under adaptive attack.
|
|
18
|
+
*
|
|
19
|
+
* So read every number for a prompt-level defence as static unless it says
|
|
20
|
+
* otherwise, and treat this envelope as raising cost rather than as a
|
|
21
|
+
* boundary. What survives an adapting attacker in the same literature is
|
|
22
|
+
* architectural: AgentDojo (arXiv:2406.13352) found tool isolation and
|
|
23
|
+
* tool filtering the effective mitigations, and this repository's real
|
|
24
|
+
* boundaries are of that kind — the permission gate, the sandbox, the
|
|
25
|
+
* egress proxy deciding by resolved address.
|
|
26
|
+
*
|
|
27
|
+
* The two details below still matter. They are what stops the framing
|
|
28
|
+
* being trivially removable by the content itself, which is a lower bar
|
|
29
|
+
* than stopping an attacker and worth clearing anyway.
|
|
30
|
+
*
|
|
11
31
|
* Two details make the difference between a boundary and a decoration, and
|
|
12
32
|
* both were missing from this repo's first envelope:
|
|
13
33
|
*
|
|
@@ -103,6 +103,29 @@ export interface SandboxExecOptions {
|
|
|
103
103
|
readonly timeout?: number
|
|
104
104
|
readonly env?: Record<string, string>
|
|
105
105
|
readonly cwd?: string
|
|
106
|
+
/**
|
|
107
|
+
* Called as output arrives, before the command has finished.
|
|
108
|
+
*
|
|
109
|
+
* Every container-tier worker already streams its output a chunk at a
|
|
110
|
+
* time — the wire carries `stdout_delta` and `stderr_delta` events —
|
|
111
|
+
* and every backend concatenated them into a string and returned that
|
|
112
|
+
* when the process exited. So a command that takes eight minutes said
|
|
113
|
+
* nothing for eight minutes, on a transport that had been reporting
|
|
114
|
+
* the whole time.
|
|
115
|
+
*
|
|
116
|
+
* Additive and optional: a backend that cannot stream simply never
|
|
117
|
+
* calls it, and `SandboxExecResult.stdout` still carries the complete
|
|
118
|
+
* output either way. A caller that wants only the result ignores this
|
|
119
|
+
* and behaves exactly as before.
|
|
120
|
+
*
|
|
121
|
+
* The callback must not throw and must not be awaited — it is on the
|
|
122
|
+
* read path of a running process, so a slow or failing consumer would
|
|
123
|
+
* otherwise become a slow or failing command.
|
|
124
|
+
*/
|
|
125
|
+
readonly onOutput?: (chunk: {
|
|
126
|
+
readonly stream: 'stdout' | 'stderr'
|
|
127
|
+
readonly data: string
|
|
128
|
+
}) => void
|
|
106
129
|
/**
|
|
107
130
|
* Cancellation for the command. A backend that honours it kills the
|
|
108
131
|
* process; one that does not simply ignores it, so this is additive.
|
package/src/types/tool/index.ts
CHANGED
|
@@ -322,6 +322,36 @@ export interface ToolDefinition<TInput = unknown> {
|
|
|
322
322
|
isReadOnly?(input: TInput): boolean
|
|
323
323
|
isDestructive?(input: TInput): boolean
|
|
324
324
|
isConcurrencySafe?(input: TInput): boolean
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Where this tool came from, when it did not come from here.
|
|
328
|
+
*
|
|
329
|
+
* Absent means host-defined: this process, code the operator installed,
|
|
330
|
+
* no untrusted party in the chain. Present means a connected server
|
|
331
|
+
* supplied both the tool and its own description of what the tool does
|
|
332
|
+
* — including whether it is read-only, which three separate gates were
|
|
333
|
+
* treating as a fact rather than as the hint the wire calls it.
|
|
334
|
+
*
|
|
335
|
+
* See {@link isTrustedReadOnly}. This field exists so a gate can tell
|
|
336
|
+
* the two apart; `isReadOnly` keeps reporting faithfully what the
|
|
337
|
+
* server said, because the outbound re-export and the destructive
|
|
338
|
+
* label shown to a human both need the server's own answer.
|
|
339
|
+
*/
|
|
340
|
+
provenance?: ToolProvenance
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface ToolProvenance {
|
|
344
|
+
/** The connected server this tool came from, named as configured. */
|
|
345
|
+
readonly server: string
|
|
346
|
+
/**
|
|
347
|
+
* The operator marked this server's read-only claims as trustworthy.
|
|
348
|
+
*
|
|
349
|
+
* Per server, never global: one switch meaning "trust annotations"
|
|
350
|
+
* hands every connected server the same reach, which is the hole it
|
|
351
|
+
* would be closing. Default false — an unmarked server's claim raises
|
|
352
|
+
* the requirement and never lowers it.
|
|
353
|
+
*/
|
|
354
|
+
readonly readOnlyHintTrusted: boolean
|
|
325
355
|
}
|
|
326
356
|
|
|
327
357
|
export type ToolPermission =
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DANGEROUS_PATTERNS } from '../constants/tools/index.js'
|
|
2
|
+
import { isTrustedReadOnly } from '../tools/trusted-read-only.js'
|
|
2
3
|
import type { ToolDefinition } from '../types/tool/index.js'
|
|
3
4
|
import type { GateDecision, VerificationRule } from '../types/verification/index.js'
|
|
4
5
|
|
|
@@ -12,7 +13,10 @@ export function evaluateRule(
|
|
|
12
13
|
): GateDecision | null {
|
|
13
14
|
switch (rule.type) {
|
|
14
15
|
case 'allow_read_only': {
|
|
15
|
-
|
|
16
|
+
// A server's own claim about its own tool cannot settle this. See
|
|
17
|
+
// `isTrustedReadOnly`: a self-declaration may raise the requirement
|
|
18
|
+
// and never lower it.
|
|
19
|
+
return isTrustedReadOnly(toolDef, toolInput) ? 'allow' : null
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
case 'deny_dangerous_patterns': {
|