@gaunt-sloth/core 2.0.0-alpha.23 → 2.0.0-alpha.25

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.
Files changed (154) hide show
  1. package/README.md +43 -18
  2. package/dist/config/colour.d.ts +38 -0
  3. package/dist/config/colour.js +36 -0
  4. package/dist/config/colour.js.map +1 -0
  5. package/dist/config/defaults.d.ts +6 -7
  6. package/dist/config/defaults.js +4 -8
  7. package/dist/config/defaults.js.map +1 -1
  8. package/dist/config/loader.d.ts +50 -3
  9. package/dist/config/loader.js +345 -29
  10. package/dist/config/loader.js.map +1 -1
  11. package/dist/config/mouse.d.ts +50 -0
  12. package/dist/config/mouse.js +44 -0
  13. package/dist/config/mouse.js.map +1 -0
  14. package/dist/config/profiles.d.ts +68 -0
  15. package/dist/config/profiles.js +93 -0
  16. package/dist/config/profiles.js.map +1 -0
  17. package/dist/config/schema.d.ts +2441 -116
  18. package/dist/config/schema.js +930 -19
  19. package/dist/config/schema.js.map +1 -1
  20. package/dist/config/shell-policy.d.ts +466 -116
  21. package/dist/config/shell-policy.js +273 -62
  22. package/dist/config/shell-policy.js.map +1 -1
  23. package/dist/config/tool-descriptions.d.ts +165 -0
  24. package/dist/config/tool-descriptions.js +229 -0
  25. package/dist/config/tool-descriptions.js.map +1 -0
  26. package/dist/config/types.d.ts +139 -6
  27. package/dist/config/types.js.map +1 -1
  28. package/dist/config.d.ts +4 -0
  29. package/dist/config.js +4 -0
  30. package/dist/config.js.map +1 -1
  31. package/dist/core/GthAbstractAgent.d.ts +85 -2
  32. package/dist/core/GthAbstractAgent.js +194 -1
  33. package/dist/core/GthAbstractAgent.js.map +1 -1
  34. package/dist/core/GthAgentRunner.d.ts +341 -58
  35. package/dist/core/GthAgentRunner.js +857 -149
  36. package/dist/core/GthAgentRunner.js.map +1 -1
  37. package/dist/core/GthLangChainAgent.d.ts +99 -0
  38. package/dist/core/GthLangChainAgent.js +388 -3
  39. package/dist/core/GthLangChainAgent.js.map +1 -1
  40. package/dist/core/approvals/annotations.d.ts +122 -0
  41. package/dist/core/approvals/annotations.js +137 -0
  42. package/dist/core/approvals/annotations.js.map +1 -0
  43. package/dist/core/approvals/grants.d.ts +216 -0
  44. package/dist/core/approvals/grants.js +469 -0
  45. package/dist/core/approvals/grants.js.map +1 -0
  46. package/dist/core/approvals/matcher.d.ts +202 -0
  47. package/dist/core/approvals/matcher.js +267 -0
  48. package/dist/core/approvals/matcher.js.map +1 -0
  49. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  50. package/dist/core/approvals/mcpSubjects.js +99 -0
  51. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  52. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  53. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  54. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  55. package/dist/core/approvals/toolHost.d.ts +46 -0
  56. package/dist/core/approvals/toolHost.js +108 -0
  57. package/dist/core/approvals/toolHost.js.map +1 -0
  58. package/dist/core/debugCapture.d.ts +16 -0
  59. package/dist/core/debugCapture.js.map +1 -1
  60. package/dist/core/launchBanner.d.ts +120 -0
  61. package/dist/core/launchBanner.js +399 -0
  62. package/dist/core/launchBanner.js.map +1 -0
  63. package/dist/core/plainToolIndication.js +61 -35
  64. package/dist/core/plainToolIndication.js.map +1 -1
  65. package/dist/core/refusal.d.ts +53 -0
  66. package/dist/core/refusal.js +129 -0
  67. package/dist/core/refusal.js.map +1 -0
  68. package/dist/core/shell/approvalStop.d.ts +75 -0
  69. package/dist/core/shell/approvalStop.js +108 -0
  70. package/dist/core/shell/approvalStop.js.map +1 -0
  71. package/dist/core/shell/arity.d.ts +6 -0
  72. package/dist/core/shell/arity.js +20 -6
  73. package/dist/core/shell/arity.js.map +1 -1
  74. package/dist/core/shell/denylist.d.ts +11 -0
  75. package/dist/core/shell/denylist.js +37 -0
  76. package/dist/core/shell/denylist.js.map +1 -0
  77. package/dist/core/shell/normalize.d.ts +41 -1
  78. package/dist/core/shell/normalize.js +58 -4
  79. package/dist/core/shell/normalize.js.map +1 -1
  80. package/dist/core/shell/openWorld.d.ts +138 -0
  81. package/dist/core/shell/openWorld.js +523 -0
  82. package/dist/core/shell/openWorld.js.map +1 -0
  83. package/dist/core/shell/rater.d.ts +560 -0
  84. package/dist/core/shell/rater.js +895 -0
  85. package/dist/core/shell/rater.js.map +1 -0
  86. package/dist/core/shell/raterModel.d.ts +41 -0
  87. package/dist/core/shell/raterModel.js +51 -0
  88. package/dist/core/shell/raterModel.js.map +1 -0
  89. package/dist/core/shell/rejection.d.ts +66 -0
  90. package/dist/core/shell/rejection.js +38 -0
  91. package/dist/core/shell/rejection.js.map +1 -0
  92. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  93. package/dist/core/toolCallRepair/grammar.js +116 -0
  94. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  95. package/dist/core/toolCallRepair/index.d.ts +2 -0
  96. package/dist/core/toolCallRepair/index.js +7 -0
  97. package/dist/core/toolCallRepair/index.js.map +1 -0
  98. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  99. package/dist/core/toolCallRepair/payload.js +341 -0
  100. package/dist/core/toolCallRepair/payload.js.map +1 -0
  101. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  102. package/dist/core/toolCallRepair/promote.js +90 -0
  103. package/dist/core/toolCallRepair/promote.js.map +1 -0
  104. package/dist/core/toolDisplay.d.ts +14 -1
  105. package/dist/core/toolDisplay.js +69 -12
  106. package/dist/core/toolDisplay.js.map +1 -1
  107. package/dist/core/toolOutputChannel.d.ts +33 -6
  108. package/dist/core/toolOutputChannel.js +61 -8
  109. package/dist/core/toolOutputChannel.js.map +1 -1
  110. package/dist/core/types.d.ts +71 -9
  111. package/dist/core/types.js.map +1 -1
  112. package/dist/providers/geminiSchemaSanitizer.d.ts +6 -3
  113. package/dist/providers/geminiSchemaSanitizer.js +152 -6
  114. package/dist/providers/geminiSchemaSanitizer.js.map +1 -1
  115. package/dist/providers/modelDiscovery.d.ts +18 -4
  116. package/dist/providers/modelDiscovery.js +67 -17
  117. package/dist/providers/modelDiscovery.js.map +1 -1
  118. package/dist/providers/openai.js +34 -0
  119. package/dist/providers/openai.js.map +1 -1
  120. package/dist/runtime/askStructured.d.ts +8 -7
  121. package/dist/runtime/askStructured.js +8 -7
  122. package/dist/runtime/askStructured.js.map +1 -1
  123. package/dist/runtime/conversation.js +89 -76
  124. package/dist/runtime/conversation.js.map +1 -1
  125. package/dist/runtime/singleShot.js +71 -60
  126. package/dist/runtime/singleShot.js.map +1 -1
  127. package/dist/utils/ProgressIndicator.d.ts +21 -0
  128. package/dist/utils/ProgressIndicator.js +30 -3
  129. package/dist/utils/ProgressIndicator.js.map +1 -1
  130. package/dist/utils/consoleUtils.d.ts +30 -0
  131. package/dist/utils/consoleUtils.js +27 -2
  132. package/dist/utils/consoleUtils.js.map +1 -1
  133. package/dist/utils/crashHandler.d.ts +87 -0
  134. package/dist/utils/crashHandler.js +128 -0
  135. package/dist/utils/crashHandler.js.map +1 -0
  136. package/dist/utils/debugDump.d.ts +58 -0
  137. package/dist/utils/debugDump.js +149 -15
  138. package/dist/utils/debugDump.js.map +1 -1
  139. package/dist/utils/llmUtils.d.ts +22 -8
  140. package/dist/utils/llmUtils.js +48 -8
  141. package/dist/utils/llmUtils.js.map +1 -1
  142. package/dist/utils/redactSecrets.js +68 -20
  143. package/dist/utils/redactSecrets.js.map +1 -1
  144. package/dist/utils/systemUtils.d.ts +17 -0
  145. package/dist/utils/systemUtils.js +17 -0
  146. package/dist/utils/systemUtils.js.map +1 -1
  147. package/package.json +5 -2
  148. package/schema/gsloth-config.schema.json +1484 -466
  149. package/dist/core/shell/allowlist.d.ts +0 -75
  150. package/dist/core/shell/allowlist.js +0 -187
  151. package/dist/core/shell/allowlist.js.map +0 -1
  152. package/dist/core/shell/judge.d.ts +0 -161
  153. package/dist/core/shell/judge.js +0 -261
  154. package/dist/core/shell/judge.js.map +0 -1
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * @packageDocumentation
3
3
  * Shell / dev-tools policy: the {@link GthDevToolsConfig} type plus all the resolvers
