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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +1 -1
  2. package/dist/config/colour.d.ts +38 -0
  3. package/dist/config/colour.js +36 -0
  4. package/dist/config/colour.js.map +1 -0
  5. package/dist/config/defaults.d.ts +1 -0
  6. package/dist/config/defaults.js +1 -0
  7. package/dist/config/defaults.js.map +1 -1
  8. package/dist/config/loader.d.ts +13 -6
  9. package/dist/config/loader.js +171 -28
  10. package/dist/config/loader.js.map +1 -1
  11. package/dist/config/mouse.d.ts +50 -0
  12. package/dist/config/mouse.js +44 -0
  13. package/dist/config/mouse.js.map +1 -0
  14. package/dist/config/schema.d.ts +2372 -114
  15. package/dist/config/schema.js +845 -16
  16. package/dist/config/schema.js.map +1 -1
  17. package/dist/config/shell-policy.d.ts +466 -116
  18. package/dist/config/shell-policy.js +273 -62
  19. package/dist/config/shell-policy.js.map +1 -1
  20. package/dist/config/tool-descriptions.d.ts +165 -0
  21. package/dist/config/tool-descriptions.js +229 -0
  22. package/dist/config/tool-descriptions.js.map +1 -0
  23. package/dist/config/types.d.ts +36 -1
  24. package/dist/config/types.js.map +1 -1
  25. package/dist/config.d.ts +3 -0
  26. package/dist/config.js +3 -0
  27. package/dist/config.js.map +1 -1
  28. package/dist/core/GthAbstractAgent.d.ts +59 -1
  29. package/dist/core/GthAbstractAgent.js +72 -0
  30. package/dist/core/GthAbstractAgent.js.map +1 -1
  31. package/dist/core/GthAgentRunner.d.ts +341 -58
  32. package/dist/core/GthAgentRunner.js +817 -149
  33. package/dist/core/GthAgentRunner.js.map +1 -1
  34. package/dist/core/GthLangChainAgent.js +55 -1
  35. package/dist/core/GthLangChainAgent.js.map +1 -1
  36. package/dist/core/approvals/annotations.d.ts +122 -0
  37. package/dist/core/approvals/annotations.js +137 -0
  38. package/dist/core/approvals/annotations.js.map +1 -0
  39. package/dist/core/approvals/grants.d.ts +216 -0
  40. package/dist/core/approvals/grants.js +469 -0
  41. package/dist/core/approvals/grants.js.map +1 -0
  42. package/dist/core/approvals/matcher.d.ts +202 -0
  43. package/dist/core/approvals/matcher.js +267 -0
  44. package/dist/core/approvals/matcher.js.map +1 -0
  45. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  46. package/dist/core/approvals/mcpSubjects.js +99 -0
  47. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  48. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  49. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  50. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  51. package/dist/core/approvals/toolHost.d.ts +46 -0
  52. package/dist/core/approvals/toolHost.js +108 -0
  53. package/dist/core/approvals/toolHost.js.map +1 -0
  54. package/dist/core/launchBanner.d.ts +120 -0
  55. package/dist/core/launchBanner.js +399 -0
  56. package/dist/core/launchBanner.js.map +1 -0
  57. package/dist/core/plainToolIndication.js +13 -7
  58. package/dist/core/plainToolIndication.js.map +1 -1
  59. package/dist/core/shell/approvalStop.d.ts +75 -0
  60. package/dist/core/shell/approvalStop.js +108 -0
  61. package/dist/core/shell/approvalStop.js.map +1 -0
  62. package/dist/core/shell/arity.d.ts +6 -0
  63. package/dist/core/shell/arity.js +20 -6
  64. package/dist/core/shell/arity.js.map +1 -1
  65. package/dist/core/shell/denylist.d.ts +11 -0
  66. package/dist/core/shell/denylist.js +37 -0
  67. package/dist/core/shell/denylist.js.map +1 -0
  68. package/dist/core/shell/normalize.d.ts +41 -1
  69. package/dist/core/shell/normalize.js +58 -4
  70. package/dist/core/shell/normalize.js.map +1 -1
  71. package/dist/core/shell/openWorld.d.ts +138 -0
  72. package/dist/core/shell/openWorld.js +523 -0
  73. package/dist/core/shell/openWorld.js.map +1 -0
  74. package/dist/core/shell/rater.d.ts +560 -0
  75. package/dist/core/shell/rater.js +895 -0
  76. package/dist/core/shell/rater.js.map +1 -0
  77. package/dist/core/shell/raterModel.d.ts +41 -0
  78. package/dist/core/shell/raterModel.js +51 -0
  79. package/dist/core/shell/raterModel.js.map +1 -0
  80. package/dist/core/shell/rejection.d.ts +66 -0
  81. package/dist/core/shell/rejection.js +38 -0
  82. package/dist/core/shell/rejection.js.map +1 -0
  83. package/dist/core/types.d.ts +71 -9
  84. package/dist/core/types.js.map +1 -1
  85. package/dist/runtime/askStructured.d.ts +8 -7
  86. package/dist/runtime/askStructured.js +8 -7
  87. package/dist/runtime/askStructured.js.map +1 -1
  88. package/dist/runtime/conversation.js +89 -76
  89. package/dist/runtime/conversation.js.map +1 -1
  90. package/dist/runtime/singleShot.js +71 -60
  91. package/dist/runtime/singleShot.js.map +1 -1
  92. package/dist/utils/ProgressIndicator.d.ts +21 -0
  93. package/dist/utils/ProgressIndicator.js +30 -3
  94. package/dist/utils/ProgressIndicator.js.map +1 -1
  95. package/dist/utils/consoleUtils.d.ts +30 -0
  96. package/dist/utils/consoleUtils.js +27 -2
  97. package/dist/utils/consoleUtils.js.map +1 -1
  98. package/dist/utils/systemUtils.d.ts +17 -0
  99. package/dist/utils/systemUtils.js +17 -0
  100. package/dist/utils/systemUtils.js.map +1 -1
  101. package/package.json +5 -2
  102. package/schema/gsloth-config.schema.json +1216 -417
  103. package/dist/core/shell/allowlist.d.ts +0 -75
  104. package/dist/core/shell/allowlist.js +0 -187
  105. package/dist/core/shell/allowlist.js.map +0 -1
  106. package/dist/core/shell/judge.d.ts +0 -161
  107. package/dist/core/shell/judge.js +0 -261
  108. package/dist/core/shell/judge.js.map +0 -1
