@gaunt-sloth/core 2.0.0-alpha.29 → 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/dist/config/schema.d.ts +48 -48
- package/dist/config/schema.js +55 -29
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +241 -26
- package/dist/config/shell-policy.js +353 -52
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +78 -32
- package/dist/config/tool-descriptions.js +72 -29
- package/dist/config/tool-descriptions.js.map +1 -1
- package/dist/config/types.d.ts +9 -4
- package/dist/config/types.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +13 -5
- package/dist/core/GthAbstractAgent.js +42 -13
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +203 -9
- package/dist/core/GthAgentRunner.js +646 -39
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.js +58 -17
- package/dist/core/GthLangChainAgent.js.map +1 -1
- 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.js +6 -2
- package/dist/core/refusal.js.map +1 -1
- 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 +16 -7
- package/dist/core/shell/approvalStop.js +18 -7
- package/dist/core/shell/approvalStop.js.map +1 -1
- 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 +5 -5
- package/dist/core/shell/normalize.js +5 -5
- package/dist/core/shell/rater.d.ts +353 -18
- package/dist/core/shell/rater.js +556 -39
- package/dist/core/shell/rater.js.map +1 -1
- package/dist/core/shell/rejection.d.ts +1 -1
- package/dist/core/types.d.ts +97 -4
- 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.js +4 -1
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/vertexai.js +4 -1
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +45 -3
- package/dist/runtime/askStructured.js +46 -5
- package/dist/runtime/askStructured.js.map +1 -1
- package/dist/runtime/conversation.d.ts +2 -1
- package/dist/runtime/conversation.js +2 -1
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.d.ts +3 -2
- package/dist/runtime/singleShot.js +3 -2
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/debugDump.d.ts +19 -0
- package/dist/utils/debugDump.js +11 -0
- package/dist/utils/debugDump.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +23 -0
- package/dist/utils/displayWidth.js +61 -6
- package/dist/utils/displayWidth.js.map +1 -1
- package/dist/utils/systemPromptNotes.d.ts +12 -1
- package/dist/utils/systemPromptNotes.js +24 -2
- package/dist/utils/systemPromptNotes.js.map +1 -1
- package/package.json +1 -1
- package/schema/gsloth-config.schema.json +48 -48
|
@@ -14,33 +14,49 @@
|
|
|
14
14
|
*
|
|
15
15
|
* 1. **A tool that is auto-approved gets NO suffix.** The absence of the sentence is what marks it
|
|
16
16
|
* free. Appending "this tool is already approved" would be the opposite of the design.
|
|
17
|
-
* 2. **The suffix is derived from the rung in force AND from the set of tools
|
|
17
|
+
* 2. **The suffix is derived from the rung in force AND from the set of tools that rung actually
|
|
18
18
|
* gates.** "A description that disagrees with what the gate will actually do is worse than no
|
|
19
|
-
* description at all" (§4.5), so {@link isGrantedAtRung} takes the gated set as a parameter
|
|
20
|
-
* both backends pass the SAME
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
19
|
+
* description at all" (§4.5), so {@link isGrantedAtRung} takes the LIVE gated set as a parameter
|
|
20
|
+
* and both backends pass the SAME `resolveGatedToolNames` result. The two can therefore not
|
|
21
|
+
* drift: widening what a rung gates ([[EXT-30]]) widens the descriptions in the same breath.
|
|
22
|
+
* That set is narrower than the rung-independent one wired into the interrupt
|
|
23
|
+
* (`resolveInterruptToolNames`), because a call the live rung does not gate is approved on
|
|
24
|
+
* arrival and so is genuinely free.
|
|
25
|
+
*
|
|
26
|
+
* **Scope, per §4.3.** The *rater* covers the shell only: every other gated tool goes to the human
|
|
27
|
+
* without a rating call until [[EXT-30]] widens the rater. The *gate* is wider than the rater. At
|
|
28
|
+
* the two deterministic rungs (`manual`, `write`) both backends gate every bound tool the rung's
|
|
29
|
+
* access class does not auto-grant — the write built-ins, the shell, MCP tools (whatever their
|
|
30
|
+
* `readOnlyHint` says) and custom tools — so at those rungs a non-granted call always reaches the
|
|
31
|
+
* human. At `assisted`, `auto` and `bypass` the gated set is the shell alone.
|
|
32
|
+
*
|
|
33
|
+
* `config/shell-policy.ts`'s `isToolGatedAtRung` is the single rule for all of that, and both the
|
|
34
|
+
* live gated set and this module's grant are projections of it, so what a rung gates and the grant
|
|
35
|
+
* reported here cannot disagree.
|
|
31
36
|
*/
|
|
32
37
|
import type { ApprovalRung } from '#src/config/shell-policy.js';
|
|
33
38
|
/**
|
|
34
39
|
* §4.5's table, **verbatim**. The wordings are normative copy the model reads; do not paraphrase,
|
|
35
40
|
* re-punctuate or "improve" them.
|
|
36
41
|
*
|
|
37
|
-
* - `
|
|
42
|
+
* - `manual` and `write` share one sentence: at both rungs a non-granted call goes to the human,
|
|
38
43
|
* so the user's approval is a certainty, not a possibility.
|
|
39
|
-
* - `
|
|
44
|
+
* - `assisted` softens `will` to `MAY`: the rater approves what it rates safe, so only some calls
|
|
40
45
|
* reach the user.
|
|
41
|
-
* - `
|
|
42
|
-
*
|
|
43
|
-
*
|
|
46
|
+
* - `auto` has its own sentence, and the difference it states is real: [[EXT-29]]'s negotiation
|
|
47
|
+
* hands a call the rater will not clear back to the **model**, with the reason, so the first
|
|
48
|
+
* consequence of an unsafe-looking call there is a refusal it can answer rather than a person
|
|
49
|
+
* being asked — what changes is who answers first, and that is the one thing a model choosing a
|
|
50
|
+
* tool can act on. A shared wording would hide it in the model's own tool-selection input.
|
|
51
|
+
*
|
|
52
|
+
* Its `MAY` therefore sits **outside** the contrast the three bullets above draw: it qualifies
|
|
53
|
+
* *"be refused by the auto-rater"*, not *"require the user's approval"*, so this sentence says
|
|
54
|
+
* nothing either way about whether a person can be asked. That silence is deliberate — the
|
|
55
|
+
* string is §4.5's row verbatim — and its cost is recorded here rather than left to be
|
|
56
|
+
* discovered: `catastrophic` escalates to a human on the FIRST hit with no rounds at all, so a
|
|
57
|
+
* person *can* be reached at `auto` and this input does not say so. Silent is not false, which
|
|
58
|
+
* is why the wording stands and why the premise quoted above still holds; a wording claiming the
|
|
59
|
+
* user is never asked would be false.
|
|
44
60
|
* - `bypass` appends nothing to anything: no gate, so no sentence could be true.
|
|
45
61
|
*/
|
|
46
62
|
export declare const RUNG_TOOL_DESCRIPTION_SUFFIXES: Readonly<Record<ApprovalRung, string | null>>;
|
|
@@ -57,11 +73,15 @@ export type BuiltInToolAccess = 'read' | 'write';
|
|
|
57
73
|
* two sets overlap on `read_file`/`write_file`/`edit_file`, which is exactly why one flat table
|
|
58
74
|
* keyed by name serves both.
|
|
59
75
|
*
|
|
60
|
-
* Deliberately absent: `run_shell_command`, the fixed dev-command tools,
|
|
61
|
-
* `gth_checklist`, `gth_status_update`, `show_a2ui_surface`, MCP/custom/A2A tools.
|
|
62
|
-
* "reading or writing files in the working folder", so none is granted by a rung's
|
|
63
|
-
*
|
|
64
|
-
*
|
|
76
|
+
* Deliberately absent: `run_shell_command`, deepagents' `execute`, the fixed dev-command tools,
|
|
77
|
+
* `gth_web_fetch`, `gth_checklist`, `gth_status_update`, `show_a2ui_surface`, MCP/custom/A2A tools.
|
|
78
|
+
* None of them is "reading or writing files in the working folder", so none is granted by a rung's
|
|
79
|
+
* access class, and at `manual`/`write` every one of them escalates to the human.
|
|
80
|
+
*
|
|
81
|
+
* **An MCP tool's own `readOnlyHint` earns it nothing here.** A server's self-declared annotation is
|
|
82
|
+
* least earned exactly where the ladder is strictest, so it must not skip the prompt at those two
|
|
83
|
+
* rungs; membership of this table is the only thing that does. Adding a name here grants it at a
|
|
84
|
+
* rung, so add one only for a tool that genuinely reads or writes files in the working folder.
|
|
65
85
|
*/
|
|
66
86
|
export declare const BUILT_IN_TOOL_ACCESS: Readonly<Record<string, BuiltInToolAccess>>;
|
|
67
87
|
/** A built-in tool the rater may offer as an already-granted alternative (§4.4). */
|
|
@@ -99,23 +119,49 @@ export interface GrantedToolSummary {
|
|
|
99
119
|
export declare const BUILT_IN_TOOL_SUMMARIES: Readonly<Record<string, string>>;
|
|
100
120
|
/** The sentence §4.5 appends at this rung, or `null` when the rung appends nothing (`bypass`). */
|
|
101
121
|
export declare function getRungToolDescriptionSuffix(rung: ApprovalRung): string | null;
|
|
122
|
+
/**
|
|
123
|
+
* §2 — does `rung`'s own grant cover this tool's **access class**, i.e. is the tool free *on its
|
|
124
|
+
* class alone*, before anything is asked about whether the gate gates it?
|
|
125
|
+
*
|
|
126
|
+
* - `read` — granted at every rung (§2.1).
|
|
127
|
+
* - `write` — granted from `write` up (§2.2, and §2.3/§2.4 which grant "everything `write` grants"),
|
|
128
|
+
* so it escalates at `manual`.
|
|
129
|
+
* - **no class at all** — the shell, deepagents' `execute`, a network call, an MCP tool, a custom or
|
|
130
|
+
* agent-authored tool: granted by no rung. There is no implicit exemption; a tool is free here
|
|
131
|
+
* only by appearing in {@link BUILT_IN_TOOL_ACCESS}.
|
|
132
|
+
*
|
|
133
|
+
* **This is the one implementation of that rule.** {@link isGrantedAtRung} decides a grant with it,
|
|
134
|
+
* and `config/shell-policy.ts`'s `isToolGatedAtRung` decides gated-set membership with it — which
|
|
135
|
+
* is in turn what the backends' interrupt set and `GthAgentRunner`'s own live-rung check are built
|
|
136
|
+
* from. So the gate cannot escalate a call the descriptions and the rater's granted list call free
|
|
137
|
+
* — the drift §4.5 names as worse than having no description at all. Two derivations of one rule is
|
|
138
|
+
* exactly what this function exists to prevent; do not inline it back into either caller.
|
|
139
|
+
*
|
|
140
|
+
* `bypass` is not special-cased: it grants everything for a reason unrelated to access class (the
|
|
141
|
+
* gate is off), which {@link isGrantedAtRung} states where that belongs.
|
|
142
|
+
*/
|
|
143
|
+
export declare function isAccessClassGrantedAtRung(toolName: string, rung: ApprovalRung): boolean;
|
|
102
144
|
/**
|
|
103
145
|
* Is `toolName` auto-approved (granted, free, no prompt and no rating) at `rung`?
|
|
104
146
|
*
|
|
105
147
|
* @param toolName The registered tool name.
|
|
106
148
|
* @param rung The rung in force for the session.
|
|
107
|
-
* @param gatedTools The
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
149
|
+
* @param gatedTools The LIVE gated set — what `rung` actually gates, i.e.
|
|
150
|
+
* `resolveGatedToolNames` for the rung in force. **This is the parameter that keeps the
|
|
151
|
+
* descriptions honest**: a tool the rung does not gate cannot require approval, whatever a rung's
|
|
152
|
+
* table row says about tool classes, so it is granted. It is deliberately NOT the wider,
|
|
153
|
+
* rung-independent set the backends wire into `interruptOn` (`resolveInterruptToolNames`): a call
|
|
154
|
+
* the live rung does not gate is auto-approved the moment it arrives at the runner, so describing
|
|
155
|
+
* it as needing approval would be a promise nothing keeps.
|
|
111
156
|
*
|
|
112
157
|
* Order:
|
|
113
158
|
* 1. `bypass` grants everything (§2.5) — the gate is off.
|
|
114
|
-
* 2. A tool the
|
|
115
|
-
* 3. A gated tool is granted only where the rung's own grant covers its access class
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
159
|
+
* 2. A tool the live rung does not gate cannot require approval, so it is granted at every rung.
|
|
160
|
+
* 3. A gated tool is granted only where the rung's own grant covers its access class —
|
|
161
|
+
* {@link isAccessClassGrantedAtRung}, the same rule `resolveGatedToolNames` selects the gated set
|
|
162
|
+
* with. At the two deterministic rungs those two uses are complementary by construction: a tool
|
|
163
|
+
* is gated there precisely when this returns false, so step 3 answers false for every tool step 2
|
|
164
|
+
* let through, and the gate and the description say the same thing.
|
|
119
165
|
*/
|
|
120
166
|
export declare function isGrantedAtRung(toolName: string, rung: ApprovalRung, gatedTools: readonly string[]): boolean;
|
|
121
167
|
/**
|
|
@@ -2,24 +2,35 @@
|
|
|
2
2
|
* §4.5's table, **verbatim**. The wordings are normative copy the model reads; do not paraphrase,
|
|
3
3
|
* re-punctuate or "improve" them.
|
|
4
4
|
*
|
|
5
|
-
* - `
|
|
5
|
+
* - `manual` and `write` share one sentence: at both rungs a non-granted call goes to the human,
|
|
6
6
|
* so the user's approval is a certainty, not a possibility.
|
|
7
|
-
* - `
|
|
7
|
+
* - `assisted` softens `will` to `MAY`: the rater approves what it rates safe, so only some calls
|
|
8
8
|
* reach the user.
|
|
9
|
-
* - `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - `auto` has its own sentence, and the difference it states is real: [[EXT-29]]'s negotiation
|
|
10
|
+
* hands a call the rater will not clear back to the **model**, with the reason, so the first
|
|
11
|
+
* consequence of an unsafe-looking call there is a refusal it can answer rather than a person
|
|
12
|
+
* being asked — what changes is who answers first, and that is the one thing a model choosing a
|
|
13
|
+
* tool can act on. A shared wording would hide it in the model's own tool-selection input.
|
|
14
|
+
*
|
|
15
|
+
* Its `MAY` therefore sits **outside** the contrast the three bullets above draw: it qualifies
|
|
16
|
+
* *"be refused by the auto-rater"*, not *"require the user's approval"*, so this sentence says
|
|
17
|
+
* nothing either way about whether a person can be asked. That silence is deliberate — the
|
|
18
|
+
* string is §4.5's row verbatim — and its cost is recorded here rather than left to be
|
|
19
|
+
* discovered: `catastrophic` escalates to a human on the FIRST hit with no rounds at all, so a
|
|
20
|
+
* person *can* be reached at `auto` and this input does not say so. Silent is not false, which
|
|
21
|
+
* is why the wording stands and why the premise quoted above still holds; a wording claiming the
|
|
22
|
+
* user is never asked would be false.
|
|
12
23
|
* - `bypass` appends nothing to anything: no gate, so no sentence could be true.
|
|
13
24
|
*/
|
|
14
25
|
export const RUNG_TOOL_DESCRIPTION_SUFFIXES = {
|
|
15
|
-
|
|
26
|
+
manual: "Calling this tool will require the user's approval. Only use it when the result cannot be " +
|
|
16
27
|
'achieved with the other provided tools.',
|
|
17
28
|
write: "Calling this tool will require the user's approval. Only use it when the result cannot be " +
|
|
18
29
|
'achieved with the other provided tools.',
|
|
19
|
-
|
|
30
|
+
assisted: "Calling this tool MAY require the user's approval if it does not look safe. Only use it when " +
|
|
31
|
+
'it is impossible to achieve the result with the other provided tools.',
|
|
32
|
+
auto: 'Calling this tool MAY be refused by the auto-rater if it does not look safe. Only use it when ' +
|
|
20
33
|
'it is impossible to achieve the result with the other provided tools.',
|
|
21
|
-
'full-auto': 'Calling this tool MAY be refused by the auto-rater if it does not look safe. Only use it ' +
|
|
22
|
-
'when it is impossible to achieve the result with the other provided tools.',
|
|
23
34
|
bypass: null,
|
|
24
35
|
};
|
|
25
36
|
/** The distinct suffix strings, for {@link stripRungToolDescriptionSuffix}'s idempotency check. */
|
|
@@ -30,11 +41,15 @@ const ALL_SUFFIXES = Array.from(new Set(Object.values(RUNG_TOOL_DESCRIPTION_SUFF
|
|
|
30
41
|
* two sets overlap on `read_file`/`write_file`/`edit_file`, which is exactly why one flat table
|
|
31
42
|
* keyed by name serves both.
|
|
32
43
|
*
|
|
33
|
-
* Deliberately absent: `run_shell_command`, the fixed dev-command tools,
|
|
34
|
-
* `gth_checklist`, `gth_status_update`, `show_a2ui_surface`, MCP/custom/A2A tools.
|
|
35
|
-
* "reading or writing files in the working folder", so none is granted by a rung's
|
|
36
|
-
*
|
|
37
|
-
*
|
|
44
|
+
* Deliberately absent: `run_shell_command`, deepagents' `execute`, the fixed dev-command tools,
|
|
45
|
+
* `gth_web_fetch`, `gth_checklist`, `gth_status_update`, `show_a2ui_surface`, MCP/custom/A2A tools.
|
|
46
|
+
* None of them is "reading or writing files in the working folder", so none is granted by a rung's
|
|
47
|
+
* access class, and at `manual`/`write` every one of them escalates to the human.
|
|
48
|
+
*
|
|
49
|
+
* **An MCP tool's own `readOnlyHint` earns it nothing here.** A server's self-declared annotation is
|
|
50
|
+
* least earned exactly where the ladder is strictest, so it must not skip the prompt at those two
|
|
51
|
+
* rungs; membership of this table is the only thing that does. Adding a name here grants it at a
|
|
52
|
+
* rung, so add one only for a tool that genuinely reads or writes files in the working folder.
|
|
38
53
|
*/
|
|
39
54
|
export const BUILT_IN_TOOL_ACCESS = {
|
|
40
55
|
// gsloth GthFileSystemToolkit (lean backend)
|
|
@@ -114,35 +129,63 @@ export const BUILT_IN_TOOL_SUMMARIES = {
|
|
|
114
129
|
export function getRungToolDescriptionSuffix(rung) {
|
|
115
130
|
return RUNG_TOOL_DESCRIPTION_SUFFIXES[rung] ?? null;
|
|
116
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* §2 — does `rung`'s own grant cover this tool's **access class**, i.e. is the tool free *on its
|
|
134
|
+
* class alone*, before anything is asked about whether the gate gates it?
|
|
135
|
+
*
|
|
136
|
+
* - `read` — granted at every rung (§2.1).
|
|
137
|
+
* - `write` — granted from `write` up (§2.2, and §2.3/§2.4 which grant "everything `write` grants"),
|
|
138
|
+
* so it escalates at `manual`.
|
|
139
|
+
* - **no class at all** — the shell, deepagents' `execute`, a network call, an MCP tool, a custom or
|
|
140
|
+
* agent-authored tool: granted by no rung. There is no implicit exemption; a tool is free here
|
|
141
|
+
* only by appearing in {@link BUILT_IN_TOOL_ACCESS}.
|
|
142
|
+
*
|
|
143
|
+
* **This is the one implementation of that rule.** {@link isGrantedAtRung} decides a grant with it,
|
|
144
|
+
* and `config/shell-policy.ts`'s `isToolGatedAtRung` decides gated-set membership with it — which
|
|
145
|
+
* is in turn what the backends' interrupt set and `GthAgentRunner`'s own live-rung check are built
|
|
146
|
+
* from. So the gate cannot escalate a call the descriptions and the rater's granted list call free
|
|
147
|
+
* — the drift §4.5 names as worse than having no description at all. Two derivations of one rule is
|
|
148
|
+
* exactly what this function exists to prevent; do not inline it back into either caller.
|
|
149
|
+
*
|
|
150
|
+
* `bypass` is not special-cased: it grants everything for a reason unrelated to access class (the
|
|
151
|
+
* gate is off), which {@link isGrantedAtRung} states where that belongs.
|
|
152
|
+
*/
|
|
153
|
+
export function isAccessClassGrantedAtRung(toolName, rung) {
|
|
154
|
+
const access = BUILT_IN_TOOL_ACCESS[toolName];
|
|
155
|
+
if (access === 'read')
|
|
156
|
+
return true;
|
|
157
|
+
if (access === 'write')
|
|
158
|
+
return rung !== 'manual';
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
117
161
|
/**
|
|
118
162
|
* Is `toolName` auto-approved (granted, free, no prompt and no rating) at `rung`?
|
|
119
163
|
*
|
|
120
164
|
* @param toolName The registered tool name.
|
|
121
165
|
* @param rung The rung in force for the session.
|
|
122
|
-
* @param gatedTools The
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
166
|
+
* @param gatedTools The LIVE gated set — what `rung` actually gates, i.e.
|
|
167
|
+
* `resolveGatedToolNames` for the rung in force. **This is the parameter that keeps the
|
|
168
|
+
* descriptions honest**: a tool the rung does not gate cannot require approval, whatever a rung's
|
|
169
|
+
* table row says about tool classes, so it is granted. It is deliberately NOT the wider,
|
|
170
|
+
* rung-independent set the backends wire into `interruptOn` (`resolveInterruptToolNames`): a call
|
|
171
|
+
* the live rung does not gate is auto-approved the moment it arrives at the runner, so describing
|
|
172
|
+
* it as needing approval would be a promise nothing keeps.
|
|
126
173
|
*
|
|
127
174
|
* Order:
|
|
128
175
|
* 1. `bypass` grants everything (§2.5) — the gate is off.
|
|
129
|
-
* 2. A tool the
|
|
130
|
-
* 3. A gated tool is granted only where the rung's own grant covers its access class
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
176
|
+
* 2. A tool the live rung does not gate cannot require approval, so it is granted at every rung.
|
|
177
|
+
* 3. A gated tool is granted only where the rung's own grant covers its access class —
|
|
178
|
+
* {@link isAccessClassGrantedAtRung}, the same rule `resolveGatedToolNames` selects the gated set
|
|
179
|
+
* with. At the two deterministic rungs those two uses are complementary by construction: a tool
|
|
180
|
+
* is gated there precisely when this returns false, so step 3 answers false for every tool step 2
|
|
181
|
+
* let through, and the gate and the description say the same thing.
|
|
134
182
|
*/
|
|
135
183
|
export function isGrantedAtRung(toolName, rung, gatedTools) {
|
|
136
184
|
if (rung === 'bypass')
|
|
137
185
|
return true;
|
|
138
186
|
if (!gatedTools.includes(toolName))
|
|
139
187
|
return true;
|
|
140
|
-
|
|
141
|
-
if (access === 'read')
|
|
142
|
-
return true;
|
|
143
|
-
if (access === 'write')
|
|
144
|
-
return rung !== 'read-only';
|
|
145
|
-
return false;
|
|
188
|
+
return isAccessClassGrantedAtRung(toolName, rung);
|
|
146
189
|
}
|
|
147
190
|
/**
|
|
148
191
|
* Remove a previously-appended §4.5 suffix (any rung's), returning the tool's own description.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-descriptions.js","sourceRoot":"","sources":["../../src/config/tool-descriptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-descriptions.js","sourceRoot":"","sources":["../../src/config/tool-descriptions.ts"],"names":[],"mappings":"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"}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -109,7 +109,12 @@ export interface GthConfig {
|
|
|
109
109
|
* summarization, tool-result offload). Selecting it emits a warning. It can exhibit
|
|
110
110
|
* path-divergence and sporadic failures and carries extra internal workarounds; prefer `lean`.
|
|
111
111
|
*
|
|
112
|
-
*
|
|
112
|
+
* **Command-scoped**, and `docs/configuration/profiles.md` carries the one list of which commands
|
|
113
|
+
* honor it — don't restate it here, or the copies drift. What the type itself must say: `review`
|
|
114
|
+
* and `pr` — including the `pr` change-requirements discovery agent — always run lean, because
|
|
115
|
+
* `@gaunt-sloth/review` cannot reach the deep backend; a run that asks for `deep` there says so
|
|
116
|
+
* rather than dropping the key silently. The ACP server is the mirror image: structurally
|
|
117
|
+
* deep-only, it always runs deep and rejects `lean` outright.
|
|
113
118
|
*/
|
|
114
119
|
agent?: {
|
|
115
120
|
backend?: 'deep' | 'lean';
|
|
@@ -194,8 +199,8 @@ export interface GthConfig {
|
|
|
194
199
|
*/
|
|
195
200
|
builtInTools?: BuiltInToolsSetting;
|
|
196
201
|
/**
|
|
197
|
-
* CFG-27 — the tool-approval **ladder**: one of the five rungs (`
|
|
198
|
-
* `
|
|
202
|
+
* CFG-27 — the tool-approval **ladder**: one of the five rungs (`manual` · `write` ·
|
|
203
|
+
* `assisted` · `auto` · `bypass`), written either as the bare rung name or as an object
|
|
199
204
|
* carrying the rater's identity profile and the declared allow/deny lists. Each rung fully
|
|
200
205
|
* determines behaviour — there are no severity thresholds, no strictness levels and no
|
|
201
206
|
* independent rater switch.
|
|
@@ -206,7 +211,7 @@ export interface GthConfig {
|
|
|
206
211
|
* discard the root's prohibitions; `allow` is REPLACED when the command states its own and
|
|
207
212
|
* inherited when it does not, so a scope may narrow what runs unprompted and may never widen
|
|
208
213
|
* what is prohibited (§3.1: a too-broad allow entry runs unrated, a missed deny entry does not).
|
|
209
|
-
* Absent = `
|
|
214
|
+
* Absent = `assisted`, resolved by `resolveApprovals`.
|
|
210
215
|
*/
|
|
211
216
|
approvals?: ApprovalsConfig;
|
|
212
217
|
tools?: StructuredToolInterface[] | BaseToolkit[] | ServerTool[];
|
package/dist/config/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAosBA,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,UAAU;IACV,WAAW;IACX,MAAM;IACN,UAAU;IACV,QAAQ;IACR,cAAc;IACd,KAAK;IACL,YAAY;IACZ,aAAa;IACb,QAAQ;CACA,CAAC"}
|
|
@@ -98,11 +98,19 @@ export declare abstract class GthAbstractAgent implements GthAgentInterface {
|
|
|
98
98
|
* enters the approvals stack, and it enters as a claim: nothing here decides whether it is
|
|
99
99
|
* believed.
|
|
100
100
|
*
|
|
101
|
-
* `gatedTools` MUST be the
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
101
|
+
* `gatedTools` MUST be the **LIVE gated set for the rung in force** — `resolveGatedToolNames` for
|
|
102
|
+
* that rung — and NOT the set the caller wires into the approval interrupt. The two are different
|
|
103
|
+
* on purpose: the interrupt is installed once, at agent init, and is deliberately
|
|
104
|
+
* rung-independent (`resolveInterruptToolNames`, the union over every rung) so that
|
|
105
|
+
* `/approvals <mode>` can move the mode underneath it for the rest of the session. Passing that
|
|
106
|
+
* wider set here would describe tools as needing approval that the live mode does not gate — and
|
|
107
|
+
* a call the live mode does not gate is auto-approved the moment it reaches the runner, so the
|
|
108
|
+
* sentence would be a promise nothing keeps.
|
|
109
|
+
*
|
|
110
|
+
* What keeps a description from promising an approval the gate will not ask for is therefore that
|
|
111
|
+
* both this and `GthAgentRunner`'s own check are projections of the SAME rule,
|
|
112
|
+
* `isToolGatedAtRung`, evaluated against the SAME live mode — §4.5's "a description that disagrees
|
|
113
|
+
* with what the gate will actually do is worse than no description at all".
|
|
106
114
|
*
|
|
107
115
|
* `additionalToolNames` covers tools the graph builder registers itself and that therefore never
|
|
108
116
|
* appear in `tools` — deepagents' own filesystem tools on the deep backend. Their descriptions
|
|
@@ -11,6 +11,7 @@ import { IterableReadableStream } from '@langchain/core/utils/stream';
|
|
|
11
11
|
import { interrupt, Command, GraphInterrupt } from '@langchain/langgraph';
|
|
12
12
|
import { extractInlineBinaryBlocks, materializeBinaryOutputs, renderAssistantContent, } from '#src/utils/binaryOutputUtils.js';
|
|
13
13
|
import { detectRefusal, buildRefusalMessage } from '#src/core/refusal.js';
|
|
14
|
+
import { answerTextOf, segmentAssistantContent, stripReasoningBlocks, } from '#src/core/reasoningBlocks.js';
|
|
14
15
|
const THINK_OPEN = '<think>';
|
|
15
16
|
const THINK_CLOSE = '</think>';
|
|
16
17
|
/**
|
|
@@ -216,11 +217,19 @@ export class GthAbstractAgent {
|
|
|
216
217
|
* enters the approvals stack, and it enters as a claim: nothing here decides whether it is
|
|
217
218
|
* believed.
|
|
218
219
|
*
|
|
219
|
-
* `gatedTools` MUST be the
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
220
|
+
* `gatedTools` MUST be the **LIVE gated set for the rung in force** — `resolveGatedToolNames` for
|
|
221
|
+
* that rung — and NOT the set the caller wires into the approval interrupt. The two are different
|
|
222
|
+
* on purpose: the interrupt is installed once, at agent init, and is deliberately
|
|
223
|
+
* rung-independent (`resolveInterruptToolNames`, the union over every rung) so that
|
|
224
|
+
* `/approvals <mode>` can move the mode underneath it for the rest of the session. Passing that
|
|
225
|
+
* wider set here would describe tools as needing approval that the live mode does not gate — and
|
|
226
|
+
* a call the live mode does not gate is auto-approved the moment it reaches the runner, so the
|
|
227
|
+
* sentence would be a promise nothing keeps.
|
|
228
|
+
*
|
|
229
|
+
* What keeps a description from promising an approval the gate will not ask for is therefore that
|
|
230
|
+
* both this and `GthAgentRunner`'s own check are projections of the SAME rule,
|
|
231
|
+
* `isToolGatedAtRung`, evaluated against the SAME live mode — §4.5's "a description that disagrees
|
|
232
|
+
* with what the gate will actually do is worse than no description at all".
|
|
224
233
|
*
|
|
225
234
|
* `additionalToolNames` covers tools the graph builder registers itself and that therefore never
|
|
226
235
|
* appear in `tools` — deepagents' own filesystem tools on the deep backend. Their descriptions
|
|
@@ -358,7 +367,12 @@ export class GthAbstractAgent {
|
|
|
358
367
|
if (refusal) {
|
|
359
368
|
return this.surfaceRefusal(refusal);
|
|
360
369
|
}
|
|
361
|
-
|
|
370
|
+
// CFG-33: Gemini's thought summaries ride inside `content` as `thought: true` text blocks,
|
|
371
|
+
// which renderAssistantContent would print as part of the answer (and write to the output
|
|
372
|
+
// file). The plain surface has never shown reasoning — every other provider's arrives
|
|
373
|
+
// out-of-band in additional_kwargs — so drop them for rendering only; graph state keeps the
|
|
374
|
+
// message whole so the thought parts still replay as history.
|
|
375
|
+
const finalContent = stripReasoningBlocks(finalMessage?.content);
|
|
362
376
|
const processedContent = !this.config.writeBinaryOutputsToFile
|
|
363
377
|
? {
|
|
364
378
|
renderedContent: renderAssistantContent(finalContent),
|
|
@@ -524,7 +538,9 @@ export class GthAbstractAgent {
|
|
|
524
538
|
// completed call when its ToolMessage arrives; a no-op for plain text chunks).
|
|
525
539
|
toolIndication.observe(chunk);
|
|
526
540
|
if (AIMessage.isInstance(chunk)) {
|
|
527
|
-
|
|
541
|
+
// CFG-33: the ANSWER text only. `.text` folds Gemini's `thought: true` blocks into the
|
|
542
|
+
// answer, which would print the model's thinking inline here and in the output file.
|
|
543
|
+
const text = answerTextOf(chunk.content);
|
|
528
544
|
totalChunks++;
|
|
529
545
|
if (text.length > 0) {
|
|
530
546
|
statusUpdate(StatusLevel.STREAM, text);
|
|
@@ -763,6 +779,20 @@ export class GthAbstractAgent {
|
|
|
763
779
|
}
|
|
764
780
|
}
|
|
765
781
|
}
|
|
782
|
+
// CFG-33 — emit a message's content segments in order. A segment already classified as the
|
|
783
|
+
// model's thinking (a Gemini `thought: true` block) goes straight to the reasoning channel;
|
|
784
|
+
// answer text still passes through the TUI-C22 think splitter, so an inline `<think>` tag is
|
|
785
|
+
// peeled exactly as before. With no reasoning block this is the previous `.text` behaviour.
|
|
786
|
+
function* emitContentSegments(segments) {
|
|
787
|
+
for (const segment of segments) {
|
|
788
|
+
if (segment.kind === 'reasoning') {
|
|
789
|
+
yield* emitSegments([segment]);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
yield* emitSegments(thinkSplitter.push(segment.text));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
766
796
|
function* flushAggregated() {
|
|
767
797
|
if (!aggregatedAIChunk)
|
|
768
798
|
return;
|
|
@@ -812,9 +842,10 @@ export class GthAbstractAgent {
|
|
|
812
842
|
// which is cumulative. TUI-C22 routes it through the think splitter so inline
|
|
813
843
|
// <think>...</think> (buffered across chunks) is peeled into the reasoning channel and
|
|
814
844
|
// stripped from the answer; text with no think tags passes straight through unchanged.
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
845
|
+
// CFG-33 classifies the chunk's content blocks first, in order, so Gemini's `thought: true`
|
|
846
|
+
// blocks reach the reasoning channel instead of the answer; answer text still goes through
|
|
847
|
+
// the think splitter, thought text does not (it is already classified).
|
|
848
|
+
yield* emitContentSegments(segmentAssistantContent(chunk.content));
|
|
818
849
|
}
|
|
819
850
|
else if (AIMessage.isInstance(chunk)) {
|
|
820
851
|
// Reasoning on a non-chunk AIMessage — a non-streamed / resumed thinking message
|
|
@@ -834,9 +865,7 @@ export class GthAbstractAgent {
|
|
|
834
865
|
});
|
|
835
866
|
aggregatedAIChunk = aggregatedAIChunk ? aggregatedAIChunk.concat(synthetic) : synthetic;
|
|
836
867
|
}
|
|
837
|
-
|
|
838
|
-
yield* emitSegments(thinkSplitter.push(chunk.text));
|
|
839
|
-
}
|
|
868
|
+
yield* emitContentSegments(segmentAssistantContent(chunk.content));
|
|
840
869
|
// A non-chunk AIMessage is a COMPLETE message, not a delta — drain any residual now
|
|
841
870
|
// (an unterminated <think> becomes reasoning, a dangling partial becomes answer) so its
|
|
842
871
|
// buffered state never leaks into a subsequent message (TUI-C22).
|