4
- * that interpret it (shell enablement, timeouts, output budget, allow-list, the EXT-10
5
- * LLM-as-judge gate, and per-command dev-tools selection).
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).
6
8
  *
7
9
  * CFG-18 — the dev/shell tools are now configured through the unified {@link GthConfig.builtInTools}
8
10
  * registry (`string[] | Record<string, boolean | BuiltInToolConfig>`), NOT the removed per-command
@@ -11,8 +13,8 @@
11
13
  * `builtInTools` registry, and that {@link GthDevToolkit} + the shell accessors below consume. This
12
14
  * keeps the toolkit/accessor surface stable while the single config surface is `builtInTools`.
13
15
  */
14
- import type { GthCommand } from '#src/core/types.js';
15
- import type { GthConfig, LLMConfig } from '#src/config/types.js';
16
+ import { type GthCommand, StatusLevel } from '#src/core/types.js';
17
+ import type { GthConfig } from '#src/config/types.js';
16
18
  /**
17
19
  * CFG-18 — the per-tool config object carried as a value in the {@link GthConfig.builtInTools}
18
20
  * registry (the object form's values, alongside a bare boolean that enables/force-disables a tool).
@@ -20,12 +22,16 @@ import type { GthConfig, LLMConfig } from '#src/config/types.js';
20
22
  * discriminated union so the registry can carry every tool's shape:
21
23
  * - the fixed dev-command tools (`run_tests`/`run_lint`/`run_build`/`run_single_test`) read
22
24
  * {@link command} — the shell command to run; its presence enables the tool;
23
- * - `run_shell_command` reads the EXT-9/10/12 knobs ({@link enabled}/{@link timeout}/
24
- * {@link maxOutputBytes}/{@link allowlist}/{@link persistAllowlist}/{@link judge}/{@link yolo} —
25
- * `yolo` is the folded former `shellYolo`);
25
+ * - `run_shell_command` reads the EXT-9/12 execution knobs ({@link enabled}/{@link timeout}/
26
+ * {@link maxOutputBytes});
26
27
  * - `gth_grep` reads {@link fileSet} (GS2-51) — which corpus to search;
27
28
  * - a plain built-in tool (`gth_checklist`, `gth_web_fetch`, …) reads {@link enabled} (or is
28
29
  * toggled with a bare boolean in the registry).
30
+ *
31
+ * CFG-26 — the APPROVAL knobs (`allowlist`, `persistAllowlist`, `judge`, `yolo`) are gone from
32
+ * here and live in the top-level {@link ApprovalsConfig}. They were fields of the object shared by
33
+ * EVERY built-in tool, so `gth_grep: { yolo: true }` used to validate; approvals are a property of
34
+ * the session, not of one tool's registry entry.
29
35
  */
