@gaunt-sloth/core 2.0.0-alpha.3 → 2.0.0-alpha.30
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,633 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/framing
|
|
3
|
+
*
|
|
4
|
+
* [[TUI-C26]] (spec §6) — **how untrusted text is made safe to LOOK AT on the approval dialog.**
|
|
5
|
+
*
|
|
6
|
+
* Two of the strings on that dialog are written by something other than the user: the command the
|
|
7
|
+
* agent proposed, and the auto-rater's `reason` for escalating it. Both are rendered to a terminal,
|
|
8
|
+
* where a string is not inert: a carriage return moves the cursor to column 0, an escape sequence
|
|
9
|
+
* moves it anywhere or clears the screen, and a newline alone is enough to lay down a line that
|
|
10
|
+
* looks exactly like the dialog's own chrome. A prompt whose menu can be forged by the text it is
|
|
11
|
+
* asking about is not a gate.
|
|
12
|
+
*
|
|
13
|
+
* ## Framing, not truncation
|
|
14
|
+
*
|
|
15
|
+
* The obvious mitigation — clamp the command to one line — is **wrong here, and it is wrong for a
|
|
16
|
+
* measured reason.** On 2026-07-30 a `git commit` destroyed a home subtree because a backticked
|
|
17
|
+
* `rm -rf` sat roughly fifteen lines into a twenty-five-line commit message, inside double quotes,
|
|
18
|
+
* where bash expands it. A single-line clamp throws away exactly the span the human is being asked
|
|
19
|
+
* to rule on while still looking like a complete prompt — and every model tested rated that command
|
|
20
|
+
* `safe`, so the dialog is the last line of defence. A clamp does not even buy immunity: newlines
|
|
21
|
+
* forge chrome with no escape sequences at all, one very long line wraps and pushes real chrome off
|
|
22
|
+
* the screen, and a trailing `approved by rater` fits on one line untouched.
|
|
23
|
+
*
|
|
24
|
+
* So nothing here discards a line to defeat impersonation. Four rules do it instead:
|
|
25
|
+
*
|
|
26
|
+
* 1. **Neutralise control characters and ANSI, unconditionally** ({@link neutralizeUntrustedText}).
|
|
27
|
+
* 2. **Every line renders inside a renderer-owned gutter carrying its line number**, so untrusted
|
|
28
|
+
* text cannot reach column 0 and an injected line cannot sit flush-left like real chrome. Long
|
|
29
|
+
* lines are wrapped *here*, with a gutter on every physical row, because a terminal's own wrap
|
|
30
|
+
* puts the continuation back at column 0.
|
|
31
|
+
* 3. **The decision-relevant sites are extracted and listed ABOVE the body**, with their line
|
|
32
|
+
* numbers ({@link findUnresolvableSites}) — every command substitution and every composition
|
|
33
|
+
* boundary. This is what makes the eye land on the payload instead of on line one of a prose
|
|
34
|
+
* paragraph, and it is the concrete form of *"show why the gate could not statically resolve the
|
|
35
|
+
* command, at the position where it could not"*.
|
|
36
|
+
* 4. **When a command is too long to show whole, elide AROUND the flagged sites** — never from the
|
|
37
|
+
* start — and state how many lines were hidden.
|
|
38
|
+
*
|
|
39
|
+
* ## Why the neutraliser matches characters and never sequences
|
|
40
|
+
*
|
|
41
|
+
* There is no ANSI-sequence parser here on purpose. An escape sequence cannot exist without its
|
|
42
|
+
* introducer: `ESC` (U+001B) or the one-byte `CSI` (U+009B). Escape every character in the control
|
|
43
|
+
* and format categories and every sequence dies with them, leaving `\x1b[2J` on the screen as five
|
|
44
|
+
* printable characters that say precisely what was in the string. A sequence-matching regex, by
|
|
45
|
+
* contrast, is a parser in the decision path — and a parser in a decision path is measured to leak:
|
|
46
|
+
* the same repo's shell-scanning experiment leaked six of twelve attacks with an escape-precise
|
|
47
|
+
* scanner and none with a rule too simple to have a blind spot.
|
|
48
|
+
*
|
|
49
|
+
* The same argument is why TAB is escaped rather than expanded. A tab's display width depends on
|
|
50
|
+
* the column it lands in, so a tab inside a budgeted line makes the width arithmetic — the thing the
|
|
51
|
+
* column-0 guarantee rests on — unanswerable. Escaping it removes the whole class of error, and
|
|
52
|
+
* costs only the appearance of an indent.
|
|
53
|
+
*
|
|
54
|
+
* ## What the column-0 guarantee promises, and what it assumes
|
|
55
|
+
*
|
|
56
|
+
* **Promise:** for any `width` of at least {@link MIN_FRAME_WIDTH}, every row this module returns
|
|
57
|
+
* fits the `width` it was given, so a terminal of that width never wraps one — and therefore no
|
|
58
|
+
* byte of untrusted text is ever drawn at column 0, where the dialog's own chrome lives.
|
|
59
|
+
*
|
|
60
|
+
* It rests on exactly three things, and each is held here rather than hoped for:
|
|
61
|
+
*
|
|
62
|
+
* 1. **The surface must paint the rows verbatim, one row per line, and never re-wrap them.** A
|
|
63
|
+
* second wrap re-creates the flush-left continuation this module exists to remove. Both surfaces
|
|
64
|
+
* do this deliberately (one Ink `<Text>` per row; one `display()` call per row).
|
|
65
|
+
* 2. **The surface must pass the real terminal width.** Rows are budgeted against what it passes,
|
|
66
|
+
* not against what the terminal turns out to be.
|
|
67
|
+
* 3. **The budget must not depend on how the terminal treats East-Asian *Ambiguous* characters.**
|
|
68
|
+
* That policy is a terminal setting this process cannot read: the gutter separators are
|
|
69
|
+
* Ambiguous, and so is much of what untrusted text can be made of. Every measurement and every
|
|
70
|
+
* cut here therefore goes through {@link maxDisplayWidth} / {@link sliceToMaxWidth}, which count
|
|
71
|
+
* Ambiguous as two columns — the widest any terminal renders them — so a row that fits fits under
|
|
72
|
+
* *either* policy. Measuring with the repo's default narrow policy would fit a row to one
|
|
73
|
+
* terminal and overrun another by up to its own length, and one column of overrun is a wrapped
|
|
74
|
+
* continuation carrying attacker-chosen bytes at column 0. The cost is under-fill on a
|
|
75
|
+
* narrow-rendering terminal, which is cosmetic.
|
|
76
|
+
*
|
|
77
|
+
* **Where it stops, and why the promise names a width:** a frame narrower than
|
|
78
|
+
* {@link MIN_FRAME_WIDTH} cannot carry the gutter and a readable command at once, so below that the
|
|
79
|
+
* renderer stops honouring the number it was given and keeps {@link MIN_CONTENT_WIDTH} columns of
|
|
80
|
+
* content instead — shrinking the command to nothing would be its own way of hiding a payload. Two
|
|
81
|
+
* consequences, and they are different things. A `width` argument smaller than the gutter plus that
|
|
82
|
+
* floor gets rows wider than the argument, because the floor wins — which is why the promise above
|
|
83
|
+
* is stated over arguments at or above {@link MIN_FRAME_WIDTH} rather than over every number. A
|
|
84
|
+
* *terminal* narrower than {@link MIN_FRAME_WIDTH} gets rows wider than itself and wraps them, and
|
|
85
|
+
* there the column-0 guarantee genuinely lapses. Both surfaces resolve their width through
|
|
86
|
+
* {@link frameWidthFor}, which never returns less than {@link MIN_FRAME_WIDTH}, so only the second
|
|
87
|
+
* case is reachable in production — and it is announced on screen rather than left to lapse
|
|
88
|
+
* silently: see {@link narrowTerminalNotice}.
|
|
89
|
+
*/
|
|
90
|
+
import { displayWidth, firstCluster, maxDisplayWidth, sliceToMaxWidth, } from '#src/utils/displayWidth.js';
|
|
91
|
+
/** Column count assumed when the surface cannot report one (not a TTY, a test, a pipe). */
|
|
92
|
+
export const DEFAULT_FRAME_WIDTH = 80;
|
|
93
|
+
/**
|
|
94
|
+
* Narrowest frame the gutter will produce, however small the terminal claims to be.
|
|
95
|
+
*
|
|
96
|
+
* Below it the gutter eats the row and the command becomes unreadable, which is its own way of
|
|
97
|
+
* hiding a payload — so a terminal narrower than this gets a frame it cannot fit rather than no
|
|
98
|
+
* frame at all. That trade costs the column-0 guarantee, and {@link narrowTerminalNotice} is what
|
|
99
|
+
* stops it costing it silently.
|
|
100
|
+
*/
|
|
101
|
+
export const MIN_FRAME_WIDTH = 20;
|
|
102
|
+
/**
|
|
103
|
+
* The width to frame at on a terminal reporting `columns`.
|
|
104
|
+
*
|
|
105
|
+
* One column is held back because a row that fills the last cell wraps on some terminals, and the
|
|
106
|
+
* result is floored at {@link MIN_FRAME_WIDTH}. Both surfaces resolve their width here so they
|
|
107
|
+
* cannot come to disagree about how much of a command a human was shown.
|
|
108
|
+
*/
|
|
109
|
+
export function frameWidthFor(columns) {
|
|
110
|
+
return Math.max(MIN_FRAME_WIDTH, usableColumns(columns) - 1);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The line a surface must paint when the terminal is too narrow for the frame to fit in it, or
|
|
114
|
+
* `undefined` when it fits.
|
|
115
|
+
*
|
|
116
|
+
* A guarantee that quietly stops holding is worse than one that says it stopped: below the floor
|
|
117
|
+
* {@link frameWidthFor} keeps the frame readable at the cost of rows wider than the terminal, the
|
|
118
|
+
* terminal wraps them, and a wrapped row puts untrusted text at column 0 — exactly what the gutter
|
|
119
|
+
* exists to prevent. The human is told rather than shown a dialog that still looks like it is
|
|
120
|
+
* guarding them.
|
|
121
|
+
*
|
|
122
|
+
* **Deliberately short, and only the consequence.** It is painted on a terminal of twenty columns
|
|
123
|
+
* or fewer, so it wraps to a few rows however it is worded and every word it does not need is
|
|
124
|
+
* another row of chrome between the reader and the command they are ruling on. It names what goes
|
|
125
|
+
* wrong rather than the width that caused it, because the width is not what the reader has to
|
|
126
|
+
* decide about; the reasoning belongs here, where it has room.
|
|
127
|
+
*/
|
|
128
|
+
export function narrowTerminalNotice(columns) {
|
|
129
|
+
if (usableColumns(columns) - 1 >= MIN_FRAME_WIDTH)
|
|
130
|
+
return undefined;
|
|
131
|
+
return '⚠ Terminal too narrow — text below can reach the left edge.';
|
|
132
|
+
}
|
|
133
|
+
/** What a surface reported, made usable: a positive whole number, or the default when unknown. */
|
|
134
|
+
function usableColumns(columns) {
|
|
135
|
+
return typeof columns === 'number' && Number.isFinite(columns)
|
|
136
|
+
? Math.max(1, Math.floor(columns))
|
|
137
|
+
: DEFAULT_FRAME_WIDTH;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Body lines shown before {@link frameUntrustedCommand} starts eliding.
|
|
141
|
+
*
|
|
142
|
+
* Sized so the extracted-site notices, the body and the menu are on screen **together** on a
|
|
143
|
+
* conventional 24-row terminal. That matters more than it looks: rule 3 puts the sites above the
|
|
144
|
+
* body specifically so the eye lands on them, and a body long enough to scroll them off the top
|
|
145
|
+
* defeats it. Bounding the body is safe here in a way the superseded single-line clamp was not,
|
|
146
|
+
* because what is elided is chosen *around the flagged sites* and the count of hidden lines is
|
|
147
|
+
* stated — the payload is what survives, not what is dropped.
|
|
148
|
+
*/
|
|
149
|
+
export const DEFAULT_FRAME_MAX_LINES = 20;
|
|
150
|
+
/**
|
|
151
|
+
* Rows either of the escalation menu's sticky blocks may occupy — what *approve for this session /
|
|
152
|
+
* always* would remember, and what *always reject* would record.
|
|
153
|
+
*
|
|
154
|
+
* Exported so both surfaces bound them identically: two prompts that disagree about how much of a
|
|
155
|
+
* grant a human was shown is the drift one shared renderer exists to prevent.
|
|
156
|
+
*
|
|
157
|
+
* Four, because that is enough for the ordinary case — a one-line command is one row, its JSON
|
|
158
|
+
* entry two — while keeping an eighteen-line command from printing itself twice more underneath
|
|
159
|
+
* its own frame and pushing the menu off the screen. See {@link clampRows} for the measurement.
|
|
160
|
+
*/
|
|
161
|
+
export const STICKY_PREVIEW_MAX_ROWS = 4;
|
|
162
|
+
/** Lines kept either side of a flagged site when the body is elided. */
|
|
163
|
+
const SITE_CONTEXT_LINES = 2;
|
|
164
|
+
/** Sites listed individually in the notices before the rest are summarised as a count. */
|
|
165
|
+
const MAX_LISTED_SITES = 8;
|
|
166
|
+
/** Columns an extracted site's excerpt may occupy on its notice line. */
|
|
167
|
+
const SITE_EXCERPT_WIDTH = 48;
|
|
168
|
+
/** Indent the whole framed block sits at, before the gutter. */
|
|
169
|
+
const FRAME_INDENT = ' ';
|
|
170
|
+
/**
|
|
171
|
+
* Separator between the line number and the untrusted content.
|
|
172
|
+
*
|
|
173
|
+
* This and {@link CONTINUATION_SEPARATOR} are East-Asian **Ambiguous**: one cell on most terminals,
|
|
174
|
+
* two where the terminal is configured for a CJK locale. Nothing here depends on which — every row
|
|
175
|
+
* is budgeted with the conservative ruler, so the glyphs may stay box-drawing without the guarantee
|
|
176
|
+
* resting on a terminal setting. Measure the prefixes rather than assuming their width if you
|
|
177
|
+
* change them.
|
|
178
|
+
*/
|
|
179
|
+
const GUTTER_SEPARATOR = '│';
|
|
180
|
+
/**
|
|
181
|
+
* Separator on the continuation rows of a line too wide for the terminal. Deliberately different
|
|
182
|
+
* from {@link GUTTER_SEPARATOR}: a wrapped row is not a new line of the command, and a reader who
|
|
183
|
+
* cannot tell the two apart cannot trust the line numbers.
|
|
184
|
+
*/
|
|
185
|
+
const CONTINUATION_SEPARATOR = '┊';
|
|
186
|
+
/** Separator on the renderer's own "lines hidden" rows. */
|
|
187
|
+
const ELISION_SEPARATOR = '⋯';
|
|
188
|
+
/**
|
|
189
|
+
* Smallest content width the gutter will leave, whatever the terminal claims.
|
|
190
|
+
*
|
|
191
|
+
* Exported alongside {@link wrapToWidth}, so the tests that pin the wrap's floor can state this
|
|
192
|
+
* term rather than repeat the number and quietly drift from it.
|
|
193
|
+
*/
|
|
194
|
+
export const MIN_CONTENT_WIDTH = 4;
|
|
195
|
+
/**
|
|
196
|
+
* Replace every control and format character with a visible, printable escape.
|
|
197
|
+
*
|
|
198
|
+
* Covers the C0 and C1 control ranges (`\p{Cc}` — which is NUL, BEL, BS, TAB, LF, CR, ESC, DEL and
|
|
199
|
+
* the 0x80–0x9F block including the one-byte CSI), the format characters (`\p{Cf}` — the bidi
|
|
200
|
+
* overrides that reorder what a reader sees, the zero-width joiners, the BOM), and the line and
|
|
201
|
+
* paragraph separators (`\p{Zl}`, `\p{Zp}`). Everything else is left exactly as written: this
|
|
202
|
+
* neutralises, it never sanitises, so the text a human rules on is the text that was proposed.
|
|
203
|
+
*
|
|
204
|
+
* The output alphabet is `\`, `x`, `u`, `{`, `}` and hex digits, none of which is a shell
|
|
205
|
+
* substitution or composition token — so escaping can neither create nor destroy a site that
|
|
206
|
+
* {@link findUnresolvableSites} would report.
|
|
207
|
+
*/
|
|
208
|
+
export function neutralizeUntrustedText(text) {
|
|
209
|
+
return text.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, escapeCodePoint);
|
|
210
|
+
}
|
|
211
|
+
/** One code point as the printable escape this module renders unprintable things with. */
|
|
212
|
+
function escapeCodePoint(character) {
|
|
213
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
214
|
+
if (codePoint <= 0xff)
|
|
215
|
+
return `\\x${codePoint.toString(16).padStart(2, '0')}`;
|
|
216
|
+
if (codePoint <= 0xffff)
|
|
217
|
+
return `\\u${codePoint.toString(16).padStart(4, '0')}`;
|
|
218
|
+
return `\\u{${codePoint.toString(16)}}`;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Every code point of `text` as {@link escapeCodePoint} renders it — the same escape alphabet the
|
|
222
|
+
* neutraliser uses, so what comes out is ASCII, one column per character under any width policy,
|
|
223
|
+
* and still incapable of forming a substitution or composition token.
|
|
224
|
+
*/
|
|
225
|
+
function escapeCodePoints(text) {
|
|
226
|
+
return [...text].map(escapeCodePoint).join('');
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Collapse untrusted text to a single safe line, for the line-structured blocks that carry their
|
|
230
|
+
* meaning in their line breaks (the §5 negotiation transcript, the rating prompt's own transcript).
|
|
231
|
+
*
|
|
232
|
+
* Neutralisation runs **first**: JavaScript's `\s` covers LF, CR and TAB but not ESC, BEL, NUL, the
|
|
233
|
+
* C1 range or the bidi overrides, so collapsing whitespace alone leaves a screen-clearing sequence
|
|
234
|
+
* intact on a line that merely looks tidy.
|
|
235
|
+
*/
|
|
236
|
+
export function neutralizeToOneLine(text) {
|
|
237
|
+
return neutralizeUntrustedText(text).replace(/\s+/gu, ' ').trim();
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Split untrusted text into the logical lines the gutter will number.
|
|
241
|
+
*
|
|
242
|
+
* `\r\n` is one break, because that is what a Windows-authored command means by it. A **lone** `\r`
|
|
243
|
+
* is not treated as a break — it is left for {@link neutralizeUntrustedText} to make visible, since
|
|
244
|
+
* a bare carriage return in a command is the cursor-to-column-0 overwrite trick far more often than
|
|
245
|
+
* it is a line ending, and turning it into a break would hide that it was ever there.
|
|
246
|
+
*
|
|
247
|
+
* Exactly one trailing break is dropped: nearly every real multi-line command ends with one, and
|
|
248
|
+
* numbering the empty line after it reads as a bug. A second trailing break is a genuinely empty
|
|
249
|
+
* last line and is numbered like any other.
|
|
250
|
+
*/
|
|
251
|
+
function splitLogicalLines(text) {
|
|
252
|
+
const lines = text.replace(/\r\n/gu, '\n').split('\n');
|
|
253
|
+
if (lines.length > 1 && lines[lines.length - 1] === '')
|
|
254
|
+
lines.pop();
|
|
255
|
+
return lines;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Every position in `command` at which the gate could not statically resolve it: each command
|
|
259
|
+
* substitution (`$(` and a backtick) and each composition boundary (`;`, `&&`, `||`, `|`).
|
|
260
|
+
*
|
|
261
|
+
* **Deliberately not quote-aware.** A shell-accurate scanner would skip a `$(` inside single quotes
|
|
262
|
+
* — and the command that motivated this whole surface hid its payload in backticks inside *double*
|
|
263
|
+
* quotes, where the shell expands it. Over-reporting costs a line of notice; under-reporting is the
|
|
264
|
+
* incident. The scan is the same order of simplicity as the ambiguity preflight that actually caught
|
|
265
|
+
* that command, and for the same reason.
|
|
266
|
+
*
|
|
267
|
+
* Scanned over the neutralised lines so the reported line and column are the ones the reader can
|
|
268
|
+
* count to on the screen.
|
|
269
|
+
*/
|
|
270
|
+
export function findUnresolvableSites(command) {
|
|
271
|
+
const sites = [];
|
|
272
|
+
const lines = splitLogicalLines(command).map(neutralizeUntrustedText);
|
|
273
|
+
lines.forEach((line, index) => {
|
|
274
|
+
let cursor = 0;
|
|
275
|
+
while (cursor < line.length) {
|
|
276
|
+
const token = tokenAt(line, cursor);
|
|
277
|
+
if (token === undefined) {
|
|
278
|
+
cursor += 1;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
sites.push({
|
|
282
|
+
line: index + 1,
|
|
283
|
+
// A position for a human to count to, not a budget — so it is measured with the repo's
|
|
284
|
+
// ordinary ruler rather than the conservative one. No row's width depends on it, and
|
|
285
|
+
// reporting a column the reader cannot find would be the only thing over-counting bought.
|
|
286
|
+
column: displayWidth(line.slice(0, cursor)) + 1,
|
|
287
|
+
kind: token === '$(' || token === '`' ? 'command substitution' : 'composition',
|
|
288
|
+
token,
|
|
289
|
+
excerpt: clipToWidth(line.slice(cursor), SITE_EXCERPT_WIDTH),
|
|
290
|
+
});
|
|
291
|
+
cursor += token.length;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
return sites;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The token starting at `cursor`, or `undefined`. Two-character tokens are tested first so `||` is
|
|
298
|
+
* one composition boundary rather than two pipes.
|
|
299
|
+
*/
|
|
300
|
+
function tokenAt(line, cursor) {
|
|
301
|
+
const pair = line.slice(cursor, cursor + 2);
|
|
302
|
+
if (pair === '$(' || pair === '&&' || pair === '||')
|
|
303
|
+
return pair;
|
|
304
|
+
const single = line[cursor];
|
|
305
|
+
if (single === '`' || single === ';' || single === '|')
|
|
306
|
+
return single;
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
/** Marks a clip, and is itself Ambiguous — so its own column count has to be reserved, not assumed. */
|
|
310
|
+
const CLIP_MARKER = '…';
|
|
311
|
+
/**
|
|
312
|
+
* `text` clipped to `width` columns, with {@link CLIP_MARKER} when anything was dropped.
|
|
313
|
+
*
|
|
314
|
+
* The marker's width is reserved from the budget rather than counted as one column: it is Ambiguous
|
|
315
|
+
* like the separators, so a budget of `width - 1` would produce a row of `width + 1` on a terminal
|
|
316
|
+
* that renders it wide — a clip that overruns the very width it was asked to fit.
|
|
317
|
+
*/
|
|
318
|
+
function clipToWidth(text, width) {
|
|
319
|
+
if (maxDisplayWidth(text) <= width)
|
|
320
|
+
return text;
|
|
321
|
+
const markerWidth = maxDisplayWidth(CLIP_MARKER);
|
|
322
|
+
if (width < markerWidth)
|
|
323
|
+
return sliceToMaxWidth(text, width);
|
|
324
|
+
return `${sliceToMaxWidth(text, width - markerWidth)}${CLIP_MARKER}`;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Frame the command the agent proposed: neutralised, gutter-numbered, wrapped to the terminal, its
|
|
328
|
+
* unresolvable sites listed above it, and elided around those sites if it is too long to show whole.
|
|
329
|
+
*/
|
|
330
|
+
export function frameUntrustedCommand(command, options) {
|
|
331
|
+
const sites = findUnresolvableSites(command);
|
|
332
|
+
return frame(command, sites, options);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Frame a block of untrusted prose — the auto-rater's `reason`, or what a sticky choice will store.
|
|
336
|
+
*
|
|
337
|
+
* The same gutter and the same neutralisation as a command, and for the same reason: the rater's
|
|
338
|
+
* explanation is model-authored text on a dialog whose chrome it must not be able to forge. Site
|
|
339
|
+
* extraction does not run, because prose has no composition boundaries to point at; a block with no
|
|
340
|
+
* sites that overruns {@link FrameOptions.maxLines} keeps its head and states what it dropped.
|
|
341
|
+
*/
|
|
342
|
+
export function frameUntrustedText(text, options) {
|
|
343
|
+
return frame(text, [], options);
|
|
344
|
+
}
|
|
345
|
+
function frame(text, sites, options) {
|
|
346
|
+
const width = Math.max(1, Math.floor(options?.width ?? DEFAULT_FRAME_WIDTH));
|
|
347
|
+
const maxLines = Math.max(1, Math.floor(options?.maxLines ?? DEFAULT_FRAME_MAX_LINES));
|
|
348
|
+
const logical = splitLogicalLines(text).map(neutralizeUntrustedText);
|
|
349
|
+
const numberWidth = String(logical.length).length;
|
|
350
|
+
// MEASURED, never counted. The separators are Ambiguous, so their column count is a terminal
|
|
351
|
+
// setting rather than a constant, and a hardcoded arithmetic gutter is right on one terminal and
|
|
352
|
+
// one column short on another — which is a wrapped row, which is untrusted text at column 0. The
|
|
353
|
+
// widest of the three prefixes is used for all of them so the budget is right for every row shape
|
|
354
|
+
// this renderer can emit, and so changing a glyph cannot silently re-open the gap.
|
|
355
|
+
const gutterWidth = Math.max(maxDisplayWidth(bodyPrefix('9'.repeat(numberWidth), numberWidth)), maxDisplayWidth(continuationPrefix(numberWidth)), maxDisplayWidth(elisionPrefix(numberWidth)));
|
|
356
|
+
const contentWidth = Math.max(MIN_CONTENT_WIDTH, width - gutterWidth);
|
|
357
|
+
const { windows, sitesHidden } = selectWindows(logical.length, sites.map((site) => site.line), maxLines);
|
|
358
|
+
const lines = [];
|
|
359
|
+
let previousEnd = 0;
|
|
360
|
+
for (const window of windows) {
|
|
361
|
+
if (window.start > previousEnd + 1) {
|
|
362
|
+
lines.push(elisionRow(window.start - previousEnd - 1, numberWidth, width));
|
|
363
|
+
}
|
|
364
|
+
for (let number = window.start; number <= window.end; number++) {
|
|
365
|
+
const rows = wrapToWidth(logical[number - 1], contentWidth);
|
|
366
|
+
rows.forEach((row, rowIndex) => {
|
|
367
|
+
lines.push(rowIndex === 0
|
|
368
|
+
? `${bodyPrefix(String(number), numberWidth)}${row}`
|
|
369
|
+
: `${continuationPrefix(numberWidth)}${row}`);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
previousEnd = window.end;
|
|
373
|
+
}
|
|
374
|
+
if (previousEnd < logical.length) {
|
|
375
|
+
lines.push(elisionRow(logical.length - previousEnd, numberWidth, width));
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
notices: buildNotices(sites, sitesHidden, width),
|
|
379
|
+
lines: clampRows(lines, options?.maxRows, numberWidth, width),
|
|
380
|
+
sites,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Rows a block gets when the caller bounds it with {@link FrameOptions.maxRows}, with the last one
|
|
385
|
+
* spent saying how many were dropped.
|
|
386
|
+
*
|
|
387
|
+
* **MEASURED, not tidiness.** The escalation menu's two sticky blocks say what each choice would
|
|
388
|
+
* store, and for a shell call the stored thing is the command as typed — so on an eighteen-line
|
|
389
|
+
* command the dialog printed the whole command in its frame, then again under
|
|
390
|
+
* *will remember* / *will refuse*, then a third time inside the JSON entry, and the MENU ITSELF
|
|
391
|
+
* ended up below the bottom of a fifty-row terminal. A block that pushes the controls off the
|
|
392
|
+
* screen has defeated the requirement it was added to satisfy: §6 wants the human to see what a
|
|
393
|
+
* choice stores *while they are making it*.
|
|
394
|
+
*
|
|
395
|
+
* The head is what survives, and that is right here where it is wrong for the command body: the
|
|
396
|
+
* command is on screen above in full, flagged sites and all, so this block answers *"which command
|
|
397
|
+
* is this about"* rather than *"what does this command do"*.
|
|
398
|
+
*/
|
|
399
|
+
function clampRows(rows, maxRows, numberWidth, width) {
|
|
400
|
+
if (maxRows === undefined || rows.length <= Math.max(1, Math.floor(maxRows)))
|
|
401
|
+
return [...rows];
|
|
402
|
+
const budget = Math.max(1, Math.floor(maxRows));
|
|
403
|
+
const kept = rows.slice(0, budget - 1);
|
|
404
|
+
const hidden = rows.length - kept.length;
|
|
405
|
+
kept.push(clipToWidth(`${elisionPrefix(numberWidth)}${hidden} more ${hidden === 1 ? 'row' : 'rows'} hidden`, width));
|
|
406
|
+
return kept;
|
|
407
|
+
}
|
|
408
|
+
/** The prefix a numbered body row carries, before the untrusted content. */
|
|
409
|
+
function bodyPrefix(number, numberWidth) {
|
|
410
|
+
return `${FRAME_INDENT}${number.padStart(numberWidth)} ${GUTTER_SEPARATOR} `;
|
|
411
|
+
}
|
|
412
|
+
/** The prefix on the continuation rows of a line this renderer wrapped. */
|
|
413
|
+
function continuationPrefix(numberWidth) {
|
|
414
|
+
return `${FRAME_INDENT}${' '.repeat(numberWidth)} ${CONTINUATION_SEPARATOR} `;
|
|
415
|
+
}
|
|
416
|
+
/** The prefix on the renderer's own "lines hidden" rows. */
|
|
417
|
+
function elisionPrefix(numberWidth) {
|
|
418
|
+
return `${FRAME_INDENT}${' '.repeat(numberWidth)} ${ELISION_SEPARATOR} `;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* A renderer-owned row standing in for the lines the elision dropped, stating how many.
|
|
422
|
+
*
|
|
423
|
+
* **Clipped, where the notices' prose wraps**, and the difference is where the row lives rather
|
|
424
|
+
* than who wrote it: this one sits in `lines`, where every row carries a gutter, so a continuation
|
|
425
|
+
* of it would need a gutter of its own and would read as a line of the command. It is short enough
|
|
426
|
+
* that clipping costs nothing, and it is bounded at all because a renderer that bounds the rows it
|
|
427
|
+
* does not own and not the ones it does is bounded by accident.
|
|
428
|
+
*/
|
|
429
|
+
function elisionRow(hidden, numberWidth, width) {
|
|
430
|
+
return clipToWidth(`${elisionPrefix(numberWidth)}${hidden} ${hidden === 1 ? 'line' : 'lines'} hidden`, width);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Which line ranges survive the {@link FrameOptions.maxLines} budget, and how many flagged sites
|
|
434
|
+
* they leave hidden.
|
|
435
|
+
*
|
|
436
|
+
* **Never from the start.** With sites to point at, the budget buys windows around *them*, earliest
|
|
437
|
+
* first, so the span the human is being asked to rule on is the span that survives — the opposite of
|
|
438
|
+
* the head-clamp that let a payload fifteen lines in disappear. Only text with no flagged site at
|
|
439
|
+
* all keeps its head, because there is then nothing to prefer.
|
|
440
|
+
*
|
|
441
|
+
* A window that will not fit stops the loop rather than being skipped: showing a later site while
|
|
442
|
+
* silently dropping an earlier one would renumber the reader's sense of what is on screen. What is
|
|
443
|
+
* dropped is counted and said out loud in the notices instead.
|
|
444
|
+
*/
|
|
445
|
+
function selectWindows(totalLines, siteLines, maxLines) {
|
|
446
|
+
if (totalLines <= maxLines)
|
|
447
|
+
return { windows: [{ start: 1, end: totalLines }], sitesHidden: 0 };
|
|
448
|
+
if (siteLines.length === 0)
|
|
449
|
+
return { windows: [{ start: 1, end: maxLines }], sitesHidden: 0 };
|
|
450
|
+
const merged = [];
|
|
451
|
+
for (const line of [...new Set(siteLines)].sort((a, b) => a - b)) {
|
|
452
|
+
const start = Math.max(1, line - SITE_CONTEXT_LINES);
|
|
453
|
+
const end = Math.min(totalLines, line + SITE_CONTEXT_LINES);
|
|
454
|
+
const last = merged[merged.length - 1];
|
|
455
|
+
if (last && start <= last.end + 1)
|
|
456
|
+
last.end = Math.max(last.end, end);
|
|
457
|
+
else
|
|
458
|
+
merged.push({ start, end });
|
|
459
|
+
}
|
|
460
|
+
const windows = [];
|
|
461
|
+
let used = 0;
|
|
462
|
+
for (const window of merged) {
|
|
463
|
+
const size = window.end - window.start + 1;
|
|
464
|
+
if (used + size > maxLines)
|
|
465
|
+
break;
|
|
466
|
+
windows.push(window);
|
|
467
|
+
used += size;
|
|
468
|
+
}
|
|
469
|
+
// The first window alone can exceed the budget when many sites cluster. Keep it, clipped from the
|
|
470
|
+
// first site forward, so a flagged line is never traded for a context line.
|
|
471
|
+
if (windows.length === 0) {
|
|
472
|
+
const first = Math.min(...siteLines);
|
|
473
|
+
windows.push({ start: first, end: Math.min(totalLines, first + maxLines - 1) });
|
|
474
|
+
}
|
|
475
|
+
const shown = (line) => windows.some((window) => line >= window.start && line <= window.end);
|
|
476
|
+
return { windows, sitesHidden: siteLines.filter((line) => !shown(line)).length };
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Wrap ONE row of the renderer's own prose across as many rows as it needs, each within `width`.
|
|
480
|
+
*
|
|
481
|
+
* **Wrapped, not clipped, and the distinction is the point.** Every row here is bounded, but the
|
|
482
|
+
* two kinds of row are bounded for different reasons and so they are bounded differently. A row
|
|
483
|
+
* carrying untrusted text is *clipped*: a continuation of it would be attacker-chosen bytes at a
|
|
484
|
+
* column the reader reads as the renderer's, so dropping the tail is the safe answer. This
|
|
485
|
+
* renderer's own sentences cannot be forged by anything, so the safe answer there is to say the
|
|
486
|
+
* whole sentence — and it matters that it does: the notice header is what tells the reader what the
|
|
487
|
+
* numbered sites mean, it is ninety columns long, and on a standard eighty-column terminal clipping
|
|
488
|
+
* truncated it exactly where it explains itself.
|
|
489
|
+
*
|
|
490
|
+
* **Applied per row, at the point each is built, never over `notices[]`.** That array holds this
|
|
491
|
+
* prose *and* the site rows carrying the untrusted excerpt — the one place untrusted text renders
|
|
492
|
+
* outside the gutter — so a wrap applied to the array as a whole would put excerpt bytes at column
|
|
493
|
+
* 0 and re-open exactly what the gutter closed.
|
|
494
|
+
*
|
|
495
|
+
* Measured and cut with the conservative ruler like everything else here, so a wrapped row fits
|
|
496
|
+
* under either ambiguous-width policy. The budget is taken from the widest of the leading indent
|
|
497
|
+
* and the hanging indent, so a row fits whichever of the two it carries — an under-fill of a column
|
|
498
|
+
* or two on prose, against arithmetic that cannot be right for one row shape and wrong for another.
|
|
499
|
+
*/
|
|
500
|
+
function wrapProse(text, width, hangingIndent) {
|
|
501
|
+
const leading = /^ */u.exec(text)?.[0] ?? '';
|
|
502
|
+
const budget = Math.max(MIN_CONTENT_WIDTH, width - Math.max(maxDisplayWidth(leading), maxDisplayWidth(hangingIndent)));
|
|
503
|
+
const rows = [];
|
|
504
|
+
let prefix = leading;
|
|
505
|
+
let line = '';
|
|
506
|
+
const push = () => {
|
|
507
|
+
rows.push(`${prefix}${line}`);
|
|
508
|
+
prefix = hangingIndent;
|
|
509
|
+
line = '';
|
|
510
|
+
};
|
|
511
|
+
for (const word of text.slice(leading.length).split(/\s+/u)) {
|
|
512
|
+
if (word.length === 0)
|
|
513
|
+
continue;
|
|
514
|
+
const candidate = line === '' ? word : `${line} ${word}`;
|
|
515
|
+
if (maxDisplayWidth(candidate) <= budget) {
|
|
516
|
+
line = candidate;
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (line !== '')
|
|
520
|
+
push();
|
|
521
|
+
if (maxDisplayWidth(word) <= budget) {
|
|
522
|
+
line = word;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
// A single word wider than the budget — a long path, or a run of wide characters. It is broken
|
|
526
|
+
// by the same wrap the body uses rather than left to overrun: this row is bounded whatever it
|
|
527
|
+
// is made of, which is the property the next row added here inherits.
|
|
528
|
+
const chunks = wrapToWidth(word, budget);
|
|
529
|
+
for (const chunk of chunks.slice(0, -1)) {
|
|
530
|
+
line = chunk;
|
|
531
|
+
push();
|
|
532
|
+
}
|
|
533
|
+
line = chunks[chunks.length - 1];
|
|
534
|
+
}
|
|
535
|
+
if (line !== '' || rows.length === 0)
|
|
536
|
+
push();
|
|
537
|
+
return rows;
|
|
538
|
+
}
|
|
539
|
+
/** The lines painted above the body: what was flagged, where, and what the elision hid. */
|
|
540
|
+
function buildNotices(sites, sitesHidden, width) {
|
|
541
|
+
if (sites.length === 0)
|
|
542
|
+
return [];
|
|
543
|
+
const plural = sites.length === 1 ? 'site' : 'sites';
|
|
544
|
+
// WRAPPED, not clipped — this is the renderer's own sentence, it is the one that explains what
|
|
545
|
+
// the numbers below mean, and at the commonest terminal width a clip removed the explanation.
|
|
546
|
+
const notices = wrapProse(`⚠ ${sites.length} ${plural} the gate could not statically resolve — ` +
|
|
547
|
+
`${sites.length === 1 ? 'it is' : 'they are'} numbered in the command below:`, width, ' ');
|
|
548
|
+
for (const site of sites.slice(0, MAX_LISTED_SITES)) {
|
|
549
|
+
const label = ` line ${site.line} · ${site.kind} ${site.token} · `;
|
|
550
|
+
// **CLIPPED, never wrapped** — this is the one row in this array that carries untrusted text,
|
|
551
|
+
// and it sits in the same array as the prose above and below it. Wrapping it would hand a
|
|
552
|
+
// continuation row to the excerpt, and that row's leading columns are the renderer's by
|
|
553
|
+
// convention only: they are exactly where a reader expects the gate's own words. The prose
|
|
554
|
+
// wraps because nothing can forge it; this clips because something can.
|
|
555
|
+
//
|
|
556
|
+
// Clipped TWICE, and the second clip is not belt-and-braces. The excerpt budget has a floor, so
|
|
557
|
+
// on a narrow terminal the floor can win and hand back a row wider than the terminal — which
|
|
558
|
+
// Ink then wraps, starting the continuation at column 0 with attacker-chosen excerpt bytes on
|
|
559
|
+
// it. The body rows cannot reach that state (their gutter leaves content width above its own
|
|
560
|
+
// minimum at every width a surface will pass), but this row can, and it is the one line here
|
|
561
|
+
// that carries untrusted text outside the gutter. The whole row is bounded by the terminal.
|
|
562
|
+
notices.push(clipToWidth(`${label}${clipToWidth(site.excerpt, Math.max(8, width - maxDisplayWidth(label)))}`, width));
|
|
563
|
+
}
|
|
564
|
+
// Renderer-owned prose, like the header: wrapped, so a narrow terminal loses none of it.
|
|
565
|
+
if (sites.length > MAX_LISTED_SITES) {
|
|
566
|
+
const rest = sites.length - MAX_LISTED_SITES;
|
|
567
|
+
notices.push(...wrapProse(` ${CLIP_MARKER} and ${rest} further flagged ${rest === 1 ? 'site' : 'sites'}, ` +
|
|
568
|
+
`up to line ${sites[sites.length - 1].line}.`, width, ' '));
|
|
569
|
+
}
|
|
570
|
+
if (sitesHidden > 0) {
|
|
571
|
+
notices.push(...wrapProse(` ⚠ ${sitesHidden} flagged ${sitesHidden === 1 ? 'site sits' : 'sites sit'} on ` +
|
|
572
|
+
'lines the elision below hid — this command is too long to show in full.', width, ' '));
|
|
573
|
+
}
|
|
574
|
+
return notices;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Split one logical line into terminal rows of at most `width` columns.
|
|
578
|
+
*
|
|
579
|
+
* The wrap belongs here rather than to the terminal because a terminal's own wrap starts the
|
|
580
|
+
* continuation at column 0, which is the forgery this module exists to prevent: a single line long
|
|
581
|
+
* enough to wrap can otherwise lay attacker-chosen bytes flush-left, gutter and all bypassed.
|
|
582
|
+
*
|
|
583
|
+
* Measured and cut with the display-width helpers, never `.length` — a CJK ideograph or an emoji is
|
|
584
|
+
* one code point in two columns, and a row measured as fitting that does not fit is a row the
|
|
585
|
+
* terminal wraps back to column 0. Conservatively, so the same holds on a terminal that renders
|
|
586
|
+
* Ambiguous characters wide.
|
|
587
|
+
*
|
|
588
|
+
* The budget is floored at {@link MIN_CONTENT_WIDTH} here rather than taken on trust. The
|
|
589
|
+
* over-wide-cluster branch below escapes a cluster it cannot draw and re-measures it, which
|
|
590
|
+
* advances only if the budget can hold at least one escape character; at a budget of zero it
|
|
591
|
+
* re-escapes its own backslashes forever and the row never terminates. {@link frame} does pass a
|
|
592
|
+
* floored width, but a loop that terminates because of what its only caller happens to do is one
|
|
593
|
+
* the next caller silently breaks.
|
|
594
|
+
*
|
|
595
|
+
* Exported for tests, which pin that termination at a degenerate width — the one width the public
|
|
596
|
+
* entry points cannot produce.
|
|
597
|
+
*/
|
|
598
|
+
export function wrapToWidth(text, width) {
|
|
599
|
+
const budget = Math.max(MIN_CONTENT_WIDTH, width);
|
|
600
|
+
if (maxDisplayWidth(text) <= budget)
|
|
601
|
+
return [text];
|
|
602
|
+
const rows = [];
|
|
603
|
+
let rest = text;
|
|
604
|
+
while (rest.length > 0) {
|
|
605
|
+
const head = sliceToMaxWidth(rest, budget);
|
|
606
|
+
if (head.length === 0) {
|
|
607
|
+
// A grapheme cluster wider than the entire content budget cannot be drawn inside the gutter
|
|
608
|
+
// at all, so it is shown the way this module shows anything else it cannot render safely:
|
|
609
|
+
// as the printable escapes of its code points. Emitting the cluster raw would leave one row
|
|
610
|
+
// unbounded — the terminal would wrap it, and the continuation would carry untrusted bytes at
|
|
611
|
+
// column 0, which is the single thing this function exists to prevent.
|
|
612
|
+
//
|
|
613
|
+
// A LIVE path, not a dead guard. A grapheme cluster has no bounded column count: display
|
|
614
|
+
// width sums additively across a run of Hangul leading jamo, and across the trailing spacing
|
|
615
|
+
// marks of an Indic or halfwidth-kana cluster, so one cluster can measure arbitrarily many
|
|
616
|
+
// columns. None of those code points is a control or format character, so the neutraliser
|
|
617
|
+
// does not touch them and they arrive here whole — forty leading jamo are a single
|
|
618
|
+
// eighty-column cluster, which overruns the content budget of a default eighty-column
|
|
619
|
+
// terminal. Deleting this branch does not remove a dead case; it restores the unbounded row.
|
|
620
|
+
//
|
|
621
|
+
// It terminates: the escapes are ASCII, one column each under every policy, and the budget
|
|
622
|
+
// is floored at MIN_CONTENT_WIDTH above, so the next pass slices a non-empty head and the
|
|
623
|
+
// loop strictly advances.
|
|
624
|
+
const cluster = firstCluster(rest);
|
|
625
|
+
rest = `${escapeCodePoints(cluster)}${rest.slice(cluster.length)}`;
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
rows.push(head);
|
|
629
|
+
rest = rest.slice(head.length);
|
|
630
|
+
}
|
|
631
|
+
return rows;
|
|
632
|
+
}
|
|
633
|
+
//# sourceMappingURL=framing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framing.js","sourceRoot":"","sources":["../../../src/core/shell/framing.ts"],"names":[],"mappings":"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"}
|