@gaunt-sloth/core 2.0.0-alpha.3 → 2.0.0-alpha.30
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 +52 -20
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/defaults.d.ts +84 -0
- package/dist/config/defaults.js +99 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/filesystem-tools.d.ts +41 -0
- package/dist/config/filesystem-tools.js +56 -0
- package/dist/config/filesystem-tools.js.map +1 -0
- package/dist/config/jsonc.d.ts +12 -0
- package/dist/config/jsonc.js +41 -0
- package/dist/config/jsonc.js.map +1 -0
- package/dist/config/loader.d.ts +246 -0
- package/dist/config/loader.js +1321 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- package/dist/config/profiles.d.ts +68 -0
- package/dist/config/profiles.js +93 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providerKeys.d.ts +69 -0
- package/dist/config/providerKeys.js +69 -0
- package/dist/config/providerKeys.js.map +1 -0
- package/dist/config/schema.d.ts +2999 -0
- package/dist/config/schema.js +1519 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/shell-policy.d.ts +896 -0
- package/dist/config/shell-policy.js +750 -0
- package/dist/config/shell-policy.js.map +1 -0
- package/dist/config/tool-descriptions.d.ts +211 -0
- package/dist/config/tool-descriptions.js +272 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +760 -0
- package/dist/config/types.js +13 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config.d.ts +34 -827
- package/dist/config.js +25 -657
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +46 -0
- package/dist/constants.js +46 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +135 -2
- package/dist/core/GthAbstractAgent.js +496 -30
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +568 -54
- package/dist/core/GthAgentRunner.js +1537 -136
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +115 -0
- package/dist/core/GthLangChainAgent.js +644 -17
- 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/debugCapture.d.ts +75 -0
- package/dist/core/debugCapture.js +101 -0
- package/dist/core/debugCapture.js.map +1 -0
- package/dist/core/gthLeanAgentFactory.d.ts +9 -0
- package/dist/core/gthLeanAgentFactory.js +10 -0
- package/dist/core/gthLeanAgentFactory.js.map +1 -0
- package/dist/core/launchBanner.d.ts +120 -0
- package/dist/core/launchBanner.js +418 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/plainToolIndication.d.ts +15 -0
- package/dist/core/plainToolIndication.js +174 -0
- package/dist/core/plainToolIndication.js.map +1 -0
- package/dist/core/reasoningBlocks.d.ts +60 -0
- package/dist/core/reasoningBlocks.js +98 -0
- package/dist/core/reasoningBlocks.js.map +1 -0
- package/dist/core/refusal.d.ts +53 -0
- package/dist/core/refusal.js +133 -0
- package/dist/core/refusal.js.map +1 -0
- package/dist/core/runStats.d.ts +52 -0
- package/dist/core/runStats.js +118 -0
- package/dist/core/runStats.js.map +1 -0
- package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
- package/dist/core/shell/ShellCommandFailedError.js +68 -0
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
- package/dist/core/shell/abstention.d.ts +88 -0
- package/dist/core/shell/abstention.js +184 -0
- package/dist/core/shell/abstention.js.map +1 -0
- package/dist/core/shell/approvalCapture.d.ts +271 -0
- package/dist/core/shell/approvalCapture.js +108 -0
- package/dist/core/shell/approvalCapture.js.map +1 -0
- package/dist/core/shell/approvalStop.d.ts +84 -0
- package/dist/core/shell/approvalStop.js +119 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/escalationSeverity.d.ts +141 -0
- package/dist/core/shell/escalationSeverity.js +89 -0
- package/dist/core/shell/escalationSeverity.js.map +1 -0
- package/dist/core/shell/framing.d.ts +190 -0
- package/dist/core/shell/framing.js +633 -0
- package/dist/core/shell/framing.js.map +1 -0
- package/dist/core/shell/hardline.d.ts +81 -0
- package/dist/core/shell/hardline.js +607 -0
- package/dist/core/shell/hardline.js.map +1 -0
- package/dist/core/shell/negotiation.d.ts +249 -0
- package/dist/core/shell/negotiation.js +355 -0
- package/dist/core/shell/negotiation.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +44 -4
- package/dist/core/shell/normalize.js +61 -7
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +263 -0
- package/dist/core/shell/openWorld.js +1188 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +935 -0
- package/dist/core/shell/rater.js +1473 -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 +69 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/toolCallRepair/grammar.d.ts +41 -0
- package/dist/core/toolCallRepair/grammar.js +116 -0
- package/dist/core/toolCallRepair/grammar.js.map +1 -0
- package/dist/core/toolCallRepair/index.d.ts +2 -0
- package/dist/core/toolCallRepair/index.js +7 -0
- package/dist/core/toolCallRepair/index.js.map +1 -0
- package/dist/core/toolCallRepair/payload.d.ts +36 -0
- package/dist/core/toolCallRepair/payload.js +341 -0
- package/dist/core/toolCallRepair/payload.js.map +1 -0
- package/dist/core/toolCallRepair/promote.d.ts +45 -0
- package/dist/core/toolCallRepair/promote.js +90 -0
- package/dist/core/toolCallRepair/promote.js.map +1 -0
- package/dist/core/toolDisplay.d.ts +123 -0
- package/dist/core/toolDisplay.js +451 -0
- package/dist/core/toolDisplay.js.map +1 -0
- package/dist/core/toolOutputChannel.d.ts +95 -0
- package/dist/core/toolOutputChannel.js +165 -0
- package/dist/core/toolOutputChannel.js.map +1 -0
- package/dist/core/types.d.ts +294 -12
- package/dist/core/types.js.map +1 -1
- package/dist/history/historyFormat.d.ts +28 -0
- package/dist/history/historyFormat.js +127 -0
- package/dist/history/historyFormat.js.map +1 -0
- package/dist/history/historyStore.d.ts +198 -0
- package/dist/history/historyStore.js +482 -0
- package/dist/history/historyStore.js.map +1 -0
- package/dist/history/recordSession.d.ts +37 -0
- package/dist/history/recordSession.js +56 -0
- package/dist/history/recordSession.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +5 -10
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/deepseek.d.ts +1 -1
- package/dist/providers/deepseek.js +5 -10
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
- package/dist/providers/geminiSchemaSanitizer.js +347 -0
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +52 -0
- package/dist/providers/geminiThinking.js +72 -0
- package/dist/providers/geminiThinking.js.map +1 -0
- package/dist/providers/google-genai.d.ts +1 -1
- package/dist/providers/google-genai.js +12 -11
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.d.ts +1 -1
- package/dist/providers/groq.js +5 -10
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/huggingface.d.ts +25 -0
- package/dist/providers/huggingface.js +69 -0
- package/dist/providers/huggingface.js.map +1 -0
- package/dist/providers/modelCatalog.d.ts +109 -0
- package/dist/providers/modelCatalog.js +245 -0
- package/dist/providers/modelCatalog.js.map +1 -0
- package/dist/providers/modelDiscovery.d.ts +153 -6
- package/dist/providers/modelDiscovery.js +295 -41
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/ollama.d.ts +19 -5
- package/dist/providers/ollama.js +60 -52
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +39 -10
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +4 -5
- package/dist/providers/openrouter.js +20 -35
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.d.ts +1 -1
- package/dist/providers/vertexai.js +12 -11
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +5 -10
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +105 -0
- package/dist/runtime/askStructured.js +120 -0
- package/dist/runtime/askStructured.js.map +1 -0
- package/dist/runtime/conversation.d.ts +60 -0
- package/dist/runtime/conversation.js +151 -0
- package/dist/runtime/conversation.js.map +1 -0
- package/dist/runtime/singleShot.d.ts +25 -5
- package/dist/runtime/singleShot.js +95 -36
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/runtime/structuredOutput.d.ts +104 -0
- package/dist/runtime/structuredOutput.js +393 -0
- package/dist/runtime/structuredOutput.js.map +1 -0
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/aiignoreUtils.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +52 -0
- package/dist/utils/consoleUtils.js +72 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/crashHandler.d.ts +87 -0
- package/dist/utils/crashHandler.js +128 -0
- package/dist/utils/crashHandler.js.map +1 -0
- package/dist/utils/debugDump.d.ts +134 -0
- package/dist/utils/debugDump.js +381 -0
- package/dist/utils/debugDump.js.map +1 -0
- package/dist/utils/debugUtils.d.ts +13 -4
- package/dist/utils/debugUtils.js +36 -13
- package/dist/utils/debugUtils.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +53 -0
- package/dist/utils/displayWidth.js +195 -0
- package/dist/utils/displayWidth.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +24 -2
- package/dist/utils/fileUtils.js +54 -12
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +39 -8
- package/dist/utils/llmUtils.js +76 -8
- package/dist/utils/llmUtils.js.map +1 -1
- package/dist/utils/redactSecrets.d.ts +63 -0
- package/dist/utils/redactSecrets.js +286 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +225 -0
- package/dist/utils/systemPromptNotes.js +340 -0
- package/dist/utils/systemPromptNotes.js.map +1 -0
- package/dist/utils/systemUtils.d.ts +49 -1
- package/dist/utils/systemUtils.js +67 -3
- package/dist/utils/systemUtils.js.map +1 -1
- package/dist/utils/toolMatching.d.ts +30 -0
- package/dist/utils/toolMatching.js +44 -0
- package/dist/utils/toolMatching.js.map +1 -0
- package/dist/utils/untrustedText.d.ts +66 -0
- package/dist/utils/untrustedText.js +80 -0
- package/dist/utils/untrustedText.js.map +1 -0
- package/package.json +22 -6
- package/schema/gsloth-config.schema.json +3134 -0
- 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,6 +1,35 @@
|
|
|
1
|
-
import { GthConfig } from '#src/config.js';
|
|
1
|
+
import { type AllowlistCounts, type ApprovalRung, GthConfig, type McpAnnotationTrustChange, type McpAnnotationTrustView, type ResolvedApprovals, type ToolAnnotationHint } from '#src/config.js';
|
|
2
2
|
import { BaseCheckpointSaver } from '@langchain/langgraph';
|
|
3
|
-
import { AgentResolvers, AgentStreamEvent, GthAgentFactory, GthAgentInterface, GthCommand, Message, StatusUpdateCallback, ToolApprovalCallback } from '#src/core/types.js';
|
|
3
|
+
import { AgentResolvers, AgentStreamEvent, type AttackHaltCallback, GthAgentFactory, GthAgentInterface, GthCommand, GthRunStats, Message, StatusUpdateCallback, ToolApprovalCallback } from '#src/core/types.js';
|
|
4
|
+
import { type ApprovalGrant } from '#src/core/approvals/grants.js';
|
|
5
|
+
import { type ApprovalDecisionCapture } from '#src/core/shell/approvalCapture.js';
|
|
6
|
+
/**
|
|
7
|
+
* GS2-81 — the page carrying WHICH commands honor `agent.backend`, pointed at rather than
|
|
8
|
+
* enumerated in the notice itself.
|
|
9
|
+
*
|
|
10
|
+
* The list belongs in exactly one place. A copy of it inside a runtime string is a second source of
|
|
11
|
+
* truth with nothing pinning it: the first draft of this notice already disagreed with the docs
|
|
12
|
+
* table written in the same commit (it omitted `workflow` agent steps), and no test could tell,
|
|
13
|
+
* because a sentence is not a claim a test can check. The docs table is checkable, and
|
|
14
|
+
* `agentBackendScope.spec.ts` pins that this URL's anchor still resolves to a real heading in it.
|
|
15
|
+
*
|
|
16
|
+
* **A GitHub blob URL, matching the other user-facing runtime doc links in this repo** (the
|
|
17
|
+
* approvals-protection pointer in `config/shell-policy.ts` and the 2.0 migration pointer in
|
|
18
|
+
* `config/schema.ts`) — a running CLI's user has no checkout for a relative path to resolve in.
|
|
19
|
+
*/
|
|
20
|
+
export declare const AGENT_BACKEND_SCOPE_DOCS_URL = "https://github.com/pukeko-robotics/gaunt-sloth/blob/main/docs/configuration/profiles.md#which-commands-honour-it";
|
|
21
|
+
/** Options for {@link GthAgentRunner#init} that qualify the run without changing how it behaves. */
|
|
22
|
+
export interface GthAgentRunnerInitOptions {
|
|
23
|
+
/**
|
|
24
|
+
* GS2-81 — the CLI verb this run belongs to, for messages only, when `command` is deliberately
|
|
25
|
+
* `undefined`. It is a SEPARATE input from `command` because `command` is not a label: it selects
|
|
26
|
+
* the mode prompt (`readModePrompt`), the per-command approvals posture (`resolveApprovals`) and
|
|
27
|
+
* the command-specific filesystem config, so a helper agent that must run on the chat prompt —
|
|
28
|
+
* the `gth pr` change-requirements discovery agent — cannot borrow it to say which verb it serves.
|
|
29
|
+
* Nothing but the wording of a notice reads this.
|
|
30
|
+
*/
|
|
31
|
+
owningCommand?: GthCommand;
|
|
32
|
+
}
|
|
4
33
|
/**
|
|
5
34
|
* Agent simplifies interaction with LLM and reduces it to calling a few methods
|
|
6
35
|
* {@link GthAgentRunner#init} and {@link GthAgentRunner#processMessages}.
|
|
@@ -19,36 +48,96 @@ export declare class GthAgentRunner {
|
|
|
19
48
|
* for non-interactive entrypoints (a scripted `exec` run with no TTY to prompt on).
|
|
20
49
|
*/
|
|
21
50
|
private toolApprovalCallback;
|
|
51
|
+
/**
|
|
52
|
+
* [[TUI-C68]] §6.1 — consumer hook invoked when the rater rates a command an `attack`, so an
|
|
53
|
+
* interactive surface can show the red banner before the run ends. Set via
|
|
54
|
+
* {@link setAttackHaltCallback}; **when unset the runner halts immediately**, which is the
|
|
55
|
+
* behaviour every surface had before a banner existed. A surface that forgets to wire it
|
|
56
|
+
* therefore keeps the halt rather than losing it.
|
|
57
|
+
*/
|
|
58
|
+
private attackHaltCallback;
|
|
22
59
|
/** The command the runner was initialized for; selects which `devTools` config applies. */
|
|
23
60
|
private command;
|
|
24
61
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* gated `run_shell_command` is auto-approved WITHOUT prompting (yolo behaviour) for the rest of
|
|
30
|
-
* this runner's life. Never persisted; defaults OFF. It does NOT disable the hardline floor —
|
|
31
|
-
* catastrophic commands are still refused at exec time in `GthDevToolkit.executeCommand`.
|
|
62
|
+
* GS2-16 — snapshot of the last turn's analytics (token usage + invoked tools), captured from
|
|
63
|
+
* the agent at {@link cleanup} time. Needed because {@link runSingleShot} reads stats AFTER it
|
|
64
|
+
* has already called `cleanup()` (which nulls the agent); interactive callers read live via
|
|
65
|
+
* {@link getRunStats} before cleanup. Defaults to an empty tally.
|
|
32
66
|
*/
|
|
33
|
-
private
|
|
67
|
+
private lastRunStats;
|
|
34
68
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
69
|
+
* CFG-27 — the runtime, session-scoped approvals posture, seeded at {@link init} from
|
|
70
|
+
* {@link resolveApprovals} and thereafter switchable for the session by `/approvals <rung>`.
|
|
71
|
+
* **This field, not the interrupt wiring, is where the rung lives.** The backends wire the
|
|
72
|
+
* interrupt rung-independently, so every tool any rung could gate arrives at the top of
|
|
73
|
+
* {@link decideToolApproval} and is judged against the rung recorded here — which is what makes
|
|
74
|
+
* `/approvals manual` take effect mid-session, and what keeps a config that pre-selects
|
|
75
|
+
* `bypass` switchable back. Never persisted.
|
|
76
|
+
*
|
|
77
|
+
* It does NOT disable the hardline floor — catastrophic commands are still refused at exec time
|
|
78
|
+
* in `GthDevToolkit.executeCommand` under every rung.
|
|
38
79
|
*/
|
|
39
|
-
private
|
|
80
|
+
private sessionApprovals;
|
|
40
81
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
82
|
+
* CFG-26 — the model the AI rater rates with, when `approvals.rater.profile` names an identity
|
|
83
|
+
* profile. Resolved ONCE at {@link init} (never mid-turn) and handed to `rateShellCommand`;
|
|
84
|
+
* `undefined` means no profile is configured and the rater uses the session model.
|
|
44
85
|
*/
|
|
45
|
-
private
|
|
46
|
-
|
|
86
|
+
private raterModel;
|
|
87
|
+
/**
|
|
88
|
+
* EXT-66 — how many rating calls this session gave up on. Counted so the notice can say "3 times
|
|
89
|
+
* this session" rather than repeating an identical line, and so a silent drift toward
|
|
90
|
+
* escalate-everything has a number attached to it.
|
|
91
|
+
*/
|
|
92
|
+
private raterTimeouts;
|
|
93
|
+
/**
|
|
94
|
+
* EXT-71 §3.1/§6 — what the escalation menu granted at run time, for the life of THIS runner
|
|
95
|
+
* instance: {@link ApprovalEntry} objects, never prefixes, and never anything from config (the
|
|
96
|
+
* declared lists are read-only input consulted straight from the posture). Instance-scoped so
|
|
97
|
+
* concurrent sessions (ACP / AG-UI multi-session) cannot stomp each other's approvals.
|
|
98
|
+
*/
|
|
99
|
+
private readonly sessionGrants;
|
|
100
|
+
/**
|
|
101
|
+
* CFG-27 §3 — what the escalation menu's *always reject* choice adds at run time, in the same
|
|
102
|
+
* grammar ([[TUI-C26]] wires that writer; the store speaks it already). The entries DECLARED in
|
|
103
|
+
* `approvals.deny` are not here — they are read-only config input, matched from the posture — and
|
|
104
|
+
* both are handed to the same matcher, so a runtime refusal and a declared one are one list.
|
|
105
|
+
*/
|
|
106
|
+
private denyGrants;
|
|
107
|
+
/**
|
|
108
|
+
* The persisted (`always`) grant store, loaded lazily on first use from
|
|
109
|
+
* `.gsloth/.gsloth-settings/shell-allowlist.json`. Null until a gated call actually needs it, and
|
|
110
|
+
* null when the file cannot be loaded at all (in which case `always` grants degrade to session).
|
|
111
|
+
*/
|
|
112
|
+
private persistedGrants;
|
|
113
|
+
private persistedGrantsLoaded;
|
|
114
|
+
/**
|
|
115
|
+
* [[EXT-29]] §5 — the state of the agent↔rater negotiation at `auto`: the transcript, §5.3's
|
|
116
|
+
* consecutive-rejection counter and the reachability bound. Instance-scoped for the same reason
|
|
117
|
+
* the grant stores are — a concurrent ACP / AG-UI session must not inherit another's argument.
|
|
118
|
+
*/
|
|
119
|
+
private readonly negotiation;
|
|
120
|
+
/**
|
|
121
|
+
* [[TUI-C27]] — the diagnostic record of every gated decision this session made, for
|
|
122
|
+
* `/debug-dump`. Instance-scoped for the same reason the negotiation and the grant stores are: a
|
|
123
|
+
* concurrent ACP / AG-UI session must not inherit another's approvals history, and a dump taken
|
|
124
|
+
* in one must not describe the other.
|
|
125
|
+
*/
|
|
126
|
+
private readonly approvalCaptures;
|
|
127
|
+
/**
|
|
128
|
+
* GS2-81 — whether the caller supplied a backend factory. When it did NOT, this runner is
|
|
129
|
+
* hard-wired to the lean default below, so a config asking for `agent.backend: 'deep'` cannot be
|
|
130
|
+
* honored no matter what it says. Recorded here (rather than inferred later) because by then the
|
|
131
|
+
* fallback has already collapsed both cases into one function.
|
|
132
|
+
*/
|
|
133
|
+
private readonly backendFactorySupplied;
|
|
47
134
|
/**
|
|
48
135
|
* @param agentFactory Produces the {@link GthAgentInterface} the runner drives.
|
|
49
136
|
* Defaults to the lean {@link GthLangChainAgent} (core). `@gaunt-sloth/agent`
|
|
50
137
|
* passes a factory returning a deep `GthDeepAgent` so the same runner can drive a
|
|
51
138
|
* `createDeepAgent` graph without core depending on deepagents.
|
|
139
|
+
* **Omitting it opts the caller out of `agent.backend`** — see {@link init}, which says so out
|
|
140
|
+
* loud rather than letting the key be dropped in silence.
|
|
52
141
|
*/
|
|
53
142
|
constructor(statusUpdate: StatusUpdateCallback, resolvers?: AgentResolvers, agentFactory?: GthAgentFactory);
|
|
54
143
|
/**
|
|
@@ -58,20 +147,156 @@ export declare class GthAgentRunner {
|
|
|
58
147
|
*/
|
|
59
148
|
setToolApprovalCallback(callback: ToolApprovalCallback | null): void;
|
|
60
149
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
150
|
+
* [[TUI-C68]] §6.1 — register the handler that shows the **attack banner**, the one way a human
|
|
151
|
+
* gets past an `attack` verdict. Pass `null` to clear.
|
|
152
|
+
*
|
|
153
|
+
* Separate from {@link setToolApprovalCallback} because it is a separate question with an
|
|
154
|
+
* inverted default: an absent approval callback means *this session has nobody to ask*, and an
|
|
155
|
+
* absent one here means *end the run*. Wiring it is what an interactive surface opts into; every
|
|
156
|
+
* other surface keeps the halt (see {@link attackHaltCallback}).
|
|
157
|
+
*/
|
|
158
|
+
setAttackHaltCallback(callback: AttackHaltCallback | null): void;
|
|
159
|
+
/**
|
|
160
|
+
* §6.1 — **the single seam between an `attack` verdict and the end of the run.** Both rating
|
|
161
|
+
* paths — the §3.2 allow-match tripwire and the ordinary rater decision — go through here, so the
|
|
162
|
+
* banner cannot be present on one and missing on the other, which is the shape of bug that leaves
|
|
163
|
+
* a halt answerable in some sessions and not others with nothing on screen to tell them apart.
|
|
164
|
+
*
|
|
165
|
+
* It returns a decision for the one answer that grants and throws for everything else:
|
|
166
|
+
*
|
|
167
|
+
* - **no callback → throw**, immediately and unchanged. §6.2's rule is that a run with nobody to
|
|
168
|
+
* ask never blocks and never times out into a grant; the way that is guaranteed is that waiting
|
|
169
|
+
* is something only a wired surface can cause.
|
|
170
|
+
* - **`run-anyway` → approve, scope `once`.** Exactly one command runs. `once` is not a default
|
|
171
|
+
* restated: it is what keeps §6.1's three "never"s true. Returning here is also returning from
|
|
172
|
+
* *before* the block that records a sticky grant, so no allow-list entry and no session grant
|
|
173
|
+
* can be written on this path — the next identical call is rated again and reaches this banner
|
|
174
|
+
* again. Nothing here touches the rung, and nothing disables the rater, the escalation or the
|
|
175
|
+
* halt for anything else.
|
|
176
|
+
* - **anything else → throw.** `stop`, and equally a value a surface invents or forgets to
|
|
177
|
+
* return: the grant is one exact answer and everything else is a refusal.
|
|
178
|
+
*/
|
|
179
|
+
private haltOrRunAnyway;
|
|
180
|
+
/**
|
|
181
|
+
* CFG-27 — switch the session-scoped rung (`/approvals <rung>`). Idempotent; returns the NEW
|
|
182
|
+
* rung so the caller can render a notice. Session-scoped only — nothing is written to config,
|
|
183
|
+
* and the declared allow/deny lists are unaffected (they are config input, not session state).
|
|
65
184
|
*/
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
|
|
185
|
+
setSessionApprovalRung(rung: ApprovalRung): ApprovalRung;
|
|
186
|
+
/** CFG-27 — the session's current approvals posture (rung + rater profile + declared lists). */
|
|
187
|
+
getSessionApprovals(): ResolvedApprovals;
|
|
188
|
+
/**
|
|
189
|
+
* [[TUI-C27]] — every gated decision this session made, oldest first, for the `/debug-dump`
|
|
190
|
+
* archive.
|
|
191
|
+
*
|
|
192
|
+
* Threaded by each surface into `writeDebugDump`, exactly as `agent.lastModelRequest` is: the
|
|
193
|
+
* writer redacts it with the same pass it applies to every other artifact, and a surface that
|
|
194
|
+
* does not thread it simply omits the file.
|
|
195
|
+
*/
|
|
196
|
+
getApprovalCaptures(): ApprovalDecisionCapture[];
|
|
197
|
+
/**
|
|
198
|
+
* CFG-26 — the allow-list sizes for the `/approvals` display: how many command prefixes the
|
|
199
|
+
* human has trusted this session, and how many are persisted in the project file.
|
|
200
|
+
*
|
|
201
|
+
* READ-ONLY BY CONSTRUCTION: it reports the persisted count only when the store has ALREADY
|
|
202
|
+
* been loaded (or persistence is on and it can be read), and never through a path that would
|
|
203
|
+
* CREATE the store as a side effect of showing a display — a status command must not mutate
|
|
204
|
+
* session state. `always: undefined` therefore means "not loaded / persistence off", which the
|
|
205
|
+
* caller renders as `—` rather than a misleading `0`.
|
|
206
|
+
*/
|
|
207
|
+
getAllowlistCounts(): AllowlistCounts;
|
|
208
|
+
/**
|
|
209
|
+
* CFG-27 — the session's deny entries for display: the declared `approvals.deny` entries
|
|
210
|
+
* (rendered one line each) followed by whatever the escalation menu's *always reject* added at
|
|
211
|
+
* run time. Both refuse a call, so both are shown.
|
|
212
|
+
*/
|
|
213
|
+
getDenylist(): string[];
|
|
214
|
+
/**
|
|
215
|
+
* §3/§4.7.4 — **the grants themselves**, for an approvals view that shows *what* was granted,
|
|
216
|
+
* *when*, and *under which effective annotations*. The counterpart of {@link getAllowlistCounts},
|
|
217
|
+
* which answers only how many.
|
|
218
|
+
*
|
|
219
|
+
* The declared config lists are deliberately NOT here. They are something a human wrote and
|
|
220
|
+
* reviewed, they carry no `grantedAt` and no scope, and `getAllowlistCounts` already counts them
|
|
221
|
+
* alongside these; mixing them in would present a config line as something the session granted.
|
|
222
|
+
*
|
|
223
|
+
* **Read-only in both senses.** It never loads the persisted store — same rule as
|
|
224
|
+
* {@link getAllowlistCounts}: a display must not create the store in order to show it, so a
|
|
225
|
+
* session that has not yet needed the file lists its session grants alone. And every grant is
|
|
226
|
+
* **deep-copied on the way out**, because the stores hand back their live records: the copy on the
|
|
227
|
+
* way in is what makes a snapshot private to its grant, and handing the same object to a renderer
|
|
228
|
+
* would put what the gate matches against one property assignment away from any consumer.
|
|
229
|
+
*/
|
|
230
|
+
getGrants(): ApprovalGrant[];
|
|
231
|
+
/**
|
|
232
|
+
* §4.7.1 — **which of each server's annotation hints this session believes**, for display.
|
|
233
|
+
*
|
|
234
|
+
* Every key either side names is listed: a configured `mcpServers` key with no policy of its own
|
|
235
|
+
* (which resolves through `defaults`), and a policy key naming a server the config does not have
|
|
236
|
+
* (which is what a typo looks like). Resolution is {@link trustedAnnotationHints}, the same
|
|
237
|
+
* function the gate derives effective annotations through, so the display cannot claim a
|
|
238
|
+
* relationship the gate does not act on.
|
|
239
|
+
*/
|
|
240
|
+
getMcpAnnotationTrust(): McpAnnotationTrustView;
|
|
241
|
+
/**
|
|
242
|
+
* §4.7.1 — **believe, or stop believing, specific hints from one server**, for the life of this
|
|
243
|
+
* session. The runtime half of `approvals.mcp.servers.<key>.trustAnnotations` (§9), so a user can
|
|
244
|
+
* do from the TUI what they can do in config.
|
|
245
|
+
*
|
|
246
|
+
* **Per hint, never per server.** `hints` names the hints this call moves and leaves every other
|
|
247
|
+
* hint of that server's exactly as it was, because believing a server's `readOnlyHint` while
|
|
248
|
+
* disbelieving its `openWorldHint` is a coherent position and the common one. A "trust this
|
|
249
|
+
* server" flag is the design §4.7.1 rejects.
|
|
250
|
+
*
|
|
251
|
+
* **The previous set is what was IN FORCE, resolved through `defaults`.** A server not named
|
|
252
|
+
* under `servers` inherits `defaults`, and naming it makes it state its relationship in full (§9)
|
|
253
|
+
* — so seeding from the empty set would mean that believing one more hint silently withdrew every
|
|
254
|
+
* hint `defaults` had granted, which is a weakening the user did not ask for and would invalidate
|
|
255
|
+
* their grants.
|
|
256
|
+
*
|
|
257
|
+
* **Session-scoped only.** Nothing is written to config: the declared block is read-only input
|
|
258
|
+
* (§9.1), exactly as the rung is.
|
|
259
|
+
*
|
|
260
|
+
* A trusted external annotation still never grants more than the same annotation grants one of
|
|
261
|
+
* our own built-ins — that holds in `core/approvals/annotations.ts` by construction, and this
|
|
262
|
+
* changes only which hints are read.
|
|
263
|
+
*/
|
|
264
|
+
setMcpAnnotationTrust(server: string, hints: readonly ToolAnnotationHint[], believe: boolean): McpAnnotationTrustChange;
|
|
265
|
+
/**
|
|
266
|
+
* §4.7.4 — which of this server's saved approvals the trust now in force weakens, for the notice
|
|
267
|
+
* that reports a trust change. **It predicts; it never removes.** The removal stays where Task
|
|
268
|
+
* 4 put it — at the call being decided — because that is the only moment the tool's declaration
|
|
269
|
+
* can be read for certain; here a server that is merely offline declares nothing and would read
|
|
270
|
+
* as having weakened everything.
|
|
271
|
+
*
|
|
272
|
+
* It compares through the same two functions the gate does: the effective-annotation source built
|
|
273
|
+
* from the posture as it stands *after* the change, and `annotationWeakenings`. A second
|
|
274
|
+
* comparison written for the display is how a warning comes to describe a rule the gate does not
|
|
275
|
+
* have.
|
|
276
|
+
*/
|
|
277
|
+
private grantsWeakenedByCurrentTrust;
|
|
69
278
|
/**
|
|
70
279
|
* Init is split into a separate method. This may create a number of connections,
|
|
71
280
|
* and we'd better have an instance by that moment, for the case things will go wrong,
|
|
72
281
|
* so we can wrap init into try-catch and then call {@link #cleanup} within finally.
|
|
73
282
|
*/
|
|
74
|
-
init(command: GthCommand | undefined, configIn: GthConfig, checkpointSaver?: BaseCheckpointSaver | undefined): Promise<void>;
|
|
283
|
+
init(command: GthCommand | undefined, configIn: GthConfig, checkpointSaver?: BaseCheckpointSaver | undefined, options?: GthAgentRunnerInitOptions): Promise<void>;
|
|
284
|
+
/**
|
|
285
|
+
* GS2-81 — `agent.backend` is a COMMAND-SCOPED key, and this is where a command opts out of it.
|
|
286
|
+
*
|
|
287
|
+
* A caller that hands the runner no factory gets the lean {@link GthLangChainAgent}, whatever the
|
|
288
|
+
* config asked for: `agent.backend: 'deep'` is then accepted by the schema, resolved into the
|
|
289
|
+
* config, and dropped on the floor. `gth review` and `gth pr` are in exactly that position — the
|
|
290
|
+
* review module builds its runner without one, and `@gaunt-sloth/review` does not depend on
|
|
291
|
+
* `@gaunt-sloth/agent`, so the deep backend is out of its reach — as is the `gth pr` change-
|
|
292
|
+
* requirements discovery agent.
|
|
293
|
+
*
|
|
294
|
+
* The check lives HERE, on the `agentFactory ?? lean` fallback itself, rather than in a list of
|
|
295
|
+
* verbs that cannot honor the key: a list is a second source of truth that goes stale the first
|
|
296
|
+
* time someone adds a command, whereas anything that reaches this fallback is by construction a
|
|
297
|
+
* run the key cannot reach. `lean` and the unset default need no warning — that IS what runs.
|
|
298
|
+
*/
|
|
299
|
+
private warnIfBackendCannotBeHonored;
|
|
75
300
|
/**
|
|
76
301
|
* processMessages deals with both streaming and non-streaming approaches.
|
|
77
302
|
*/
|
|
@@ -91,44 +316,315 @@ export declare class GthAgentRunner {
|
|
|
91
316
|
* streamed across all resume turns (empty when nothing was resumed).
|
|
92
317
|
*
|
|
93
318
|
* No-ops (returns '') when the agent does not support interrupts (`getPendingToolInterrupts`/
|
|
94
|
-
* `streamResume` absent)
|
|
319
|
+
* `streamResume` absent) — that is the only exemption. As of EXT-52 BOTH backends gate
|
|
320
|
+
* `run_shell_command` and expose the interrupt surface, so the lean (default) agent is now
|
|
321
|
+
* exactly the agent this loop serves; only an agent implementation without those methods
|
|
322
|
+
* (e.g. a test double) skips it.
|
|
95
323
|
*/
|
|
96
324
|
private resolveToolInterrupts;
|
|
97
325
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
326
|
+
* EXT-71 §3.1/§3.2, EXT-70 §4.7.5 — the subject a pending tool call presents to the rule matcher.
|
|
327
|
+
*
|
|
328
|
+
* A gated `run_shell_command` is a **shell** subject and nothing else: it is matched by `shell`
|
|
329
|
+
* entries, against the command. It is deliberately NOT also offered as a `tool` subject named
|
|
330
|
+
* `run_shell_command`, which would create a second allow path to every shell command carrying a
|
|
331
|
+
* different §3.2 `rate` default and a match that never saw the command it was approving.
|
|
104
332
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
333
|
+
* **Everything else splits by provenance**, which is the distinction §4.7.1 rests on: `tool` is
|
|
334
|
+
* the TRUSTED provenance, read verbatim, so an MCP tool arriving as one would be asking the
|
|
335
|
+
* trusted path for a third party's annotations — a gate any server can opt itself out of. Every
|
|
336
|
+
* MCP-namespaced name therefore becomes an `mcpTool` subject carrying the user's own `mcpServers`
|
|
337
|
+
* key, and one whose server cannot be resolved stays an `mcpTool` subject under an unnameable
|
|
338
|
+
* server rather than falling back to `tool` (see `approvalSubjectForToolName`).
|
|
339
|
+
*
|
|
340
|
+
* **The host (§4.7.4)** is attached here, so the one subject the whole decision runs on carries
|
|
341
|
+
* it: the rule matcher treats a `host` on an entry as an additional exact-match condition, and a
|
|
342
|
+
* grant the menu writes records it. A call naming no single host has none, which fails toward a
|
|
343
|
+
* prompt at both sites.
|
|
344
|
+
*
|
|
345
|
+
* Widening which tools the gate actually suspends on is still [[EXT-30]]; this decides what a
|
|
346
|
+
* suspended call *is* whenever one arrives.
|
|
347
|
+
*
|
|
348
|
+
* @param hosts Every distinct host the call's arguments name ({@link toolCallHosts}).
|
|
349
|
+
*/
|
|
350
|
+
private approvalSubjectFor;
|
|
351
|
+
/**
|
|
352
|
+
* §4.7.5 — the user's own `mcpServers` keys, the only identity a server has here. Own enumerable
|
|
353
|
+
* keys via `Object.keys`, so nothing inherited can pose as a configured server.
|
|
354
|
+
*/
|
|
355
|
+
private configuredMcpServerKeys;
|
|
356
|
+
/**
|
|
357
|
+
* EXT-70 §4.7.1 — the source a `hint` entry reads a call's EFFECTIVE annotations through, built
|
|
358
|
+
* from the session's `approvals.mcp` block and the two declared-annotation lookups.
|
|
359
|
+
*
|
|
360
|
+
* Built per decision rather than cached at {@link init}, for two reasons that both bite: the
|
|
361
|
+
* agent registers its tools *inside* `agent.init()`, so an init-time snapshot would be empty; and
|
|
362
|
+
* a re-init re-resolves the tool list, which for MCP may hand back different declarations.
|
|
363
|
+
*
|
|
364
|
+
* The two lookups are deliberately different in kind. `builtIn` reads OUR OWN authored table and
|
|
365
|
+
* never the bound tool list — the bound list contains every server's tools, and a `builtIn`
|
|
366
|
+
* lookup over it would read a third party's declaration through the trusted-verbatim path.
|
|
367
|
+
* `mcp` reads what the servers declared, keyed by the registered tool name so the server key is
|
|
368
|
+
* never split apart and re-joined differently.
|
|
369
|
+
*/
|
|
370
|
+
private effectiveToolAnnotationSource;
|
|
371
|
+
/**
|
|
372
|
+
* Decide a single pending tool call. Spec order — **deny → bypass → escalate → allow → rater →
|
|
373
|
+
* human prompt**, with the hardline floor at exec time regardless. The two adjacencies that carry
|
|
374
|
+
* the design are that deny comes BEFORE `bypass` and escalate comes AFTER it:
|
|
375
|
+
*
|
|
376
|
+
* 1. **deny** (§3) — a declared entry or a runtime *always reject* grant is refused with no
|
|
377
|
+
* prompt and no rating call. It is consulted FIRST, and it is the one
|
|
378
|
+
* check that **still applies under `bypass`**: choosing `bypass` says *"stop asking me"*, not
|
|
379
|
+
* *"forget what I told you never to do"*. A deny entry MAY match a compound command, because a
|
|
380
|
+
* prohibition that catches something unresolvable errs in the direction that costs nothing.
|
|
381
|
+
* 2. **`bypass`** — the gate is off for this session; approve at scope `once`.
|
|
382
|
+
* 3. **escalate** (§3.2) — a declared entry always asks the human, whatever the rung would have
|
|
383
|
+
* done, **including outranking the automatic grants of `manual` and `write`** and any allow
|
|
384
|
+
* entry that also matched. It goes straight to the human with **no rating call**, and it never
|
|
385
|
+
* enters the `auto` negotiation. It is **inert at `bypass`**, which is why it sits below
|
|
386
|
+
* the rung check: the rung chosen for this session wins, and a stop that must survive `bypass`
|
|
387
|
+
* is a deny entry and only that.
|
|
388
|
+
* 4. **allow** (§3, §3.2) — a declared entry or a grant the human made at an earlier prompt this
|
|
389
|
+
* session (or persisted), matched against the whole normalized command and only when that
|
|
390
|
+
* command statically resolves. An allow match settles the human's part: no prompt. Whether the rater
|
|
391
|
+
* still reviews the call is the entry's own `rate` (§3.2) — honored at the rater rungs and
|
|
392
|
+
* inert at the deterministic ones, so no entry can smuggle a model call into `manual` or
|
|
393
|
+
* `write` — and a rated allow match is a TRIPWIRE, not a re-adjudication
|
|
394
|
+
* ({@link mapAllowMatchedVerdictToAction}).
|
|
395
|
+
* 5. **auto-rater** (`assisted` / `auto` only) — `safe` approves, `destructive` and
|
|
396
|
+
* `catastrophic` escalate, and `attack` HALTS the run ({@link AttackHaltError}). The other
|
|
397
|
+
* three rungs consult no model at all. A command whose target the gate cannot statically
|
|
398
|
+
* resolve is rated **exactly like any other** ([[EXT-81]]), with a neutral note in the rating
|
|
399
|
+
* prompt naming the shape the parser saw. It used to skip the call and be refused straight
|
|
400
|
+
* back to the model instead; §6.1's rule is that a deterministic layer fires only where it is
|
|
401
|
+
* confident something is a threat, and a parser reporting it could not read a string has
|
|
402
|
+
* detected nothing. At those same two rungs a **tool**
|
|
403
|
+
* call is instead floored deterministically by §4.7.3's open-world rule
|
|
404
|
+
* ({@link openWorldToolFloorReason} into {@link applyDestructiveFloor} — the one floor the
|
|
405
|
+
* shell path also reaches): a call whose effective `openWorldHint` is true is `destructive`,
|
|
406
|
+
* whatever its `readOnlyHint` says.
|
|
407
|
+
* 6. **human prompt** — the approval callback; when the human grants `session`/`always` scope,
|
|
408
|
+
* **that command** is recorded as an `exact` entry (§3.1/§6 — the menu never widens), so the
|
|
409
|
+
* same command stops re-prompting and a longer variant of it still asks.
|
|
410
|
+
*
|
|
411
|
+
* §6.2 — where no human can answer (CI, a one-shot run, a server), an escalation is **not** a
|
|
412
|
+
* rejection handed back to the model: it is an immediate non-zero exit
|
|
413
|
+
* ({@link NonInteractiveEscalationError}) carrying the command, the rating and its reason. No
|
|
414
|
+
* prompt, no waiting, and never a timeout into approval. Declaring commands in `approvals.allow`
|
|
415
|
+
* is the supported way to make a pipeline pass.
|
|
108
416
|
*
|
|
109
417
|
* Hardline catastrophic commands remain refused at exec time regardless of any approval here
|
|
110
418
|
* (defense in depth in `GthDevToolkit.executeCommand`), so an allow-listed `rm -rf /` still
|
|
111
419
|
* cannot run.
|
|
420
|
+
*
|
|
421
|
+
* **Step 0 is the rung.** The backends wire the interrupt over every tool ANY rung could gate,
|
|
422
|
+
* because the graph is built once and `/approvals <rung>` moves the rung under it for the rest of
|
|
423
|
+
* the session. So a call arriving here has not yet been judged against the rung in force: this is
|
|
424
|
+
* where that happens, on `sessionApprovals.rung`, which a mid-session switch has already updated.
|
|
425
|
+
* A call the live rung does not gate is approved on the spot — no rule matching, no rating, no
|
|
426
|
+
* prompt — which is what keeps `assisted`, `auto` and `bypass` behaving exactly as they did
|
|
427
|
+
* when the interrupt held the shell alone. It sits ABOVE the deny check for the same reason: an
|
|
428
|
+
* ungated call never reached this method at all before, so a deny entry could not fire on one, and
|
|
429
|
+
* a security fix for two rungs is not the place to change that. (The shell is gated at every rung
|
|
430
|
+
* whenever the shell gate is on, so §2.5's rule that the deny list survives `bypass` is untouched.)
|
|
112
431
|
*/
|
|
113
432
|
private decideToolApproval;
|
|
114
|
-
/** Whether the EXT-10 LLM-as-judge safety gate is enabled for the active command's config. */
|
|
115
|
-
private isShellJudgeOn;
|
|
116
|
-
/** Whether the EXT-9 Tier-2 allow-list is enabled for the active command's devTools config. */
|
|
117
|
-
private isShellAllowlistOn;
|
|
118
433
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
434
|
+
* [[TUI-C27]] — {@link decideToolApprovalInner} with the record closed off on EVERY exit.
|
|
435
|
+
*
|
|
436
|
+
* The final action is written here rather than at each of the decision's many returns, because
|
|
437
|
+
* "what became of the call" is one fact with one source: what this method returns or throws. A
|
|
438
|
+
* per-return assignment is a list that a new branch joins without noticing, and the branch that
|
|
439
|
+
* would be forgotten is the one that ends the run.
|
|
122
440
|
*/
|
|
123
|
-
private
|
|
124
|
-
/**
|
|
125
|
-
private
|
|
441
|
+
private recordedDecision;
|
|
442
|
+
/** The decision itself; {@link decideToolApproval} wraps it with §5.3's reset. */
|
|
443
|
+
private decideToolApprovalInner;
|
|
126
444
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
445
|
+
* [[TUI-C27]] — attribute the deciding stage and hand the decision straight back.
|
|
446
|
+
*
|
|
447
|
+
* A one-liner so a stage can be recorded ON the `return` that carries it rather than on the line
|
|
448
|
+
* above: two statements let an early return be added between them, and the record would then name
|
|
449
|
+
* a stage that did not decide.
|
|
450
|
+
*/
|
|
451
|
+
private stage;
|
|
452
|
+
/**
|
|
453
|
+
* One rating call, with EXT-66's timeout reporting attached. Extracted so the §3.2 tripwire (a
|
|
454
|
+
* rated allow match) and the ordinary rater path cannot drift apart in WHAT they hand the rater —
|
|
455
|
+
* only in what they do with the answer.
|
|
456
|
+
*/
|
|
457
|
+
private rateCommand;
|
|
458
|
+
/**
|
|
459
|
+
* EXT-58 (§4.3/§4.4) — the built-in tools already granted at the session's CURRENT rung, as
|
|
460
|
+
* names plus one-line locally-authored descriptions, for the rater prompt.
|
|
461
|
+
*
|
|
462
|
+
* Two filters make this safe to place outside the rater's fenced untrusted block:
|
|
463
|
+
* - the names come from what the agent actually registered
|
|
464
|
+
* ({@link GthAgentInterface.getRegisteredToolNames}), so the rater can only ever offer a tool
|
|
465
|
+
* this session has;
|
|
466
|
+
* - the descriptions come from core's own `BUILT_IN_TOOL_SUMMARIES` table, so no MCP, custom or
|
|
467
|
+
* A2A tool's own (attacker-influenceable) description can reach the prompt.
|
|
468
|
+
*
|
|
469
|
+
* Empty when the agent does not expose its tools — the rater then gets no list and, per the
|
|
470
|
+
* prompt, offers nothing.
|
|
471
|
+
*/
|
|
472
|
+
private getGrantedBuiltInTools;
|
|
473
|
+
/**
|
|
474
|
+
* §3/§3.3 — the three rule lists this session decides by: the DECLARED entries from config
|
|
475
|
+
* (read-only input) concatenated with the runtime grants the escalation menu made. One set of
|
|
476
|
+
* lists, handed to the one comparison engine; the concatenation cannot change any outcome
|
|
477
|
+
* because `resolveApprovalRules` consults every deny entry before any escalate entry and every
|
|
478
|
+
* escalate entry before any allow entry.
|
|
479
|
+
*
|
|
480
|
+
* The persisted store is loaded here rather than at {@link init} — lazily, once per instance, and
|
|
481
|
+
* NEVER at `bypass`, where the allow list is moot and a session that has switched the gate off
|
|
482
|
+
* should not be reading or rewriting the project's grant file.
|
|
483
|
+
*/
|
|
484
|
+
private approvalRuleLists;
|
|
485
|
+
/**
|
|
486
|
+
* Lazily load (once per instance) the persisted `always` grant store.
|
|
487
|
+
*
|
|
488
|
+
* CFG-27 removed the `persistAllowlist` switch: §3 makes persistence a per-decision choice in
|
|
489
|
+
* the escalation menu (`approve` forgets, `always approve` persists), and a global "never
|
|
490
|
+
* persist" setting would only duplicate a keystroke. Returns null when the store cannot be
|
|
491
|
+
* loaded at all, in which case `always` grants degrade to `session` (in-memory only).
|
|
492
|
+
*
|
|
493
|
+
* The v1→v2 migration notice is routed to `statusUpdate` from here, which is the only place that
|
|
494
|
+
* knows how to reach the user.
|
|
495
|
+
*/
|
|
496
|
+
private getPersistedGrants;
|
|
497
|
+
/**
|
|
498
|
+
* §3.1/§4.7.4/§6 — **the grant a sticky choice would write for this call**, or `undefined` when
|
|
499
|
+
* none is on offer. The one place that question is answered, so the menu's *this is what will be
|
|
500
|
+
* stored* line (§6) and the store can never disagree.
|
|
501
|
+
*
|
|
502
|
+
* - **A shell call** records the command itself as an `exact` entry (§3.1) — never a prefix,
|
|
503
|
+
* never a pattern. One that does not statically resolve (composition, substitution,
|
|
504
|
+
* redirection) is not on offer: no allow entry of any matcher matches such a command, so the
|
|
505
|
+
* entry would be inert, and an inert entry sitting in a list §3 requires to be inspectable
|
|
506
|
+
* tells the user something is in force when nothing is.
|
|
507
|
+
* - **A tool call** records identity — the tool, its server, and the host where the call carries
|
|
508
|
+
* one (§4.7.4, {@link toolGrantEntry}) — never arguments, which would produce a grant that
|
|
509
|
+
* never matches twice. A call naming no host records the tool alone, which is §6's own example
|
|
510
|
+
* (*always approve `mcp__jira__create_issue`*, where no host is involved); what keeps that from
|
|
511
|
+
* being unbounded is §3.2's default that a tool entry is still `rate: true`, so the rater goes
|
|
512
|
+
* on seeing every call's full arguments.
|
|
513
|
+
*
|
|
514
|
+
* Four cases have **no grant on offer at all**, each fail-closed:
|
|
515
|
+
*
|
|
516
|
+
* 1. **`bypass`** — the gate is off for this session and nothing is remembered from it.
|
|
517
|
+
* 2. **`run_shell_command` arriving as a tool subject.** That is what a shell call with no
|
|
518
|
+
* readable `command` argument presents as, and it names no host, so without this it would take
|
|
519
|
+
* the tool-only arm and write a `{"type":"tool","pattern":"run_shell_command"}` grant that
|
|
520
|
+
* auto-approves every future call whose command cannot even be read. This exclusion is what
|
|
521
|
+
* stops that, not a side effect of anything else, and it must survive [[EXT-30]] widening the
|
|
522
|
+
* gate.
|
|
523
|
+
* 3. **A call naming more than one distinct host.** The grammar has no entry for it. `host` is a
|
|
524
|
+
* single optional string on every tool arm of `approvalEntrySchema`, and every arm is a
|
|
525
|
+
* `z.strictObject`, so recording the *set* is not a policy this code may choose — a `hosts`
|
|
526
|
+
* array is an unrecognized-key error, and writing one would be a §3.1 grammar change. Of the
|
|
527
|
+
* two entries that would parse, the host-bound one displays a bound the grant does not have,
|
|
528
|
+
* which §6 forbids (the menu shows exactly what will be stored). And a grammar that did record
|
|
529
|
+
* the set, matching only when all of it recurred, would fail §4.7.4's opening test anyway: a
|
|
530
|
+
* tool whose host set varies per call would get a grant that never matches a second time — not
|
|
531
|
+
* a narrower grant, the useless one §4.7.4 rejects by name.
|
|
532
|
+
*
|
|
533
|
+
* **What this arm does not claim.** It is not a narrowing. A hostless entry imposes no host
|
|
534
|
+
* condition at all (`resolveApprovalRules`), so the tool-only grant that any host-less call to
|
|
535
|
+
* the same tool produces already auto-approves a multi-host one. Refusing here withholds a
|
|
536
|
+
* grant; it does not close a hole, and the reason to keep it is the grammar above rather than
|
|
537
|
+
* any breadth it prevents. Asserted, so this cannot drift back into a claim the system does not
|
|
538
|
+
* support.
|
|
539
|
+
* 4. **An MCP call whose server could not be resolved** ({@link toolGrantEntry} returns `null`) —
|
|
540
|
+
* a call nobody can attribute is not one anything can remember.
|
|
541
|
+
*/
|
|
542
|
+
private stickyGrantFor;
|
|
543
|
+
/**
|
|
544
|
+
* [[TUI-C26]] §6 — **the entry the escalation menu's *always reject* choice would record**, or
|
|
545
|
+
* `undefined` when the grammar cannot hold one. The deny mirror of {@link stickyGrantFor}, and a
|
|
546
|
+
* separate function rather than a flag on it, because the two answer different questions.
|
|
547
|
+
*
|
|
548
|
+
* **Nearly every reason an allow entry is withheld does not apply here.** §3 has one rule for
|
|
549
|
+
* this and it runs the other way — *undecidable is a non-match on the allow side and a match on
|
|
550
|
+
* the deny side* — so:
|
|
551
|
+
*
|
|
552
|
+
* - **A command that does not statically resolve gets an entry.** `stickyGrantFor` refuses one
|
|
553
|
+
* because no allow entry of any matcher would ever match it, making the entry inert; a deny
|
|
554
|
+
* entry for the same command is matched against the whole normalized command *and* every
|
|
555
|
+
* segment a shell would run, so it is the opposite of inert.
|
|
556
|
+
* - **A `catastrophic` verdict changes nothing.** §4.2 withdraws the sticky grants there; it says
|
|
557
|
+
* nothing about refusals, and refusing more is never the direction that needs withdrawing.
|
|
558
|
+
* - **`bypass` changes nothing either**, and that is a positive statement rather than a gap. Deny
|
|
559
|
+
* is resolved at step (1) of {@link decideToolApprovalInner}, *before* the `bypass` return, so
|
|
560
|
+
* a recorded refusal is in force at every rung — which is why this does not copy the allow
|
|
561
|
+
* side's `bypass` guard.
|
|
562
|
+
* - **A call naming several hosts gets the host-less entry.** On the allow side that would show a
|
|
563
|
+
* bound the grant does not have; here the entry covers every host of that tool, which is
|
|
564
|
+
* broader than the call and safe in the direction breadth is safe. The menu shows exactly that
|
|
565
|
+
* entry, so the breadth is on screen rather than inferred.
|
|
566
|
+
* - **`run_shell_command` arriving as a TOOL subject gets a tool entry** — a shell call whose
|
|
567
|
+
* `command` argument cannot even be read. On the allow side that entry would auto-approve every
|
|
568
|
+
* future unreadable shell call, which is why it is excluded there; as a refusal it stops the
|
|
569
|
+
* shell tool for the session, and the dialog says so in the words the entry is written in.
|
|
570
|
+
*
|
|
571
|
+
* The one genuine exclusion is an **MCP call whose server could not be attributed**
|
|
572
|
+
* ({@link toolGrantEntry} returns `null`): the grammar's `server` cannot be the empty string, so
|
|
573
|
+
* the entry would be dropped by its own validator and the human would be told a refusal had been
|
|
574
|
+
* recorded when none was. A shell command that normalizes to nothing is excluded for the same
|
|
575
|
+
* reason — an empty `pattern` is not a legal entry.
|
|
576
|
+
*/
|
|
577
|
+
private denyEntryFor;
|
|
578
|
+
/**
|
|
579
|
+
* §6 — record the menu's *always reject* choice, for the life of this runner instance.
|
|
580
|
+
*
|
|
581
|
+
* It lands in the same store `approvals.deny` entries are matched from ({@link approvalRuleLists}
|
|
582
|
+
* concatenates the two), so a refusal the human made at the prompt and one they wrote in their
|
|
583
|
+
* config are one list to the matcher and one list to `/approvals`.
|
|
584
|
+
*
|
|
585
|
+
* **Session-lifetime, and there is nothing else to choose.** There is no persisted deny file;
|
|
586
|
+
* whether there should be is a question about a file users live with, not about this prompt. What
|
|
587
|
+
* the surfaces must not do is say otherwise — a confirmation promising a persistence that did not
|
|
588
|
+
* happen is §6's *offered and then refused* with the evidence hidden.
|
|
589
|
+
*/
|
|
590
|
+
private recordDenial;
|
|
591
|
+
/**
|
|
592
|
+
* §3.1/§6 — record a human-granted approval at the given scope. `once` remembers nothing.
|
|
593
|
+
* `session` adds the entry to the in-memory store; `always` additionally persists it (falling
|
|
594
|
+
* back to session-only when the file cannot be written).
|
|
595
|
+
*
|
|
596
|
+
* What is recorded was decided by {@link stickyGrantFor} and shown to the human before they
|
|
597
|
+
* answered; this only stamps it with when and at what scope.
|
|
130
598
|
*/
|
|
131
599
|
private recordApproval;
|
|
600
|
+
/**
|
|
601
|
+
* §4.7.4 — **drop a tool grant the tool has since weakened out from under, with a notice naming
|
|
602
|
+
* the tool, the server and the hint that moved.**
|
|
603
|
+
*
|
|
604
|
+
* The human approved a tool *as annotated*; a tool that re-annotates itself into a more dangerous
|
|
605
|
+
* shape is a different proposition wearing the same name, so the grant is invalidated and the next
|
|
606
|
+
* call prompts again. Only a **trusted** server can produce a weakening — an untrusted server's
|
|
607
|
+
* effective set is the constant fail-closed default (§4.7.1) and cannot move — which is exactly
|
|
608
|
+
* where it matters, since the trusted server is the one whose rug-pull would otherwise ride an
|
|
609
|
+
* existing grant.
|
|
610
|
+
*
|
|
611
|
+
* **Scoped to the call being decided, never a sweep of the store.** A sweep would read every held
|
|
612
|
+
* grant against a source that can only answer for the tools registered right now, so a server that
|
|
613
|
+
* happened to be offline would read as having weakened everything it ever declared — and the
|
|
614
|
+
* grants would be deleted for it.
|
|
615
|
+
*
|
|
616
|
+
* **The scope is every grant that could auto-approve THIS call, which is at most two.** A grant
|
|
617
|
+
* with no `host` imposes no host condition, so it matches a call that carries one; looking up only
|
|
618
|
+
* the entry this call would grant (`host` included) would miss the tool-only grant that is about
|
|
619
|
+
* to auto-approve it, and the weakening would ride straight through — the exact failure §4.7.4
|
|
620
|
+
* exists to stop. The host-bound entry of a DIFFERENT host is deliberately not a candidate: it
|
|
621
|
+
* does not match this call either, so this call's annotations say nothing about it.
|
|
622
|
+
*
|
|
623
|
+
* **Only allow-side grants.** A weakening makes a tool more dangerous, so dropping an *always
|
|
624
|
+
* reject* over one would be the unsafe direction: the reason to withdraw an approval is the reason
|
|
625
|
+
* to keep a refusal.
|
|
626
|
+
*/
|
|
627
|
+
private invalidateWeakenedGrants;
|
|
132
628
|
/**
|
|
133
629
|
* Event-stream counterpart to {@link processMessages}: drives the agent's typed
|
|
134
630
|
* {@link AgentStreamEvent} path using the runner's own thread-bound `runConfig`, so a
|
|
@@ -144,7 +640,7 @@ export declare class GthAgentRunner {
|
|
|
144
640
|
* leaves the graph suspended on a `humanInTheLoopMiddleware` interrupt rather than
|
|
145
641
|
* completing. This is the event-stream counterpart to the readline path's
|
|
146
642
|
* {@link resolveToolInterrupts}: it drains any pending interrupts through
|
|
147
|
-
* {@link decideToolApproval} (allow-list →
|
|
643
|
+
* {@link decideToolApproval} (bypass → allow-list → rater → bridged human prompt), resumes via
|
|
148
644
|
* `streamWithEventsResume({ decisions })`, and loops until the graph completes with no
|
|
149
645
|
* pending interrupts — so the executed command's output renders into the TUI. Without
|
|
150
646
|
* this the TUI silently finalized an empty turn (approval gate was dead code on the
|
|
@@ -156,18 +652,36 @@ export declare class GthAgentRunner {
|
|
|
156
652
|
* resolve any tool-approval interrupts it suspended on, yielding the resumed run's typed
|
|
157
653
|
* {@link AgentStreamEvent}s so the renderer (the Ink TUI) shows the executed command's
|
|
158
654
|
* output. Each pending tool call is consulted via {@link decideToolApproval} — the SAME
|
|
159
|
-
*
|
|
655
|
+
* gate the readline path uses (bypass → allow-list approve → CFG-26 AI rater →
|
|
160
656
|
* bridged human callback, defaulting to REJECT when no handler is wired) — and the
|
|
161
657
|
* collected decisions are sent back via `streamWithEventsResume` as a LangChain HITL
|
|
162
658
|
* resume (`{ decisions }`). Because a resumed run can suspend again on the next gated
|
|
163
659
|
* tool call, this loops until the graph completes with no pending interrupts.
|
|
164
660
|
*
|
|
165
661
|
* No-ops (yields nothing) when the agent does not support interrupts
|
|
166
|
-
* (`getPendingToolInterrupts`/`streamWithEventsResume` absent)
|
|
167
|
-
*
|
|
662
|
+
* (`getPendingToolInterrupts`/`streamWithEventsResume` absent) — that is the only exemption.
|
|
663
|
+
* As of EXT-52 BOTH backends gate `run_shell_command` and expose the interrupt surface, so the
|
|
664
|
+
* lean (default) agent is now exactly the agent this loop serves; only an agent implementation
|
|
665
|
+
* without those methods (e.g. a test double) skips it. Aborts (`signal`) propagate through the
|
|
666
|
+
* resumed stream.
|
|
168
667
|
*/
|
|
169
668
|
private resolveToolInterruptsWithEvents;
|
|
170
669
|
getAgent(): GthAgentInterface | null;
|
|
670
|
+
/**
|
|
671
|
+
* GS2-16 — reset the current turn's analytics tally on both the live agent and the runner's
|
|
672
|
+
* cached snapshot, so a new turn starts clean. Fail-soft (an agent without stats support is a
|
|
673
|
+
* no-op). Called at the top of each `processMessages` / `processMessagesWithEvents`.
|
|
674
|
+
*/
|
|
675
|
+
private resetRunStats;
|
|
676
|
+
/** GS2-16 — read the live agent's run stats (fail-soft; empty tally if unavailable). */
|
|
677
|
+
private captureRunStats;
|
|
678
|
+
/**
|
|
679
|
+
* GS2-16 — the analytics harvested from the just-finished turn (token usage + invoked tools),
|
|
680
|
+
* to thread into the opt-in history recorder. Reads live from the agent when one is present,
|
|
681
|
+
* otherwise the snapshot captured at {@link cleanup} (the single-shot path reads post-cleanup).
|
|
682
|
+
* Never throws.
|
|
683
|
+
*/
|
|
684
|
+
getRunStats(): GthRunStats;
|
|
171
685
|
/**
|
|
172
686
|
* Rotate the thread the runner drives by minting a fresh `runConfig` (new `thread_id`),
|
|
173
687
|
* so subsequent turns start from an empty checkpointer thread rather than retrieving the
|