@gaunt-sloth/core 2.0.0-alpha.26 → 2.0.0-alpha.27
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.js +64 -15
- package/dist/config/schema.js.map +1 -1
- package/dist/config/types.d.ts +17 -8
- package/dist/config/types.js.map +1 -1
- package/dist/constants.d.ts +7 -4
- package/dist/constants.js +7 -4
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +10 -4
- package/dist/core/GthAgentRunner.js +24 -7
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.js +16 -11
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/launchBanner.js +36 -17
- package/dist/core/launchBanner.js.map +1 -1
- 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/openWorld.d.ts +137 -12
- package/dist/core/shell/openWorld.js +677 -12
- package/dist/core/shell/openWorld.js.map +1 -1
- package/dist/core/shell/rater.d.ts +68 -38
- package/dist/core/shell/rater.js +105 -61
- package/dist/core/shell/rater.js.map +1 -1
- package/dist/core/shell/rejection.d.ts +7 -4
- package/dist/core/shell/rejection.js +3 -3
- package/dist/core/shell/rejection.js.map +1 -1
- package/dist/core/toolDisplay.d.ts +12 -3
- package/dist/core/toolDisplay.js +27 -7
- package/dist/core/toolDisplay.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +30 -0
- package/dist/utils/displayWidth.js +140 -0
- package/dist/utils/displayWidth.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +28 -8
- package/dist/utils/systemPromptNotes.js +47 -49
- package/dist/utils/systemPromptNotes.js.map +1 -1
- 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 +2 -1
- package/schema/gsloth-config.schema.json +3 -1
|
@@ -38,15 +38,49 @@
|
|
|
38
38
|
* upstream. The one hard limit is unchanged and non-negotiable: **never fire on the mere presence of
|
|
39
39
|
* a URL anywhere in the string**, because `git commit -m "closes https://…"` must stay silent.
|
|
40
40
|
*
|
|
41
|
+
* ## TWO CONSUMERS, TWO INPUT SETS — read this before merging them back together
|
|
42
|
+
*
|
|
43
|
+
* This module answers the host question for **two** callers whose error costs differ, so it has two
|
|
44
|
+
* entry points and they are deliberately not the same function:
|
|
45
|
+
*
|
|
46
|
+
* - {@link findOpenWorldHostLiterals} — **the floor**. Its finding rewrites a `safe` verdict to
|
|
47
|
+
* `destructive` with no model in the loop, so it fires only where the parser resolved the whole
|
|
48
|
+
* command. "The parser could not resolve this" is a fact about the checker, not a detection about
|
|
49
|
+
* the command, and this layer floors only what is deterministically known.
|
|
50
|
+
* - {@link findComposedOpenWorld} — **the note**. It reads the parts of a command the parser could
|
|
51
|
+
* NOT resolve as a whole, and its finding is handed to the rater as context. It changes no
|
|
52
|
+
* outcome by itself.
|
|
53
|
+
*
|
|
54
|
+
* **The error-cost regime is the third distinct one in this codebase, and it is the widest — about
|
|
55
|
+
* WHICH HOSTS ARE NAMED.** The §8 hardline REFUSES unappealably, so it must be the narrowest. This
|
|
56
|
+
* module's floor RAISES a prompt, so it over-matches (below). Naming a host in the note only
|
|
57
|
+
* INFORMS THE MODEL: a host named that turns out not to be contacted costs one sentence of
|
|
58
|
+
* attention and no interruption at all. So do not "fix" a note false positive by narrowing the host
|
|
59
|
+
* extractor; that trades a free cost for a silent one.
|
|
60
|
+
*
|
|
61
|
+
* **That licence covers which hosts are named. It does not cover WHAT THE NOTE SAYS THEY DO.** A
|
|
62
|
+
* flow sentence asserts a mechanism — that fetched bytes are executed, that a file's contents are
|
|
63
|
+
* sent — and the rater cannot check that against a shell; it can only believe it. A mechanism that
|
|
64
|
+
* is false on an ordinary command is this node's own named failure mode arriving one layer in: an
|
|
65
|
+
* escalation laundered through the model instead of the parser, unfalsifiable because a note said
|
|
66
|
+
* it. So each flow arm fires only where its claim is **true of the program named**, and everything
|
|
67
|
+
* else falls through to the flowless sentence — which still names the hosts and says outright that
|
|
68
|
+
* the flow is not known. Saying less is not a loss of assistance; asserting a false mechanism is a
|
|
69
|
+
* loss of the layer.
|
|
70
|
+
*
|
|
71
|
+
* **And a flow sentence names EVERY host of the part it describes**, for the reason
|
|
72
|
+
* {@link findOpenWorldHostLiterals} returns every match rather than the first: the first is the
|
|
73
|
+
* proxy, and a sentence that names the reassuring host while hiding the other is worse than no
|
|
74
|
+
* sentence.
|
|
75
|
+
*
|
|
41
76
|
* ## The shape of the matcher
|
|
42
77
|
*
|
|
43
78
|
* Ported from the measured prototype (`project-takahe _spikes/open-world-preflight/`).
|
|
44
79
|
*
|
|
45
|
-
* 1. **Decline on anything unclassifiable
|
|
46
|
-
* composition (separator, line break, `$(…)`, backtick, redirection), and
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* explanation than "its target cannot be statically resolved".
|
|
80
|
+
* 1. **Decline on anything unclassifiable** — for the FLOOR only. {@link classifyCommand} returns
|
|
81
|
+
* `null` on any composition (separator, line break, `$(…)`, backtick, redirection), and a
|
|
82
|
+
* deterministic floor must not claim "it names a host" about a string whose target it could not
|
|
83
|
+
* statically resolve. The note path picks those up instead, by reading the parts.
|
|
50
84
|
* 2. **Step past wrappers** (`sudo -u root`, `env FOO=1`, `nohup --`, …) to the head.
|
|
51
85
|
* 3. **Look the head up** in {@link NETWORK_HEADS}, keyed by *where a host may legitimately appear*.
|
|
52
86
|
* 4. **Test only the candidate operands** for a host literal.
|
|
@@ -104,13 +138,14 @@ export declare function isHostLiteral(operand: string): boolean;
|
|
|
104
138
|
* caller can never accidentally hand this a form that has already lost the composition boundary
|
|
105
139
|
* the decline below depends on.
|
|
106
140
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* (`curl … | sh`, `cat .env | curl …`) is
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
141
|
+
* **This is the FLOOR's input set, and it is narrow on purpose.** It returns `[]` — declining rather
|
|
142
|
+
* than flooring — for any command {@link classifyCommand} cannot classify: those compose, substitute
|
|
143
|
+
* or redirect, and a deterministic rewrite of the rater's verdict must rest on a target this module
|
|
144
|
+
* actually resolved. A composed fetch (`curl … | sh`, `cat .env | curl …`) is therefore **not
|
|
145
|
+
* floored**; it is reported to the rater as context by {@link findComposedOpenWorld} instead, which
|
|
146
|
+
* is a different question with a different error cost (module docblock). The same decline is why
|
|
147
|
+
* `sed -i 's|http://a|http://b|' config.yml` is not this preflight's finding: the `|` inside the sed
|
|
148
|
+
* expression reads as composition.
|
|
114
149
|
*
|
|
115
150
|
* **Every match is returned, not the first.** The first is not the target: for
|
|
116
151
|
* `curl -x http://proxy.corp.local:3128 https://evil.example.net/x` it is the proxy, and for
|
|
@@ -136,3 +171,93 @@ export declare function isHostLiteral(operand: string): boolean;
|
|
|
136
171
|
* @returns The matched host literals, in argv order (used verbatim in the escalation reason).
|
|
137
172
|
*/
|
|
138
173
|
export declare function findOpenWorldHostLiterals(command: string): string[];
|
|
174
|
+
/**
|
|
175
|
+
* The data flow the parts of a composed command line perform together — the fact that is not visible
|
|
176
|
+
* in any one part, and the only reason this note is worth a rater's attention.
|
|
177
|
+
*/
|
|
178
|
+
export type ComposedFlow =
|
|
179
|
+
/**
|
|
180
|
+
* A fetch is piped into a program that can run its standard input. `stdinIsTheProgram` says
|
|
181
|
+
* whether it does on this line ({@link interpreterRunsStdin}) — `curl … | sh` runs the fetched
|
|
182
|
+
* bytes, `curl … | python3 -m json.tool` reads them as data — and the two get different
|
|
183
|
+
* sentences, because only one of them executes what the host serves.
|
|
184
|
+
*/
|
|
185
|
+
{
|
|
186
|
+
readonly kind: 'fetch-into-interpreter';
|
|
187
|
+
readonly hosts: readonly string[];
|
|
188
|
+
readonly interpreter: string;
|
|
189
|
+
readonly stdinIsTheProgram: boolean;
|
|
190
|
+
}
|
|
191
|
+
/** A local program's output is piped into a program that sends it to a host. */
|
|
192
|
+
| {
|
|
193
|
+
readonly kind: 'local-into-transfer';
|
|
194
|
+
readonly producer: string;
|
|
195
|
+
readonly transfer: string;
|
|
196
|
+
readonly hosts: readonly string[];
|
|
197
|
+
}
|
|
198
|
+
/** A substitution's output becomes an argument the program SENDS. */
|
|
199
|
+
| {
|
|
200
|
+
readonly kind: 'substitution-into-transfer';
|
|
201
|
+
readonly transfer: string;
|
|
202
|
+
readonly hosts: readonly string[];
|
|
203
|
+
}
|
|
204
|
+
/** A transfer agent is told to read a local file and send its contents. */
|
|
205
|
+
| {
|
|
206
|
+
readonly kind: 'file-into-transfer';
|
|
207
|
+
readonly transfer: string;
|
|
208
|
+
readonly hosts: readonly string[];
|
|
209
|
+
readonly path: string | null;
|
|
210
|
+
};
|
|
211
|
+
/** What the note path found in a command the parser could not resolve as a whole. */
|
|
212
|
+
export interface ComposedOpenWorldFinding {
|
|
213
|
+
/**
|
|
214
|
+
* Host literals in a fetch/transfer position, found by reading the parts SEPARATELY.
|
|
215
|
+
*
|
|
216
|
+
* **Not the floor's set and never passed to it** — {@link findOpenWorldHostLiterals} is the floor's
|
|
217
|
+
* only input, and it declines every command this function accepts.
|
|
218
|
+
*/
|
|
219
|
+
readonly hosts: readonly string[];
|
|
220
|
+
/** The flow across the parts, or `null` when none is determinable. */
|
|
221
|
+
readonly flow: ComposedFlow | null;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Read a command the gate's parser could NOT resolve part by part, and report the host literals and
|
|
225
|
+
* the data flow across those parts — or `null` when the command resolves, or when no part names a
|
|
226
|
+
* host.
|
|
227
|
+
*
|
|
228
|
+
* **This feeds the rater's note and nothing else.** It is never consulted by the destructive floor:
|
|
229
|
+
* see the module docblock for why the two questions have different input sets, and
|
|
230
|
+
* {@link findOpenWorldHostLiterals} for the floor's.
|
|
231
|
+
*
|
|
232
|
+
* The `null` on a resolvable command is the guard that keeps the rater from being told about the
|
|
233
|
+
* same host twice in two registers — a command the parser resolved is the floor's, and the floor's
|
|
234
|
+
* own note already names its hosts.
|
|
235
|
+
*
|
|
236
|
+
* Both the normalized and the raw form are read, for the reason {@link findOpenWorldHostLiterals}
|
|
237
|
+
* gives: normalization collapses `\x` to `x`, which defeats `c\url` and destroys a Windows path
|
|
238
|
+
* separator, so the raw pass is the only one that still sees `C:\Windows\System32\curl.exe`.
|
|
239
|
+
*
|
|
240
|
+
* @param command The raw command string as the model proposed it.
|
|
241
|
+
*/
|
|
242
|
+
export declare function findComposedOpenWorld(command: string): ComposedOpenWorldFinding | null;
|
|
243
|
+
/**
|
|
244
|
+
* The opening line of the composed open-world note.
|
|
245
|
+
*
|
|
246
|
+
* **It states the two facts and asserts no third one.** A part of this line names a host in a
|
|
247
|
+
* fetch/transfer position, and nothing about the command has been decided. The second half is what
|
|
248
|
+
* keeps this out of the floor note's register: that one may say the command *"is never
|
|
249
|
+
* auto-approved"* because a floor really did fire, and here no floor exists — repeating its sentence
|
|
250
|
+
* would tell the rater the outcome is settled when the rating is the only thing that decides it.
|
|
251
|
+
*/
|
|
252
|
+
export declare const COMPOSED_OPEN_WORLD_PREAMBLE: string;
|
|
253
|
+
/**
|
|
254
|
+
* Build the composed open-world note for a command, or `null` when there is nothing to say.
|
|
255
|
+
*
|
|
256
|
+
* One sentence of mechanism when the flow is determinable, plus the hosts the rest of the line names
|
|
257
|
+
* ({@link residualSentence}); when it is not, {@link flowlessSentence}. **Every host on the finding
|
|
258
|
+
* that can be quoted is named either way** — which arm fired must never decide how much the rater is
|
|
259
|
+
* told about the counterparties.
|
|
260
|
+
*
|
|
261
|
+
* @param command The raw command string as the model proposed it.
|
|
262
|
+
*/
|
|
263
|
+
export declare function buildComposedOpenWorldNote(command: string): string | null;
|