@gaunt-sloth/core 2.0.0-beta.0 → 2.0.0-beta.2
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/LICENSE +6 -6
- package/dist/config/loader.d.ts +13 -0
- package/dist/config/loader.js +55 -11
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +10 -0
- package/dist/config/schema.js +46 -24
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +142 -1
- package/dist/config/shell-policy.js +114 -7
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/types.d.ts +19 -0
- package/dist/config/types.js.map +1 -1
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +31 -0
- package/dist/core/GthAbstractAgent.js +159 -4
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +314 -18
- package/dist/core/GthAgentRunner.js +1032 -101
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/approvals/approvalRequest.d.ts +247 -0
- package/dist/core/approvals/approvalRequest.js +315 -0
- package/dist/core/approvals/approvalRequest.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +165 -9
- package/dist/core/approvals/grants.js +702 -57
- package/dist/core/approvals/grants.js.map +1 -1
- package/dist/core/plainToolIndication.d.ts +11 -1
- package/dist/core/plainToolIndication.js +25 -10
- package/dist/core/plainToolIndication.js.map +1 -1
- package/dist/core/shell/abstention.d.ts +1 -1
- package/dist/core/shell/abstention.js +65 -11
- package/dist/core/shell/abstention.js.map +1 -1
- package/dist/core/shell/alignment.d.ts +491 -0
- package/dist/core/shell/alignment.js +687 -0
- package/dist/core/shell/alignment.js.map +1 -0
- package/dist/core/shell/approvalCapture.d.ts +53 -41
- package/dist/core/shell/approvalCapture.js +0 -42
- package/dist/core/shell/approvalCapture.js.map +1 -1
- package/dist/core/shell/approvalStop.d.ts +121 -3
- package/dist/core/shell/approvalStop.js +153 -90
- package/dist/core/shell/approvalStop.js.map +1 -1
- package/dist/core/shell/hardline.d.ts +26 -4
- package/dist/core/shell/hardline.js +321 -16
- package/dist/core/shell/hardline.js.map +1 -1
- package/dist/core/shell/negotiation.d.ts +352 -63
- package/dist/core/shell/negotiation.js +295 -123
- package/dist/core/shell/negotiation.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +135 -0
- package/dist/core/shell/openWorld.js +572 -35
- package/dist/core/shell/openWorld.js.map +1 -1
- package/dist/core/shell/provenance.d.ts +91 -0
- package/dist/core/shell/provenance.js +136 -0
- package/dist/core/shell/provenance.js.map +1 -0
- package/dist/core/shell/rater.d.ts +394 -163
- package/dist/core/shell/rater.js +536 -238
- package/dist/core/shell/rater.js.map +1 -1
- package/dist/core/shell/raterHealth.d.ts +101 -0
- package/dist/core/shell/raterHealth.js +121 -0
- package/dist/core/shell/raterHealth.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +17 -8
- package/dist/core/shell/raterModel.js +11 -8
- package/dist/core/shell/raterModel.js.map +1 -1
- package/dist/core/shell/rejection.d.ts +20 -5
- package/dist/core/shell/rejection.js +18 -4
- package/dist/core/shell/rejection.js.map +1 -1
- package/dist/core/toolDisplay.d.ts +42 -0
- package/dist/core/toolDisplay.js +31 -0
- package/dist/core/toolDisplay.js.map +1 -1
- package/dist/core/types.d.ts +134 -16
- package/dist/core/types.js.map +1 -1
- package/dist/providers/configurationPassthrough.d.ts +36 -6
- package/dist/providers/configurationPassthrough.js +40 -28
- package/dist/providers/configurationPassthrough.js.map +1 -1
- package/dist/providers/openrouter.js +36 -6
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +21 -0
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js +103 -21
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/fileUtils.d.ts +7 -1
- package/dist/utils/fileUtils.js +17 -3
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/globalConfigUtils.d.ts +5 -2
- package/dist/utils/globalConfigUtils.js +14 -3
- package/dist/utils/globalConfigUtils.js.map +1 -1
- package/dist/utils/vertexaiUtils.js +61 -3
- package/dist/utils/vertexaiUtils.js.map +1 -1
- package/package.json +4 -4
- package/schema/gsloth-config.schema.json +34 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type AllowlistCounts, type ApprovalRung, GthConfig, type McpAnnotationTrustChange, type McpAnnotationTrustView, type ResolvedApprovals, type ToolAnnotationHint } from '#src/config.js';
|
|
1
|
+
import { type AllowlistCounts, type ApprovalRefusal, type ApprovalRefusalLift, type ApprovalRung, GthConfig, type McpAnnotationTrustChange, type McpAnnotationTrustView, type ResolvedApprovals, type ToolAnnotationHint } from '#src/config.js';
|
|
2
2
|
import { BaseCheckpointSaver } from '@langchain/langgraph';
|
|
3
|
-
import { AgentResolvers, AgentStreamEvent, type AttackHaltCallback, GthAgentFactory, GthAgentInterface, GthCommand, GthRunStats, Message, StatusUpdateCallback, ToolApprovalCallback } from '#src/core/types.js';
|
|
3
|
+
import { AgentResolvers, AgentStreamEvent, type ApprovalOutcomeCallback, type AttackHaltCallback, GthAgentFactory, GthAgentInterface, GthCommand, GthRunStats, Message, StatusUpdateCallback, ToolApprovalCallback } from '#src/core/types.js';
|
|
4
4
|
import { type ApprovalGrant } from '#src/core/approvals/grants.js';
|
|
5
5
|
import { type ApprovalDecisionCapture } from '#src/core/shell/approvalCapture.js';
|
|
6
|
+
import { type NegotiationDisplay } from '#src/core/shell/negotiation.js';
|
|
6
7
|
/**
|
|
7
8
|
* EXT-114 — the page describing the `subagents` config key, pointed at rather than paraphrased in
|
|
8
9
|
* the notice itself.
|
|
@@ -22,7 +23,15 @@ export interface GthAgentRunnerInitOptions {
|
|
|
22
23
|
* the mode prompt (`readModePrompt`), the per-command approvals posture (`resolveApprovals`) and
|
|
23
24
|
* the command-specific filesystem config, so a helper agent that must run on the chat prompt —
|
|
24
25
|
* the `gth pr` change-requirements discovery agent — cannot borrow it to say which verb it serves.
|
|
25
|
-
*
|
|
26
|
+
*
|
|
27
|
+
* **It is read for the wording of a notice, and for one safety decision.** [[EXT-106]] §4.6 asks
|
|
28
|
+
* whether this run's human messages are the user's own words, and answers it from
|
|
29
|
+
* `command ?? owningCommand` ({@link import('../config/shell-policy.js').commandCarriesUserProvenance}
|
|
30
|
+
* at the top of {@link GthAgentRunner#init}) — precisely because a command-less helper agent whose
|
|
31
|
+
* human turn is content the product FETCHED must not be classified by the absence of a verb. The
|
|
32
|
+
* key has to be out-of-band metadata like this field: a marker inside the message would be
|
|
33
|
+
* forgeable by the attacker-controlled text it is supposed to classify. Both that predicate and
|
|
34
|
+
* the provenance window itself fail closed, so leaving this unset never widens anything.
|
|
26
35
|
*/
|
|
27
36
|
owningCommand?: GthCommand;
|
|
28
37
|
/**
|
|
@@ -54,6 +63,12 @@ export declare class GthAgentRunner {
|
|
|
54
63
|
* for non-interactive entrypoints (a scripted `exec` run with no TTY to prompt on).
|
|
55
64
|
*/
|
|
56
65
|
private toolApprovalCallback;
|
|
66
|
+
/**
|
|
67
|
+
* [[EXT-150]] — consumer hook invoked once per human-answered approval, AFTER the answer has been
|
|
68
|
+
* recorded, carrying the lifetime it landed with. Set via {@link setApprovalOutcomeCallback}; when
|
|
69
|
+
* unset the outcome is simply not reported, which is where every surface was before this existed.
|
|
70
|
+
*/
|
|
71
|
+
private approvalOutcomeCallback;
|
|
57
72
|
/**
|
|
58
73
|
* [[TUI-C68]] §6.1 — consumer hook invoked when the rater rates a command an `attack`, so an
|
|
59
74
|
* interactive surface can show the red banner before the run ends. Set via
|
|
@@ -62,6 +77,22 @@ export declare class GthAgentRunner {
|
|
|
62
77
|
* therefore keeps the halt rather than losing it.
|
|
63
78
|
*/
|
|
64
79
|
private attackHaltCallback;
|
|
80
|
+
/**
|
|
81
|
+
* [[TUI-C69]] §5.4/§5.5 — **the surface showing this negotiation while it happens**, when one
|
|
82
|
+
* is. Set via {@link setNegotiationDisplay}.
|
|
83
|
+
*
|
|
84
|
+
* Its presence is the answer to *"is anyone watching?"*, and BOTH halves of the visible
|
|
85
|
+
* negotiation are keyed on it: the rounds are handed over as they are decided, and a negotiated
|
|
86
|
+
* approval is held on screen for {@link NEGOTIATED_APPROVAL_COOLDOWN_MS} before it takes effect.
|
|
87
|
+
*
|
|
88
|
+
* **`null` means neither happens, and that is the point.** An `exec` or CI run has nobody to
|
|
89
|
+
* show an approval to, so an 800 ms hold there would tax every headless run and every gate for a
|
|
90
|
+
* display that does not exist. Deliberately NOT keyed on {@link toolApprovalCallback}: that one
|
|
91
|
+
* answers *"is there a human to ASK"*, a different question with a different answer — a piped
|
|
92
|
+
* readline session can have one wired and no live display, and the §6.2 non-interactive path has
|
|
93
|
+
* a display and no one to ask.
|
|
94
|
+
*/
|
|
95
|
+
private negotiationDisplay;
|
|
65
96
|
/** The command the runner was initialized for; selects which `devTools` config applies. */
|
|
66
97
|
private command;
|
|
67
98
|
/**
|
|
@@ -90,12 +121,24 @@ export declare class GthAgentRunner {
|
|
|
90
121
|
* `undefined` means no profile is configured and the rater uses the session model.
|
|
91
122
|
*/
|
|
92
123
|
private raterModel;
|
|
124
|
+
/**
|
|
125
|
+
* [[EXT-127]] — the model the ALIGNMENT CHECKER runs on, when `approvals.alignmentChecker` (or, by
|
|
126
|
+
* its read-site default, `approvals.rater`) names an identity profile. Resolved ONCE at
|
|
127
|
+
* {@link init}, exactly as {@link raterModel} is; `undefined` means the session model.
|
|
128
|
+
*/
|
|
129
|
+
private alignmentCheckerModel;
|
|
93
130
|
/**
|
|
94
131
|
* EXT-66 — how many rating calls this session gave up on. Counted so the notice can say "3 times
|
|
95
132
|
* this session" rather than repeating an identical line, and so a silent drift toward
|
|
96
133
|
* escalate-everything has a number attached to it.
|
|
97
134
|
*/
|
|
98
135
|
private raterTimeouts;
|
|
136
|
+
/**
|
|
137
|
+
* [[EXT-82]] — the consecutive-failure tracker behind the session-level signal, per runner and
|
|
138
|
+
* therefore per session (the ACP surface runs several at once). See
|
|
139
|
+
* {@link import('#src/core/shell/raterHealth.js').RaterHealth}.
|
|
140
|
+
*/
|
|
141
|
+
private readonly raterHealth;
|
|
99
142
|
/**
|
|
100
143
|
* EXT-71 §3.1/§6 — what the escalation menu granted at run time, for the life of THIS runner
|
|
101
144
|
* instance: {@link ApprovalEntry} objects, never prefixes, and never anything from config (the
|
|
@@ -117,6 +160,19 @@ export declare class GthAgentRunner {
|
|
|
117
160
|
*/
|
|
118
161
|
private persistedGrants;
|
|
119
162
|
private persistedGrantsLoaded;
|
|
163
|
+
/**
|
|
164
|
+
* [[EXT-107]] — the persisted (`always`) **refusal** store, from
|
|
165
|
+
* `.gsloth/.gsloth-settings/shell-denylist.json`. The mirror of {@link persistedGrants}, and the
|
|
166
|
+
* reason the menu's most emphatic answer is no longer its most forgetful one.
|
|
167
|
+
*
|
|
168
|
+
* Null when the file cannot be loaded at all, in which case an `always` refusal degrades to a
|
|
169
|
+
* session one. **Not "a re-prompt next session, never an execution":** nothing in the unreadable
|
|
170
|
+
* file applies to any call, so next session a call it covered is left to the rest of the gate — it
|
|
171
|
+
* may be refused by another rule, it may be prompted for, or it may run without asking, under
|
|
172
|
+
* `bypass` or a matching saved allow. The user is told at load time.
|
|
173
|
+
*/
|
|
174
|
+
private persistedDenials;
|
|
175
|
+
private persistedDenialsLoaded;
|
|
120
176
|
/**
|
|
121
177
|
* [[EXT-29]] §5 — the state of the agent↔rater negotiation at `auto`: the transcript, §5.3's
|
|
122
178
|
* consecutive-rejection counter and the reachability bound. Instance-scoped for the same reason
|
|
@@ -142,6 +198,21 @@ export declare class GthAgentRunner {
|
|
|
142
198
|
* Without a handler the runner rejects pending tool calls (see {@link toolApprovalCallback}).
|
|
143
199
|
*/
|
|
144
200
|
setToolApprovalCallback(callback: ToolApprovalCallback | null): void;
|
|
201
|
+
/**
|
|
202
|
+
* [[EXT-150]] — register the handler that is told **what a human's answer actually landed as**.
|
|
203
|
+
* Pass `null` to clear.
|
|
204
|
+
*
|
|
205
|
+
* Separate from {@link setToolApprovalCallback} for the reason {@link setAttackHaltCallback} is
|
|
206
|
+
* separate: it is a different question asked at a different moment. The approval callback is
|
|
207
|
+
* consulted *before* anything is written and returns the human's REQUEST; the scope that survives
|
|
208
|
+
* is decided here afterwards, because [[EXT-149]] degrades an `always` whose write did not reach
|
|
209
|
+
* disk to the `session` answer it really is. A surface with no way to hear that can only confirm
|
|
210
|
+
* the key that was pressed — and core's own ERROR naming the unwritten file then contradicts it.
|
|
211
|
+
*
|
|
212
|
+
* It reports; it never decides. Nothing downstream reads it, so a surface that ignores it, or
|
|
213
|
+
* never wires it, changes no behaviour of the gate.
|
|
214
|
+
*/
|
|
215
|
+
setApprovalOutcomeCallback(callback: ApprovalOutcomeCallback | null): void;
|
|
145
216
|
/**
|
|
146
217
|
* [[TUI-C68]] §6.1 — register the handler that shows the **attack banner**, the one way a human
|
|
147
218
|
* gets past an `attack` verdict. Pass `null` to clear.
|
|
@@ -152,6 +223,17 @@ export declare class GthAgentRunner {
|
|
|
152
223
|
* other surface keeps the halt (see {@link attackHaltCallback}).
|
|
153
224
|
*/
|
|
154
225
|
setAttackHaltCallback(callback: AttackHaltCallback | null): void;
|
|
226
|
+
/**
|
|
227
|
+
* [[TUI-C69]] §5.4/§5.5 — **declare that this surface is showing the negotiation as it happens.**
|
|
228
|
+
* Pass `null` to clear.
|
|
229
|
+
*
|
|
230
|
+
* §5.4's requirement is not decoration: *"the spec's own justification for letting the agent
|
|
231
|
+
* argue with the rater at all is that a human can watch it, and an argument conducted in the dark
|
|
232
|
+
* is a different thing from one that can be interrupted."* Wiring this is a surface saying it
|
|
233
|
+
* has somewhere to draw that, which is also what makes §5.5's hold meaningful — see
|
|
234
|
+
* {@link negotiationDisplay} for why one seam carries both.
|
|
235
|
+
*/
|
|
236
|
+
setNegotiationDisplay(display: NegotiationDisplay | null): void;
|
|
155
237
|
/**
|
|
156
238
|
* §6.1 — **the single seam between an `attack` verdict and the end of the run.** Both rating
|
|
157
239
|
* paths — the §3.2 allow-match tripwire and the ordinary rater decision — go through here, so the
|
|
@@ -202,11 +284,56 @@ export declare class GthAgentRunner {
|
|
|
202
284
|
*/
|
|
203
285
|
getAllowlistCounts(): AllowlistCounts;
|
|
204
286
|
/**
|
|
205
|
-
* CFG-27 —
|
|
206
|
-
*
|
|
207
|
-
*
|
|
287
|
+
* CFG-27/[[EXT-107]] — **every refusal in force, and which of the three lists holds it**: the
|
|
288
|
+
* declared `approvals.deny` entries, the escalation menu's session-scoped refusals, and the ones
|
|
289
|
+
* saved to the project's deny file.
|
|
290
|
+
*
|
|
291
|
+
* The origins are kept apart rather than concatenated into one list of strings, because they have
|
|
292
|
+
* different lifetimes and different owners and only two of the three can be lifted from here.
|
|
293
|
+
* A merged list makes {@link liftRefusal} impossible to describe honestly.
|
|
294
|
+
*
|
|
295
|
+
* **This is the one list.** {@link liftRefusal} resolves its argument against exactly this
|
|
296
|
+
* sequence, so the number a user reads and the number they type cannot name different entries.
|
|
297
|
+
*
|
|
298
|
+
* Numbering is 1-based, and the order is config → saved → session: the entries a user cannot lift
|
|
299
|
+
* here come first and stay put, so the numbers of the ones they can are not reshuffled by a
|
|
300
|
+
* config edit between two renderings.
|
|
301
|
+
*/
|
|
302
|
+
getRefusals(): ApprovalRefusal[];
|
|
303
|
+
/**
|
|
304
|
+
* [[EXT-107]] — the refusals in force with their entries attached, which is what
|
|
305
|
+
* {@link liftRefusal} needs and {@link getRefusals} renders. ONE builder, so the displayed order
|
|
306
|
+
* and the removal order are the same order by construction rather than by two functions agreeing.
|
|
307
|
+
*
|
|
308
|
+
* A saved refusal is held in both runtime stores ({@link recordDenial} writes both), so the
|
|
309
|
+
* session list is filtered against the saved one by entry identity — the same de-duplication
|
|
310
|
+
* {@link getGrants} does. A configured entry is NOT de-duplicated against them: it is a different
|
|
311
|
+
* thing with a different owner, and hiding it would let a lift report success while the config
|
|
312
|
+
* line went on refusing the call.
|
|
313
|
+
*/
|
|
314
|
+
private refusalRecords;
|
|
315
|
+
/**
|
|
316
|
+
* [[EXT-107]] — **lift one refusal**, by its number in {@link getRefusals}. The escape hatch, and
|
|
317
|
+
* the reason persisting a refusal is safe to ship: a saved refusal the user cannot find or undo
|
|
318
|
+
* is a trap, and the person who hits it first is whoever pressed `[d]` by reflex and needed the
|
|
319
|
+
* command an hour later. Telling them to delete a file they have not been told exists is not an
|
|
320
|
+
* answer.
|
|
321
|
+
*
|
|
322
|
+
* A saved refusal is removed from the file AND from the in-memory store, because it is in both —
|
|
323
|
+
* dropping only the file would leave the call refused for the rest of the session by a rule the
|
|
324
|
+
* display no longer shows.
|
|
325
|
+
*
|
|
326
|
+
* **A configured entry is reported, never removed.** `approvals.deny` is something the user
|
|
327
|
+
* wrote; rewriting their config file out from under them is not a thing a session command may do,
|
|
328
|
+
* and silently no-oping would be worse. They are told where it lives.
|
|
329
|
+
*
|
|
330
|
+
* **`stillSaved` is the deletion that did not reach disk** ([[EXT-149]]). The file rewrite can
|
|
331
|
+
* fail after the in-memory removal has succeeded — a checkout that is not writable, a settings
|
|
332
|
+
* directory that has gone — and the entry then comes back in the next session. The store now
|
|
333
|
+
* answers that question ({@link PersistedApprovalGrants.remove}) instead of reporting every
|
|
334
|
+
* removal as landed, so the notice can stop promising *it will not come back*.
|
|
208
335
|
*/
|
|
209
|
-
|
|
336
|
+
liftRefusal(index: number): ApprovalRefusalLift;
|
|
210
337
|
/**
|
|
211
338
|
* §3/§4.7.4 — **the grants themselves**, for an approvals view that shows *what* was granted,
|
|
212
339
|
* *when*, and *under which effective annotations*. The counterpart of {@link getAllowlistCounts},
|
|
@@ -424,6 +551,88 @@ export declare class GthAgentRunner {
|
|
|
424
551
|
* whenever the shell gate is on, so §2.5's rule that the deny list survives `bypass` is untouched.)
|
|
425
552
|
*/
|
|
426
553
|
private decideToolApproval;
|
|
554
|
+
/**
|
|
555
|
+
* [[EXT-29]] §5.3 / [[TUI-C69]] §5.4 — **a person was reached, so the exchange is over**: the
|
|
556
|
+
* gate's own transcript is spent and the surface showing it is told, on the same event.
|
|
557
|
+
*
|
|
558
|
+
* Every direct `humanReached()` in this class goes through here, which is what keeps the two from
|
|
559
|
+
* drifting: a new site that spent the transcript without telling the display would leave a
|
|
560
|
+
* finished argument standing on screen, and at an escalation it would put the same exchange on an
|
|
561
|
+
* unscrollable dialog twice — once live, once in the prompt about to render all of it.
|
|
562
|
+
*/
|
|
563
|
+
private endNegotiation;
|
|
564
|
+
/**
|
|
565
|
+
* [[TUI-C69]] §5.4 — **take the finished argument off the screen WITHOUT spending the gate's
|
|
566
|
+
* transcript.**
|
|
567
|
+
*
|
|
568
|
+
* The two are separate on purpose. `humanReached()` clears the rounds *and* the reachability
|
|
569
|
+
* bound, which is correct only when a person was actually reached; calling it merely to tidy the
|
|
570
|
+
* panel would hand the agent a fresh {@link MAX_REJECTIONS_BEFORE_HUMAN} budget it had already
|
|
571
|
+
* spent, turning a display concern into a way to argue indefinitely. [[EXT-108]] made an approved
|
|
572
|
+
* call stop clearing the transcript for exactly this reason, so the tidy-up cannot be the thing
|
|
573
|
+
* that puts it back.
|
|
574
|
+
*
|
|
575
|
+
* Guarded, like {@link showNegotiationRound}: a surface that throws while clearing must never
|
|
576
|
+
* change what the gate decided, or become the reason a turn ends.
|
|
577
|
+
*/
|
|
578
|
+
private clearNegotiationDisplay;
|
|
579
|
+
/**
|
|
580
|
+
* [[TUI-C69]] §5.4 — **tell the agent to forget the tool-call ids the rater bounced**, on every
|
|
581
|
+
* event that ends a turn's display state: a new turn, and `/clear`.
|
|
582
|
+
*
|
|
583
|
+
* The runner decides *when* because it owns the turn boundary; the agent holds the set because it
|
|
584
|
+
* owns the rendering. Fail-soft in the shape {@link resetRunStats} uses — an agent without the
|
|
585
|
+
* method (a test double, a renderer-less agent) is simply skipped, and a tone hint must never be
|
|
586
|
+
* the reason a turn fails to start.
|
|
587
|
+
*/
|
|
588
|
+
private clearRaterClarifications;
|
|
589
|
+
/**
|
|
590
|
+
* [[TUI-C69]] §5.4 — **hand one round of the argument to the surface that is showing it**, the
|
|
591
|
+
* moment the gate decided it.
|
|
592
|
+
*
|
|
593
|
+
* The round travels raw. Every surface lays it out from the SAME rows the escalation prompt
|
|
594
|
+
* draws, at its own terminal width, so the rounds a person watches and the rounds they later rule
|
|
595
|
+
* on cannot be two different renderings of one exchange — and the rater's turns are yellow in
|
|
596
|
+
* both because the rows carry the voice rather than a colour.
|
|
597
|
+
*
|
|
598
|
+
* No-ops when no surface is watching, which is the §5.5 seam as well as this one.
|
|
599
|
+
*/
|
|
600
|
+
private showNegotiationRound;
|
|
601
|
+
/**
|
|
602
|
+
* [[TUI-C69]] §5.4/§5.5 — **the rater agreeing is the last round of the argument, and it is held
|
|
603
|
+
* on screen before it takes effect.**
|
|
604
|
+
*
|
|
605
|
+
* Two things, in this order, because the order is the requirement: the approving round is drawn,
|
|
606
|
+
* and only then does the minimum visible interval run. A hold before the draw would be a pause
|
|
607
|
+
* over nothing.
|
|
608
|
+
*
|
|
609
|
+
* **It is a visibility pause, not a reading window**, and it must never be relied on as an
|
|
610
|
+
* opportunity to evaluate the command — nobody reads a command in 800 ms. What it buys is that
|
|
611
|
+
* the approving round is on screen as its own event instead of being overwritten by the tool
|
|
612
|
+
* output that follows it immediately.
|
|
613
|
+
*
|
|
614
|
+
* **What it is NOT, on either surface, is a guaranteed abort window — do not restore that claim
|
|
615
|
+
* without building the mechanism.** Stated precisely, because the previous wording asserted a
|
|
616
|
+
* mechanism that is not here and a comment like that stops the next reader checking:
|
|
617
|
+
*
|
|
618
|
+
* - **Event/TUI path.** An abort raised during the hold does end the run with the tool unrun, but
|
|
619
|
+
* *this code is not why*. The runner never re-checks the signal after the hold — it issues the
|
|
620
|
+
* approving resume regardless — and what stops the tool is that LangGraph refuses an
|
|
621
|
+
* already-aborted signal downstream. True today, and true by someone else's invariant.
|
|
622
|
+
* - **Plain/readline path.** Not true at all. `resolveToolInterrupts` threads no signal, and
|
|
623
|
+
* `waitForEscape` is armed inside `streamFromInput` and torn down before the hold begins — the
|
|
624
|
+
* hold happens *between* streams. Esc during it is not handled, so the command runs. The pause
|
|
625
|
+
* still buys the visibility above, which is why it is not conditioned on the surface.
|
|
626
|
+
*
|
|
627
|
+
* Making the affordance real by construction — checking the signal here, and threading one into
|
|
628
|
+
* the readline path so there is something to check — is deliberately left out of scope rather
|
|
629
|
+
* than half-built, since a window honoured on one surface and not the other is the more dangerous
|
|
630
|
+
* shape: it is what teaches the user the gesture that then silently fails.
|
|
631
|
+
*
|
|
632
|
+
* **Both halves are gated on a surface being wired**, so a headless `exec`/CI run neither draws
|
|
633
|
+
* nor sleeps and pays nothing. See {@link negotiationDisplay}.
|
|
634
|
+
*/
|
|
635
|
+
private showNegotiatedApproval;
|
|
427
636
|
/**
|
|
428
637
|
* [[TUI-C27]] — {@link decideToolApprovalInner} with the record closed off on EVERY exit.
|
|
429
638
|
*
|
|
@@ -435,6 +644,17 @@ export declare class GthAgentRunner {
|
|
|
435
644
|
private recordedDecision;
|
|
436
645
|
/** The decision itself; {@link decideToolApproval} wraps it with §5.3's reset. */
|
|
437
646
|
private decideToolApprovalInner;
|
|
647
|
+
/**
|
|
648
|
+
* [[EXT-150]] — tell the surface what its own answer landed as, and hand the decision straight
|
|
649
|
+
* back.
|
|
650
|
+
*
|
|
651
|
+
* A one-liner returning the decision, exactly as {@link stage} is, and for the identical reason:
|
|
652
|
+
* the report happens ON the return that carries the decision rather than on the line above it, so
|
|
653
|
+
* no early return can later be inserted between the two and leave a surface waiting for an answer
|
|
654
|
+
* that never comes. There is one return out of {@link decideToolApprovalInner} after the callback
|
|
655
|
+
* is awaited, and this is it.
|
|
656
|
+
*/
|
|
657
|
+
private reportOutcome;
|
|
438
658
|
/**
|
|
439
659
|
* [[TUI-C27]] — attribute the deciding stage and hand the decision straight back.
|
|
440
660
|
*
|
|
@@ -449,6 +669,23 @@ export declare class GthAgentRunner {
|
|
|
449
669
|
* only in what they do with the answer.
|
|
450
670
|
*/
|
|
451
671
|
private rateCommand;
|
|
672
|
+
/**
|
|
673
|
+
* [[EXT-127]] — **one alignment check**, with the `user` role fed from the settled provenance
|
|
674
|
+
* channel and nothing else.
|
|
675
|
+
*
|
|
676
|
+
* **`provenance` is `ShellNegotiationState.retainedUserMessages()`, read ONCE by the caller and
|
|
677
|
+
* handed down**, exactly as §4.6's floor reads it. Three things are true of it and only of it: it
|
|
678
|
+
* is EMPTY until `admitUserProvenance` positively established that this session's human turns are
|
|
679
|
+
* the user's own words (so `review` and `pr`, which fold a fetched diff into a human message,
|
|
680
|
+
* contribute nothing); it is not `noteUserMessages`' raw store, which answers a different question
|
|
681
|
+
* for a different reader; and it is not `humanMessageTexts`, the unfiltered upstream. Reading any
|
|
682
|
+
* of the other three here would make the one place this design says provenance is structural the
|
|
683
|
+
* one place it is not.
|
|
684
|
+
*
|
|
685
|
+
* The prior rounds come from the transcript's own alignment decisions, so the checker meets its
|
|
686
|
+
* earlier turns as its own turns rather than as a quoted summary of them.
|
|
687
|
+
*/
|
|
688
|
+
private checkAlignment;
|
|
452
689
|
/**
|
|
453
690
|
* EXT-58 (§4.3/§4.4) — the built-in tools already granted at the session's CURRENT rung, as
|
|
454
691
|
* names plus one-line locally-authored descriptions, for the rater prompt.
|
|
@@ -488,6 +725,25 @@ export declare class GthAgentRunner {
|
|
|
488
725
|
* knows how to reach the user.
|
|
489
726
|
*/
|
|
490
727
|
private getPersistedGrants;
|
|
728
|
+
/**
|
|
729
|
+
* [[EXT-107]] — lazily load (once per instance) the persisted **refusal** store, the mirror of
|
|
730
|
+
* {@link getPersistedGrants}. Returns null when it cannot be loaded at all, in which case an
|
|
731
|
+
* `always` refusal degrades to `session`.
|
|
732
|
+
*
|
|
733
|
+
* **No `bypass` guard, and no v1 migration**, and the two are the same decision. The allow side
|
|
734
|
+
* skips the file at `bypass` so a session with the gate switched off neither reads nor *rewrites*
|
|
735
|
+
* the project's grants; the only thing that rewrites on load is the v1 `prefixes` migration, and
|
|
736
|
+
* turning it off here makes this store's load a pure read. That is what lets it be consulted at
|
|
737
|
+
* every rung — which it must be, because a refusal is resolved before the `bypass` return and a
|
|
738
|
+
* saved refusal that lapsed when the gate was relaxed would be worthless.
|
|
739
|
+
*
|
|
740
|
+
* It is also loaded by the `/approvals` DISPLAY, unlike its allow-side twin. The rule there — a
|
|
741
|
+
* display must not create the store in order to show it — is a rule about writing, and this load
|
|
742
|
+
* cannot write. The reason to break the symmetry is that the display is the escape hatch: a saved
|
|
743
|
+
* refusal a user cannot see is one they cannot lift, and a fresh session has made no gated call
|
|
744
|
+
* yet.
|
|
745
|
+
*/
|
|
746
|
+
private getPersistedDenials;
|
|
491
747
|
/**
|
|
492
748
|
* §3.1/§4.7.4/§6 — **the grant a sticky choice would write for this call**, or `undefined` when
|
|
493
749
|
* none is on offer. The one place that question is answered, so the menu's *this is what will be
|
|
@@ -560,7 +816,7 @@ export declare class GthAgentRunner {
|
|
|
560
816
|
* - **`run_shell_command` arriving as a TOOL subject gets a tool entry** — a shell call whose
|
|
561
817
|
* `command` argument cannot even be read. On the allow side that entry would auto-approve every
|
|
562
818
|
* future unreadable shell call, which is why it is excluded there; as a refusal it stops the
|
|
563
|
-
* shell tool
|
|
819
|
+
* shell tool outright, and the dialog says so in the words the entry is written in.
|
|
564
820
|
*
|
|
565
821
|
* The one genuine exclusion is an **MCP call whose server could not be attributed**
|
|
566
822
|
* ({@link toolGrantEntry} returns `null`): the grammar's `server` cannot be the empty string, so
|
|
@@ -570,16 +826,38 @@ export declare class GthAgentRunner {
|
|
|
570
826
|
*/
|
|
571
827
|
private denyEntryFor;
|
|
572
828
|
/**
|
|
573
|
-
* §6 — record the menu's *always reject* choice
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
829
|
+
* §6/[[EXT-107]] — record the menu's *always reject* choice at the given scope. `once` remembers
|
|
830
|
+
* nothing; `session` holds the refusal for the life of this runner instance; `always` additionally
|
|
831
|
+
* writes it to the project's deny file, so it is still in force after a restart.
|
|
832
|
+
*
|
|
833
|
+
* It lands in the same lists `approvals.deny` entries are matched from ({@link approvalRuleLists}
|
|
834
|
+
* concatenates the three), so a refusal the human made at the prompt and one they wrote in their
|
|
835
|
+
* config are one list to the matcher.
|
|
836
|
+
*
|
|
837
|
+
* **The recorded scope is derived from whether the entry REACHED THE FILE** ([[EXT-149]]) — what
|
|
838
|
+
* {@link PersistedApprovalGrants.add} returns — and not from whether the store was allowed to try.
|
|
839
|
+
* Those differ on a read-only checkout, where the file is simply absent, so the load did not fail,
|
|
840
|
+
* `canPersist()` is true, and every write throws: asking permission stamped `always` on an answer
|
|
841
|
+
* that reached no disk, and nothing told the user. **Do not describe that gap's cost as "one
|
|
842
|
+
* re-prompt next session":** the entry is not on disk, so next session it applies to nothing and
|
|
843
|
+
* the call is left to the rest of the gate — another rule may refuse it, it may be prompted for,
|
|
844
|
+
* or it may run without asking under `bypass` or a matching saved allow.
|
|
845
|
+
*
|
|
846
|
+
* **Two records, not one patched afterwards.** {@link ApprovalGrantStore.add} holds the very
|
|
847
|
+
* object it is handed whenever the grant carries no annotation snapshot — which every refusal
|
|
848
|
+
* does, since {@link denyEntryFor} builds an entry and never a snapshot — so stamping one object
|
|
849
|
+
* `always` and correcting it after the write would reach inside whatever {@link denyGrants}
|
|
850
|
+
* already holds and whatever the display renders from it. The persisted store is handed the
|
|
851
|
+
* `always` record it will hold if the write lands (and takes back if it does not); the session
|
|
852
|
+
* store is handed its own record, stamped from the answer.
|
|
853
|
+
*
|
|
854
|
+
* The persisted store is still told even when it cannot write, because telling it is what reports
|
|
855
|
+
* the refused or failed write to the user, and it declines to hold what it did not write.
|
|
856
|
+
*
|
|
857
|
+
* **It RETURNS the lifetime it recorded** ([[EXT-150]]) — the same value it stamps the record with,
|
|
858
|
+
* handed back rather than left for a caller to re-derive. The surface that asked has to say what
|
|
859
|
+
* happened, and re-deriving it there would put the "did the write land" question in two places
|
|
860
|
+
* that could come to disagree, which is the whole failure this and [[EXT-149]] are about.
|
|
583
861
|
*/
|
|
584
862
|
private recordDenial;
|
|
585
863
|
/**
|
|
@@ -589,6 +867,24 @@ export declare class GthAgentRunner {
|
|
|
589
867
|
*
|
|
590
868
|
* What is recorded was decided by {@link stickyGrantFor} and shown to the human before they
|
|
591
869
|
* answered; this only stamps it with when and at what scope.
|
|
870
|
+
*
|
|
871
|
+
* **The stamped scope says whether this grant REACHED THE FILE**, the mirror of
|
|
872
|
+
* {@link recordDenial} and derived the same way ([[EXT-149]]): an `always` whose store is absent,
|
|
873
|
+
* whose file could not be read and so must not be rewritten ([[EXT-144]]), or whose write threw on
|
|
874
|
+
* a checkout nothing can write, is recorded as the `session` grant it actually is — and the store
|
|
875
|
+
* reports the last of those rather than swallowing it.
|
|
876
|
+
*
|
|
877
|
+
* Two records rather than one patched afterwards, for the reason argued in {@link recordDenial}:
|
|
878
|
+
* {@link ApprovalGrantStore.add} holds the object it is handed for a grant with no annotation
|
|
879
|
+
* snapshot — every shell grant — so a scope corrected after the write would be corrected inside
|
|
880
|
+
* {@link sessionGrants}.
|
|
881
|
+
*
|
|
882
|
+
* The store is still loaded only for `always`, unchanged: a display must not create the file in
|
|
883
|
+
* order to show it, and a `session` grant has no business opening it.
|
|
884
|
+
*
|
|
885
|
+
* **It RETURNS the lifetime it recorded** ([[EXT-150]]), the mirror of {@link recordDenial} and for
|
|
886
|
+
* the same reason: the surface's confirmation is written from this value rather than from the key
|
|
887
|
+
* the human pressed.
|
|
592
888
|
*/
|
|
593
889
|
private recordApproval;
|
|
594
890
|
/**
|