@@ -0,0 +1,229 @@
1
+ /**
2
+ * §4.5's table, **verbatim**. The wordings are normative copy the model reads; do not paraphrase,
3
+ * re-punctuate or "improve" them.
4
+ *
5
+ * - `read-only` and `write` share one sentence: at both rungs a non-granted call goes to the human,
6
+ * so the user's approval is a certainty, not a possibility.
7
+ * - `auto-safe` softens `will` to `MAY`: the rater approves what it rates safe, so only some calls
8
+ * reach the user.
9
+ * - `full-auto` gets its **own** wording because the consequence differs — the user is not asked
10
+ * there, so promising the user's approval would be false. What can happen is a refusal by the
11
+ * rater.
12
+ * - `bypass` appends nothing to anything: no gate, so no sentence could be true.
13
+ */
14
+ export const RUNG_TOOL_DESCRIPTION_SUFFIXES = {
15
+ 'read-only': "Calling this tool will require the user's approval. Only use it when the result cannot be " +
16
+ 'achieved with the other provided tools.',
17
+ write: "Calling this tool will require the user's approval. Only use it when the result cannot be " +
18
+ 'achieved with the other provided tools.',
19
+ 'auto-safe': "Calling this tool MAY require the user's approval if it does not look safe. Only use it when " +
20
+ '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
+ bypass: null,
24
+ };
25
+ /** The distinct suffix strings, for {@link stripRungToolDescriptionSuffix}'s idempotency check. */
26
+ const ALL_SUFFIXES = Array.from(new Set(Object.values(RUNG_TOOL_DESCRIPTION_SUFFIXES).filter((s) => s !== null)));
27
+ /**
28
+ * Access class per built-in tool name. Covers BOTH backends' names: gsloth's own
29
+ * `GthFileSystemToolkit` (lean) and deepagents' filesystem tools (`ls`/`glob`/`grep`, deep) — the
30
+ * two sets overlap on `read_file`/`write_file`/`edit_file`, which is exactly why one flat table
31
+ * keyed by name serves both.
32
+ *
33
+ * Deliberately absent: `run_shell_command`, the fixed dev-command tools, `gth_web_fetch`,
34
+ * `gth_checklist`, `gth_status_update`, `show_a2ui_surface`, MCP/custom/A2A tools. None of them is
35
+ * "reading or writing files in the working folder", so none is granted by a rung's access class.
36
+ * (They are still *granted today* — the gate does not gate them — which {@link isGrantedAtRung}
37
+ * decides on the gated set, not on this table.)
38
+ */
39
+ export const BUILT_IN_TOOL_ACCESS = {
40
+ // gsloth GthFileSystemToolkit (lean backend)
41
+ read_file: 'read',
42
+ read_multiple_files: 'read',
43
+ gth_read_binary: 'read',
44
+ list_directory: 'read',
45
+ list_directory_with_sizes: 'read',
46
+ directory_tree: 'read',
47
+ search_files: 'read',
48
+ get_file_info: 'read',
49
+ list_allowed_directories: 'read',
50
+ write_file: 'write',
51
+ edit_file: 'write',
52
+ create_directory: 'write',
53
+ move_file: 'write',
54
+ delete_file: 'write',
55
+ delete_directory: 'write',
56
+ // deepagents filesystem tools (deep backend) not already named above
57
+ ls: 'read',
58
+ glob: 'read',
59
+ grep: 'read',
60
+ // gsloth built-ins that read file contents in the working folder
61
+ gth_grep: 'read',
62
+ };
63
+ /**
64
+ * §4.3/§4.4 — the one-line descriptions handed to the rater with the granted tools' names.
65
+ *
66
+ * **Authored here on purpose.** §4.3 admits this list as *"trusted, locally-generated text, not
67
+ * part of the fenced block"*, so it may not be assembled from tool `description` fields at large:
68
+ * an MCP server's tool description is attacker-influenceable text, and placing it outside the
69
+ * fenced block would open an injection channel straight past the rater's untrusted-input preamble.
70
+ * Only tools named in THIS table are ever offered, so an MCP, custom or A2A tool can never
71
+ * contribute text to the rater prompt.
72
+ *
73
+ * Restricted to tools that could plausibly stand in for a shell command — the file tools, content
74
+ * search, and the fixed dev-command tools (whose command is human-authored config, so suggesting
75
+ * `run_tests` over `npm test` is exactly the trade this section exists to make). `gth_checklist`,
76
+ * `gth_status_update` and `show_a2ui_surface` are omitted: they substitute for nothing a model
77
+ * would otherwise shell out for, and a suggestion list is only useful while it is short.
78
+ *
79
+ * **`gth_web_fetch` is deliberately NOT here**, though it is ungated at every rung. §4.5's
80
+ * justification for disclosing the posture at all is that the granted tools are "by construction,
81
+ * the constrained ones confined to the working folder" — a network fetch is not. Offering it would
82
+ * let a refused `curl`/`wget` come back as a suggestion whose §7 clause tells the model, verbatim,
83
+ * that the alternative "will not interrupt the user": a refused egress turned into a free one,
84
+ * through the rater rather than through the gate. An `attack` halts before any message reaches the
85
+ * model, but a merely `destructive` fetch would not. Same reasoning excludes MCP and custom tools,
86
+ * which additionally supply text we did not author.
87
+ */
88
+ export const BUILT_IN_TOOL_SUMMARIES = {
89
+ read_file: 'Read one file in the working folder.',
90
+ read_multiple_files: 'Read several files in the working folder in one call.',
91
+ gth_read_binary: 'Read an image or other binary file in the working folder.',
92
+ list_directory: 'List the entries of a directory in the working folder.',
93
+ list_directory_with_sizes: 'List a directory in the working folder with entry sizes.',
94
+ directory_tree: 'Show a recursive tree of a directory in the working folder.',
95
+ search_files: 'Find files in the working folder by name pattern.',
96
+ get_file_info: 'Show size, timestamps and type of a file in the working folder.',
97
+ list_allowed_directories: 'List the directories the file tools are allowed to touch.',
98
+ write_file: 'Create or overwrite a file in the working folder.',
99
+ edit_file: 'Apply a targeted edit to a file in the working folder.',
100
+ create_directory: 'Create a directory in the working folder.',
101
+ move_file: 'Move or rename a file in the working folder.',
102
+ delete_file: 'Delete a file in the working folder.',
103
+ delete_directory: 'Delete a directory in the working folder.',
104
+ ls: 'List the entries of a directory in the working folder.',
105
+ glob: 'Find files in the working folder by glob pattern.',
106
+ grep: 'Search file contents in the working folder by regular expression.',
107
+ gth_grep: 'Search file contents in the working folder by regular expression.',
108
+ run_tests: "Run the project's configured test command.",
109
+ run_single_test: "Run one test file with the project's configured test command.",
110
+ run_lint: "Run the project's configured lint command.",
111
+ run_build: "Run the project's configured build command.",
112
+ };
113
+ /** The sentence §4.5 appends at this rung, or `null` when the rung appends nothing (`bypass`). */
114
+ export function getRungToolDescriptionSuffix(rung) {
115
+ return RUNG_TOOL_DESCRIPTION_SUFFIXES[rung] ?? null;
116
+ }
117
+ /**
118
+ * Is `toolName` auto-approved (granted, free, no prompt and no rating) at `rung`?
119
+ *
120
+ * @param toolName The registered tool name.
121
+ * @param rung The rung in force for the session.
122
+ * @param gatedTools The names the gate actually wires into the approval interrupt. **This is the
123
+ * parameter that keeps the descriptions honest**: a tool the gate does not gate cannot require
124
+ * approval, whatever a rung's table row says about tool classes, so it is granted. Both backends
125
+ * pass the same set they hand to `interruptOn` / `humanInTheLoopMiddleware`.
126
+ *
127
+ * Order:
128
+ * 1. `bypass` grants everything (§2.5) — the gate is off.
129
+ * 2. A tool the gate does not gate is granted at every rung (§4.3's scope boundary).
130
+ * 3. A gated tool is granted only where the rung's own grant covers its access class: read tools
131
+ * from `read-only` up (§2.1), write tools from `write` up (§2.2, and §2.3/§2.4 which grant
132
+ * "everything `write` grants"). A gated tool with no access class — the shell, a network call,
133
+ * an MCP tool — is granted at no rung but `bypass`.
134
+ */
135
+ export function isGrantedAtRung(toolName, rung, gatedTools) {
136
+ if (rung === 'bypass')
137
+ return true;
138
+ if (!gatedTools.includes(toolName))
139
+ return true;
140
+ const access = BUILT_IN_TOOL_ACCESS[toolName];
141
+ if (access === 'read')
142
+ return true;
143
+ if (access === 'write')
144
+ return rung !== 'read-only';
145
+ return false;
146
+ }
147
+ /**
148
+ * Remove a previously-appended §4.5 suffix (any rung's), returning the tool's own description.
149
+ *
150
+ * Makes {@link applyRungAwareToolDescriptions} idempotent and re-appliable at a different rung —
151
+ * which matters because a resolver may hand back the SAME tool instance on a re-init (an MCP
152
+ * client caches its tool objects), and a second pass would otherwise stack sentences.
153
+ */
154
+ export function stripRungToolDescriptionSuffix(description) {
155
+ let result = description;
156
+ // Loop: a description that was appended to twice by an older build still ends up clean.
157
+ let changed = true;
158
+ while (changed) {
159
+ changed = false;
160
+ for (const suffix of ALL_SUFFIXES) {
161
+ if (result.endsWith(suffix)) {
162
+ result = result.slice(0, -suffix.length).replace(/\s+$/, '');
163
+ changed = true;
164
+ }
165
+ }
166
+ }
167
+ return result;
168
+ }
169
+ /**
170
+ * §4.5 — at tool-registration time, append the rung's sentence to the description of every tool
171
+ * that is NOT auto-approved at that rung, and leave every granted tool's description untouched.
172
+ *
173
+ * Mutates in place (and returns the same array): the tools are about to be handed to
174
+ * `createAgent`/`createDeepAgent`, and the array is freshly resolved per init. Idempotent — any
175
+ * previously-appended suffix is stripped first, so re-registration at a different rung replaces
176
+ * the sentence rather than stacking one.
177
+ *
178
+ * A tool with no name is left alone (provider-native "magic object" tools carry no name and cannot
179
+ * be classified); a tool with an empty description gets the suffix as its whole description rather
180
+ * than a leading space.
181
+ */
182
+ export function applyRungAwareToolDescriptions(tools, options) {
183
+ const suffix = getRungToolDescriptionSuffix(options.rung);
184
+ for (const tool of tools) {
185
+ if (!tool || typeof tool.name !== 'string' || tool.name.length === 0)
186
+ continue;
187
+ if (typeof tool.description !== 'string')
188
+ continue;
189
+ const base = stripRungToolDescriptionSuffix(tool.description);
190
+ // A granted tool carries NO sentence — its absence is what marks it free (§4.5).
191
+ if (suffix === null || isGrantedAtRung(tool.name, options.rung, options.gatedTools)) {
192
+ tool.description = base;
193
+ continue;
194
+ }
195
+ tool.description = base.length > 0 ? `${base} ${suffix}` : suffix;
196
+ }
197
+ return tools;
198
+ }
199
+ /**
200
+ * §4.3/§4.4 — the names and one-line descriptions of the built-in tools already granted at `rung`,
201
+ * for the rater prompt.
202
+ *
203
+ * Filtered three ways, each of which matters:
204
+ * - to tools **actually registered** in this session (a suggestion naming a tool the model does not
205
+ * have is worse than no suggestion);
206
+ * - to tools in {@link BUILT_IN_TOOL_SUMMARIES}, so only locally-authored text ever reaches the
207
+ * rater prompt (never an MCP/custom tool's own description);
208
+ * - to tools **granted at the rung** — suggesting a tool that would itself need approval defeats
209
+ * the point.
210
+ *
211
+ * Order follows the registration order so the prompt is stable across runs.
212
+ */
213
+ export function describeGrantedBuiltInTools(registeredToolNames, rung, gatedTools) {
214
+ const seen = new Set();
215
+ const summaries = [];
216
+ for (const name of registeredToolNames) {
217
+ if (seen.has(name))
218
+ continue;
219
+ seen.add(name);
220
+ const description = BUILT_IN_TOOL_SUMMARIES[name];
221
+ if (!description)
222
+ continue;
223
+ if (!isGrantedAtRung(name, rung, gatedTools))
224
+ continue;
225
+ summaries.push({ name, description });
226
+ }
227
+ return summaries;
228
+ }
229
+ //# sourceMappingURL=tool-descriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-descriptions.js","sourceRoot":"","sources":["../../src/config/tool-descriptions.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
8
8
  import type { BaseToolkit, StructuredToolInterface } from '@langchain/core/tools';