30
36
  export interface BuiltInToolConfig {
31
37
  /**
@@ -41,23 +47,6 @@ export interface BuiltInToolConfig {
41
47
  timeout?: number;
42
48
  /** `run_shell_command`: captured-output byte budget. See {@link SHELL_DEFAULT_MAX_OUTPUT_BYTES}. */
43
49
  maxOutputBytes?: number;
44
- /** `run_shell_command`: EXT-9 Tier-2 scoped allow-list master switch (default `true`). */
45
- allowlist?: boolean;
46
- /** `run_shell_command`: persist `always`-scoped approvals to the project file (default `true`). */
47
- persistAllowlist?: boolean;
48
- /** `run_shell_command`: EXT-10 LLM-as-judge safety gate (default OFF). */
49
- judge?: boolean | {
50
- enabled?: boolean;
51
- autoApproveLow?: boolean;
52
- blockHigh?: boolean;
53
- model?: LLMConfig;
54
- };
55
- /**
56
- * `run_shell_command`: opt out of the per-command approval prompt — the explicit "yolo" bypass
57
- * (folded former top-level `shellYolo`). Dangerous by design; off by default. Example:
58
- * `{ "run_shell_command": { "yolo": true } }`.
59
- */
60
- yolo?: boolean;
61
50
  /**
62
51
  * `gth_grep` (GS2-51): which corpus the content-search tool scans, applied consistently to BOTH
63
52
  * execution engines (native ripgrep and the in-process JS fallback):
@@ -87,7 +76,7 @@ export interface BuiltInToolConfig {
87
76
  * { "builtInTools": {
88
77
  * "gth_checklist": true,
89
78
  * "gth_web_fetch": true,
90
- * "run_shell_command": { "timeout": 300000, "judge": { "enabled": true } }
79
+ * "run_shell_command": { "timeout": 300000 }
91
80
  * } }
92
81
  * ```
93
82
  * Turn the (code-mode default-on) shell OFF: `{ "builtInTools": { "run_shell_command": false } }`.
@@ -156,17 +145,16 @@ export interface GthDevToolsConfig {
156
145
  * lacks (it can read/write files but not run commands).
157
146
  *
158
147
  * EXT-12 — default: ON in `code` mode, OFF elsewhere. When this is ABSENT/undefined,
159
- * `code` mode emits the tool (still GATED behind the per-command approval prompt — the
160
- * absent-config default NEVER implies yolo); `exec` / `ask --write` keep it OFF. An
161
- * EXPLICIT value always wins: `shell: false` (or `{ enabled: false }`) is a hard escape
162
- * hatch that fully disables it even in `code`. Accepts a bare boolean or an
163
- * `{ enabled }` object for symmetry with future per-tool options.
148
+ * `code` mode emits the tool (still GATED behind the approval gate — the absent-config
149
+ * default NEVER implies bypass); `exec` / `ask --write` keep it OFF. An EXPLICIT value
150
+ * always wins: `shell: false` (or `{ enabled: false }`) is a hard escape hatch that fully
151
+ * disables it even in `code`. Accepts a bare boolean or an `{ enabled }` object.
164
152
  *
165
- * Because the model chooses the command, every invocation is gated behind a
166
- * per-command human confirmation dialog (LangChain `humanInTheLoopMiddleware`,
167
- * wired via deepagents' `interruptOn`) UNLESS {@link shellYolo} bypasses it.
168
- * The confirmation not string-filtering is the guardrail, so the command
169
- * is passed through verbatim (pipes / `$` / `;` are all legitimate).
153
+ * Because the model chooses the command, every invocation is gated behind the CFG-26
154
+ * approvals gate (LangChain `humanInTheLoopMiddleware`, wired via deepagents' `interruptOn`)
155
+ * UNLESS `approvals.mode: "bypass"` turns the gate off. The gate — not string-filtering — is
156
+ * the guardrail, so the command is passed through verbatim (pipes / `$` / `;` are all
157
+ * legitimate).
170
158
  *
171
159
  * The object form also tunes the EXT-9 Tier-1 hardening applied to every run
172
160
  * (these have safe defaults so bare `shell: true` is already hardened):
@@ -179,63 +167,21 @@ export interface GthDevToolsConfig {
179
167
  *
180
168
  * A hardcoded hardline blocklist of catastrophic commands (rm -rf /, mkfs, dd
181
169
  * to a block device, fork bomb, shutdown/reboot, …) is refused even under
182
- * {@link shellYolo}; that floor is not configurable.
170
+ * `approvals.mode: "bypass"`; that floor is not configurable.
183
171
  *
184
172
  * On-disk (CFG-18) these live on the `run_shell_command` entry of `builtInTools`, e.g.
185
173
  * `{ "builtInTools": { "run_shell_command": true } }` or
186
174
  * `{ "builtInTools": { "run_shell_command": { "timeout": 300000, "maxOutputBytes": 200000 } } }`.
187
175
  *
188
- * The object form additionally accepts EXT-9 Tier-2 allow-list knobs:
189
- * - `allowlist`: master switch for the scoped approval allow-list (session +
190
- * persisted `always`). Default `true` once a command is approved at `session`/
191
- * `always` scope, flag-variants of the same classified operation auto-approve
192
- * without re-prompting. Set `false` to require fresh approval for every command.
193
- * - `persistAllowlist`: whether `always`-scoped approvals are written to the project
194
- * allow-list file (`.gsloth/.gsloth-settings/shell-allowlist.json`). Default `true`.
195
- * When `false`, an `always` decision behaves like `session` (in-memory only).
196
- *
197
- * The object form also accepts the EXT-10 LLM-as-judge safety gate (default OFF):
198
- * - `judge`: an opt-in, tiered auto-approve pre-filter that vets each `run_shell_command`
199
- * with a lightweight judge model BEFORE the human prompt. It auto-approves clearly-safe
200
- * commands (fatigue reducer), escalates the rest to the existing human prompt, and may
201
- * reject clearly-catastrophic ones. Default OFF because it costs one LLM call per command.
202
- * Accepts a bare boolean (`judge: true` → defaults: auto-approve low, escalate medium/high,
203
- * judge model = `config.llm`) or an object:
204
- * - `enabled`: turn the gate on.
205
- * - `autoApproveLow`: auto-approve `low`-risk, statically-resolvable commands. Default true.
206
- * - `blockHigh`: reject clearly-catastrophic (`high` + destructive) verdicts WITHOUT
207
- * prompting. Default false (conservative; EXT-9's hardline floor already refuses truly
208
- * catastrophic commands at exec time).
209
- * - `model`: an optional separate (e.g. cheaper) judge model config. Defaults to `config.llm`.
210
- * Hardening (always on when the judge runs): the command is normalized + XML-tagged as
211
- * UNTRUSTED input in the judge prompt; a judge throw/timeout/parse-failure fails CLOSED
212
- * (escalate, never auto-approve); commands whose target can't be statically resolved
213
- * (shell composition / substitution / redirection) and interpreter+script invocations that
214
- * leak ALL_CAPS env vars are NEVER auto-approved.
176
+ * CFG-26 the approval knobs that used to live here (`allowlist`, `persistAllowlist`,
177
+ * `judge`, `yolo`) moved to the top-level `approvals` block ({@link ApprovalsConfig}); read
178
+ * them through {@link resolveApprovals}, never from this object.
215
179
  */
216
180
  shell?: boolean | {
217
181
  enabled?: boolean;
218
182
  timeout?: number;
219
183
  maxOutputBytes?: number;
220
- allowlist?: boolean;
221
- persistAllowlist?: boolean;
222
- judge?: boolean | {
223
- enabled?: boolean;
224
- autoApproveLow?: boolean;
225
- blockHigh?: boolean;
226
- model?: LLMConfig;
227
- };
228
184
  };
229
- /**
230
- * Opt-out of the per-command confirmation dialog for {@link shell}
231
- * (`run_shell_command`) — the explicit "yolo" bypass. When `true` AND `shell`
232
- * is enabled, the shell tool runs without any approval interrupt: the model's
233
- * commands execute immediately. Dangerous by design; off by default.
234
- *
235
- * On-disk (CFG-18) this is the `yolo` knob of the `run_shell_command` entry, e.g.
236
- * `{ "builtInTools": { "run_shell_command": { "yolo": true } } }`.
237
- */
238
- shellYolo?: boolean;
239
185
  }
