@gaunt-sloth/core 2.0.0-alpha.35 → 2.0.0-alpha.36
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/configDiscovery.d.ts +79 -0
- package/dist/config/configDiscovery.js +80 -0
- package/dist/config/configDiscovery.js.map +1 -0
- package/dist/config/loader.d.ts +6 -5
- package/dist/config/loader.js +229 -92
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +3 -2
- package/dist/config/schema.js +57 -9
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +58 -26
- package/dist/config/shell-policy.js +60 -27
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +6 -6
- package/dist/config/tool-descriptions.js +7 -7
- package/dist/config/tool-descriptions.js.map +1 -1
- package/dist/config/types.d.ts +34 -31
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +11 -13
- package/dist/core/GthAbstractAgent.js +9 -11
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +17 -33
- package/dist/core/GthAgentRunner.js +31 -41
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.js +25 -34
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/toolAnnotationSources.d.ts +4 -4
- package/dist/core/approvals/toolAnnotationSources.js +8 -8
- package/dist/core/debugCapture.d.ts +2 -3
- package/dist/core/debugCapture.js +1 -2
- package/dist/core/debugCapture.js.map +1 -1
- package/dist/core/gthLeanAgentFactory.d.ts +4 -4
- package/dist/core/gthLeanAgentFactory.js +4 -4
- package/dist/core/reasoningBlocks.d.ts +7 -2
- package/dist/core/reasoningBlocks.js +7 -2
- package/dist/core/reasoningBlocks.js.map +1 -1
- package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
- package/dist/core/shell/ShellCommandFailedError.js +3 -4
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
- package/dist/core/shell/approvalStop.d.ts +68 -29
- package/dist/core/shell/approvalStop.js +178 -28
- package/dist/core/shell/approvalStop.js.map +1 -1
- package/dist/core/shell/hardline.d.ts +22 -0
- package/dist/core/shell/hardline.js +81 -0
- package/dist/core/shell/hardline.js.map +1 -1
- package/dist/core/shell/negotiation.d.ts +81 -2
- package/dist/core/shell/negotiation.js +144 -11
- package/dist/core/shell/negotiation.js.map +1 -1
- package/dist/core/shell/rater.d.ts +7 -69
- package/dist/core/shell/rater.js +5 -24
- package/dist/core/shell/rater.js.map +1 -1
- package/dist/core/shell/raterVocabulary.d.ts +119 -0
- package/dist/core/shell/raterVocabulary.js +114 -0
- package/dist/core/shell/raterVocabulary.js.map +1 -0
- package/dist/core/types.d.ts +17 -3
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/configurationPassthrough.d.ts +107 -0
- package/dist/providers/configurationPassthrough.js +148 -0
- package/dist/providers/configurationPassthrough.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +8 -0
- package/dist/providers/geminiThinking.js +33 -13
- package/dist/providers/geminiThinking.js.map +1 -1
- package/dist/providers/google-genai.js +11 -0
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.js +12 -0
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/ollama.js +12 -0
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openrouter.d.ts +23 -0
- package/dist/providers/openrouter.js +71 -4
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +12 -0
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.js +20 -0
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/conversation.d.ts +2 -2
- package/dist/runtime/conversation.js +19 -4
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.d.ts +2 -3
- package/dist/runtime/singleShot.js +22 -7
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/systemPromptNotes.d.ts +17 -20
- package/dist/utils/systemPromptNotes.js +22 -24
- package/dist/utils/systemPromptNotes.js.map +1 -1
- package/dist/utils/untrustedText.d.ts +24 -4
- package/dist/utils/untrustedText.js +25 -4
- package/dist/utils/untrustedText.js.map +1 -1
- package/package.json +4 -4
- package/schema/gsloth-config.schema.json +0 -1
|
@@ -23,18 +23,145 @@
|
|
|
23
23
|
* thrown run as `ok: false`, and each command entry point turns that into `setExitCode(1)` — so
|
|
24
24
|
* "immediate non-zero exit carrying the explanation" is what a caller already gets, without a new
|
|
25
25
|
* exit path that could diverge from the existing one.
|
|
26
|
+
*
|
|
27
|
+
* ## [[TUI-C71]] — a stop message is UNTRUSTED TEXT, and it is made safe in two places
|
|
28
|
+
*
|
|
29
|
+
* Every string a stop carries except this module's own sentences is written by something other
|
|
30
|
+
* than the user: the command the agent proposed, the rater's outcome and reason, the
|
|
31
|
+
* `approvals.escalate` entry that matched, and the whole §5 negotiation. On an `attack` verdict
|
|
32
|
+
* that is the *worst* text in the system by construction — the rater judged the command's own
|
|
33
|
+
* structure to evidence deception or obfuscation — so a stop is the one message most likely to
|
|
34
|
+
* carry a payload crafted to forge terminal output, and the last one that may be printed raw.
|
|
35
|
+
*
|
|
36
|
+
* The defence [[TUI-C26]] built has two halves, and they belong at different times:
|
|
37
|
+
*
|
|
38
|
+
* 1. **Neutralisation is done HERE, at construction, unconditionally.** It needs no width and no
|
|
39
|
+
* screen, it is idempotent, and it is the half every consumer needs — a thrown stop reaches the
|
|
40
|
+
* Ink transcript, `--no-tui` stderr, a session log, the approvals archive (`record.error`), an
|
|
41
|
+
* eval turn record, AG-UI and a CI job's output, and only some of those are terminals. Doing it
|
|
42
|
+
* at each render site would mean every present and future consumer had to remember; doing it
|
|
43
|
+
* once here means none of them can forget. After it, `{@link message}` contains no control or
|
|
44
|
+
* format character at all: a carriage return is the five printable characters `\x0d`, and —
|
|
45
|
+
* because LF is a control character too — untrusted text cannot even open a new line, let alone
|
|
46
|
+
* reach column 0 on one.
|
|
47
|
+
* 2. **The gutter and the width bound are done at RENDER, by the surface** ({@link
|
|
48
|
+
* approvalStopRows} for the plain surfaces, `<ApprovalStopMessage>` for the Ink TUI). They
|
|
49
|
+
* cannot be done here: framing is arithmetic against a terminal width, an `Error.message` has
|
|
50
|
+
* no width, and a block wrapped for an 80-column terminal is wrong for every other one. What
|
|
51
|
+
* they buy over neutralisation alone is the last case it cannot close — one very long
|
|
52
|
+
* neutralised line that the *terminal* wraps, whose continuation starts at column 0 carrying
|
|
53
|
+
* whatever the attacker chose to put at that offset. That route is measured, not theorised: on
|
|
54
|
+
* a 120-column terminal the neutralised `Command:` line of a hostile command is ~164 columns.
|
|
55
|
+
*
|
|
56
|
+
* **Because half of it is the surface's job, the surfaces are enumerated rather than assumed.**
|
|
57
|
+
* Every place that catches an `ApprovalStopError` and prints it calls {@link approvalStopRows} (or
|
|
58
|
+
* the Ink component): `runtime/singleShot.ts`, `runtime/conversation.ts`,
|
|
59
|
+
* `agent/modules/interactiveSessionModule.ts` (both its per-turn catch and its outermost one, which
|
|
60
|
+
* is where the `-m` path lands), `review/modules/reviewModule.ts`, `app/commands/prCommand.ts`
|
|
61
|
+
* (which catches what the PR-discovery agent throws) and `app/tui/components/App.tsx`.
|
|
62
|
+
* **Adding a surface that catches a stop means adding it to that list.**
|
|
63
|
+
*
|
|
64
|
+
* **That list is over the ERROR, and the error is not the only way a stop reaches a screen.** Some
|
|
65
|
+
* consumers take {@link message} and hand the *string* onward, at which point no `instanceof` can
|
|
66
|
+
* find it again: `runConversation` records it as a turn's `error`, and `evalRunner` folds that into
|
|
67
|
+
* an eval case's `reasons`, which `batch/reporters/textReporter.ts` prints — a terminal, reached
|
|
68
|
+
* without any of its code ever seeing this class. That one frames the text itself. So the honest
|
|
69
|
+
* statement is not "every other consumer is a non-terminal" but: **a consumer that prints to a
|
|
70
|
+
* terminal frames what it prints, whether it holds the error or only its text**, and the ones that
|
|
71
|
+
* legitimately do neither are the file and protocol sinks (the approvals archive, the JUnit/JSON
|
|
72
|
+
* reports, the AG-UI event) plus `reviewModule`'s `displayDebug` of the raw error, which is a debug
|
|
73
|
+
* channel. All of them still inherit the neutralisation, which is what keeps the worst case a
|
|
74
|
+
* wrapped line rather than a repainted screen.
|
|
75
|
+
*
|
|
76
|
+
* **The structured fields stay RAW; only the message is neutralised.** That is what answers the
|
|
77
|
+
* obvious objection to construction-time work — that it stops the error being a faithful record of
|
|
78
|
+
* what was proposed. `{@link ApprovalStopError.command}`, `reason`, `outcome`, `escalatedBy` and
|
|
79
|
+
* `negotiation` are the command as the agent wrote it, byte for byte, for any consumer that needs
|
|
80
|
+
* the truth rather than a screen; the message is the *presentation*, and a presentation that can
|
|
81
|
+
* repaint the terminal is not one. The render path frames from those raw fields, so its gutter
|
|
82
|
+
* numbers the command's real lines rather than one escaped line.
|
|
83
|
+
*
|
|
84
|
+
* **One parts list, two renderings** ({@link ApprovalStopPart}). The message string and the framed
|
|
85
|
+
* block are both derived from {@link ApprovalStopError.parts}, so they cannot come to describe one
|
|
86
|
+
* stop two ways — the same argument `renderNegotiationTranscript` makes for its own two surfaces.
|
|
87
|
+
* A part is tagged with *who wrote it*, which is the only distinction any of this rests on: the
|
|
88
|
+
* gate's own prose can be painted as it is because nothing can forge it, and everything else goes
|
|
89
|
+
* through the shared renderer.
|
|
26
90
|
*/
|
|
91
|
+
import { frameUntrustedCommand, frameUntrustedText, frameWidthFor, narrowTerminalNotice, neutralizeUntrustedText, } from '#src/core/shell/framing.js';
|
|
92
|
+
/**
|
|
93
|
+
* The parts as the one string an `Error.message` has to be, with every untrusted part neutralised.
|
|
94
|
+
*
|
|
95
|
+
* A `block` is neutralised **line by line**, so the renderer-owned breaks that carry its meaning
|
|
96
|
+
* survive while its content still cannot introduce one. Everything else is neutralised whole,
|
|
97
|
+
* which — LF being a control character — is what collapses an untrusted value onto the single line
|
|
98
|
+
* its label put it on.
|
|
99
|
+
*/
|
|
100
|
+
function composeMessage(parts) {
|
|
101
|
+
return parts.map(messageLineFor).join('\n');
|
|
102
|
+
}
|
|
103
|
+
function messageLineFor(part) {
|
|
104
|
+
switch (part.kind) {
|
|
105
|
+
case 'own':
|
|
106
|
+
return part.text;
|
|
107
|
+
case 'block':
|
|
108
|
+
return part.text.split('\n').map(neutralizeUntrustedText).join('\n');
|
|
109
|
+
default:
|
|
110
|
+
return ` ${part.label}: ${neutralizeUntrustedText(part.text)}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The stop as terminal rows, for a surface that paints lines rather than components.
|
|
115
|
+
*
|
|
116
|
+
* Every row is painted **verbatim, one row per line, and never re-wrapped** — that is the
|
|
117
|
+
* condition `core/shell/framing` states its column-0 guarantee over, and a caller that joins these
|
|
118
|
+
* and hands them to something that wraps has undone the whole point.
|
|
119
|
+
*
|
|
120
|
+
* `columns` is what the surface reports (`stdout.columns`), not a frame width: it is resolved
|
|
121
|
+
* through {@link frameWidthFor} here so the plain surfaces, the Ink TUI and the approval dialog
|
|
122
|
+
* cannot come to disagree about how much of a command a human was shown. When it is below core's
|
|
123
|
+
* floor the frame is wider than the terminal and the guarantee lapses — {@link narrowTerminalNotice}
|
|
124
|
+
* leads the block in that case, so it lapses out loud.
|
|
125
|
+
*/
|
|
126
|
+
export function approvalStopRows(parts, options) {
|
|
127
|
+
const width = frameWidthFor(options?.columns);
|
|
128
|
+
const rows = [];
|
|
129
|
+
const tooNarrow = narrowTerminalNotice(options?.columns);
|
|
130
|
+
if (tooNarrow)
|
|
131
|
+
rows.push(tooNarrow);
|
|
132
|
+
for (const part of parts) {
|
|
133
|
+
if (part.kind === 'own') {
|
|
134
|
+
rows.push(part.text);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// The label is the gate's OWN word for what follows, and it stays on the gate's own row rather
|
|
138
|
+
// than being prefixed to the first framed row — a label sharing a row with untrusted text is a
|
|
139
|
+
// row a reader cannot tell apart from one the model wrote all of.
|
|
140
|
+
if (part.kind !== 'block')
|
|
141
|
+
rows.push(` ${part.label}:`);
|
|
142
|
+
const framed = part.kind === 'command'
|
|
143
|
+
? frameUntrustedCommand(part.text, { width })
|
|
144
|
+
: frameUntrustedText(part.text, { width });
|
|
145
|
+
rows.push(...framed.notices, ...framed.lines);
|
|
146
|
+
}
|
|
147
|
+
return rows;
|
|
148
|
+
}
|
|
27
149
|
/**
|
|
28
150
|
* Base class for the two run-ending approvals outcomes, so a caller that wants to present them as
|
|
29
|
-
* an ending rather than a crash can catch both with one `instanceof`.
|
|
30
|
-
*
|
|
151
|
+
* an ending rather than a crash can catch both with one `instanceof`.
|
|
152
|
+
*
|
|
153
|
+
* A surface that can frame should render {@link parts} (see {@link approvalStopRows}); one that
|
|
154
|
+
* cannot shows {@link message}, which is already neutralised and is already the whole explanation.
|
|
31
155
|
*/
|
|
32
156
|
export class ApprovalStopError extends Error {
|
|
33
|
-
/** The command that ended the run. */
|
|
157
|
+
/** The command that ended the run, exactly as the agent proposed it. */
|
|
34
158
|
command;
|
|
35
|
-
|
|
36
|
-
|
|
159
|
+
/** The message's pieces, tagged with who wrote each — see {@link ApprovalStopPart}. */
|
|
160
|
+
parts;
|
|
161
|
+
constructor(parts, command) {
|
|
162
|
+
super(composeMessage(parts));
|
|
37
163
|
this.command = command;
|
|
164
|
+
this.parts = parts;
|
|
38
165
|
// Restore the prototype chain across the ES5 `extends Error` downlevel, so `instanceof` works.
|
|
39
166
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
40
167
|
this.name = new.target.name;
|
|
@@ -45,11 +172,11 @@ export class ApprovalStopError extends Error {
|
|
|
45
172
|
* credential targeting, privilege escalation, persistence, deception, obfuscation). Ends the agent
|
|
46
173
|
* loop; the model is told nothing and offered nothing.
|
|
47
174
|
*
|
|
48
|
-
* **This is what reaches a surface that cannot ask.** An interactive surface is offered
|
|
49
|
-
* banner first — `GthAgentRunner.setAttackHaltCallback`, where typing `run anyway` runs
|
|
50
|
-
* command and everything else stops the run — and this error is thrown when no banner is
|
|
51
|
-
* when the banner is answered with anything but that phrase. A non-interactive session
|
|
52
|
-
* nothing and so gets this message directly (§6.2).
|
|
175
|
+
* **This is what reaches a surface that cannot ask.** An interactive surface is offered [[TUI-C68]]
|
|
176
|
+
* §6.1's red banner first — `GthAgentRunner.setAttackHaltCallback`, where typing `run anyway` runs
|
|
177
|
+
* this one command and everything else stops the run — and this error is thrown when no banner is
|
|
178
|
+
* wired, or when the banner is answered with anything but that phrase. A non-interactive session
|
|
179
|
+
* wires nothing and so gets this message directly (§6.2).
|
|
53
180
|
*
|
|
54
181
|
* The recovery this message names is deliberately the **allow-list**, not `bypass`. §4.2 makes
|
|
55
182
|
* `approvals.allow` the supported way to run such a command unattended (it is consulted before the
|
|
@@ -61,13 +188,21 @@ export class AttackHaltError extends ApprovalStopError {
|
|
|
61
188
|
/** The rater's explanation of what the command's structure showed. */
|
|
62
189
|
reason;
|
|
63
190
|
constructor(command, reason) {
|
|
64
|
-
super(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
191
|
+
super([
|
|
192
|
+
{
|
|
193
|
+
kind: 'own',
|
|
194
|
+
text: 'Run halted: the auto-rater rated this command as an attack, which ends the run.',
|
|
195
|
+
},
|
|
196
|
+
{ kind: 'command', label: 'Command', text: command },
|
|
197
|
+
{ kind: 'value', label: 'Reason', text: reason },
|
|
198
|
+
{
|
|
199
|
+
kind: 'own',
|
|
200
|
+
text: `This is not negotiable. If this command is legitimate and you need it to run, declare ` +
|
|
201
|
+
`it in approvals.allow — that list is consulted before the auto-rater, so it never ` +
|
|
202
|
+
`reaches a halt. Dropping to approvals "bypass" also works, but it turns off the rater, ` +
|
|
203
|
+
`the prompts and the halt for every command in the run.`,
|
|
204
|
+
},
|
|
205
|
+
], command);
|
|
71
206
|
this.reason = reason;
|
|
72
207
|
}
|
|
73
208
|
}
|
|
@@ -96,20 +231,35 @@ export class NonInteractiveEscalationError extends ApprovalStopError {
|
|
|
96
231
|
*/
|
|
97
232
|
negotiation;
|
|
98
233
|
constructor(command, outcome, reason, escalatedBy, negotiation) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
(
|
|
105
|
-
|
|
106
|
-
|
|
234
|
+
const parts = [
|
|
235
|
+
{ kind: 'own', text: 'Approval required, but this session has no one to ask.' },
|
|
236
|
+
{ kind: 'command', label: 'Command', text: command },
|
|
237
|
+
];
|
|
238
|
+
if (outcome)
|
|
239
|
+
parts.push({ kind: 'value', label: 'Rating', text: outcome });
|
|
240
|
+
if (reason)
|
|
241
|
+
parts.push({ kind: 'value', label: 'Reason', text: reason });
|
|
242
|
+
if (negotiation)
|
|
243
|
+
parts.push({ kind: 'block', text: negotiation });
|
|
244
|
+
if (escalatedBy) {
|
|
245
|
+
parts.push({ kind: 'value', label: 'Matched approvals.escalate', text: escalatedBy });
|
|
246
|
+
parts.push({
|
|
247
|
+
kind: 'own',
|
|
248
|
+
text: `An escalate entry always asks a human, whatever the rung would have done, so no ` +
|
|
107
249
|
`entry in approvals.allow can answer it. Remove the escalate entry if this command ` +
|
|
108
|
-
`should run unattended
|
|
109
|
-
|
|
250
|
+
`should run unattended.`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
parts.push({
|
|
255
|
+
kind: 'own',
|
|
256
|
+
text: `Declare the commands this run is allowed to execute in approvals.allow — write each ` +
|
|
110
257
|
`one as an explicit entry, for example { "type": "shell", "matcher": "exact", ` +
|
|
111
258
|
`"pattern": "npm test" }. That list is consulted before the auto-rater and never ` +
|
|
112
|
-
`escalates
|
|
259
|
+
`escalates.`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
super(parts, command);
|
|
113
263
|
this.outcome = outcome;
|
|
114
264
|
this.reason = reason;
|
|
115
265
|
this.escalatedBy = escalatedBy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,28 @@
|
|
|
24
24
|
* fork-bomb literal (the string *is* the fork bomb).
|
|
25
25
|
*/
|
|
26
26
|
export declare const HARDLINE_PATTERNS: ReadonlyArray<readonly [RegExp, string]>;
|
|
27
|
+
/**
|
|
28
|
+
* **Every string a §8 decision is made of, in one record — exported ONLY for the snapshot gate**
|
|
29
|
+
* (`shellHardlinePatternSurface.spec.ts`), which freezes it as a literal in its own source. Nothing
|
|
30
|
+
* in production reads it, and nothing should: it is the module's pattern surface made addressable so
|
|
31
|
+
* that a narrowing cannot land silently. **Any new pattern constant joins it**, and the gate's
|
|
32
|
+
* key-set check fails if one does not.
|
|
33
|
+
*
|
|
34
|
+
* **Membership is decided by what the patterns are BUILT FROM, not by what this file declares** —
|
|
35
|
+
* which is why {@link COMMAND_SEPARATOR_CLASS}, imported from `core/shell/normalize`, is a member.
|
|
36
|
+
* Floor behaviour depends on it and it is edited in another module, so a change there is exactly the
|
|
37
|
+
* silent narrowing this exists to make loud.
|
|
38
|
+
*
|
|
39
|
+
* A value composed at module load carries its parts with it: {@link CMD_POS} expands
|
|
40
|
+
* {@link WRAPPER_ARMS}, and {@link ROOT_TARGET} expands `quotedOrBare`, so those parts are frozen
|
|
41
|
+
* whether or not they are also members. The parts are listed anyway, because a diff that names
|
|
42
|
+
* `H_TOKEN_EXCLUSIONS` is actionable where one that names only `CHOWN_HEAD` is a wall of regex.
|
|
43
|
+
*
|
|
44
|
+
* Regexes are recorded as `String(re)` rather than `re.source` **because the flags are part of the
|
|
45
|
+
* behaviour**: a `g` flag makes `.test()` stateful across calls, a change no source-only record
|
|
46
|
+
* would show.
|
|
47
|
+
*/
|
|
48
|
+
export declare const HARDLINE_PATTERN_SURFACE: Readonly<Record<string, string | readonly string[]>>;
|
|
27
49
|
/**
|
|
28
50
|
* Whether one pipeline both reads credential material and transmits data off the machine.
|
|
29
51
|
* Exported for tests, which pin BOTH directions: the credential-upload shapes must match, and
|
|
@@ -39,6 +39,34 @@
|
|
|
39
39
|
* escalation behind it at every rung but `bypass`.** {@link CMD_POS} carries the worked example of
|
|
40
40
|
* a case measured and dropped.
|
|
41
41
|
*
|
|
42
|
+
* **How it may SHRINK — a narrowing ships with its removal set pinned.** Any edit that makes the
|
|
43
|
+
* floor refuse strictly fewer commands is a narrowing: a new exclusion, a loosened bound, a
|
|
44
|
+
* tightened anchor, a narrower character class. **The suite cannot review one.** If the edit had
|
|
45
|
+
* removed a refusal that any spec asserts, that spec would be red — so a green run proves only that
|
|
46
|
+
* everything the edit removed was unasserted, which is the same set as "uncovered". Green is
|
|
47
|
+
* structurally silent about a narrowing, and an AI review of the diff is no better: both read what
|
|
48
|
+
* the change still does, and the whole risk is in what it quietly stopped doing.
|
|
49
|
+
*
|
|
50
|
+
* So the change states its **removal set** — the commands that matched before and do not match
|
|
51
|
+
* after — characterised from the shape of the edit, never sampled from the corpus
|
|
52
|
+
* (`shellHardlineCorpus.spec.ts` sees only its own cases *by construction*; its docblock says so). A
|
|
53
|
+
* regex's removal set cannot be enumerated exactly, so the requirement is a characterisation plus
|
|
54
|
+
* probes pinned at its dangerous end. **Derived by the reviewer independently, not by the author** —
|
|
55
|
+
* an author who missed the removal while writing the edit will miss it again while describing it.
|
|
56
|
+
* {@link HARDLINE_PATTERN_SURFACE} is what makes a narrowing ASK for that declaration: every pattern
|
|
57
|
+
* constant this module binds at module scope is frozen in `shellHardlinePatternSurface.spec.ts`, so
|
|
58
|
+
* moving one turns a cell red and only a deliberate edit to that spec's literal turns it green
|
|
59
|
+
* again. A pattern built inside a function body, and any narrowing that lives in control flow rather
|
|
60
|
+
* than in a string, are outside it — that spec's docblock names them.
|
|
61
|
+
*
|
|
62
|
+
* **The asymmetry above flips at the exfiltration boundary — check which half you are editing.**
|
|
63
|
+
* For the destructive-verb arms a miss still has the confirmation dialog and the rater behind it,
|
|
64
|
+
* which is what makes "prefer fewer false positives" safe there. For the exfiltration arms,
|
|
65
|
+
* removing a refusal **is** the harm, and "a miss still has the rater behind it" is the precise
|
|
66
|
+
* reliance §3 forbids: the deterministic subset exists *because* the `attack` outcome must not
|
|
67
|
+
* depend on the rater alone. "Strictly subtractive — it can only remove refusals, never add one" is
|
|
68
|
+
* a safety property in the first half and a description of the vulnerability in the second.
|
|
69
|
+
*
|
|
42
70
|
* §8.1 — **the floor is never advertised.** It is documented for people reading the code and the
|
|
43
71
|
* spec, never offered to a user as a reason to feel safe; user-facing copy cites only protections
|
|
44
72
|
* the user can inspect and extend (the deny list).
|
|
@@ -634,6 +662,59 @@ const CREDENTIAL_SOURCE_PATTERNS = [
|
|
|
634
662
|
// The whole environment, piped somewhere.
|
|
635
663
|
new RegExp(CMD_POS + '(?:printenv|env)\\s*(?=\\||$)'),
|
|
636
664
|
];
|
|
665
|
+
/**
|
|
666
|
+
* **Every string a §8 decision is made of, in one record — exported ONLY for the snapshot gate**
|
|
667
|
+
* (`shellHardlinePatternSurface.spec.ts`), which freezes it as a literal in its own source. Nothing
|
|
668
|
+
* in production reads it, and nothing should: it is the module's pattern surface made addressable so
|
|
669
|
+
* that a narrowing cannot land silently. **Any new pattern constant joins it**, and the gate's
|
|
670
|
+
* key-set check fails if one does not.
|
|
671
|
+
*
|
|
672
|
+
* **Membership is decided by what the patterns are BUILT FROM, not by what this file declares** —
|
|
673
|
+
* which is why {@link COMMAND_SEPARATOR_CLASS}, imported from `core/shell/normalize`, is a member.
|
|
674
|
+
* Floor behaviour depends on it and it is edited in another module, so a change there is exactly the
|
|
675
|
+
* silent narrowing this exists to make loud.
|
|
676
|
+
*
|
|
677
|
+
* A value composed at module load carries its parts with it: {@link CMD_POS} expands
|
|
678
|
+
* {@link WRAPPER_ARMS}, and {@link ROOT_TARGET} expands `quotedOrBare`, so those parts are frozen
|
|
679
|
+
* whether or not they are also members. The parts are listed anyway, because a diff that names
|
|
680
|
+
* `H_TOKEN_EXCLUSIONS` is actionable where one that names only `CHOWN_HEAD` is a wall of regex.
|
|
681
|
+
*
|
|
682
|
+
* Regexes are recorded as `String(re)` rather than `re.source` **because the flags are part of the
|
|
683
|
+
* behaviour**: a `g` flag makes `.test()` stateful across calls, a change no source-only record
|
|
684
|
+
* would show.
|
|
685
|
+
*/
|
|
686
|
+
export const HARDLINE_PATTERN_SURFACE = Object.freeze({
|
|
687
|
+
// Structural / shared.
|
|
688
|
+
COMMAND_SEPARATOR_CLASS,
|
|
689
|
+
WRAPPER_FLAGS,
|
|
690
|
+
WRAPPER_ARMS: Object.freeze([...WRAPPER_ARMS]),
|
|
691
|
+
CMD_POS,
|
|
692
|
+
TARGET_TOKEN_END,
|
|
693
|
+
H_SPACE,
|
|
694
|
+
H_TOKEN_EXCLUSIONS,
|
|
695
|
+
H_TOKEN_CHAR,
|
|
696
|
+
H_OPERAND_CHAR,
|
|
697
|
+
// The destructive-verb arms and the fragments they share.
|
|
698
|
+
ROOT_TARGET,
|
|
699
|
+
SYSTEM_DIR_TARGET,
|
|
700
|
+
RECURSIVE_FLAG,
|
|
701
|
+
CHOWN_SKIPPABLE_ARG,
|
|
702
|
+
CHOWN_HEAD,
|
|
703
|
+
// The pattern list itself, sources only: the human descriptions beside them are prose, and a
|
|
704
|
+
// reworded one is not a narrowing, so they stay out rather than train a reader to update the
|
|
705
|
+
// frozen literal without reading why the cell went red.
|
|
706
|
+
HARDLINE_PATTERNS: Object.freeze(HARDLINE_PATTERNS.map(([pattern]) => String(pattern))),
|
|
707
|
+
// The exfiltration arms.
|
|
708
|
+
PIPELINE_SPLIT_RE: String(PIPELINE_SPLIT_RE),
|
|
709
|
+
NETWORK_SINK_RE: String(NETWORK_SINK_RE),
|
|
710
|
+
RSYNC_REMOTE_TARGET,
|
|
711
|
+
RSYNC_REMOTE_SINK_RE: String(RSYNC_REMOTE_SINK_RE),
|
|
712
|
+
TOKEN_END,
|
|
713
|
+
NOT_PUBLIC_KEY,
|
|
714
|
+
DOTENV_RE: String(DOTENV_RE),
|
|
715
|
+
DOTENV_AS_OUTPUT_TARGET: String(DOTENV_AS_OUTPUT_TARGET),
|
|
716
|
+
CREDENTIAL_SOURCE_PATTERNS: Object.freeze(CREDENTIAL_SOURCE_PATTERNS.map((pattern) => String(pattern))),
|
|
717
|
+
});
|
|
637
718
|
/**
|
|
638
719
|
* Whether one pipeline both reads credential material and transmits data off the machine.
|
|
639
720
|
* Exported for tests, which pin BOTH directions: the credential-upload shapes must match, and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hardline.js","sourceRoot":"","sources":["../../../src/core/shell/hardline.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hardline.js","sourceRoot":"","sources":["../../../src/core/shell/hardline.ts"],"names":[],"mappings":"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"}
|
|
@@ -137,13 +137,22 @@ export declare class ShellNegotiationState {
|
|
|
137
137
|
* **The round is appended BEFORE either bound is tested**, so the rating that escalates is itself
|
|
138
138
|
* on the transcript the human sees. §5.6's escalation example turns on this: what matters on the
|
|
139
139
|
* screen is that the agent proposed the same command three times, and the third proposal is the
|
|
140
|
-
* one being escalated.
|
|
140
|
+
* one being escalated. {@link renderNegotiationRows} marks that last round as the pending request
|
|
141
|
+
* rather than as a prior one, which is the half of this the reader needs and cannot derive.
|
|
141
142
|
*/
|
|
142
143
|
recordRejection(round: RaterNegotiationRound): NegotiationVerdict;
|
|
143
144
|
/**
|
|
144
145
|
* §5.3 — a tool call the gate let through. Resets the consecutive counter **and clears the
|
|
145
146
|
* transcript with it**; the reachability bound is deliberately untouched.
|
|
146
147
|
*
|
|
148
|
+
* **What it must NOT truncate is the human's record of how hard the agent pushed.** The counter's
|
|
149
|
+
* reset is §5.3's and is sound; the reader at an escalation is asking a different question, and
|
|
150
|
+
* the answer to theirs is {@link NegotiationCounters.rejectionsSinceHuman}, which this leaves
|
|
151
|
+
* standing. {@link renderNegotiationRows} reports that count rather than the surviving rounds, so
|
|
152
|
+
* an argument this erased is still declared even though its rounds are gone; every erased round
|
|
153
|
+
* itself survives whole in [[TUI-C27]]'s archive, which captures per RATING CALL and so is not
|
|
154
|
+
* truncated by anything here.
|
|
155
|
+
*
|
|
147
156
|
* **"Approved" is what the gate can observe, and it is not quite §5.3's "successful".** The
|
|
148
157
|
* decision site sees whether a call was allowed to run, never whether it then exited zero — and
|
|
149
158
|
* the honest alternatives were worse: a tool-result stream carries the *rejected* call's own
|
|
@@ -190,6 +199,10 @@ export declare class ShellNegotiationState {
|
|
|
190
199
|
* was rejected"* and *"the call was the third consecutive rejection, so the next one goes to a
|
|
191
200
|
* person"* are different facts, and a bug report carrying only the first invites the wrong
|
|
192
201
|
* conclusion about why a session started interrupting.
|
|
202
|
+
*
|
|
203
|
+
* **`rejectionsSinceHuman` is also what the escalation prompt COUNTS**, and that is the one place
|
|
204
|
+
* it faces a person rather than a debugging archive. It is read at the escalation site before
|
|
205
|
+
* {@link humanReached} spends it; reading it after would report zero.
|
|
193
206
|
*/
|
|
194
207
|
counters(): NegotiationCounters;
|
|
195
208
|
/** Drop everything, including the user messages — the TUI's `/clear` rotates the thread. */
|
|
@@ -211,6 +224,31 @@ export interface NegotiationRow {
|
|
|
211
224
|
voice: NegotiationVoice;
|
|
212
225
|
text: string;
|
|
213
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Terminal rows one element of a round — the command, the justification, the rater's answer — may
|
|
229
|
+
* occupy on a screen before the rest of it is elided.
|
|
230
|
+
*
|
|
231
|
+
* **MEASURED, and it is the height bound this block needs rather than a cap on rounds.** At 80
|
|
232
|
+
* columns three rounds of paragraph-length argument cost 37 rows, of which one round was 12; the
|
|
233
|
+
* whole prompt was 64 rows against a 20-row budget, so the human saw the command and the verdict
|
|
234
|
+
* and neither the later rounds nor the menu line. A cap on the NUMBER of rounds would have changed
|
|
235
|
+
* none of that — §5.3 already bounds the transcript at
|
|
236
|
+
* {@link MAX_CONSECUTIVE_REJECTIONS} — because the cost is per row, not per round. Bounding each
|
|
237
|
+
* element keeps every round structurally on the screen, which is what §5.6 calls the most important
|
|
238
|
+
* thing on it, and pays for it out of the tail of a paragraph the reader was never going to need in
|
|
239
|
+
* full: the archive keeps every round whole ([[TUI-C27]]'s capture is per rating call).
|
|
240
|
+
*
|
|
241
|
+
* Two rather than one so a wrapped command keeps the continuation that shows how it differs from
|
|
242
|
+
* the round above it — the comparison the block exists to make.
|
|
243
|
+
*/
|
|
244
|
+
export declare const NEGOTIATION_MAX_ROWS_PER_ELEMENT = 2;
|
|
245
|
+
/**
|
|
246
|
+
* Rounds a screen shows, newest last. A backstop rather than today's binding constraint: §5.3
|
|
247
|
+
* escalates at {@link MAX_CONSECUTIVE_REJECTIONS}, so a transcript reaching a human is never longer
|
|
248
|
+
* than this — and an unscrollable prompt must not acquire an unbounded section the moment that
|
|
249
|
+
* number is raised. What is dropped is said out loud in the heading, which carries the true count.
|
|
250
|
+
*/
|
|
251
|
+
export declare const NEGOTIATION_MAX_ROUNDS_SHOWN = 3;
|
|
214
252
|
/**
|
|
215
253
|
* §6/§5.4 — render a negotiation for the human being asked to rule on it, one terminal row per
|
|
216
254
|
* element, each tagged with the voice speaking it.
|
|
@@ -233,9 +271,46 @@ export interface NegotiationRow {
|
|
|
233
271
|
* A wrapped row keeps the voice of the row it continues. A continuation painted as chrome would put
|
|
234
272
|
* the rater's words in the agent's colour at exactly the width where a long argument is hardest to
|
|
235
273
|
* read, which is the confusion §5.4 exists to remove.
|
|
274
|
+
*
|
|
275
|
+
* ## The three things this block must not get wrong about its own exchange
|
|
276
|
+
*
|
|
277
|
+
* Every label here sits over data that is already correct, so a label that lies does so silently —
|
|
278
|
+
* and each of these lied toward approving, at the moment a human was deciding whether to overrule
|
|
279
|
+
* a refusal.
|
|
280
|
+
*
|
|
281
|
+
* - **`attempts` is the count, and it is not `rounds.length`.** §5.3 clears the transcript on an
|
|
282
|
+
* approved call, so the rounds handed over are the attempts since the last *approval*, while the
|
|
283
|
+
* fact the reader is weighing is how hard the agent pushed since the last *human* — the caller's
|
|
284
|
+
* `rejectionsSinceHuman`. A measured escalation attempted the same command five times and
|
|
285
|
+
* rendered three. Omit it and this falls back to `rounds.length`, which is the honest reading of
|
|
286
|
+
* a caller that has no better number rather than a claim that none were erased.
|
|
287
|
+
* - **The last round IS the pending rating, not a prior one.** {@link
|
|
288
|
+
* ShellNegotiationState.recordRejection} appends before either bound is tested, deliberately, so
|
|
289
|
+
* that the rating being escalated is on the transcript the human sees. The old heading called
|
|
290
|
+
* them all prior rounds, which both under-reported the argument and put the pending command on
|
|
291
|
+
* the screen twice with nothing saying they were the same call.
|
|
292
|
+
* - **The first round was rated on the command alone.** A cleared transcript IS the round-1 case
|
|
293
|
+
* (see {@link ShellNegotiationState.contextFor}), so §5.1 withheld the justification and the user
|
|
294
|
+
* messages from that rating — while {@link ShellNegotiationState.recordRejection} stores the
|
|
295
|
+
* justification the agent supplied whatever the round. Printed unmarked, the round reads as a
|
|
296
|
+
* rater brushing past a direct answer to its own objection, which is the opposite of what
|
|
297
|
+
* happened and makes the rater look stubborn exactly where the reader is deciding whether to
|
|
298
|
+
* overrule it. The withholding is correct and is not what this marks. The same marker answers
|
|
299
|
+
* *"were the user's own words in view when this was rated?"* — a justification claiming the user
|
|
300
|
+
* asked for a command is exactly what a round-1 rating cannot check, and the user's messages are
|
|
301
|
+
* several rows on a surface with none to spare.
|
|
302
|
+
*
|
|
303
|
+
* **The prompt this feeds does not scroll and nothing else on it can give up rows**, so the height
|
|
304
|
+
* bound is {@link NEGOTIATION_MAX_ROWS_PER_ELEMENT} and every fact above is carried by a row that
|
|
305
|
+
* already exists.
|
|
236
306
|
*/
|
|
237
307
|
export declare function renderNegotiationRows(rounds: readonly RaterNegotiationRound[], options?: {
|
|
238
308
|
width?: number;
|
|
309
|
+
/**
|
|
310
|
+
* Rejected attempts since a human was last involved — `rejectionsSinceHuman` from {@link
|
|
311
|
+
* ShellNegotiationState.counters}, read BEFORE the escalation spends it.
|
|
312
|
+
*/
|
|
313
|
+
attempts?: number;
|
|
239
314
|
}): NegotiationRow[];
|
|
240
315
|
/**
|
|
241
316
|
* The same transcript as one string, for a consumer with no screen to lay it out on: §6.2's
|
|
@@ -245,5 +320,9 @@ export declare function renderNegotiationRows(rounds: readonly RaterNegotiationR
|
|
|
245
320
|
* Defined as {@link renderNegotiationRows} joined, so the string and the rows can never come to
|
|
246
321
|
* describe one exchange two ways — the whole reason there is one renderer and two surfaces.
|
|
247
322
|
* `null` when there is nothing to show.
|
|
323
|
+
*
|
|
324
|
+
* `attempts` carries the same fact it carries on a screen. It is the one thing a caller must pass
|
|
325
|
+
* for this message to be true, because the count it defaults to is the one an approved call
|
|
326
|
+
* truncated.
|
|
248
327
|
*/
|
|
249
|
-
export declare function renderNegotiationTranscript(rounds: readonly RaterNegotiationRound[]): string | null;
|
|
328
|
+
export declare function renderNegotiationTranscript(rounds: readonly RaterNegotiationRound[], attempts?: number): string | null;
|