9
9
  import type { StatusLevel } from '#src/core/types.js';
10
- import type { BuiltInToolsSetting } from '#src/config/shell-policy.js';
10
+ import type { ApprovalsConfig, BuiltInToolsSetting } from '#src/config/shell-policy.js';
11
11
  /**
12
12
  * GS2-43 — the seven configurable prompt segments. Each maps to a prompt file with a
13
13
  * well-known default name (`.gsloth.backstory.md`, `.gsloth.guidelines.md`,
@@ -59,6 +59,12 @@ export type PromptsConfig = Partial<Record<PromptSegmentName, PromptSegmentSetti
59
59
  export interface CommandToolingConfig {
60
60
  filesystem?: string[] | 'all' | 'read' | 'none';
61
61
  builtInTools?: BuiltInToolsSetting;
62
+ /**
63
+ * §9.1 — per-command approvals posture. It overrides only the fields it NAMES: `mode`,
64
+ * `rater`, `raterTimeoutMs` and `allow` replace the root's, while `deny` and `escalate`
65
+ * concatenate with it. See {@link GthConfig.approvals}.
66
+ */
67
+ approvals?: ApprovalsConfig;
62
68
  customTools?: CustomToolsConfig | false;
63
69
  /** See {@link GthConfig.allowedTools}. */