240
186
  /**
241
187
  * Default per-command shell timeout (ms) when {@link GthDevToolsConfig.shell}
@@ -282,50 +228,454 @@ export declare function getShellTimeoutMs(devTools: GthDevToolsConfig | undefine
282
228
  */
283
229
  export declare function getShellMaxOutputBytes(devTools: GthDevToolsConfig | undefined): number;
284
230
  /**
285
- * Whether the EXT-9 Tier-2 scoped allow-list is active. Default `true`; only the object
286
- * form's `allowlist: false` disables it (a bare `shell: true` keeps it on). When off, the
287
- * runner prompts for every `run_shell_command` regardless of prior approvals.
231
+ * Resolve the {@link GthDevToolsConfig} that applies to the active command from the unified
232
+ * {@link GthConfig.builtInTools} registry (CFG-18 replaces the removed per-command `devTools`).
233
+ * Mirrors the per-command selection used by `builtInToolsConfig.getDefaultTools`: `exec`
234
+ * `commands.exec`, `ask --write` → `commands.ask`, `code` → `commands.code`; `undefined` elsewhere
235
+ * (the dev/shell tools are inert there). The effective registry for the scope is the per-command
236
+ * `builtInTools` if set, else the root `builtInTools` — matching `getEffectiveConfig`'s replace
237
+ * merge. Shared in core so the runner's allow-list/judge gates stay in lockstep with where the
238
+ * shell tool is actually emitted.
239
+ */
240
+ export declare function getEffectiveDevToolsConfig(config: Pick<GthConfig, 'commands' | 'builtInTools' | 'askWriteMode'> | undefined, command: GthCommand | undefined): GthDevToolsConfig | undefined;
241
+ /**
242
+ * CFG-27 (spec §1, §2) — **the ladder**. There is ONE approvals setting and it is a single ordered
243
+ * ladder of five rungs; each rung fully determines behaviour. There are no severity thresholds, no
244
+ * strictness levels and no independent rater on/off switch.
245
+ *
246
+ * | # | Rung | Rater | LLM cost |
247
+ * |---|---|---|---|
248
+ * | 1 | `read-only` | no | none |
249
+ * | 2 | `write` | no | none |
250
+ * | 3 | `auto-safe` | yes | 1 call per gated call |
251
+ * | 4 | `full-auto` | yes | 1–2 calls per gated call |
252
+ * | 5 | `bypass` | no | none |
253
+ *
254
+ * Rungs 1, 2 and 5 are fully deterministic: no model is consulted, so behaviour is reproducible
255
+ * and costs nothing.
256
+ *
257
+ * **`bypass` is NOT a higher-autonomy rung than `full-auto`** (§2.5). Both let the agent act
258
+ * without asking; `bypass` is the same autonomy with the checks removed. The ordering below is the
259
+ * order the rungs are *offered* in, and must never be presented as though `full-auto` were an
260
+ * incomplete `bypass`.
261
+ *
262
+ * Identifiers are kebab-case (§9.1) because the same token must work as a config value, a
263
+ * slash-command argument and a CLI flag — a space breaks the last two. Display names keep their
264
+ * spaces; see {@link APPROVAL_RUNG_LABELS}.
265
+ */
266
+ export declare const APPROVAL_RUNGS: readonly ['read-only', 'write', 'auto-safe', 'full-auto', 'bypass'];
267
+ /** One rung of {@link APPROVAL_RUNGS}. */
268
+ export type ApprovalRung = (typeof APPROVAL_RUNGS)[number];
269
+ /**
270
+ * §9.1 rule / §10 rule 4 — the display spelling of each rung, with spaces. An identifier and a
271
+ * label do not have to match and only one of them has to survive a shell, so user-facing prose
272
+ * uses these and never the kebab-case identifiers.
273
+ */
274
+ export declare const APPROVAL_RUNG_LABELS: Record<ApprovalRung, string>;
275
+ /**
276
+ * §10 — the one sentence shown wherever a rung is chosen or displayed. **Copied verbatim from the
277
+ * specification**; the wording is constrained by four normative rules there (state what the rung
278
+ * PERMITS, state the allow-list carve-out, never claim safety this system cannot deliver, use the
279
+ * display spelling) plus §8.1 (the hardline floor is real but is NEVER advertised — descriptions
280
+ * cite only protections the user can inspect and extend, i.e. the deny list). Do not "improve"
281
+ * these: `auto-safe` in particular MUST keep the sentence saying files are still rewritten and
282
+ * deleted without asking.
283
+ *
284
+ * The only departure from the source text is that §10's markdown emphasis markers (`**not**` in
285
+ * *Full auto*) are dropped, since these strings are rendered as plain terminal copy.
286
+ */
287
+ export declare const APPROVAL_RUNG_DESCRIPTIONS: Record<ApprovalRung, string>;
288
+ /** Narrowing type guard for a raw string that may name a rung. */
289
+ export declare function isApprovalRung(value: unknown): value is ApprovalRung;
290
+ /** The rungs at which every gated call is rated by the model (§2.3, §2.4). */
291
+ export declare function isRatedRung(rung: ApprovalRung): boolean;
292
+ /**
293
+ * EXT-71 §3.1 — the **subject** axis of a rule entry, and only that. The schema twin is
294
+ * `APPROVAL_ENTRY_TYPES` in `config/schema.ts`. What holds the two together is
295
+ * `approvalEntrySchema.spec.ts`, where a list of `ApprovalEntry`-typed literals is parsed by that
296
+ * schema: a value either side stops accepting fails there. That is a weaker pin than a direct
297
+ * equality assertion — it catches a narrowing, not a widening on one side alone.
288
298
  */
