@gaunt-sloth/core 2.0.0-alpha.3 → 2.0.0-alpha.31
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/README.md +52 -20
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/defaults.d.ts +84 -0
- package/dist/config/defaults.js +99 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/filesystem-tools.d.ts +41 -0
- package/dist/config/filesystem-tools.js +56 -0
- package/dist/config/filesystem-tools.js.map +1 -0
- package/dist/config/jsonc.d.ts +12 -0
- package/dist/config/jsonc.js +41 -0
- package/dist/config/jsonc.js.map +1 -0
- package/dist/config/loader.d.ts +246 -0
- package/dist/config/loader.js +1321 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- package/dist/config/profiles.d.ts +68 -0
- package/dist/config/profiles.js +93 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providerKeys.d.ts +69 -0
- package/dist/config/providerKeys.js +69 -0
- package/dist/config/providerKeys.js.map +1 -0
- package/dist/config/schema.d.ts +2999 -0
- package/dist/config/schema.js +1519 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/shell-policy.d.ts +896 -0
- package/dist/config/shell-policy.js +750 -0
- package/dist/config/shell-policy.js.map +1 -0
- package/dist/config/tool-descriptions.d.ts +211 -0
- package/dist/config/tool-descriptions.js +272 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +760 -0
- package/dist/config/types.js +13 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config.d.ts +34 -827
- package/dist/config.js +25 -657
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +46 -0
- package/dist/constants.js +46 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +135 -2
- package/dist/core/GthAbstractAgent.js +496 -30
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +568 -54
- package/dist/core/GthAgentRunner.js +1537 -136
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +115 -0
- package/dist/core/GthLangChainAgent.js +644 -17
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/annotations.d.ts +122 -0
- package/dist/core/approvals/annotations.js +137 -0
- package/dist/core/approvals/annotations.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +216 -0
- package/dist/core/approvals/grants.js +469 -0
- package/dist/core/approvals/grants.js.map +1 -0
- package/dist/core/approvals/matcher.d.ts +202 -0
- package/dist/core/approvals/matcher.js +267 -0
- package/dist/core/approvals/matcher.js.map +1 -0
- package/dist/core/approvals/mcpSubjects.d.ts +40 -0
- package/dist/core/approvals/mcpSubjects.js +99 -0
- package/dist/core/approvals/mcpSubjects.js.map +1 -0
- package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
- package/dist/core/approvals/toolAnnotationSources.js +277 -0
- package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
- package/dist/core/approvals/toolHost.d.ts +46 -0
- package/dist/core/approvals/toolHost.js +108 -0
- package/dist/core/approvals/toolHost.js.map +1 -0
- package/dist/core/debugCapture.d.ts +75 -0
- package/dist/core/debugCapture.js +101 -0
- package/dist/core/debugCapture.js.map +1 -0
- package/dist/core/gthLeanAgentFactory.d.ts +9 -0
- package/dist/core/gthLeanAgentFactory.js +10 -0
- package/dist/core/gthLeanAgentFactory.js.map +1 -0
- package/dist/core/launchBanner.d.ts +120 -0
- package/dist/core/launchBanner.js +418 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/plainToolIndication.d.ts +15 -0
- package/dist/core/plainToolIndication.js +174 -0
- package/dist/core/plainToolIndication.js.map +1 -0
- package/dist/core/reasoningBlocks.d.ts +60 -0
- package/dist/core/reasoningBlocks.js +98 -0
- package/dist/core/reasoningBlocks.js.map +1 -0
- package/dist/core/refusal.d.ts +53 -0
- package/dist/core/refusal.js +133 -0
- package/dist/core/refusal.js.map +1 -0
- package/dist/core/runStats.d.ts +52 -0
- package/dist/core/runStats.js +118 -0
- package/dist/core/runStats.js.map +1 -0
- package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
- package/dist/core/shell/ShellCommandFailedError.js +68 -0
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
- 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/approvalCapture.d.ts +271 -0
- package/dist/core/shell/approvalCapture.js +108 -0
- package/dist/core/shell/approvalCapture.js.map +1 -0
- package/dist/core/shell/approvalStop.d.ts +84 -0
- package/dist/core/shell/approvalStop.js +119 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/escalationSeverity.d.ts +141 -0
- package/dist/core/shell/escalationSeverity.js +89 -0
- package/dist/core/shell/escalationSeverity.js.map +1 -0
- package/dist/core/shell/framing.d.ts +190 -0
- package/dist/core/shell/framing.js +633 -0
- package/dist/core/shell/framing.js.map +1 -0
- package/dist/core/shell/hardline.d.ts +81 -0
- package/dist/core/shell/hardline.js +607 -0
- package/dist/core/shell/hardline.js.map +1 -0
- package/dist/core/shell/negotiation.d.ts +249 -0
- package/dist/core/shell/negotiation.js +355 -0
- package/dist/core/shell/negotiation.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +44 -4
- package/dist/core/shell/normalize.js +61 -7
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +263 -0
- package/dist/core/shell/openWorld.js +1188 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +935 -0
- package/dist/core/shell/rater.js +1473 -0
- package/dist/core/shell/rater.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +41 -0
- package/dist/core/shell/raterModel.js +51 -0
- package/dist/core/shell/raterModel.js.map +1 -0
- package/dist/core/shell/rejection.d.ts +69 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/toolCallRepair/grammar.d.ts +41 -0
- package/dist/core/toolCallRepair/grammar.js +116 -0
- package/dist/core/toolCallRepair/grammar.js.map +1 -0
- package/dist/core/toolCallRepair/index.d.ts +2 -0
- package/dist/core/toolCallRepair/index.js +7 -0
- package/dist/core/toolCallRepair/index.js.map +1 -0
- package/dist/core/toolCallRepair/payload.d.ts +36 -0
- package/dist/core/toolCallRepair/payload.js +341 -0
- package/dist/core/toolCallRepair/payload.js.map +1 -0
- package/dist/core/toolCallRepair/promote.d.ts +45 -0
- package/dist/core/toolCallRepair/promote.js +90 -0
- package/dist/core/toolCallRepair/promote.js.map +1 -0
- package/dist/core/toolDisplay.d.ts +123 -0
- package/dist/core/toolDisplay.js +451 -0
- package/dist/core/toolDisplay.js.map +1 -0
- package/dist/core/toolOutputChannel.d.ts +95 -0
- package/dist/core/toolOutputChannel.js +165 -0
- package/dist/core/toolOutputChannel.js.map +1 -0
- package/dist/core/types.d.ts +294 -12
- package/dist/core/types.js.map +1 -1
- package/dist/history/historyFormat.d.ts +28 -0
- package/dist/history/historyFormat.js +127 -0
- package/dist/history/historyFormat.js.map +1 -0
- package/dist/history/historyStore.d.ts +198 -0
- package/dist/history/historyStore.js +482 -0
- package/dist/history/historyStore.js.map +1 -0
- package/dist/history/recordSession.d.ts +37 -0
- package/dist/history/recordSession.js +56 -0
- package/dist/history/recordSession.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +5 -10
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/deepseek.d.ts +1 -1
- package/dist/providers/deepseek.js +5 -10
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
- package/dist/providers/geminiSchemaSanitizer.js +347 -0
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +52 -0
- package/dist/providers/geminiThinking.js +72 -0
- package/dist/providers/geminiThinking.js.map +1 -0
- package/dist/providers/google-genai.d.ts +1 -1
- package/dist/providers/google-genai.js +12 -11
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.d.ts +1 -1
- package/dist/providers/groq.js +5 -10
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/huggingface.d.ts +25 -0
- package/dist/providers/huggingface.js +69 -0
- package/dist/providers/huggingface.js.map +1 -0
- package/dist/providers/modelCatalog.d.ts +109 -0
- package/dist/providers/modelCatalog.js +245 -0
- package/dist/providers/modelCatalog.js.map +1 -0
- package/dist/providers/modelDiscovery.d.ts +153 -6
- package/dist/providers/modelDiscovery.js +295 -41
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/ollama.d.ts +19 -5
- package/dist/providers/ollama.js +60 -52
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +39 -10
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +4 -5
- package/dist/providers/openrouter.js +20 -35
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.d.ts +1 -1
- package/dist/providers/vertexai.js +12 -11
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +5 -10
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +105 -0
- package/dist/runtime/askStructured.js +120 -0
- package/dist/runtime/askStructured.js.map +1 -0
- package/dist/runtime/conversation.d.ts +60 -0
- package/dist/runtime/conversation.js +151 -0
- package/dist/runtime/conversation.js.map +1 -0
- package/dist/runtime/singleShot.d.ts +25 -5
- package/dist/runtime/singleShot.js +95 -36
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/runtime/structuredOutput.d.ts +104 -0
- package/dist/runtime/structuredOutput.js +393 -0
- package/dist/runtime/structuredOutput.js.map +1 -0
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/aiignoreUtils.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +52 -0
- package/dist/utils/consoleUtils.js +72 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/crashHandler.d.ts +87 -0
- package/dist/utils/crashHandler.js +128 -0
- package/dist/utils/crashHandler.js.map +1 -0
- package/dist/utils/debugDump.d.ts +134 -0
- package/dist/utils/debugDump.js +381 -0
- package/dist/utils/debugDump.js.map +1 -0
- package/dist/utils/debugUtils.d.ts +13 -4
- package/dist/utils/debugUtils.js +36 -13
- package/dist/utils/debugUtils.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +53 -0
- package/dist/utils/displayWidth.js +195 -0
- package/dist/utils/displayWidth.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +24 -2
- package/dist/utils/fileUtils.js +54 -12
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +39 -8
- package/dist/utils/llmUtils.js +76 -8
- package/dist/utils/llmUtils.js.map +1 -1
- package/dist/utils/redactSecrets.d.ts +63 -0
- package/dist/utils/redactSecrets.js +286 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +225 -0
- package/dist/utils/systemPromptNotes.js +340 -0
- package/dist/utils/systemPromptNotes.js.map +1 -0
- package/dist/utils/systemUtils.d.ts +49 -1
- package/dist/utils/systemUtils.js +67 -3
- package/dist/utils/systemUtils.js.map +1 -1
- package/dist/utils/toolMatching.d.ts +30 -0
- package/dist/utils/toolMatching.js +44 -0
- package/dist/utils/toolMatching.js.map +1 -0
- 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 +22 -6
- package/schema/gsloth-config.schema.json +3134 -0
- package/dist/core/shell/allowlist.d.ts +0 -75
- package/dist/core/shell/allowlist.js +0 -187
- package/dist/core/shell/allowlist.js.map +0 -1
- package/dist/core/shell/judge.d.ts +0 -161
- package/dist/core/shell/judge.js +0 -261
- package/dist/core/shell/judge.js.map +0 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many gated decisions one session keeps. A ring buffer for the same reason the debug-log one
|
|
3
|
+
* is: each record carries a full rating prompt (a few KB), and a long `auto` session makes a lot of
|
|
4
|
+
* them. The newest are the ones a bug report is about, so the oldest are evicted.
|
|
5
|
+
*/
|
|
6
|
+
export const APPROVAL_CAPTURE_MAX = 50;
|
|
7
|
+
/**
|
|
8
|
+
* The per-session log of gated decisions.
|
|
9
|
+
*
|
|
10
|
+
* **Instance-scoped, on the runner, and never a module singleton** — the same reason
|
|
11
|
+
* `ShellNegotiationState` and the grant stores are: a concurrent ACP / AG-UI session must not
|
|
12
|
+
* inherit another session's approvals history, and a dump taken in one must not describe the other.
|
|
13
|
+
*/
|
|
14
|
+
export class ApprovalCaptureLog {
|
|
15
|
+
records = [];
|
|
16
|
+
/**
|
|
17
|
+
* Open a record for a gated call and return it LIVE, already in the buffer.
|
|
18
|
+
*
|
|
19
|
+
* The caller mutates the returned object as the decision is made. See the module docblock for why
|
|
20
|
+
* the push happens here rather than at the end: a halt throws out of the decision, and a record
|
|
21
|
+
* assembled at the end would be lost on exactly the calls most worth keeping.
|
|
22
|
+
*/
|
|
23
|
+
begin(record) {
|
|
24
|
+
this.records.push(record);
|
|
25
|
+
if (this.records.length > APPROVAL_CAPTURE_MAX)
|
|
26
|
+
this.records.shift();
|
|
27
|
+
return record;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The records so far, oldest first. A copy of the ARRAY: the records themselves are handed over
|
|
31
|
+
* live, because a decision still in flight is one the archive should show as it stands rather
|
|
32
|
+
* than not at all.
|
|
33
|
+
*/
|
|
34
|
+
snapshot() {
|
|
35
|
+
return [...this.records];
|
|
36
|
+
}
|
|
37
|
+
/** Drop everything — the TUI's `/clear` rotates the thread. */
|
|
38
|
+
clear() {
|
|
39
|
+
this.records = [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A short, KEY-FREE label for the rater model: its id and its provider type, and nothing else.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately not a param dump. A live `BaseChatModel` carries an `apiKey` and a client instance,
|
|
46
|
+
* and this string goes into an archive people attach to bug reports — so it reads a fixed handful of
|
|
47
|
+
* scalar fields through guards, exactly as `debugDump`'s own live-model descriptor does, rather than
|
|
48
|
+
* serialising anything of the instance.
|
|
49
|
+
*/
|
|
50
|
+
export function raterModelLabel(model) {
|
|
51
|
+
if (!model || typeof model !== 'object')
|
|
52
|
+
return undefined;
|
|
53
|
+
const m = model;
|
|
54
|
+
let type;
|
|
55
|
+
try {
|
|
56
|
+
type = typeof m._llmType === 'function' ? m._llmType() : undefined;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
type = undefined;
|
|
60
|
+
}
|
|
61
|
+
const id = [m.model, m.modelName, m.modelId].find((value) => typeof value === 'string');
|
|
62
|
+
if (id && type)
|
|
63
|
+
return `${type}/${id}`;
|
|
64
|
+
return id ?? type;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Describe the §5.1 context a rating is about to be made with — **as it was handed to the prompt
|
|
68
|
+
* builder**, so the record and the prompt cannot come to describe two different ratings.
|
|
69
|
+
*
|
|
70
|
+
* The note is the legibility half of the acceptance: an empty user-messages window means one thing
|
|
71
|
+
* at round 1 (by design) and something else entirely at round 3, and a reader of the archive has no
|
|
72
|
+
* way to tell them apart from a `[]`.
|
|
73
|
+
*/
|
|
74
|
+
export function describeRaterNegotiation(negotiation) {
|
|
75
|
+
const priorRounds = [...(negotiation?.priorRounds ?? [])];
|
|
76
|
+
const userMessages = [...(negotiation?.userMessages ?? [])];
|
|
77
|
+
const roundOne = priorRounds.length === 0;
|
|
78
|
+
const populated = userMessages.length > 0;
|
|
79
|
+
return {
|
|
80
|
+
round: priorRounds.length + 1,
|
|
81
|
+
roundOne,
|
|
82
|
+
contextSupplied: negotiation !== undefined,
|
|
83
|
+
...(negotiation?.justification !== undefined
|
|
84
|
+
? { justification: negotiation.justification }
|
|
85
|
+
: {}),
|
|
86
|
+
priorRounds,
|
|
87
|
+
userMessages,
|
|
88
|
+
userMessagesPopulated: populated,
|
|
89
|
+
userMessagesNote: userMessagesNote(negotiation !== undefined, roundOne, populated),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** The sentence {@link describeRaterNegotiation} puts on the window, for a reader with no spec. */
|
|
93
|
+
function userMessagesNote(contextSupplied, roundOne, populated) {
|
|
94
|
+
if (!contextSupplied) {
|
|
95
|
+
return ('No negotiation context was supplied for this rating (the rung does not negotiate, or this ' +
|
|
96
|
+
'was the allow-list tripwire), so the rater saw the command alone. An empty window here is ' +
|
|
97
|
+
'not a defect.');
|
|
98
|
+
}
|
|
99
|
+
if (roundOne) {
|
|
100
|
+
return ('Round 1: spec §5.1 admits no justification, no transcript and no user messages, so the ' +
|
|
101
|
+
'rater saw the command alone. An empty window here is BY DESIGN, not a defect.');
|
|
102
|
+
}
|
|
103
|
+
return populated
|
|
104
|
+
? "Round 2 or later: the user's own recent messages WERE in the rater's view for this rating."
|
|
105
|
+
: 'Round 2 or later, and the window is still empty — the conversation had no non-blank user ' +
|
|
106
|
+
'message to admit. This one IS worth looking at.';
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=approvalCapture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approvalCapture.js","sourceRoot":"","sources":["../../../src/core/shell/approvalCapture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/approvalStop
|
|
3
|
+
*
|
|
4
|
+
* CFG-27 — the two ways the approvals gate **ends a run** rather than answering a tool call.
|
|
5
|
+
* Both are thrown from `GthAgentRunner.decideToolApproval`, both carry the command and the reason
|
|
6
|
+
* a person needs to see, and both are re-thrown UNCHANGED by `processMessages` (which otherwise
|
|
7
|
+
* wraps failures as `Agent processing failed: …`) so the explanation reaches the user intact.
|
|
8
|
+
*
|
|
9
|
+
* They are errors rather than {@link ../types.js ToolApprovalDecision}s on purpose. A decision is
|
|
10
|
+
* something the model observes as a `ToolMessage` and can respond to; these two are precisely the
|
|
11
|
+
* cases where the spec says the model gets no move at all:
|
|
12
|
+
*
|
|
13
|
+
* - **{@link AttackHaltError}** (§4.2) — an `attack` outcome *halts the run*. "A halt ends the
|
|
14
|
+
* agent loop. It is not a rejection the model can respond to, and no rung except `bypass` can
|
|
15
|
+
* turn it into anything else."
|
|
16
|
+
* - **{@link NonInteractiveEscalationError}** (§6.2) — where no human can answer, *every*
|
|
17
|
+
* escalation is an immediate non-zero exit carrying the command, the rating and its reason.
|
|
18
|
+
* There is no prompt, no waiting, and never a timeout into approval. Teams that need specific
|
|
19
|
+
* commands to run unattended declare them in `approvals.allow` (§3), which is consulted before
|
|
20
|
+
* the rater and therefore never escalates.
|
|
21
|
+
*
|
|
22
|
+
* Exit code: neither class sets one. The single-shot runtime (`runSingleShot`) already reports a
|
|
23
|
+
* thrown run as `ok: false`, and each command entry point turns that into `setExitCode(1)` — so
|
|
24
|
+
* "immediate non-zero exit carrying the explanation" is what a caller already gets, without a new
|
|
25
|
+
* exit path that could diverge from the existing one.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* 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`. Every surface shows the
|
|
30
|
+
* message as-is, which is already the whole explanation.
|
|
31
|
+
*/
|
|
32
|
+
export declare abstract class ApprovalStopError extends Error {
|
|
33
|
+
/** The command that ended the run. */
|
|
34
|
+
readonly command: string;
|
|
35
|
+
protected constructor(message: string, command: string);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* §4.2 — an `attack` outcome: the command's own **structure** evidenced compromise (§4.1.1 —
|
|
39
|
+
* credential targeting, privilege escalation, persistence, deception, obfuscation). Ends the agent
|
|
40
|
+
* loop; the model is told nothing and offered nothing.
|
|
41
|
+
*
|
|
42
|
+
* **This is what reaches a surface that cannot ask.** An interactive surface is offered §6.1's red
|
|
43
|
+
* banner first — `GthAgentRunner.setAttackHaltCallback`, where typing `run anyway` runs this one
|
|
44
|
+
* command and everything else stops the run — and this error is thrown when no banner is wired, or
|
|
45
|
+
* when the banner is answered with anything but that phrase. A non-interactive session wires
|
|
46
|
+
* nothing and so gets this message directly (§6.2).
|
|
47
|
+
*
|
|
48
|
+
* The recovery this message names is deliberately the **allow-list**, not `bypass`. §4.2 makes
|
|
49
|
+
* `approvals.allow` the supported way to run such a command unattended (it is consulted before the
|
|
50
|
+
* rater, so it never reaches a halt at all); `bypass` also works and is far blunter — it turns off
|
|
51
|
+
* the rater, the escalation and the halt together, for every command, for the whole run. It is a
|
|
52
|
+
* last resort, not the answer, and the wording says so in that order.
|
|
53
|
+
*/
|
|
54
|
+
export declare class AttackHaltError extends ApprovalStopError {
|
|
55
|
+
/** The rater's explanation of what the command's structure showed. */
|
|
56
|
+
readonly reason: string;
|
|
57
|
+
constructor(command: string, reason: string);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* §6.2 — an escalation with nobody to ask (CI, one-shot runs, servers). Fails the build loudly,
|
|
61
|
+
* with everything a person needs in order to see why.
|
|
62
|
+
*/
|
|
63
|
+
export declare class NonInteractiveEscalationError extends ApprovalStopError {
|
|
64
|
+
/** The rater's outcome, when a rating existed (the unrated rungs have none). */
|
|
65
|
+
readonly outcome: string | undefined;
|
|
66
|
+
/** The rater's explanation, when a rating existed. */
|
|
67
|
+
readonly reason: string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* EXT-71 §3.2 — the declared `approvals.escalate` entry that sent this call to a human, when one
|
|
70
|
+
* did. It changes the recovery the message names: pointing someone at `approvals.allow` when they
|
|
71
|
+
* themselves wrote an escalate entry sends them to a list that cannot win, since a match on
|
|
72
|
+
* `escalate` outranks a match on `allow`.
|
|
73
|
+
*/
|
|
74
|
+
readonly escalatedBy: string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* [[EXT-29]] §6 — the §5 negotiation that preceded this escalation, rendered, when there was one.
|
|
77
|
+
*
|
|
78
|
+
* §6.2's message is the ONLY thing a person sees on this path — there is no prompt to attach a
|
|
79
|
+
* transcript to — so an unattended run that ended after three rejections would otherwise report
|
|
80
|
+
* the last command and give no hint that the agent had already been told twice what to fix.
|
|
81
|
+
*/
|
|
82
|
+
readonly negotiation: string | undefined;
|
|
83
|
+
constructor(command: string, outcome?: string, reason?: string, escalatedBy?: string, negotiation?: string);
|
|
84
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/approvalStop
|
|
3
|
+
*
|
|
4
|
+
* CFG-27 — the two ways the approvals gate **ends a run** rather than answering a tool call.
|
|
5
|
+
* Both are thrown from `GthAgentRunner.decideToolApproval`, both carry the command and the reason
|
|
6
|
+
* a person needs to see, and both are re-thrown UNCHANGED by `processMessages` (which otherwise
|
|
7
|
+
* wraps failures as `Agent processing failed: …`) so the explanation reaches the user intact.
|
|
8
|
+
*
|
|
9
|
+
* They are errors rather than {@link ../types.js ToolApprovalDecision}s on purpose. A decision is
|
|
10
|
+
* something the model observes as a `ToolMessage` and can respond to; these two are precisely the
|
|
11
|
+
* cases where the spec says the model gets no move at all:
|
|
12
|
+
*
|
|
13
|
+
* - **{@link AttackHaltError}** (§4.2) — an `attack` outcome *halts the run*. "A halt ends the
|
|
14
|
+
* agent loop. It is not a rejection the model can respond to, and no rung except `bypass` can
|
|
15
|
+
* turn it into anything else."
|
|
16
|
+
* - **{@link NonInteractiveEscalationError}** (§6.2) — where no human can answer, *every*
|
|
17
|
+
* escalation is an immediate non-zero exit carrying the command, the rating and its reason.
|
|
18
|
+
* There is no prompt, no waiting, and never a timeout into approval. Teams that need specific
|
|
19
|
+
* commands to run unattended declare them in `approvals.allow` (§3), which is consulted before
|
|
20
|
+
* the rater and therefore never escalates.
|
|
21
|
+
*
|
|
22
|
+
* Exit code: neither class sets one. The single-shot runtime (`runSingleShot`) already reports a
|
|
23
|
+
* thrown run as `ok: false`, and each command entry point turns that into `setExitCode(1)` — so
|
|
24
|
+
* "immediate non-zero exit carrying the explanation" is what a caller already gets, without a new
|
|
25
|
+
* exit path that could diverge from the existing one.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* 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`. Every surface shows the
|
|
30
|
+
* message as-is, which is already the whole explanation.
|
|
31
|
+
*/
|
|
32
|
+
export class ApprovalStopError extends Error {
|
|
33
|
+
/** The command that ended the run. */
|
|
34
|
+
command;
|
|
35
|
+
constructor(message, command) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.command = command;
|
|
38
|
+
// Restore the prototype chain across the ES5 `extends Error` downlevel, so `instanceof` works.
|
|
39
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
40
|
+
this.name = new.target.name;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* §4.2 — an `attack` outcome: the command's own **structure** evidenced compromise (§4.1.1 —
|
|
45
|
+
* credential targeting, privilege escalation, persistence, deception, obfuscation). Ends the agent
|
|
46
|
+
* loop; the model is told nothing and offered nothing.
|
|
47
|
+
*
|
|
48
|
+
* **This is what reaches a surface that cannot ask.** An interactive surface is offered §6.1's red
|
|
49
|
+
* banner first — `GthAgentRunner.setAttackHaltCallback`, where typing `run anyway` runs this one
|
|
50
|
+
* command and everything else stops the run — and this error is thrown when no banner is wired, or
|
|
51
|
+
* when the banner is answered with anything but that phrase. A non-interactive session wires
|
|
52
|
+
* nothing and so gets this message directly (§6.2).
|
|
53
|
+
*
|
|
54
|
+
* The recovery this message names is deliberately the **allow-list**, not `bypass`. §4.2 makes
|
|
55
|
+
* `approvals.allow` the supported way to run such a command unattended (it is consulted before the
|
|
56
|
+
* rater, so it never reaches a halt at all); `bypass` also works and is far blunter — it turns off
|
|
57
|
+
* the rater, the escalation and the halt together, for every command, for the whole run. It is a
|
|
58
|
+
* last resort, not the answer, and the wording says so in that order.
|
|
59
|
+
*/
|
|
60
|
+
export class AttackHaltError extends ApprovalStopError {
|
|
61
|
+
/** The rater's explanation of what the command's structure showed. */
|
|
62
|
+
reason;
|
|
63
|
+
constructor(command, reason) {
|
|
64
|
+
super(`Run halted: the auto-rater rated this command as an attack, which ends the run.\n` +
|
|
65
|
+
` Command: ${command}\n` +
|
|
66
|
+
` Reason: ${reason}\n` +
|
|
67
|
+
`This is not negotiable. If this command is legitimate and you need it to run, declare ` +
|
|
68
|
+
`it in approvals.allow — that list is consulted before the auto-rater, so it never ` +
|
|
69
|
+
`reaches a halt. Dropping to approvals "bypass" also works, but it turns off the rater, ` +
|
|
70
|
+
`the prompts and the halt for every command in the run.`, command);
|
|
71
|
+
this.reason = reason;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* §6.2 — an escalation with nobody to ask (CI, one-shot runs, servers). Fails the build loudly,
|
|
76
|
+
* with everything a person needs in order to see why.
|
|
77
|
+
*/
|
|
78
|
+
export class NonInteractiveEscalationError extends ApprovalStopError {
|
|
79
|
+
/** The rater's outcome, when a rating existed (the unrated rungs have none). */
|
|
80
|
+
outcome;
|
|
81
|
+
/** The rater's explanation, when a rating existed. */
|
|
82
|
+
reason;
|
|
83
|
+
/**
|
|
84
|
+
* EXT-71 §3.2 — the declared `approvals.escalate` entry that sent this call to a human, when one
|
|
85
|
+
* did. It changes the recovery the message names: pointing someone at `approvals.allow` when they
|
|
86
|
+
* themselves wrote an escalate entry sends them to a list that cannot win, since a match on
|
|
87
|
+
* `escalate` outranks a match on `allow`.
|
|
88
|
+
*/
|
|
89
|
+
escalatedBy;
|
|
90
|
+
/**
|
|
91
|
+
* [[EXT-29]] §6 — the §5 negotiation that preceded this escalation, rendered, when there was one.
|
|
92
|
+
*
|
|
93
|
+
* §6.2's message is the ONLY thing a person sees on this path — there is no prompt to attach a
|
|
94
|
+
* transcript to — so an unattended run that ended after three rejections would otherwise report
|
|
95
|
+
* the last command and give no hint that the agent had already been told twice what to fix.
|
|
96
|
+
*/
|
|
97
|
+
negotiation;
|
|
98
|
+
constructor(command, outcome, reason, escalatedBy, negotiation) {
|
|
99
|
+
super(`Approval required, but this session has no one to ask.\n` +
|
|
100
|
+
` Command: ${command}\n` +
|
|
101
|
+
(outcome ? ` Rating: ${outcome}\n` : '') +
|
|
102
|
+
(reason ? ` Reason: ${reason}\n` : '') +
|
|
103
|
+
(negotiation ? `${negotiation}\n` : '') +
|
|
104
|
+
(escalatedBy
|
|
105
|
+
? ` Matched approvals.escalate: ${escalatedBy}\n` +
|
|
106
|
+
`An escalate entry always asks a human, whatever the rung would have done, so no ` +
|
|
107
|
+
`entry in approvals.allow can answer it. Remove the escalate entry if this command ` +
|
|
108
|
+
`should run unattended.`
|
|
109
|
+
: `Declare the commands this run is allowed to execute in approvals.allow — write each ` +
|
|
110
|
+
`one as an explicit entry, for example { "type": "shell", "matcher": "exact", ` +
|
|
111
|
+
`"pattern": "npm test" }. That list is consulted before the auto-rater and never ` +
|
|
112
|
+
`escalates.`), command);
|
|
113
|
+
this.outcome = outcome;
|
|
114
|
+
this.reason = reason;
|
|
115
|
+
this.escalatedBy = escalatedBy;
|
|
116
|
+
this.negotiation = negotiation;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=approvalStop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;GAIG;AACH,MAAM,OAAgB,iBAAkB,SAAQ,KAAK;IACnD,sCAAsC;IAC7B,OAAO,CAAS;IAEzB,YAAsB,OAAe,EAAE,OAAe;QACpD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,+FAA+F;QAC/F,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IACpD,sEAAsE;IAC7D,MAAM,CAAS;IAExB,YAAY,OAAe,EAAE,MAAc;QACzC,KAAK,CACH,mFAAmF;YACjF,cAAc,OAAO,IAAI;YACzB,aAAa,MAAM,IAAI;YACvB,wFAAwF;YACxF,oFAAoF;YACpF,yFAAyF;YACzF,wDAAwD,EAC1D,OAAO,CACR,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,6BAA8B,SAAQ,iBAAiB;IAClE,gFAAgF;IACvE,OAAO,CAAqB;IACrC,sDAAsD;IAC7C,MAAM,CAAqB;IACpC;;;;;OAKG;IACM,WAAW,CAAqB;IAEzC;;;;;;OAMG;IACM,WAAW,CAAqB;IAEzC,YACE,OAAe,EACf,OAAgB,EAChB,MAAe,EACf,WAAoB,EACpB,WAAoB;QAEpB,KAAK,CACH,0DAA0D;YACxD,cAAc,OAAO,IAAI;YACzB,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,CAAC,WAAW;gBACV,CAAC,CAAC,iCAAiC,WAAW,IAAI;oBAChD,kFAAkF;oBAClF,oFAAoF;oBACpF,wBAAwB;gBAC1B,CAAC,CAAC,sFAAsF;oBACtF,+EAA+E;oBAC/E,kFAAkF;oBAClF,YAAY,CAAC,EACnB,OAAO,CACR,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF"}
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
* redirections. Such commands NEVER auto-match an allow-list entry — they always go to
|
|
19
19
|
* fresh human approval. This is what stops `git checkout x; rm -rf /` from matching an
|
|
20
20
|
* approved `git checkout *`.
|
|
21
|
+
*
|
|
22
|
+
* EXT-55: "newlines" in that list was aspirational until this node. `normalizeCommand` folded
|
|
23
|
+
* a line break to a SPACE, so the check below never saw one and `ls -la\nrm -rf /` classified
|
|
24
|
+
* as the single command `ls`. The separator set now lives in ONE place
|
|
25
|
+
* ({@link import('./normalize.js').COMMAND_SEPARATOR_CLASS}) and a line break survives
|
|
26
|
+
* normalization, so a multi-line command is categorically ambiguous — exactly like `;`.
|
|
21
27
|
*/
|
|
22
28
|
/**
|
|
23
29
|
* Result of classifying a command for allow-list matching.
|
package/dist/core/shell/arity.js
CHANGED
|
@@ -18,7 +18,14 @@
|
|
|
18
18
|
* redirections. Such commands NEVER auto-match an allow-list entry — they always go to
|
|
19
19
|
* fresh human approval. This is what stops `git checkout x; rm -rf /` from matching an
|
|
20
20
|
* approved `git checkout *`.
|
|
21
|
+
*
|
|
22
|
+
* EXT-55: "newlines" in that list was aspirational until this node. `normalizeCommand` folded
|
|
23
|
+
* a line break to a SPACE, so the check below never saw one and `ls -la\nrm -rf /` classified
|
|
24
|
+
* as the single command `ls`. The separator set now lives in ONE place
|
|
25
|
+
* ({@link import('./normalize.js').COMMAND_SEPARATOR_CLASS}) and a line break survives
|
|
26
|
+
* normalization, so a multi-line command is categorically ambiguous — exactly like `;`.
|
|
21
27
|
*/
|
|
28
|
+
import { COMMAND_SEPARATOR_RE, LINE_BREAK_RE } from '#src/core/shell/normalize.js';
|
|
22
29
|
/**
|
|
23
30
|
* Arity table: command-prefix string → number of leading tokens (binary + subcommands,
|
|
24
31
|
* flags excluded) that define the "human-understandable command". Longest matching
|
|
@@ -181,11 +188,9 @@ const ARITY = {
|
|
|
181
188
|
* Note: `&&`/`||`/`|` are covered by the bare `&`/`|` character scan; listed conceptually.
|
|
182
189
|
*/
|
|
183
190
|
function hasUnsafeComposition(normalized) {
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// Shell control / separator operators and background.
|
|
188
|
-
if (/[;|&]/.test(normalized))
|
|
191
|
+
// Shell control / separator operators, background, AND line breaks — one shared definition of
|
|
192
|
+
// "a new command begins here" (EXT-55), so this can never drift from the hardline floor's.
|
|
193
|
+
if (COMMAND_SEPARATOR_RE.test(normalized))
|
|
189
194
|
return true;
|
|
190
195
|
// Command substitution: $(...) or `...`.
|
|
191
196
|
if (/\$\(/.test(normalized))
|
|
@@ -235,7 +240,9 @@ export function tokenize(command) {
|
|
|
235
240
|
inToken = true;
|
|
236
241
|
continue;
|
|
237
242
|
}
|
|
238
|
-
|
|
243
|
+
// EXT-55: line breaks are whitespace here too, so a token can never be glued across a line
|
|
244
|
+
// (`ls\n-o` must tokenize as `ls` + `-o`, or a caller inspecting the argv would miss the flag).
|
|
245
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
239
246
|
if (inToken) {
|
|
240
247
|
tokens.push(current);
|
|
241
248
|
current = '';
|
|
@@ -294,6 +301,13 @@ export function meaningfulPrefixTokens(argv) {
|
|
|
294
301
|
* @param normalize Normalizer to apply for the detection form (inject normalizeCommand).
|
|
295
302
|
*/
|
|
296
303
|
export function classifyCommand(command, normalize) {
|
|
304
|
+
// EXT-55 — the boundary question is answered HERE, not delegated to the injected normalizer.
|
|
305
|
+
// `normalize` is a parameter, so this function cannot assume it preserved the command
|
|
306
|
+
// separators (folding them away is exactly the bug this node fixed). A line break anywhere
|
|
307
|
+
// INSIDE the command means more than one command → never classifiable. `.trim()` first: a
|
|
308
|
+
// purely leading/trailing break separates nothing, and models routinely emit `"npm test\n"`.
|
|
309
|
+
if (LINE_BREAK_RE.test(command.trim()))
|
|
310
|
+
return null;
|
|
297
311
|
const normalized = normalize(command);
|
|
298
312
|
if (!normalized)
|
|
299
313
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arity.js","sourceRoot":"","sources":["../../../src/core/shell/arity.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"arity.js","sourceRoot":"","sources":["../../../src/core/shell/arity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a raw command into the segments a shell would run, normalized and **case preserved**.
|
|
3
|
+
*
|
|
4
|
+
* Substitution bodies are segments in their own right: `echo $(npm publish)` runs `npm publish`,
|
|
5
|
+
* so a deny entry for `npm publish` must see it. Splitting on `$(`, `` ` `` and `)` yields the
|
|
6
|
+
* body as its own segment (and leaves harmless empty fragments, which are dropped).
|
|
7
|
+
*
|
|
8
|
+
* Case is preserved because the matcher folds it per list and per matcher — a `regexp` entry is
|
|
9
|
+
* compiled exactly as the user wrote it, and folding here would decide that for it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function commandSegments(command: string): string[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/denylist
|
|
3
|
+
*
|
|
4
|
+
* **Command segmentation** — splitting a command into every command a shell would actually run.
|
|
5
|
+
*
|
|
6
|
+
* This is what lets a restrictive rule (`deny`, `escalate`) see inside a compound command. The
|
|
7
|
+
* asymmetry it serves lives in `core/approvals/matcher.ts`, which is the one comparison engine:
|
|
8
|
+
*
|
|
9
|
+
* - **No allow entry matches a command that does not statically resolve.** Composition,
|
|
10
|
+
* substitution and redirection are a non-match for every allow entry, so a grant can never be
|
|
11
|
+
* extended with a `; rm -rf /`.
|
|
12
|
+
* - **A deny or escalate entry MAY match one**, and is compared against every segment as well as
|
|
13
|
+
* the whole string, because a prohibition that catches something unresolvable errs in the
|
|
14
|
+
* direction that costs nothing. Without that, `git push --force; ls` would sail straight past a
|
|
15
|
+
* declared deny entry for `git push --force` — a prohibition any trailing `; ls` defeats is not a
|
|
16
|
+
* prohibition.
|
|
17
|
+
*/
|
|
18
|
+
import { COMMAND_SEPARATOR_CLASS, normalizeCommand } from '#src/core/shell/normalize.js';
|
|
19
|
+
/** Splits the normalized command at every point where a shell would begin a NEW command. */
|
|
20
|
+
const SEGMENT_SPLIT_RE = new RegExp(`[${COMMAND_SEPARATOR_CLASS}]|\\$\\(|\\)|\``, 'g');
|
|
21
|
+
/**
|
|
22
|
+
* Split a raw command into the segments a shell would run, normalized and **case preserved**.
|
|
23
|
+
*
|
|
24
|
+
* Substitution bodies are segments in their own right: `echo $(npm publish)` runs `npm publish`,
|
|
25
|
+
* so a deny entry for `npm publish` must see it. Splitting on `$(`, `` ` `` and `)` yields the
|
|
26
|
+
* body as its own segment (and leaves harmless empty fragments, which are dropped).
|
|
27
|
+
*
|
|
28
|
+
* Case is preserved because the matcher folds it per list and per matcher — a `regexp` entry is
|
|
29
|
+
* compiled exactly as the user wrote it, and folding here would decide that for it.
|
|
30
|
+
*/
|
|
31
|
+
export function commandSegments(command) {
|
|
32
|
+
return normalizeCommand(command)
|
|
33
|
+
.split(SEGMENT_SPLIT_RE)
|
|
34
|
+
.map((segment) => segment.trim())
|
|
35
|
+
.filter((segment) => segment.length > 0);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=denylist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"denylist.js","sourceRoot":"","sources":["../../../src/core/shell/denylist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEzF,4FAA4F;AAC5F,MAAM,gBAAgB,GAAG,IAAI,MAAM,CAAC,IAAI,uBAAuB,iBAAiB,EAAE,GAAG,CAAC,CAAC;AAEvF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,gBAAgB,CAAC,OAAO,CAAC;SAC7B,KAAK,CAAC,gBAAgB,CAAC;SACvB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;SAChC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/escalationSeverity
|
|
3
|
+
*
|
|
4
|
+
* [[TUI-C26]] (spec §6) — **how severe the escalation is, said in words.**
|
|
5
|
+
*
|
|
6
|
+
* Every escalation used to look identical: the same yellow line, the same
|
|
7
|
+
* `⚠ Auto-rater (<outcome>):`, on both surfaces. So `npm install lodash` and a typosquatted
|
|
8
|
+
* `curl | bash` produced the same dialog, and a dialog that looks the same for everything trains
|
|
9
|
+
* the reader to answer it the same way — which costs more than having no dialog at all.
|
|
10
|
+
*
|
|
11
|
+
* ## Why the words carry it, not the colour
|
|
12
|
+
*
|
|
13
|
+
* Colour is not reliably available. `NO_COLOR` is set on plenty of machines, output gets piped, a
|
|
14
|
+
* monochrome or high-contrast terminal renders every tone identically, and a reader may simply not
|
|
15
|
+
* be looking at hue. So each outcome carries **three** independent signals — a glyph, a tone and a
|
|
16
|
+
* sentence — and the sentence is the one that always arrives. A change that makes two outcomes read
|
|
17
|
+
* the same in words has removed the signal even if the colours still differ.
|
|
18
|
+
*
|
|
19
|
+
* **What the sentence says is the consequence, not the severity word again.** `catastrophic` is
|
|
20
|
+
* defined by needing something from *outside* the session to undo (rescue media, a backup, a
|
|
21
|
+
* re-provision), and that — not the adjective — is what a person can act on.
|
|
22
|
+
*
|
|
23
|
+
* ## The map is total, and `attack` is in it
|
|
24
|
+
*
|
|
25
|
+
* A `Record<RaterOutcome, …>` rather than a lookup with a default: an outcome added to
|
|
26
|
+
* {@link RATER_OUTCOMES} must be given words here, and cannot silently inherit another outcome's.
|
|
27
|
+
*
|
|
28
|
+
* `attack` is present for that totality and **not because the approval prompt renders it**. An
|
|
29
|
+
* `attack` verdict halts the run (`AttackHaltError`) on both rating paths, so it never reaches an
|
|
30
|
+
* approval dialog: it is answered at [[TUI-C68]]'s §6.1 banner, whose copy is
|
|
31
|
+
* {@link attackBannerCopy} — here, beside the heading it reuses, so the two cannot come to describe
|
|
32
|
+
* one verdict two ways.
|
|
33
|
+
*
|
|
34
|
+
* `attack` shares the `danger` tone and glyph with `catastrophic` deliberately: the two are not
|
|
35
|
+
* ranked against each other (they answer different questions — *can this be undone?* versus *is
|
|
36
|
+
* something hostile acting here?*), so distinguishing them by loudness would assert an ordering the
|
|
37
|
+
* rater's own schema refuses. They are distinguished where it matters, in what they say.
|
|
38
|
+
*/
|
|
39
|
+
import type { RaterOutcome } from '#src/core/shell/rater.js';
|
|
40
|
+
/**
|
|
41
|
+
* How loud a surface should be about an outcome. A surface maps this to its own vocabulary — Ink
|
|
42
|
+
* colours on the TUI, the `display*` channel on the readline prompt — so neither has to hold its
|
|
43
|
+
* own opinion about which outcome is worse than which.
|
|
44
|
+
*/
|
|
45
|
+
export type EscalationTone = 'notice' | 'warn' | 'danger';
|
|
46
|
+
/** Everything a surface needs to render one outcome, so no surface invents its own wording. */
|
|
47
|
+
export interface RaterOutcomeDisplay {
|
|
48
|
+
/**
|
|
49
|
+
* The heading a surface paints above the rater's reason, glyph and sentence included.
|
|
50
|
+
*
|
|
51
|
+
* It keeps the `Auto-rater (<outcome>):` opening the dialog has always had — that is the
|
|
52
|
+
* attribution, and the reason underneath it is the rater's, not the gate's — and adds the
|
|
53
|
+
* consequence after it.
|
|
54
|
+
*/
|
|
55
|
+
heading: string;
|
|
56
|
+
/** The tone the heading and the framed reason are painted in. */
|
|
57
|
+
tone: EscalationTone;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The label above the framed reason.
|
|
61
|
+
*
|
|
62
|
+
* The heading now carries a sentence of the gate's own, so without this the model-authored prose
|
|
63
|
+
* beneath it could be read as a continuation of what the gate said. §6 wants the opposite: the
|
|
64
|
+
* reason is the rater's, quoted, and it is framed exactly like the command for the same reason.
|
|
65
|
+
*/
|
|
66
|
+
export declare const RATER_REASON_LABEL = " the rater's own words:";
|
|
67
|
+
/** How to render {@link RaterOutcome} on an approval dialog. */
|
|
68
|
+
export declare function describeRaterOutcome(outcome: RaterOutcome): RaterOutcomeDisplay;
|
|
69
|
+
/**
|
|
70
|
+
* [[TUI-C68]] §6.1 — **the phrase that runs an `attack`-rated command anyway.** The only string on
|
|
71
|
+
* the banner that is not a refusal.
|
|
72
|
+
*
|
|
73
|
+
* A typed phrase rather than a key, because an irreversible decision must not be reachable by the
|
|
74
|
+
* muscle memory built answering routine prompts. A key merely *disjoint from today's menu* stops
|
|
75
|
+
* being disjoint the moment someone binds one; a phrase is immune to any future binding. `run`
|
|
76
|
+
* alone was considered and rejected as the leading token of half of what anyone types into a
|
|
77
|
+
* terminal.
|
|
78
|
+
*
|
|
79
|
+
* The label is never `bypass`: that is a rung (§2.5) and a far broader thing — it turns off the
|
|
80
|
+
* rater, the escalation and the halt together, for every command, for the whole run — and a user
|
|
81
|
+
* must never read this banner as switching to it.
|
|
82
|
+
*/
|
|
83
|
+
export declare const RUN_ANYWAY_PHRASE = "run anyway";
|
|
84
|
+
/**
|
|
85
|
+
* §6.1 — does what the human typed grant this one command?
|
|
86
|
+
*
|
|
87
|
+
* **The whole matching rule, in one place both surfaces call**, so what the banner *says* is
|
|
88
|
+
* answerable and what a surface *accepts* cannot drift. Trimmed, lower-cased, compared whole:
|
|
89
|
+
* `RUN ANYWAY` and a phrase with surrounding spaces grant; `run`, `runanyway`, `run anyway please`
|
|
90
|
+
* and the phrase with a doubled inner space do not. No prefix, no initial, no `y`, no alias.
|
|
91
|
+
*
|
|
92
|
+
* **Everything that is not the phrase is a refusal, and that is the property the banner rests on.**
|
|
93
|
+
* An approval prompt grants only on an offered key and treats every other keystroke as a rejection;
|
|
94
|
+
* a text buffer inverts that by accumulating keystrokes instead of rejecting them, and a matcher
|
|
95
|
+
* this narrow is what puts it back — the buffer may hold anything at all, and only one value of it
|
|
96
|
+
* runs the command.
|
|
97
|
+
*
|
|
98
|
+
* Deliberately NOT whitespace-normalising the middle: a rule that repairs what the user typed is a
|
|
99
|
+
* rule that grants on something they did not type, and retyping a phrase costs nothing next to
|
|
100
|
+
* running this command by accident.
|
|
101
|
+
*/
|
|
102
|
+
export declare function grantsRunAnyway(typed: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* §6.1 — the banner's own words, shared so no surface invents its own.
|
|
105
|
+
*
|
|
106
|
+
* It carries what every surface must say and nothing about how any one of them is driven: the
|
|
107
|
+
* readline prompt reads a line in cooked mode and has no `q` or `Esc` to bind, so a keyboard line
|
|
108
|
+
* here would be false on one of the two surfaces. A surface adds its own keys beside these.
|
|
109
|
+
*/
|
|
110
|
+
export interface AttackBannerCopy {
|
|
111
|
+
/** The banner's title row — what happened, above everything else. */
|
|
112
|
+
title: string;
|
|
113
|
+
/** The `attack` heading from {@link describeRaterOutcome}, glyph and consequence included. */
|
|
114
|
+
heading: string;
|
|
115
|
+
/**
|
|
116
|
+
* **The irreversibility line, and it is UNCONDITIONAL** — on every attack banner, whatever the
|
|
117
|
+
* rating said, and not only where the command was also rated `catastrophic`.
|
|
118
|
+
*
|
|
119
|
+
* The banner is rare by construction (§4.1.1 forbids `attack` firing on ordinary work), so the
|
|
120
|
+
* line cannot become noise; and a static string cannot fail the way a model's explanation can. It
|
|
121
|
+
* is the last thing between a human and an action nothing else will stop.
|
|
122
|
+
*/
|
|
123
|
+
irreversible: string;
|
|
124
|
+
/** What each control does, in the phrase's own words. */
|
|
125
|
+
controls: readonly string[];
|
|
126
|
+
/** The label a surface puts in front of the buffer the human types into. */
|
|
127
|
+
prompt: string;
|
|
128
|
+
/**
|
|
129
|
+
* What a surface says once the phrase has been accepted.
|
|
130
|
+
*
|
|
131
|
+
* It states the scope of what was just granted, because that is the half a user cannot observe:
|
|
132
|
+
* the command running is visible, and *only this one running* is not. Shared so neither surface
|
|
133
|
+
* can promise a persistence the runner does not perform.
|
|
134
|
+
*/
|
|
135
|
+
granted: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* §6.1 — the banner copy. A function rather than an exported object so one surface cannot mutate
|
|
139
|
+
* the strings another is about to paint.
|
|
140
|
+
*/
|
|
141
|
+
export declare function attackBannerCopy(): AttackBannerCopy;
|