@gaunt-sloth/core 2.0.0-alpha.24 → 2.0.0-alpha.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- 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/defaults.d.ts +1 -0
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/loader.d.ts +13 -6
- package/dist/config/loader.js +171 -28
- 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/schema.d.ts +2372 -114
- package/dist/config/schema.js +845 -16
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +466 -116
- package/dist/config/shell-policy.js +273 -62
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +165 -0
- package/dist/config/tool-descriptions.js +229 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +36 -1
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +59 -1
- package/dist/core/GthAbstractAgent.js +72 -0
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +341 -58
- package/dist/core/GthAgentRunner.js +817 -149
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +16 -0
- package/dist/core/GthLangChainAgent.js +72 -2
- 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/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/launchBanner.d.ts +120 -0
- package/dist/core/launchBanner.js +399 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/plainToolIndication.js +13 -7
- package/dist/core/plainToolIndication.js.map +1 -1
- package/dist/core/shell/approvalStop.d.ts +75 -0
- package/dist/core/shell/approvalStop.js +108 -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/normalize.d.ts +41 -1
- package/dist/core/shell/normalize.js +58 -4
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +138 -0
- package/dist/core/shell/openWorld.js +523 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +560 -0
- package/dist/core/shell/rater.js +895 -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/rejection.d.ts +66 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/types.d.ts +71 -9
- package/dist/core/types.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +8 -7
- package/dist/runtime/askStructured.js +8 -7
- package/dist/runtime/askStructured.js.map +1 -1
- package/dist/runtime/conversation.js +89 -76
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.js +71 -60
- package/dist/runtime/singleShot.js.map +1 -1
- 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/consoleUtils.d.ts +30 -0
- package/dist/utils/consoleUtils.js +27 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/systemUtils.d.ts +17 -0
- package/dist/utils/systemUtils.js +17 -0
- package/dist/utils/systemUtils.js.map +1 -1
- package/package.json +5 -2
- package/schema/gsloth-config.schema.json +1216 -417
- 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
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Shell / dev-tools policy: the {@link GthDevToolsConfig} type plus all the resolvers
|
|
4
|
+
* that interpret it (shell enablement, timeouts, output budget, per-command dev-tools
|
|
5
|
+
* selection), and — since CFG-27 — the {@link ApprovalsConfig} value and its resolver
|
|
6
|
+
* {@link resolveApprovals} (the five-rung ladder, the rater's identity profile, and the declared
|
|
7
|
+
* allow/deny lists).
|
|
8
|
+
*
|
|
9
|
+
* CFG-18 — the dev/shell tools are now configured through the unified {@link GthConfig.builtInTools}
|
|
10
|
+
* registry (`string[] | Record<string, boolean | BuiltInToolConfig>`), NOT the removed per-command
|
|
11
|
+
* `commands.<mode>.devTools` key. {@link GthDevToolsConfig} is therefore no longer an on-disk shape:
|
|
12
|
+
* it is the internal, resolved view that {@link getEffectiveDevToolsConfig} builds from the effective
|
|
13
|
+
* `builtInTools` registry, and that {@link GthDevToolkit} + the shell accessors below consume. This
|
|
14
|
+
* keeps the toolkit/accessor surface stable while the single config surface is `builtInTools`.
|
|
15
|
+
*/
|
|
16
|
+
import { StatusLevel } from '#src/core/types.js';
|
|
1
17
|
/**
|
|
2
18
|
* The fixed dev-command tools: each maps a `command` string (from its {@link BuiltInToolConfig})
|
|
3
19
|
* to a run_* tool emitted by {@link GthDevToolkit}.
|
|
@@ -113,66 +129,11 @@ export function getShellMaxOutputBytes(devTools) {
|
|
|
113
129
|
}
|
|
114
130
|
return SHELL_DEFAULT_MAX_OUTPUT_BYTES;
|
|
115
131
|
}
|
|
116
|
-
/**
|
|
117
|
-
* Whether the EXT-9 Tier-2 scoped allow-list is active. Default `true`; only the object
|
|
118
|
-
* form's `allowlist: false` disables it (a bare `shell: true` keeps it on). When off, the
|
|
119
|
-
* runner prompts for every `run_shell_command` regardless of prior approvals.
|
|
120
|
-
*/
|
|
121
|
-
export function isShellAllowlistEnabled(devTools) {
|
|
122
|
-
const shell = devTools?.shell;
|
|
123
|
-
if (shell && typeof shell === 'object' && shell.allowlist === false)
|
|
124
|
-
return false;
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Whether `always`-scoped approvals are persisted to the project allow-list file. Default
|
|
129
|
-
* `true`; only the object form's `persistAllowlist: false` disables persistence (an
|
|
130
|
-
* `always` decision then behaves as `session`).
|
|
131
|
-
*/
|
|
132
|
-
export function isShellAllowlistPersisted(devTools) {
|
|
133
|
-
const shell = devTools?.shell;
|
|
134
|
-
if (shell && typeof shell === 'object' && shell.persistAllowlist === false)
|
|
135
|
-
return false;
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Whether the EXT-10 LLM-as-judge safety gate is enabled for the given dev-tools config.
|
|
140
|
-
* Default OFF (only the object form's `judge` truthy enables it), mirroring
|
|
141
|
-
* {@link isShellToolEnabled}. A bare `shell: true` keeps the judge OFF — it costs an LLM call
|
|
142
|
-
* per command and must be opted into explicitly.
|
|
143
|
-
*/
|
|
144
|
-
export function isShellJudgeEnabled(devTools) {
|
|
145
|
-
const shell = devTools?.shell;
|
|
146
|
-
if (!shell || typeof shell !== 'object')
|
|
147
|
-
return false;
|
|
148
|
-
const judge = shell.judge;
|
|
149
|
-
if (typeof judge === 'boolean')
|
|
150
|
-
return judge;
|
|
151
|
-
if (judge && typeof judge === 'object')
|
|
152
|
-
return judge.enabled === true;
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Resolve the EXT-10 judge gate settings from a dev-tools config, applying safe defaults
|
|
157
|
-
* (auto-approve low, do NOT block high). `enabled` reflects {@link isShellJudgeEnabled}.
|
|
158
|
-
*/
|
|
159
|
-
export function getShellJudgeSettings(devTools) {
|
|
160
|
-
const enabled = isShellJudgeEnabled(devTools);
|
|
161
|
-
const shell = devTools?.shell;
|
|
162
|
-
const judge = shell && typeof shell === 'object' && shell.judge && typeof shell.judge === 'object'
|
|
163
|
-
? shell.judge
|
|
164
|
-
: undefined;
|
|
165
|
-
return {
|
|
166
|
-
enabled,
|
|
167
|
-
autoApproveLow: judge?.autoApproveLow ?? true,
|
|
168
|
-
blockHigh: judge?.blockHigh ?? false,
|
|
169
|
-
model: judge?.model,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
132
|
/**
|
|
173
133
|
* Build the internal, resolved {@link GthDevToolsConfig} from a normalized `builtInTools` registry:
|
|
174
134
|
* the fixed dev-command tools read their `command` string, and `run_shell_command` maps to the
|
|
175
|
-
* `shell`
|
|
135
|
+
* `shell` view the accessors below consume (CFG-26: the approval knobs are no longer here — they
|
|
136
|
+
* live in the top-level `approvals` block, resolved by {@link resolveApprovals}). Returns `undefined` when the registry
|
|
176
137
|
* carries no dev/shell entry at all, so callers treat it exactly like an unset `devTools` (the
|
|
177
138
|
* `code`-mode shell default still applies downstream via {@link isShellToolEnabled}).
|
|
178
139
|
*/
|
|
@@ -197,12 +158,7 @@ function devToolsConfigFromRegistry(registry) {
|
|
|
197
158
|
enabled: entry.enabled,
|
|
198
159
|
timeout: entry.timeout,
|
|
199
160
|
maxOutputBytes: entry.maxOutputBytes,
|
|
200
|
-
allowlist: entry.allowlist,
|
|
201
|
-
persistAllowlist: entry.persistAllowlist,
|
|
202
|
-
judge: entry.judge,
|
|
203
161
|
};
|
|
204
|
-
if (entry.yolo !== undefined)
|
|
205
|
-
resolved.shellYolo = entry.yolo;
|
|
206
162
|
}
|
|
207
163
|
hasAny = true;
|
|
208
164
|
}
|
|
@@ -235,4 +191,259 @@ export function getEffectiveDevToolsConfig(config, command) {
|
|
|
235
191
|
const effective = cmdConfig?.builtInTools ?? config.builtInTools;
|
|
236
192
|
return devToolsConfigFromRegistry(normalizeBuiltInTools(effective));
|
|
237
193
|
}
|
|
194
|
+
/* -------------------------------------------------------------------------------------------- *
|
|
195
|
+
* CFG-27 — the `approvals` ladder: one ordered set of five rungs, plus the declared lists.
|
|
196
|
+
* -------------------------------------------------------------------------------------------- */
|
|
197
|
+
/**
|
|
198
|
+
* CFG-27 (spec §1, §2) — **the ladder**. There is ONE approvals setting and it is a single ordered
|
|
199
|
+
* ladder of five rungs; each rung fully determines behaviour. There are no severity thresholds, no
|
|
200
|
+
* strictness levels and no independent rater on/off switch.
|
|
201
|
+
*
|
|
202
|
+
* | # | Rung | Rater | LLM cost |
|
|
203
|
+
* |---|---|---|---|
|
|
204
|
+
* | 1 | `read-only` | no | none |
|
|
205
|
+
* | 2 | `write` | no | none |
|
|
206
|
+
* | 3 | `auto-safe` | yes | 1 call per gated call |
|
|
207
|
+
* | 4 | `full-auto` | yes | 1–2 calls per gated call |
|
|
208
|
+
* | 5 | `bypass` | no | none |
|
|
209
|
+
*
|
|
210
|
+
* Rungs 1, 2 and 5 are fully deterministic: no model is consulted, so behaviour is reproducible
|
|
211
|
+
* and costs nothing.
|
|
212
|
+
*
|
|
213
|
+
* **`bypass` is NOT a higher-autonomy rung than `full-auto`** (§2.5). Both let the agent act
|
|
214
|
+
* without asking; `bypass` is the same autonomy with the checks removed. The ordering below is the
|
|
215
|
+
* order the rungs are *offered* in, and must never be presented as though `full-auto` were an
|
|
216
|
+
* incomplete `bypass`.
|
|
217
|
+
*
|
|
218
|
+
* Identifiers are kebab-case (§9.1) because the same token must work as a config value, a
|
|
219
|
+
* slash-command argument and a CLI flag — a space breaks the last two. Display names keep their
|
|
220
|
+
* spaces; see {@link APPROVAL_RUNG_LABELS}.
|
|
221
|
+
*/
|
|
222
|
+
export const APPROVAL_RUNGS = ['read-only', 'write', 'auto-safe', 'full-auto', 'bypass'];
|
|
223
|
+
/**
|
|
224
|
+
* §9.1 rule / §10 rule 4 — the display spelling of each rung, with spaces. An identifier and a
|
|
225
|
+
* label do not have to match and only one of them has to survive a shell, so user-facing prose
|
|
226
|
+
* uses these and never the kebab-case identifiers.
|
|
227
|
+
*/
|
|
228
|
+
export const APPROVAL_RUNG_LABELS = {
|
|
229
|
+
'read-only': 'Read only',
|
|
230
|
+
write: 'Write',
|
|
231
|
+
'auto-safe': 'Auto safe',
|
|
232
|
+
'full-auto': 'Full auto',
|
|
233
|
+
bypass: 'Bypass',
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* §10 — the one sentence shown wherever a rung is chosen or displayed. **Copied verbatim from the
|
|
237
|
+
* specification**; the wording is constrained by four normative rules there (state what the rung
|
|
238
|
+
* PERMITS, state the allow-list carve-out, never claim safety this system cannot deliver, use the
|
|
239
|
+
* display spelling) plus §8.1 (the hardline floor is real but is NEVER advertised — descriptions
|
|
240
|
+
* cite only protections the user can inspect and extend, i.e. the deny list). Do not "improve"
|
|
241
|
+
* these: `auto-safe` in particular MUST keep the sentence saying files are still rewritten and
|
|
242
|
+
* deleted without asking.
|
|
243
|
+
*
|
|
244
|
+
* The only departure from the source text is that §10's markdown emphasis markers (`**not**` in
|
|
245
|
+
* *Full auto*) are dropped, since these strings are rendered as plain terminal copy.
|
|
246
|
+
*/
|
|
247
|
+
export const APPROVAL_RUNG_DESCRIPTIONS = {
|
|
248
|
+
'read-only': 'Gaunt Sloth may automatically read and list files in the current working folder. It asks ' +
|
|
249
|
+
'for approval for anything else, until you tell it to always allow a command.',
|
|
250
|
+
write: 'Gaunt Sloth may automatically read, edit, create and delete files in the current working ' +
|
|
251
|
+
'folder. It asks for approval for anything else, until you tell it to always allow a command.',
|
|
252
|
+
'auto-safe': 'Same as write, plus the auto-rater rates everything else and automatically approves what it ' +
|
|
253
|
+
'rates as safe; anything questionable comes to you. Gaunt Sloth can still rewrite and delete ' +
|
|
254
|
+
'files in your working folder without asking — "safe" means each action is checked for ' +
|
|
255
|
+
'reaching outside that folder or harming your system, not that nothing changes.',
|
|
256
|
+
'full-auto': 'The auto-rater steers Gaunt Sloth: it decides for itself and does not stop to ask you. This ' +
|
|
257
|
+
'is safer than bypass — the auto-rater still stops the run on a command that reads your keys ' +
|
|
258
|
+
'or passwords, weakens permissions, installs itself to run again later, or hides what it ' +
|
|
259
|
+
'does; it brings anything it cannot undo to you rather than deciding alone; and your deny ' +
|
|
260
|
+
'list still applies — but it is not safe. Gaunt Sloth will change and delete things. Use it ' +
|
|
261
|
+
'where the consequences are recoverable, and put real gates (deployment approvals, ' +
|
|
262
|
+
'two-factor, branch protection) on anything that is not.',
|
|
263
|
+
bypass: 'No gate. Gaunt Sloth runs whatever it decides to run, without asking and without rating. ' +
|
|
264
|
+
'Only the refusals configured in the deny list in your config still apply.',
|
|
265
|
+
};
|
|
266
|
+
/** Narrowing type guard for a raw string that may name a rung. */
|
|
267
|
+
export function isApprovalRung(value) {
|
|
268
|
+
return typeof value === 'string' && APPROVAL_RUNGS.includes(value);
|
|
269
|
+
}
|
|
270
|
+
/** The rungs at which every gated call is rated by the model (§2.3, §2.4). */
|
|
271
|
+
export function isRatedRung(rung) {
|
|
272
|
+
return rung === 'auto-safe' || rung === 'full-auto';
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* §4.7 — the four MCP `ToolAnnotations` hint names, and the whole vocabulary. It is the same list
|
|
276
|
+
* on both sides of the design: what a `hint` pattern may name ({@link ApprovalHintPattern}) and what
|
|
277
|
+
* a user may believe from a server ({@link McpServerApprovalsConfig.trustAnnotations}).
|
|
278
|
+
*
|
|
279
|
+
* **The schema twin `HINT_ANNOTATION_KEYS` in `config/schema.ts` is a deliberate duplicate, and the
|
|
280
|
+
* reason is layering, not oversight.** Neither file may import the other. `schema.ts` states in its
|
|
281
|
+
* own header that it must stay pure and cwd/fs-independent because it feeds `z.toJSONSchema()`, and
|
|
282
|
+
* importing this module would pull `core/types.js` and the whole runtime policy surface into it;
|
|
283
|
+
* importing `schema.ts` here would in turn pull zod into every module that only wanted a policy
|
|
284
|
+
* type. So the vocabulary is written once per layer on purpose — do not "simplify" it by making one
|
|
285
|
+
* import the other.
|
|
286
|
+
*
|
|
287
|
+
* What keeps the two honest instead is the equality assertion in `mcpApprovalsBlock.spec.ts`, which
|
|
288
|
+
* fails the moment they drift. Drift matters in one direction especially: a name the config accepts
|
|
289
|
+
* but the derivation never reads fails silently, and it fails toward trusting. Change one list,
|
|
290
|
+
* change the other.
|
|
291
|
+
*/
|
|
292
|
+
export const TOOL_ANNOTATION_HINTS = [
|
|
293
|
+
'readOnlyHint',
|
|
294
|
+
'destructiveHint',
|
|
295
|
+
'idempotentHint',
|
|
296
|
+
'openWorldHint',
|
|
297
|
+
];
|
|
298
|
+
/**
|
|
299
|
+
* §1.1 — **the default rung is `auto-safe`, everywhere.** It is the default in every interactive
|
|
300
|
+
* context, it does NOT vary with the configured model, and there is no separate non-interactive
|
|
301
|
+
* default. What changes without a human is what an escalation *does* (§6.2: an immediate non-zero
|
|
302
|
+
* exit, never an approval), not which rung the session starts on. A context-dependent default
|
|
303
|
+
* would reintroduce exactly the hidden branching this ladder exists to remove.
|
|
304
|
+
*/
|
|
305
|
+
export const DEFAULT_APPROVAL_RUNG = 'auto-safe';
|
|
306
|
+
/** Normalize the scalar/object union to the object form. The scalar is sugar for `{ mode }`. */
|
|
307
|
+
function toApprovalsObject(raw) {
|
|
308
|
+
if (raw === undefined)
|
|
309
|
+
return undefined;
|
|
310
|
+
if (typeof raw === 'string')
|
|
311
|
+
return { mode: raw };
|
|
312
|
+
return raw;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* CFG-27 — resolve the effective {@link ResolvedApprovals} for the active command.
|
|
316
|
+
*
|
|
317
|
+
* There is no defaults *matrix*: §1.1 makes `auto-safe` the default in every context, so this
|
|
318
|
+
* resolver neither detects nor accepts a "context". Precedence is the only thing it decides, and
|
|
319
|
+
* §9.1 splits it in two:
|
|
320
|
+
*
|
|
321
|
+
* - **The scalars — `mode`, `rater`, `raterTimeoutMs` — are replaced** when the per-command value
|
|
322
|
+
* states them and **inherited from the root when it does not**. So the scalar sugar
|
|
323
|
+
* `"code": { "approvals": "bypass" }` is exactly `{ mode: 'bypass' }` merged over the root: it
|
|
324
|
+
* sets the rung and nothing else.
|
|
325
|
+
* - **`deny` and `escalate` never replace: they CONCATENATE across every scope.** A
|
|
326
|
+
* command-specific `deny` *adds to* the root's. Removing an inherited prohibition for one command
|
|
327
|
+
* is deliberately not expressible.
|
|
328
|
+
* - **`allow` is REPLACED when the per-command value states its own, and inherited when it does
|
|
329
|
+
* not.** A per-command scope may therefore narrow what runs unprompted, and may never widen what
|
|
330
|
+
* is prohibited.
|
|
331
|
+
* - **`mcp` (EXT-70 §4.7) follows `allow`, not the restrictive lists**: replaced when the
|
|
332
|
+
* per-command value states it, inherited when it does not. Believing a hint is a PERMISSIVE act
|
|
333
|
+
* in both directions — it can make an `allow` hint entry fire and can make a `deny` hint entry
|
|
334
|
+
* stop firing — so it merges the way the permissive list does, and a per-command scope can
|
|
335
|
+
* narrow the session's trust (`"mcp": {}` believes nothing) but never inherits half of it by
|
|
336
|
+
* accident. Deep-merging the two scopes' `servers` maps was rejected for the same reason: it
|
|
337
|
+
* would leave a deliberately distrustful per-command block silently carrying the root's trust.
|
|
338
|
+
*
|
|
339
|
+
* **The two halves differ because the costs differ (§3.1), not for tidiness.** A missed allow entry
|
|
340
|
+
* escalates and a missed deny entry falls through to the rater — neither is an execution — while a
|
|
341
|
+
* too-broad allow entry *runs, unrated and unprompted*. Concatenating the restrictive lists fails
|
|
342
|
+
* toward a prompt; concatenating the permissive one fails toward an execution, and would leave a
|
|
343
|
+
* deliberately restrictive per-command rung with no way to shed the root's standing grants. Do not
|
|
344
|
+
* "regularize" these three into one policy: the direction each list fails in is the whole design.
|
|
345
|
+
*
|
|
346
|
+
* On the restrictive side the pressure runs the other way (§11.1f). Were the per-command value to
|
|
347
|
+
* replace the root wholesale, the friendliest spelling of "stop asking me about `code`" would also
|
|
348
|
+
* delete every `deny` entry — at the one rung where the deny list and the §8 floor are the only
|
|
349
|
+
* checks left. A prohibition a nested config key can quietly delete is not a hardline.
|
|
350
|
+
*
|
|
351
|
+
* Concatenation order cannot change any outcome (`resolveApprovalRules` consults every deny entry
|
|
352
|
+
* before any escalate entry and every escalate entry before any allow entry), so root-first is a
|
|
353
|
+
* convention for readability — matching `GthAgentRunner.approvalRuleLists`, where the declared
|
|
354
|
+
* entries precede the runtime grants — and never a precedence.
|
|
355
|
+
*
|
|
356
|
+
* Defaults are applied HERE, at the read site, rather than in `DEFAULT_CONFIG` — so the
|
|
357
|
+
* effective-config snapshot the `/config` panel renders never churns (à la GS2-34
|
|
358
|
+
* `injectModelContext` / GS2-63 `output.header`).
|
|
359
|
+
*
|
|
360
|
+
* This is the per-command half. The cross-LAYER half (a project config's lists adding to a global
|
|
361
|
+
* config's rather than replacing them) is the additive-array policy in `config/loader.ts`; both are
|
|
362
|
+
* needed, since either alone still loses a list silently.
|
|
363
|
+
*
|
|
364
|
+
* @param command The active command; selects the per-command block.
|
|
365
|
+
*/
|
|
366
|
+
export function resolveApprovals(config, command) {
|
|
367
|
+
const root = toApprovalsObject(config?.approvals);
|
|
368
|
+
const perCommand = toApprovalsObject(command
|
|
369
|
+
? config?.commands?.[command]
|
|
370
|
+
?.approvals
|
|
371
|
+
: undefined);
|
|
372
|
+
return {
|
|
373
|
+
rung: perCommand?.mode ?? root?.mode ?? DEFAULT_APPROVAL_RUNG,
|
|
374
|
+
rater: perCommand?.rater ?? root?.rater,
|
|
375
|
+
// `??`, so an EXPLICIT empty list is honoured: `allow: []` on a command states "nothing is
|
|
376
|
+
// pre-trusted here" and must not read as "said nothing, inherit the root's".
|
|
377
|
+
allow: perCommand?.allow ?? root?.allow ?? [],
|
|
378
|
+
deny: [...(root?.deny ?? []), ...(perCommand?.deny ?? [])],
|
|
379
|
+
escalate: [...(root?.escalate ?? []), ...(perCommand?.escalate ?? [])],
|
|
380
|
+
raterTimeoutMs: perCommand?.raterTimeoutMs ?? root?.raterTimeoutMs,
|
|
381
|
+
// `??`, so an EXPLICIT empty block is honoured exactly as an explicit empty `allow` is: it
|
|
382
|
+
// states "believe nothing external here" and must not read as "said nothing, inherit the root".
|
|
383
|
+
mcp: perCommand?.mcp ?? root?.mcp,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* EXT-52 — the ONE shell approval-gate policy both agent backends resolve
|
|
388
|
+
* (`GthLangChainAgent` = lean/default, `GthDeepAgent` = deep). It decides whether the opt-in
|
|
389
|
+
* `run_shell_command` tool is gated behind the per-command approval interrupt, and which status
|
|
390
|
+
* notice (if any) the backend should surface. The backends differ only in HOW they install the
|
|
391
|
+
* interrupt; the policy and its user-facing copy live here so the two can never drift (and so a
|
|
392
|
+
* later rename of this config surface has one place to change).
|
|
393
|
+
*
|
|
394
|
+
* CFG-27 — **the tool is gated whenever it is enabled, at every rung including `bypass`.** CFG-26
|
|
395
|
+
* used to leave it UNGATED under `bypass` outside interactive `code`, which the ladder cannot
|
|
396
|
+
* afford: §2.5 makes the declared **deny list the one check `bypass` keeps**, and a deny entry can
|
|
397
|
+
* only fire if the call reaches `GthAgentRunner.decideToolApproval` — an ungated call never does.
|
|
398
|
+
* Gating unconditionally also keeps the rung switchable mid-session (`/approvals <rung>`), since a
|
|
399
|
+
* tool wired without the interrupt cannot be re-gated without rebuilding the agent.
|
|
400
|
+
*
|
|
401
|
+
* What each rung then does is decided in `decideToolApproval`, not here:
|
|
402
|
+
* • `bypass` — deny list, then approve without prompting or rating.
|
|
403
|
+
* • `read-only`/`write` — deny list, allow-list, else escalate to the human.
|
|
404
|
+
* • `auto-safe`/`full-auto` — deny list, allow-list, then the rater.
|
|
405
|
+
* • With the shell tool disabled — or on a non-dev-tools command (chat/api/…) — nothing is gated
|
|
406
|
+
* and nothing is announced.
|
|
407
|
+
*
|
|
408
|
+
* Shell enablement itself is resolved through {@link getEffectiveDevToolsConfig} +
|
|
409
|
+
* {@link isShellToolEnabled}, so the gate stays in lockstep with where `GthDevToolkit` actually
|
|
410
|
+
* emits the tool; the posture comes from {@link resolveApprovals}, so this and the runner can
|
|
411
|
+
* never disagree about which rung is in force.
|
|
412
|
+
*/
|
|
413
|
+
export function resolveShellApprovalGate(config, command) {
|
|
414
|
+
const devTools = getEffectiveDevToolsConfig(config, command);
|
|
415
|
+
const gateShell = isShellToolEnabled(devTools, command);
|
|
416
|
+
if (!gateShell)
|
|
417
|
+
return { gateShell };
|
|
418
|
+
const { rung } = resolveApprovals(config, command);
|
|
419
|
+
if (rung === 'bypass') {
|
|
420
|
+
return {
|
|
421
|
+
gateShell,
|
|
422
|
+
notice: {
|
|
423
|
+
level: StatusLevel.WARNING,
|
|
424
|
+
message: 'Shell tool (run_shell_command): commands run without asking and without rating ' +
|
|
425
|
+
'(approvals: bypass). Only your deny list still applies — type /approvals auto-safe to ' +
|
|
426
|
+
'rate commands again.',
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
if (isRatedRung(rung)) {
|
|
431
|
+
return {
|
|
432
|
+
gateShell,
|
|
433
|
+
notice: {
|
|
434
|
+
level: StatusLevel.INFO,
|
|
435
|
+
message: `Shell tool (run_shell_command) rated by the auto-rater (approvals: ${rung}); ` +
|
|
436
|
+
'anything it does not rate safe is still ' +
|
|
437
|
+
(rung === 'auto-safe' ? 'escalated to you.' : 'refused or escalated.'),
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
gateShell,
|
|
443
|
+
notice: {
|
|
444
|
+
level: StatusLevel.INFO,
|
|
445
|
+
message: `Shell tool (run_shell_command) enabled with per-command approval (approvals: ${rung}).`,
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
238
449
|
//# sourceMappingURL=shell-policy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-policy.js","sourceRoot":"","sources":["../../src/config/shell-policy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shell-policy.js","sourceRoot":"","sources":["../../src/config/shell-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* EXT-58 — **rung-aware tool descriptions** (`docs/gaunt-sloth-2.0/approvals-and-rater-spec.md`
|
|
4
|
+
* §4.5) and the table of built-in tools the rater may offer as a granted alternative (§4.4).
|
|
5
|
+
*
|
|
6
|
+
* §4.5 is the *prevention* half of the approvals design. Shell commands are gated; built-in tools
|
|
7
|
+
* at or below the current rung are not, so a model that reaches for the shell to do something a
|
|
8
|
+
* granted built-in already does converts a free action into an interruption. A model chooses a
|
|
9
|
+
* tool by reading that tool's description, so the approvals posture is stated **there** — not as a
|
|
10
|
+
* global preference in the system prompt, which sits far from the decision and can drift out of
|
|
11
|
+
* step with the gate.
|
|
12
|
+
*
|
|
13
|
+
* Two properties are load-bearing and are why this module exists at all:
|
|
14
|
+
*
|
|
15
|
+
* 1. **A tool that is auto-approved gets NO suffix.** The absence of the sentence is what marks it
|
|
16
|
+
* free. Appending "this tool is already approved" would be the opposite of the design.
|
|
17
|
+
* 2. **The suffix is derived from the rung in force AND from the set of tools the gate actually
|
|
18
|
+
* gates.** "A description that disagrees with what the gate will actually do is worse than no
|
|
19
|
+
* description at all" (§4.5), so {@link isGrantedAtRung} takes the gated set as a parameter and
|
|
20
|
+
* both backends pass the SAME set they wire into the approval interrupt. The two can therefore
|
|
21
|
+
* not drift: widening the gate ([[EXT-30]]) widens the descriptions in the same breath.
|
|
22
|
+
*
|
|
23
|
+
* **Scope boundary, per §4.3:** *"The first implementation and the measurement corpus cover the
|
|
24
|
+
* shell only. Every other tool is granted or escalated by the rung without a rating call until
|
|
25
|
+
* EXT-30 widens the gate."* Today `run_shell_command` is the only tool either backend wires into
|
|
26
|
+
* the approval interrupt, so it is the only tool that can ever require approval — every other tool
|
|
27
|
+
* is **granted**, and a granted tool gets no suffix. The rung-vs-access-class rule below
|
|
28
|
+
* (read tools granted from `read-only` up, write tools from `write` up) is implemented in full and
|
|
29
|
+
* unit-tested against an injected wider gated set, so EXT-30 inherits it rather than re-deriving
|
|
30
|
+
* it; it simply has no observable effect while the gated set is the shell alone.
|
|
31
|
+
*/
|
|
32
|
+
import type { ApprovalRung } from '#src/config/shell-policy.js';
|
|
33
|
+
/**
|
|
34
|
+
* §4.5's table, **verbatim**. The wordings are normative copy the model reads; do not paraphrase,
|
|
35
|
+
* re-punctuate or "improve" them.
|
|
36
|
+
*
|
|
37
|
+
* - `read-only` and `write` share one sentence: at both rungs a non-granted call goes to the human,
|
|
38
|
+
* so the user's approval is a certainty, not a possibility.
|
|
39
|
+
* - `auto-safe` softens `will` to `MAY`: the rater approves what it rates safe, so only some calls
|
|
40
|
+
* reach the user.
|
|
41
|
+
* - `full-auto` gets its **own** wording because the consequence differs — the user is not asked
|
|
42
|
+
* there, so promising the user's approval would be false. What can happen is a refusal by the
|
|
43
|
+
* rater.
|
|
44
|
+
* - `bypass` appends nothing to anything: no gate, so no sentence could be true.
|
|
45
|
+
*/
|
|
46
|
+
export declare const RUNG_TOOL_DESCRIPTION_SUFFIXES: Readonly<Record<ApprovalRung, string | null>>;
|
|
47
|
+
/**
|
|
48
|
+
* The access class of a built-in tool, in the sense §2 grants them: `read` = reading and listing
|
|
49
|
+
* files within the working folder; `write` = creating, editing, moving and deleting files within
|
|
50
|
+
* the working folder. A tool that does neither (the shell, a network fetch, an MCP call) has no
|
|
51
|
+
* class and is granted by no rung but `bypass`.
|
|
52
|
+
*/
|
|
53
|
+
export type BuiltInToolAccess = 'read' | 'write';
|
|
54
|
+
/**
|
|
55
|
+
* Access class per built-in tool name. Covers BOTH backends' names: gsloth's own
|
|
56
|
+
* `GthFileSystemToolkit` (lean) and deepagents' filesystem tools (`ls`/`glob`/`grep`, deep) — the
|
|
57
|
+
* two sets overlap on `read_file`/`write_file`/`edit_file`, which is exactly why one flat table
|
|
58
|
+
* keyed by name serves both.
|
|
59
|
+
*
|
|
60
|
+
* Deliberately absent: `run_shell_command`, the fixed dev-command tools, `gth_web_fetch`,
|
|
61
|
+
* `gth_checklist`, `gth_status_update`, `show_a2ui_surface`, MCP/custom/A2A tools. None of them is
|
|
62
|
+
* "reading or writing files in the working folder", so none is granted by a rung's access class.
|
|
63
|
+
* (They are still *granted today* — the gate does not gate them — which {@link isGrantedAtRung}
|
|
64
|
+
* decides on the gated set, not on this table.)
|
|
65
|
+
*/
|
|
66
|
+
export declare const BUILT_IN_TOOL_ACCESS: Readonly<Record<string, BuiltInToolAccess>>;
|
|
67
|
+
/** A built-in tool the rater may offer as an already-granted alternative (§4.4). */
|
|
68
|
+
export interface GrantedToolSummary {
|
|
69
|
+
/** The registered tool name, exactly as the model sees it. */
|
|
70
|
+
name: string;
|
|
71
|
+
/** One line, authored here — locally-generated trusted text (§4.3), never a tool's own blurb. */
|
|
72
|
+
description: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* §4.3/§4.4 — the one-line descriptions handed to the rater with the granted tools' names.
|
|
76
|
+
*
|
|
77
|
+
* **Authored here on purpose.** §4.3 admits this list as *"trusted, locally-generated text, not
|
|
78
|
+
* part of the fenced block"*, so it may not be assembled from tool `description` fields at large:
|
|
79
|
+
* an MCP server's tool description is attacker-influenceable text, and placing it outside the
|
|
80
|
+
* fenced block would open an injection channel straight past the rater's untrusted-input preamble.
|
|
81
|
+
* Only tools named in THIS table are ever offered, so an MCP, custom or A2A tool can never
|
|
82
|
+
* contribute text to the rater prompt.
|
|
83
|
+
*
|
|
84
|
+
* Restricted to tools that could plausibly stand in for a shell command — the file tools, content
|
|
85
|
+
* search, and the fixed dev-command tools (whose command is human-authored config, so suggesting
|
|
86
|
+
* `run_tests` over `npm test` is exactly the trade this section exists to make). `gth_checklist`,
|
|
87
|
+
* `gth_status_update` and `show_a2ui_surface` are omitted: they substitute for nothing a model
|
|
88
|
+
* would otherwise shell out for, and a suggestion list is only useful while it is short.
|
|
89
|
+
*
|
|
90
|
+
* **`gth_web_fetch` is deliberately NOT here**, though it is ungated at every rung. §4.5's
|
|
91
|
+
* justification for disclosing the posture at all is that the granted tools are "by construction,
|
|
92
|
+
* the constrained ones confined to the working folder" — a network fetch is not. Offering it would
|
|
93
|
+
* let a refused `curl`/`wget` come back as a suggestion whose §7 clause tells the model, verbatim,
|
|
94
|
+
* that the alternative "will not interrupt the user": a refused egress turned into a free one,
|
|
95
|
+
* through the rater rather than through the gate. An `attack` halts before any message reaches the
|
|
96
|
+
* model, but a merely `destructive` fetch would not. Same reasoning excludes MCP and custom tools,
|
|
97
|
+
* which additionally supply text we did not author.
|
|
98
|
+
*/
|
|
99
|
+
export declare const BUILT_IN_TOOL_SUMMARIES: Readonly<Record<string, string>>;
|
|
100
|
+
/** The sentence §4.5 appends at this rung, or `null` when the rung appends nothing (`bypass`). */
|
|
101
|
+
export declare function getRungToolDescriptionSuffix(rung: ApprovalRung): string | null;
|
|
102
|
+
/**
|
|
103
|
+
* Is `toolName` auto-approved (granted, free, no prompt and no rating) at `rung`?
|
|
104
|
+
*
|
|
105
|
+
* @param toolName The registered tool name.
|
|
106
|
+
* @param rung The rung in force for the session.
|
|
107
|
+
* @param gatedTools The names the gate actually wires into the approval interrupt. **This is the
|
|
108
|
+
* parameter that keeps the descriptions honest**: a tool the gate does not gate cannot require
|
|
109
|
+
* approval, whatever a rung's table row says about tool classes, so it is granted. Both backends
|
|
110
|
+
* pass the same set they hand to `interruptOn` / `humanInTheLoopMiddleware`.
|
|
111
|
+
*
|
|
112
|
+
* Order:
|
|
113
|
+
* 1. `bypass` grants everything (§2.5) — the gate is off.
|
|
114
|
+
* 2. A tool the gate does not gate is granted at every rung (§4.3's scope boundary).
|
|
115
|
+
* 3. A gated tool is granted only where the rung's own grant covers its access class: read tools
|
|
116
|
+
* from `read-only` up (§2.1), write tools from `write` up (§2.2, and §2.3/§2.4 which grant
|
|
117
|
+
* "everything `write` grants"). A gated tool with no access class — the shell, a network call,
|
|
118
|
+
* an MCP tool — is granted at no rung but `bypass`.
|
|
119
|
+
*/
|
|
120
|
+
export declare function isGrantedAtRung(toolName: string, rung: ApprovalRung, gatedTools: readonly string[]): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Remove a previously-appended §4.5 suffix (any rung's), returning the tool's own description.
|
|
123
|
+
*
|
|
124
|
+
* Makes {@link applyRungAwareToolDescriptions} idempotent and re-appliable at a different rung —
|
|
125
|
+
* which matters because a resolver may hand back the SAME tool instance on a re-init (an MCP
|
|
126
|
+
* client caches its tool objects), and a second pass would otherwise stack sentences.
|
|
127
|
+
*/
|
|
128
|
+
export declare function stripRungToolDescriptionSuffix(description: string): string;
|
|
129
|
+
/** The minimal shape {@link applyRungAwareToolDescriptions} needs; keeps this module langchain-free. */
|
|
130
|
+
export interface DescribableTool {
|
|
131
|
+
name?: string;
|
|
132
|
+
description?: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* §4.5 — at tool-registration time, append the rung's sentence to the description of every tool
|
|
136
|
+
* that is NOT auto-approved at that rung, and leave every granted tool's description untouched.
|
|
137
|
+
*
|
|
138
|
+
* Mutates in place (and returns the same array): the tools are about to be handed to
|
|
139
|
+
* `createAgent`/`createDeepAgent`, and the array is freshly resolved per init. Idempotent — any
|
|
140
|
+
* previously-appended suffix is stripped first, so re-registration at a different rung replaces
|
|
141
|
+
* the sentence rather than stacking one.
|
|
142
|
+
*
|
|
143
|
+
* A tool with no name is left alone (provider-native "magic object" tools carry no name and cannot
|
|
144
|
+
* be classified); a tool with an empty description gets the suffix as its whole description rather
|
|
145
|
+
* than a leading space.
|
|
146
|
+
*/
|
|
147
|
+
export declare function applyRungAwareToolDescriptions<T extends DescribableTool>(tools: T[], options: {
|
|
148
|
+
rung: ApprovalRung;
|
|
149
|
+
gatedTools: readonly string[];
|
|
150
|
+
}): T[];
|
|
151
|
+
/**
|
|
152
|
+
* §4.3/§4.4 — the names and one-line descriptions of the built-in tools already granted at `rung`,
|
|
153
|
+
* for the rater prompt.
|
|
154
|
+
*
|
|
155
|
+
* Filtered three ways, each of which matters:
|
|
156
|
+
* - to tools **actually registered** in this session (a suggestion naming a tool the model does not
|
|
157
|
+
* have is worse than no suggestion);
|
|
158
|
+
* - to tools in {@link BUILT_IN_TOOL_SUMMARIES}, so only locally-authored text ever reaches the
|
|
159
|
+
* rater prompt (never an MCP/custom tool's own description);
|
|
160
|
+
* - to tools **granted at the rung** — suggesting a tool that would itself need approval defeats
|
|
161
|
+
* the point.
|
|
162
|
+
*
|
|
163
|
+
* Order follows the registration order so the prompt is stable across runs.
|
|
164
|
+
*/
|
|
165
|
+
export declare function describeGrantedBuiltInTools(registeredToolNames: readonly string[], rung: ApprovalRung, gatedTools: readonly string[]): GrantedToolSummary[];
|