289
- export declare function isShellAllowlistEnabled(devTools: GthDevToolsConfig | undefined): boolean;
299
+ export type ApprovalEntryType = 'shell' | 'tool' | 'mcpTool';
300
+ /** EXT-71 §3.1 — the **comparison** axis of a rule entry, and only that. */
301
+ export type ApprovalMatcher = 'exact' | 'glob' | 'regexp' | 'hint';
290
302
  /**
291
- * Whether `always`-scoped approvals are persisted to the project allow-list file. Default
292
- * `true`; only the object form's `persistAllowlist: false` disables persistence (an
293
- * `always` decision then behaves as `session`).
303
+ * §4.7 — the four MCP `ToolAnnotations` hint names, and the whole vocabulary. It is the same list
304
+ * on both sides of the design: what a `hint` pattern may name ({@link ApprovalHintPattern}) and what
305
+ * a user may believe from a server ({@link McpServerApprovalsConfig.trustAnnotations}).
306
+ *
307
+ * **The schema twin `HINT_ANNOTATION_KEYS` in `config/schema.ts` is a deliberate duplicate, and the
308
+ * reason is layering, not oversight.** Neither file may import the other. `schema.ts` states in its
309
+ * own header that it must stay pure and cwd/fs-independent because it feeds `z.toJSONSchema()`, and
310
+ * importing this module would pull `core/types.js` and the whole runtime policy surface into it;
311
+ * importing `schema.ts` here would in turn pull zod into every module that only wanted a policy
312
+ * type. So the vocabulary is written once per layer on purpose — do not "simplify" it by making one
313
+ * import the other.
314
+ *
315
+ * What keeps the two honest instead is the equality assertion in `mcpApprovalsBlock.spec.ts`, which
316
+ * fails the moment they drift. Drift matters in one direction especially: a name the config accepts
317
+ * but the derivation never reads fails silently, and it fails toward trusting. Change one list,
318
+ * change the other.
294
319
  */
295
- export declare function isShellAllowlistPersisted(devTools: GthDevToolsConfig | undefined): boolean;
320
+ export declare const TOOL_ANNOTATION_HINTS: readonly ['readOnlyHint', 'destructiveHint', 'idempotentHint', 'openWorldHint'];
321
+ /** One of {@link TOOL_ANNOTATION_HINTS}. */
322
+ export type ToolAnnotationHint = (typeof TOOL_ANNOTATION_HINTS)[number];
296
323
  /**
297
- * Resolved settings for the EXT-10 LLM-as-judge safety gate.
324
+ * EXT-71 §3.1 a `hint` pattern: the four MCP `ToolAnnotations` booleans, each mapped to the
325
+ * value it must EFFECTIVELY hold (§4.7.1). All named hints must match (AND within the entry);
326
+ * hints not named are unconstrained; `false` is the spelling of negation. At least one must be
327
+ * named — an empty object is a config error, never a match-everything.
298
328
  */