64
70
  allowedTools?: string[];
@@ -187,6 +193,22 @@ export interface GthConfig {
187
193
  * a per-command value replaces the top-level one.
188
194
  */
189
195
  builtInTools?: BuiltInToolsSetting;
196
+ /**
197
+ * CFG-27 — the tool-approval **ladder**: one of the five rungs (`read-only` · `write` ·
198
+ * `auto-safe` · `full-auto` · `bypass`), written either as the bare rung name or as an object
199
+ * carrying the rater's identity profile and the declared allow/deny lists. Each rung fully
200
+ * determines behaviour — there are no severity thresholds, no strictness levels and no
201
+ * independent rater switch.
202
+ *
203
+ * Settable at the root or per command (`commands.<command>.approvals`). §9.1 — a per-command
204
+ * value overrides only the fields it NAMES. `mode`, `rater` and `raterTimeoutMs` replace the
205
+ * root's; `deny` and `escalate` CONCATENATE across every scope, so a per-command rung can never
206
+ * discard the root's prohibitions; `allow` is REPLACED when the command states its own and
207
+ * inherited when it does not, so a scope may narrow what runs unprompted and may never widen
208
+ * what is prohibited (§3.1: a too-broad allow entry runs unrated, a missed deny entry does not).
209
+ * Absent = `auto-safe`, resolved by `resolveApprovals`.
210
+ */
211
+ approvals?: ApprovalsConfig;
190
212
  tools?: StructuredToolInterface[] | BaseToolkit[] | ServerTool[];
191
213
  /**
192
214
  * Restrict the agent to this allow-list of tool names, applied after every tool source
@@ -262,6 +284,13 @@ export interface GthConfig {
262
284
  * Use colour in output
263
285
  */
264
286
  useColour: boolean;
287
+ /**
288
+ * Enable terminal mouse reporting in the Ink TUI, making its affordances clickable.
289
+ * On by default in an interactive terminal. While it is on the terminal's own text selection
290
+ * needs a modifier (Shift, or Option in some macOS terminals) — set this to `false`, or set
291
+ * `GTH_NO_MOUSE`, to get unmodified selection back.
292
+ */
293
+ useMouse: boolean;
265
294
  /**
266
295
  * Stream session log instead of writing it when inference streaming is complete.
267
296
  * (only works when {@link streamOutput} is true)
@@ -351,6 +380,12 @@ export interface GthConfig {
351
380
  api?: {
352
381
  filesystem?: string[] | 'all' | 'read' | 'none';
353
382
  builtInTools?: BuiltInToolsSetting;
383
+ /**
384
+ * §9.1 — per-command approvals posture. It overrides only the fields it NAMES: `mode`,
385
+ * `rater`, `raterTimeoutMs` and `allow` replace the root's, while `deny` and `escalate`
386
+ * concatenate with it. See {@link GthConfig.approvals}.
387
+ */
388
+ approvals?: ApprovalsConfig;
354
389
  port?: number;
355
390
  cors?: {
356
391
  allowOrigin?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAqoBA,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"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAwqBA,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"}
package/dist/config.d.ts CHANGED
@@ -15,12 +15,15 @@
15
15
  * - `config/defaults.ts` — {@link DEFAULT_CONFIG}.
16
16
  * - `config/loader.ts` — discovery + the layered load/merge pipeline.
17
17
  * - `config/schema.ts` — the Zod schema (single source of truth) + JSON-Schema generator.
18
+ * - `config/tool-descriptions.ts` — EXT-58: the rung-aware tool-description suffixes (§4.5) and
19
+ * the granted-built-in table the rater's alternative suggestion draws on (§4.4).
18
20
  *
19
21
  * Every name that was previously exported from `config.ts` is re-exported here, so the
20
22
  * public import path `@gaunt-sloth/core/config.js` (and `#src/config.js`) is unchanged.
21
23
  */
22
24
  export * from '#src/config/types.js';
23
25
  export * from '#src/config/shell-policy.js';
26
+ export * from '#src/config/tool-descriptions.js';
24
27
  export * from '#src/config/defaults.js';
25
28
  export * from '#src/config/loader.js';
26
29
  export * from '#src/config/profiles.js';
package/dist/config.js CHANGED
@@ -15,12 +15,15 @@
15
15
  * - `config/defaults.ts` — {@link DEFAULT_CONFIG}.
16
16
  * - `config/loader.ts` — discovery + the layered load/merge pipeline.
17
17
  * - `config/schema.ts` — the Zod schema (single source of truth) + JSON-Schema generator.
18
+ * - `config/tool-descriptions.ts` — EXT-58: the rung-aware tool-description suffixes (§4.5) and
19
+ * the granted-built-in table the rater's alternative suggestion draws on (§4.4).
18
20
  *
19
21
  * Every name that was previously exported from `config.ts` is re-exported here, so the
20
22
  * public import path `@gaunt-sloth/core/config.js` (and `#src/config.js`) is unchanged.
21
23
  */
22
24
  export * from '#src/config/types.js';
23
25
  export * from '#src/config/shell-policy.js';
26
+ export * from '#src/config/tool-descriptions.js';
24
27
  export * from '#src/config/defaults.js';
25
28
  export * from '#src/config/loader.js';
26
29
  export * from '#src/config/profiles.js';
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC"}
@@ -1,5 +1,6 @@
1
- import { GthConfig, ServerTool } from '#src/config.js';
1
+ import { GthConfig, ServerTool, type ApprovalRung, type DescribableTool } from '#src/config.js';
2
2
  import { AgentResolvers, AgentStreamEvent, GthAgentInterface, GthCommand, GthCompiledGraph, GthRunStats, Message, PendingToolInterrupt, StatusUpdateCallback } from '#src/core/types.js';
3
+ import type { DeclaredToolAnnotations } from '#src/core/approvals/annotations.js';
3
4
  import type { DebugCapture, DebugRequestExtras, LastModelRequest } from '#src/core/debugCapture.js';
4
5
  import { AIMessageChunk, BaseMessage } from '@langchain/core/messages';
5
6
  import { RunnableConfig } from '@langchain/core/runnables';
@@ -75,6 +76,22 @@ export declare abstract class GthAbstractAgent implements GthAgentInterface {
75
76
  * fully fail-soft (accumulation is guarded and never throws into a run).
76
77
  */
77
78
  private runStatsAcc;
79
+ /**
80
+ * EXT-58 — the names of the tools registered with the graph at the last {@link init}, recorded by
81
+ * {@link registerApprovalsAwareTools}. Read by `GthAgentRunner` to build the rater's
82
+ * granted-built-in list (§4.4), so a suggestion can only ever name a tool the model actually has.
83
+ */
84
+ private registeredToolNames;
85
+ /**
86
+ * EXT-70 §4.7.1 — what the connected MCP servers DECLARED about their own tools, captured from
87
+ * the same registration hook as {@link registeredToolNames} and keyed by the registered tool
88
+ * name. Read by `GthAgentRunner` as the `mcp` half of a `DeclaredToolAnnotationLookup`.
89
+ *
90
+ * It is a record of claims, never of decisions: no trust is applied here (that is
91
+ * `createEffectiveToolAnnotationSource`'s only job), and an absent tool yields the fail-closed
92
+ * defaults rather than "declared nothing".
93
+ */
94
+ private declaredMcpToolAnnotations;
78
95
  constructor(statusUpdate: StatusUpdateCallback, resolvers?: AgentResolvers);
79
96
  /**
80
97
  * GS2-63 — emit one line of the technical run-header preamble (the Workdir/Model/Tools/Middleware
@@ -85,6 +102,47 @@ export declare abstract class GthAbstractAgent implements GthAgentInterface {
85
102
  * output, warnings and errors keep using {@link statusUpdate} directly.
86
103
  */
87
104
  protected headerStatus(message: string): void;
105
+ /**
106
+ * EXT-58 (spec §4.5) — the ONE tool-registration hook both backends call with their final tool
107
+ * array, just before handing it to the graph builder. It does two things:
108
+ *
109
+ * 1. Appends the rung's approval sentence to every tool that is **not** auto-approved at that
110
+ * rung, and leaves every granted tool's description untouched (the absence of the sentence is
111
+ * what marks a tool free). See {@link applyRungAwareToolDescriptions}.
112
+ * 2. Records the registered tool names for {@link getRegisteredToolNames}, which feeds the
113
+ * rater's granted-alternative list (§4.4).
114
+ * 3. EXT-70 §4.7.1 — records what the MCP servers declared about their own tools, for
115
+ * {@link getDeclaredMcpToolAnnotations}. This is the ONE place a `tools/list` annotation
116
+ * enters the approvals stack, and it enters as a claim: nothing here decides whether it is
117
+ * believed.
118
+ *
119
+ * `gatedTools` MUST be the same set the caller wires into the approval interrupt
120
+ * (`humanInTheLoopMiddleware`'s `interruptOn` on lean, deepagents' `interruptOn` on deep). That
121
+ * shared parameter is what makes it impossible for a description to promise an approval the gate
122
+ * will not ask for — §4.5's "a description that disagrees with what the gate will actually do is
123
+ * worse than no description at all".
124
+ *
125
+ * `additionalToolNames` covers tools the graph builder registers itself and that therefore never
126
+ * appear in `tools` — deepagents' own filesystem tools on the deep backend. Their descriptions
127
+ * are deepagents', not ours, so they cannot be suffixed here; they are recorded only so the
128
+ * rater's suggestion list reflects what the deep model actually has.
129
+ */
130
+ protected registerApprovalsAwareTools<T extends DescribableTool>(tools: T[], options: {
131
+ rung: ApprovalRung;
132
+ gatedTools: readonly string[];
133
+ additionalToolNames?: readonly string[];
134
+ }): T[];
135
+ /**
136
+ * EXT-58 — the tool names registered with the graph at the last {@link init} (empty before it).
137
+ * The runner intersects these with the built-in summaries table to build the rater's
138
+ * granted-alternative list, so the rater can never name a tool this session does not have.
139
+ */
140
+ getRegisteredToolNames(): string[];
141
+ /**
142
+ * EXT-70 §4.7.1 — what the MCP servers declared for their tools at the last {@link init}, keyed
143
+ * by registered tool name (empty before it, and on a session with no MCP servers).
144
+ */
145
+ getDeclaredMcpToolAnnotations(): ReadonlyMap<string, DeclaredToolAnnotations>;
88
146
  /**
89
147
  * GS2-16 — clear the per-run analytics tally so the next turn starts from zero. The runner
90
148
  * calls this at each turn boundary because it (and this agent) are reused across turns in an
@@ -1,5 +1,7 @@
1
+ import { applyRungAwareToolDescriptions, } from '#src/config.js';
1
2
  import { StatusLevel, } from '#src/core/types.js';
2
3
  import { accumulateMessage, createRunStatsAccumulator, finalizeRunStats, } from '#src/core/runStats.js';
4
+ import { collectDeclaredMcpToolAnnotations } from '#src/core/approvals/toolAnnotationSources.js';
3
5
  import { createPlainToolIndication } from '#src/core/plainToolIndication.js';
4
6
  import { debugLog, debugLogError, debugLogObject } from '#src/utils/debugUtils.js';
5
7
  import { ProgressIndicator } from '#src/utils/ProgressIndicator.js';
@@ -205,6 +207,22 @@ export class GthAbstractAgent {
205
207
  * fully fail-soft (accumulation is guarded and never throws into a run).
206
208
  */
207
209
  runStatsAcc = createRunStatsAccumulator();
210
+ /**
211
+ * EXT-58 — the names of the tools registered with the graph at the last {@link init}, recorded by
212
+ * {@link registerApprovalsAwareTools}. Read by `GthAgentRunner` to build the rater's
213
+ * granted-built-in list (§4.4), so a suggestion can only ever name a tool the model actually has.
214
+ */
215
+ registeredToolNames = [];
216
+ /**
217
+ * EXT-70 §4.7.1 — what the connected MCP servers DECLARED about their own tools, captured from
218
+ * the same registration hook as {@link registeredToolNames} and keyed by the registered tool
219
+ * name. Read by `GthAgentRunner` as the `mcp` half of a `DeclaredToolAnnotationLookup`.
220
+ *
221
+ * It is a record of claims, never of decisions: no trust is applied here (that is
222
+ * `createEffectiveToolAnnotationSource`'s only job), and an absent tool yields the fail-closed
223
+ * defaults rather than "declared nothing".
224
+ */
225
+ declaredMcpToolAnnotations = new Map();
208
226
  constructor(statusUpdate, resolvers) {
209
227
  this.statusUpdate = (level, message) => {
210
228
  statusUpdate(level, message);
@@ -224,6 +242,60 @@ export class GthAbstractAgent {
224
242
  return;
225
243
  this.statusUpdate(StatusLevel.INFO, message);
226
244
  }
245
+ /**
246
+ * EXT-58 (spec §4.5) — the ONE tool-registration hook both backends call with their final tool
247
+ * array, just before handing it to the graph builder. It does two things:
248
+ *
249
+ * 1. Appends the rung's approval sentence to every tool that is **not** auto-approved at that
250
+ * rung, and leaves every granted tool's description untouched (the absence of the sentence is
251
+ * what marks a tool free). See {@link applyRungAwareToolDescriptions}.
252
+ * 2. Records the registered tool names for {@link getRegisteredToolNames}, which feeds the
253
+ * rater's granted-alternative list (§4.4).
254
+ * 3. EXT-70 §4.7.1 — records what the MCP servers declared about their own tools, for
255
+ * {@link getDeclaredMcpToolAnnotations}. This is the ONE place a `tools/list` annotation
256
+ * enters the approvals stack, and it enters as a claim: nothing here decides whether it is
257
+ * believed.
258
+ *
259
+ * `gatedTools` MUST be the same set the caller wires into the approval interrupt
260
+ * (`humanInTheLoopMiddleware`'s `interruptOn` on lean, deepagents' `interruptOn` on deep). That
261
+ * shared parameter is what makes it impossible for a description to promise an approval the gate
262
+ * will not ask for — §4.5's "a description that disagrees with what the gate will actually do is
263
+ * worse than no description at all".
264
+ *
265
+ * `additionalToolNames` covers tools the graph builder registers itself and that therefore never
266
+ * appear in `tools` — deepagents' own filesystem tools on the deep backend. Their descriptions
267
+ * are deepagents', not ours, so they cannot be suffixed here; they are recorded only so the
268
+ * rater's suggestion list reflects what the deep model actually has.
269
+ */
270
+ registerApprovalsAwareTools(tools, options) {
271
+ applyRungAwareToolDescriptions(tools, {
272
+ rung: options.rung,
273
+ gatedTools: options.gatedTools,
274
+ });
275
+ const names = tools
276
+ .map((tool) => tool?.name)
277
+ .filter((name) => typeof name === 'string' && name.length > 0);
278
+ this.registeredToolNames = [...names, ...(options.additionalToolNames ?? [])];
279
+ // `additionalToolNames` are deliberately NOT consulted: they are names the graph builder
280
+ // registers itself, with no tool object and therefore no declaration to read.
281
+ this.declaredMcpToolAnnotations = collectDeclaredMcpToolAnnotations(tools);
282
+ return tools;
283
+ }
284
+ /**
285
+ * EXT-58 — the tool names registered with the graph at the last {@link init} (empty before it).
286
+ * The runner intersects these with the built-in summaries table to build the rater's
287
+ * granted-alternative list, so the rater can never name a tool this session does not have.
288
+ */
289
+ getRegisteredToolNames() {
290
+ return [...this.registeredToolNames];
291
+ }
292
+ /**
293
+ * EXT-70 §4.7.1 — what the MCP servers declared for their tools at the last {@link init}, keyed
294
+ * by registered tool name (empty before it, and on a session with no MCP servers).
295
+ */
296
+ getDeclaredMcpToolAnnotations() {
297
+ return this.declaredMcpToolAnnotations;
298
+ }
227
299
  /**
228
300
  * GS2-16 — clear the per-run analytics tally so the next turn starts from zero. The runner
229
301
  * calls this at each turn boundary because it (and this agent) are reused across turns in an