299
- export interface ShellJudgeSettings {
300
- /** Whether the judge gate runs at all. */
301
- enabled: boolean;
302
- /** Auto-approve `low`-risk, statically-resolvable commands (the fatigue reducer). */
303
- autoApproveLow: boolean;
304
- /** Reject clearly-catastrophic (`high` + destructive) verdicts without prompting. */
305
- blockHigh: boolean;
306
- /** Optional separate judge model config; when absent the runner uses `config.llm`. */
307
- model?: LLMConfig;
329
+ export interface ApprovalHintPattern {
330
+ readOnlyHint?: boolean;
331
+ destructiveHint?: boolean;
332
+ idempotentHint?: boolean;
333
+ openWorldHint?: boolean;
334
+ }
335
+ /** Fields every rule entry may carry, whatever its subject. */
336
+ interface ApprovalEntryCommon {
337
+ /**
338
+ * §3.2 — whether the rater still reviews a call this entry matched, honored at the rater rungs
339
+ * and inert at the deterministic ones. Valid on EVERY type. Absent takes the §3.2 default: an
340
+ * entry skips the rater only to the extent that it recorded what the rater would have seen, so
341
+ * `shell` + `exact` defaults to `false` and everything else to `true`.
342
+ */
343
+ rate?: boolean;
344
+ }
345
+ /** §3.1 — a `shell` entry: a command, compared against the normalized command string. */
346
+ export interface ShellApprovalEntry extends ApprovalEntryCommon {
347
+ type: 'shell';
348
+ /** `hint` is absent on purpose — a command carries no tool annotations. */
349
+ matcher: 'exact' | 'glob' | 'regexp';
350
+ pattern: string;
351
+ }
352
+ /** §3.1 — a `tool` entry: a built-in or custom in-process tool, compared against the tool name. */
353
+ export interface ToolApprovalEntry extends ApprovalEntryCommon {
354
+ type: 'tool';
355
+ matcher: ApprovalMatcher;
356
+ pattern: string | ApprovalHintPattern;
357
+ /** §4.7.4 — optional exact-match bound on the call's host. Tool subjects only. */
358
+ host?: string;
359
+ }
360
+ /** §3.1 — an `mcpTool` entry: one server's tool. */
361
+ export interface McpToolApprovalEntry extends ApprovalEntryCommon {
362
+ type: 'mcpTool';
363
+ matcher: ApprovalMatcher;
364
+ pattern: string | ApprovalHintPattern;
365
+ /**
366
+ * §4.7.5 — **required**, and the user's own key in `mcpServers`: the only identity a server has
367
+ * that is stable, unique and user-authored. The literal `*` means every server, which is why a
368
+ * configured server may not be named `*`.
369
+ */
370
+ server: string;
371
+ /** §4.7.4 — optional exact-match bound on the call's host. */
372
+ host?: string;
308
373
  }
309
374
  /**
310
- * Whether the EXT-10 LLM-as-judge safety gate is enabled for the given dev-tools config.
311
- * Default OFF (only the object form's `judge` truthy enables it), mirroring
312
- * {@link isShellToolEnabled}. A bare `shell: true` keeps the judge OFF — it costs an LLM call
313
- * per command and must be opted into explicitly.
375
+ * EXT-71 §3.1 **one entry** in `allow`, `deny` or `escalate`. All three lists take the same
376
+ * shape; the list an entry sits in decides only what a match DOES (deny over escalate over allow).
377
+ *
378
+ * `type`, `matcher` and `pattern` are always required — no field is inferred and no entry reads
379
+ * two ways. The runtime validator is `approvalEntrySchema` in `config/schema.ts`, which is
380
+ * stricter than TypeScript can be: it rejects unknown fields, an empty or unknown-key `hint`
381
+ * pattern, and a `regexp` that does not compile or is over the length cap.
314
382
  */
315
- export declare function isShellJudgeEnabled(devTools: GthDevToolsConfig | undefined): boolean;
383
+ export type ApprovalEntry = ShellApprovalEntry | ToolApprovalEntry | McpToolApprovalEntry;
316
384
  /**
317
- * Resolve the EXT-10 judge gate settings from a dev-tools config, applying safe defaults
318
- * (auto-approve low, do NOT block high). `enabled` reflects {@link isShellJudgeEnabled}.
385
+ * EXT-70 §4.7.1/§9 the approvals relationship with ONE MCP server, keyed by the user's own
386
+ * `mcpServers` config key (§4.7.5 the only identity a server has that is stable, unique and
387
+ * user-authored; nothing a server declares about itself ever participates).
388
+ *
389
+ * `trustAnnotations` names the hints that are BELIEVED from that server. It is a list rather than a
390
+ * boolean because trusting `readOnlyHint` while distrusting `openWorldHint` is a coherent position
391
+ * and the common one, and because a single "trusted server" flag throws that distinction away for
392
+ * nothing. **Absent or empty means what the default means: nothing external is believed** — every
393
+ * hint of that server's collapses to the MCP fail-closed default, so its declarations cannot
394
+ * perturb any rule.
319
395
  */
320
- export declare function getShellJudgeSettings(devTools: GthDevToolsConfig | undefined): ShellJudgeSettings;
396
+ export interface McpServerApprovalsConfig {
397
+ /** §4.7.1 — the hints believed from this server. Absent or empty trusts nothing. */
398
+ trustAnnotations?: ToolAnnotationHint[];
399
+ }
321
400
  /**
322
- * Resolve the {@link GthDevToolsConfig} that applies to the active command from the unified
323
- * {@link GthConfig.builtInTools} registry (CFG-18 — replaces the removed per-command `devTools`).
324
- * Mirrors the per-command selection used by `builtInToolsConfig.getDefaultTools`: `exec` →
325
- * `commands.exec`, `ask --write` `commands.ask`, `code` `commands.code`; `undefined` elsewhere
326
- * (the dev/shell tools are inert there). The effective registry for the scope is the per-command
327
- * `builtInTools` if set, else the root `builtInTools` matching `getEffectiveConfig`'s replace
328
- * merge. Shared in core so the runner's allow-list/judge gates stay in lockstep with where the
329
- * shell tool is actually emitted.
401
+ * EXT-70 §4.7/§9 the `approvals.mcp` block: the per-server relationship, keyed by the user's own
402
+ * `mcpServers` config key.
403
+ *
404
+ * **`defaults` applies to servers NOT named under `servers`** §9's own gloss. A server that names
405
+ * itself states its own relationship in full, so `{"jira": {}}` trusts nothing however permissive
406
+ * `defaults` is: trust by omission is exactly the failure §4.7.1 exists to prevent, and the fail-
407
+ * closed direction is the one a silent config edit must fall in.
408
+ *
409
+ * A key here is **not** validated against `mcpServers`. A user may write the policy before adding
410
+ * the server, and coupling the two would make config ORDER matter.
411
+ *
412
+ * This block cannot live inside `mcpServers`, which is modelled permissively because it carries
413
+ * runtime objects.
330
414
  */
331
- export declare function getEffectiveDevToolsConfig(config: Pick<GthConfig, 'commands' | 'builtInTools' | 'askWriteMode'> | undefined, command: GthCommand | undefined): GthDevToolsConfig | undefined;
415
+ export interface McpApprovalsConfig {
416
+ /** The relationship with every server not named under {@link servers}. */
417
+ defaults?: McpServerApprovalsConfig;
418
+ /** Per-server relationships, keyed by the user's own `mcpServers` config key (§4.7.5). */
419
+ servers?: Record<string, McpServerApprovalsConfig>;
420
+ }
421
+ /**
422
+ * On-disk `approvals` object form (root or per command). The **scalar form is exactly sugar for
423
+ * `{ mode: <value> }`** (§9.1) — the union exists so the extras have a home when they are needed,
424
+ * not so there are two ways to say the same thing.
425
+ */
426
+ export interface ApprovalsObjectConfig {
427
+ /** The rung. Absent = {@link DEFAULT_APPROVAL_RUNG}. */
428
+ mode?: ApprovalRung;
429
+ /**
430
+ * §9.1 — the identity profile the rater runs under, as a **bare name** (strict resolution,
431
+ * GS2-62: a name that does not resolve is a hard config error, never a silent fallback).
432
+ * Omitted = the main model. It is the only rater knob; nesting a one-field object is what this
433
+ * design removed.
434
+ */
435
+ rater?: string;
436
+ /** §3 — declared allow-list: what the human has trusted. Read-only input. */
437
+ allow?: ApprovalEntry[];
438
+ /** §3 — declared deny-list: what never runs. Read-only input; applies under `bypass` too. */
439
+ deny?: ApprovalEntry[];
440
+ /**
441
+ * §3/§3.2 — declared escalate list: a match always asks the human, whatever the rung would have
442
+ * done, and with no rating call. Read-only input; inert under `bypass` (§2.5).
443
+ */
444
+ escalate?: ApprovalEntry[];
445
+ /**
446
+ * EXT-66 — wall-clock budget (ms) for ONE rating call. Absent = {@link RATER_DEFAULT_TIMEOUT_MS}
447
+ * (30s), which is a hosted-model number: a local rater is knowably slower, and when it runs out
448
+ * of time the gate escalates, so an unreachable timeout turns the permissive rung into one that
449
+ * asks about everything while every layer reports success.
450
+ */
451
+ raterTimeoutMs?: number;
452
+ /**
453
+ * EXT-70 §4.7/§9 — the per-server MCP relationship. Read through
454
+ * `createEffectiveToolAnnotationSource` (`core/approvals/annotations.ts`), which is the ONE place
455
+ * an effective annotation set is derived; nothing else re-reads this block.
456
+ */
457
+ mcp?: McpApprovalsConfig;
458
+ }
459
+ /** On-disk `approvals` value: the rung on its own, or the object when the extras are needed. */
460
+ export type ApprovalsConfig = ApprovalRung | ApprovalsObjectConfig;
461
+ /**
462
+ * The fully-defaulted approvals posture for one command. `allow`/`deny`/`escalate` are the
463
+ * **declared** lists straight from config — read-only input that the runner merges with the
464
+ * runtime stores the escalation menu writes, and that is never written back to config (§9.1).
465
+ */
466
+ export interface ResolvedApprovals {
467
+ /** The rung in force. */
468
+ rung: ApprovalRung;
469
+ /** Identity profile the rater runs under, or `undefined` for the session model. */
470
+ rater?: string;
471
+ /** Declared allow-list entries (§3.1). Empty when none are declared. */
472
+ allow: ApprovalEntry[];
473
+ /** Declared deny-list entries (§3.1). Empty when none are declared. */
474
+ deny: ApprovalEntry[];
475
+ /** Declared escalate-list entries (§3.1). Empty when none are declared. */
476
+ escalate: ApprovalEntry[];
477
+ /**
478
+ * EXT-66 — wall-clock budget (ms) for one rating call, or `undefined` to let the rater apply
479
+ * `RATER_DEFAULT_TIMEOUT_MS`. Left `undefined` rather than defaulted here so the effective-config
480
+ * snapshot does not churn, exactly as `rater` is.
481
+ */
482
+ raterTimeoutMs?: number;
483
+ /**
484
+ * EXT-70 §4.7 — the declared per-server MCP relationship, or `undefined` when no scope states
485
+ * one (which reads the same as an empty block: nothing external is believed). Left `undefined`
486
+ * rather than defaulted here for the same reason `rater` is — so the effective-config snapshot
487
+ * does not churn.
488
+ */
489
+ mcp?: McpApprovalsConfig;
490
+ }
491
+ /**
492
+ * CFG-26 — how many command prefixes the allow-list holds, for the `/approvals` display.
493
+ * `always: undefined` means the persisted store has not been loaded — rendered `—` rather than a
494
+ * misleading `0`, since a display must never create the store.
495
+ */
496
+ export interface AllowlistCounts {
497
+ session: number;
498
+ always: number | undefined;
499
+ }
500
+ /**
501
+ * EXT-70 §4.7.1 — what one server's annotations are believed on, for display.
502
+ *
503
+ * `configured` says whether {@link server} is a key under `mcpServers` in the loaded config. It is
504
+ * an advisory rather than a validity test: §9 deliberately does NOT check `approvals.mcp` keys
505
+ * against `mcpServers`, so a user may write the policy before adding the server and config ORDER
506
+ * never matters. What it buys interactively is that a mistyped key — which trusts nothing while
507
+ * reading as though it did — can be pointed out rather than swallowed.
508
+ */
509
+ export interface McpServerAnnotationTrust {
510
+ /** §4.7.5 — the user's own `mcpServers` config key. */
511
+ server: string;
512
+ /** The hints believed from this server, resolved through `defaults` where it is not named. */
513
+ trusted: ToolAnnotationHint[];
514
+ /** Whether this key names a server in the loaded config's `mcpServers`. */
515
+ configured: boolean;
516
+ }
517
+ /** EXT-70 §4.7.1 — the whole believed-annotation picture, for the `/approvals` display. */
518
+ export interface McpAnnotationTrustView {
519
+ /** §9 — the hints believed from servers NOT named under `servers`. */
520
+ defaults: ToolAnnotationHint[];
521
+ /** Per-server, for every key either the config names or the policy does. */
522
+ servers: McpServerAnnotationTrust[];
523
+ }
524
+ /**
525
+ * EXT-70 §4.7.1/§4.7.4 — the outcome of believing (or ceasing to believe) hints from one server,
526
+ * so the surface that asked can report exactly what landed and what it costs.
527
+ *
528
+ * **`weakening` is the field that keeps the human un-surprised.** Withdrawing trust pushes a hint
529
+ * back to its fail-closed default, which for three of the four is a *weakening*, so the saved
530
+ * approvals made for that server while the hint was believed will be invalidated (§4.7.4) at the
531
+ * next call to that tool. That has to be said where the user withdraws trust, not only in the
532
+ * notice that arrives later.
533
+ */
534
+ export interface McpAnnotationTrustChange extends McpServerAnnotationTrust {
535
+ /** Hints this change started believing (absent from the previous set). */
536
+ added: ToolAnnotationHint[];
537
+ /** Hints this change stopped believing (present in the previous set). */
538
+ removed: ToolAnnotationHint[];
539
+ /**
540
+ * §4.7.4 — the subset of {@link removed} whose withdrawal can weaken an effective set, and
541
+ * therefore invalidate a grant. Empty on a grant of trust, which can never weaken: every
542
+ * weakening move ends at the fail-closed default, and believing a hint only ever moves away
543
+ * from it.
544
+ */
545
+ weakening: ToolAnnotationHint[];
546
+ /**
547
+ * §4.7.4 — the saved approvals for this server that the *resulting* trust actually weakens, each
548
+ * rendered by `describeApprovalEntry`. They are the ones that will be withdrawn, with the §4.7.4
549
+ * notice, at the next call to that tool.
550
+ *
551
+ * **A prediction, never a deletion.** Invalidation stays scoped to the call being decided, because
552
+ * a sweep would read every held grant against a source that can only answer for the tools
553
+ * registered right now — a server that happened to be offline would read as having weakened
554
+ * everything it ever declared, and the grants would go. Listing them is safe where deleting them
555
+ * is not.
556
+ *
557
+ * **It over-reports in two distinct ways, and both are the same trade.** The comparison is each
558
+ * grant's recorded snapshot against the set in force *now*, not the set before this change against
559
+ * the set after it. So (1) a server that is offline when trust moves declares nothing, resolves to
560
+ * the fail-closed constant, and every grant it holds is named; and (2) a grant already weakened
561
+ * for some other reason — an earlier withdrawal, a `tools/list` that took a hint back — is named
562
+ * under whichever withdrawal happens to run next, including one that moved nothing relevant to it.
563
+ * A named grant is therefore one that the trust now in force weakens, which is what the user needs
564
+ * to know; it is not a claim that *this* withdrawal is what weakened it. Reading it as the latter
565
+ * is how a test comes to assert causation the field never promised.
566
+ *
567
+ * Empty is likewise not "nothing is at risk" but "nothing this session can see is": with
568
+ * {@link weakening} non-empty the rule still holds for any grant made while those hints were
569
+ * believed, which is what the notice says in that case. Counted read-only, like
570
+ * {@link AllowlistCounts}: the persisted store is consulted only when it is already loaded.
571
+ */
572
+ invalidates: string[];
573
+ }
574
+ /**
575
+ * §1.1 — **the default rung is `auto-safe`, everywhere.** It is the default in every interactive
576
+ * context, it does NOT vary with the configured model, and there is no separate non-interactive
577
+ * default. What changes without a human is what an escalation *does* (§6.2: an immediate non-zero
578
+ * exit, never an approval), not which rung the session starts on. A context-dependent default
579
+ * would reintroduce exactly the hidden branching this ladder exists to remove.
580
+ */
581
+ export declare const DEFAULT_APPROVAL_RUNG: ApprovalRung;
582
+ /**
583
+ * CFG-27 — resolve the effective {@link ResolvedApprovals} for the active command.
584
+ *
585
+ * There is no defaults *matrix*: §1.1 makes `auto-safe` the default in every context, so this
586
+ * resolver neither detects nor accepts a "context". Precedence is the only thing it decides, and
587
+ * §9.1 splits it in two:
588
+ *
589
+ * - **The scalars — `mode`, `rater`, `raterTimeoutMs` — are replaced** when the per-command value
590
+ * states them and **inherited from the root when it does not**. So the scalar sugar
591
+ * `"code": { "approvals": "bypass" }` is exactly `{ mode: 'bypass' }` merged over the root: it
592
+ * sets the rung and nothing else.
593
+ * - **`deny` and `escalate` never replace: they CONCATENATE across every scope.** A
594
+ * command-specific `deny` *adds to* the root's. Removing an inherited prohibition for one command
595
+ * is deliberately not expressible.
596
+ * - **`allow` is REPLACED when the per-command value states its own, and inherited when it does
597
+ * not.** A per-command scope may therefore narrow what runs unprompted, and may never widen what
598
+ * is prohibited.
599
+ * - **`mcp` (EXT-70 §4.7) follows `allow`, not the restrictive lists**: replaced when the
600
+ * per-command value states it, inherited when it does not. Believing a hint is a PERMISSIVE act
601
+ * in both directions — it can make an `allow` hint entry fire and can make a `deny` hint entry
602
+ * stop firing — so it merges the way the permissive list does, and a per-command scope can
603
+ * narrow the session's trust (`"mcp": {}` believes nothing) but never inherits half of it by
604
+ * accident. Deep-merging the two scopes' `servers` maps was rejected for the same reason: it
605
+ * would leave a deliberately distrustful per-command block silently carrying the root's trust.
606
+ *
607
+ * **The two halves differ because the costs differ (§3.1), not for tidiness.** A missed allow entry
608
+ * escalates and a missed deny entry falls through to the rater — neither is an execution — while a
609
+ * too-broad allow entry *runs, unrated and unprompted*. Concatenating the restrictive lists fails
610
+ * toward a prompt; concatenating the permissive one fails toward an execution, and would leave a
611
+ * deliberately restrictive per-command rung with no way to shed the root's standing grants. Do not
612
+ * "regularize" these three into one policy: the direction each list fails in is the whole design.
613
+ *
614
+ * On the restrictive side the pressure runs the other way (§11.1f). Were the per-command value to
615
+ * replace the root wholesale, the friendliest spelling of "stop asking me about `code`" would also
616
+ * delete every `deny` entry — at the one rung where the deny list and the §8 floor are the only
617
+ * checks left. A prohibition a nested config key can quietly delete is not a hardline.
618
+ *
619
+ * Concatenation order cannot change any outcome (`resolveApprovalRules` consults every deny entry
620
+ * before any escalate entry and every escalate entry before any allow entry), so root-first is a
621
+ * convention for readability — matching `GthAgentRunner.approvalRuleLists`, where the declared
622
+ * entries precede the runtime grants — and never a precedence.
623
+ *
624
+ * Defaults are applied HERE, at the read site, rather than in `DEFAULT_CONFIG` — so the
625
+ * effective-config snapshot the `/config` panel renders never churns (à la GS2-34
626
+ * `injectModelContext` / GS2-63 `output.header`).
627
+ *
628
+ * This is the per-command half. The cross-LAYER half (a project config's lists adding to a global
629
+ * config's rather than replacing them) is the additive-array policy in `config/loader.ts`; both are
630
+ * needed, since either alone still loses a list silently.
631
+ *
632
+ * @param command The active command; selects the per-command block.
633
+ */
634
+ export declare function resolveApprovals(config: Pick<GthConfig, 'commands' | 'approvals'> | undefined, command: GthCommand | undefined): ResolvedApprovals;
635
+ /** A status notice a backend should surface after resolving the shell approval gate. */
636
+ export interface ShellApprovalGateNotice {
637
+ /** Severity to pass to the agent's `statusUpdate` callback. */
638
+ level: StatusLevel;
639
+ /** The user-facing message. */
640
+ message: string;
641
+ }
642
+ /** The resolved shell approval-gate policy: whether to gate, and what to tell the user. */
643
+ export interface ShellApprovalGateDecision {
644
+ /**
645
+ * Whether `run_shell_command` must be wired behind the per-command approval interrupt
646
+ * (langchain's `humanInTheLoopMiddleware` — installed directly on the lean backend, via
647
+ * deepagents' `interruptOn` on the deep one).
648
+ */
649
+ gateShell: boolean;
650
+ /** The notice to surface, when this configuration warrants one. */
651
+ notice?: ShellApprovalGateNotice;
652
+ }
653
+ /**
654
+ * EXT-52 — the ONE shell approval-gate policy both agent backends resolve
655
+ * (`GthLangChainAgent` = lean/default, `GthDeepAgent` = deep). It decides whether the opt-in
656
+ * `run_shell_command` tool is gated behind the per-command approval interrupt, and which status
657
+ * notice (if any) the backend should surface. The backends differ only in HOW they install the
658
+ * interrupt; the policy and its user-facing copy live here so the two can never drift (and so a
659
+ * later rename of this config surface has one place to change).
660
+ *
661
+ * CFG-27 — **the tool is gated whenever it is enabled, at every rung including `bypass`.** CFG-26
662
+ * used to leave it UNGATED under `bypass` outside interactive `code`, which the ladder cannot
663
+ * afford: §2.5 makes the declared **deny list the one check `bypass` keeps**, and a deny entry can
664
+ * only fire if the call reaches `GthAgentRunner.decideToolApproval` — an ungated call never does.
665
+ * Gating unconditionally also keeps the rung switchable mid-session (`/approvals <rung>`), since a
666
+ * tool wired without the interrupt cannot be re-gated without rebuilding the agent.
667
+ *
668
+ * What each rung then does is decided in `decideToolApproval`, not here:
669
+ * • `bypass` — deny list, then approve without prompting or rating.
670
+ * • `read-only`/`write` — deny list, allow-list, else escalate to the human.
671
+ * • `auto-safe`/`full-auto` — deny list, allow-list, then the rater.
672
+ * • With the shell tool disabled — or on a non-dev-tools command (chat/api/…) — nothing is gated
673
+ * and nothing is announced.
674
+ *
675
+ * Shell enablement itself is resolved through {@link getEffectiveDevToolsConfig} +
676
+ * {@link isShellToolEnabled}, so the gate stays in lockstep with where `GthDevToolkit` actually
677
+ * emits the tool; the posture comes from {@link resolveApprovals}, so this and the runner can
678
+ * never disagree about which rung is in force.
679
+ */
680
+ export declare function resolveShellApprovalGate(config: Pick<GthConfig, 'commands' | 'builtInTools' | 'askWriteMode' | 'approvals'> | undefined, command: GthCommand | undefined): ShellApprovalGateDecision;
681
+ export {};