@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.
Files changed (271) hide show
  1. package/README.md +52 -20
  2. package/dist/config/colour.d.ts +38 -0
  3. package/dist/config/colour.js +36 -0
  4. package/dist/config/colour.js.map +1 -0
  5. package/dist/config/defaults.d.ts +84 -0
  6. package/dist/config/defaults.js +99 -0
  7. package/dist/config/defaults.js.map +1 -0
  8. package/dist/config/filesystem-tools.d.ts +41 -0
  9. package/dist/config/filesystem-tools.js +56 -0
  10. package/dist/config/filesystem-tools.js.map +1 -0
  11. package/dist/config/jsonc.d.ts +12 -0
  12. package/dist/config/jsonc.js +41 -0
  13. package/dist/config/jsonc.js.map +1 -0
  14. package/dist/config/loader.d.ts +246 -0
  15. package/dist/config/loader.js +1321 -0
  16. package/dist/config/loader.js.map +1 -0
  17. package/dist/config/mouse.d.ts +50 -0
  18. package/dist/config/mouse.js +44 -0
  19. package/dist/config/mouse.js.map +1 -0
  20. package/dist/config/profiles.d.ts +68 -0
  21. package/dist/config/profiles.js +93 -0
  22. package/dist/config/profiles.js.map +1 -0
  23. package/dist/config/providerKeys.d.ts +69 -0
  24. package/dist/config/providerKeys.js +69 -0
  25. package/dist/config/providerKeys.js.map +1 -0
  26. package/dist/config/schema.d.ts +2999 -0
  27. package/dist/config/schema.js +1519 -0
  28. package/dist/config/schema.js.map +1 -0
  29. package/dist/config/shell-policy.d.ts +896 -0
  30. package/dist/config/shell-policy.js +750 -0
  31. package/dist/config/shell-policy.js.map +1 -0
  32. package/dist/config/tool-descriptions.d.ts +211 -0
  33. package/dist/config/tool-descriptions.js +272 -0
  34. package/dist/config/tool-descriptions.js.map +1 -0
  35. package/dist/config/types.d.ts +760 -0
  36. package/dist/config/types.js +13 -0
  37. package/dist/config/types.js.map +1 -0
  38. package/dist/config.d.ts +34 -827
  39. package/dist/config.js +25 -657
  40. package/dist/config.js.map +1 -1
  41. package/dist/constants.d.ts +46 -0
  42. package/dist/constants.js +46 -0
  43. package/dist/constants.js.map +1 -1
  44. package/dist/core/GthAbstractAgent.d.ts +135 -2
  45. package/dist/core/GthAbstractAgent.js +496 -30
  46. package/dist/core/GthAbstractAgent.js.map +1 -1
  47. package/dist/core/GthAgentRunner.d.ts +568 -54
  48. package/dist/core/GthAgentRunner.js +1537 -136
  49. package/dist/core/GthAgentRunner.js.map +1 -1
  50. package/dist/core/GthLangChainAgent.d.ts +115 -0
  51. package/dist/core/GthLangChainAgent.js +644 -17
  52. package/dist/core/GthLangChainAgent.js.map +1 -1
  53. package/dist/core/approvals/annotations.d.ts +122 -0
  54. package/dist/core/approvals/annotations.js +137 -0
  55. package/dist/core/approvals/annotations.js.map +1 -0
  56. package/dist/core/approvals/grants.d.ts +216 -0
  57. package/dist/core/approvals/grants.js +469 -0
  58. package/dist/core/approvals/grants.js.map +1 -0
  59. package/dist/core/approvals/matcher.d.ts +202 -0
  60. package/dist/core/approvals/matcher.js +267 -0
  61. package/dist/core/approvals/matcher.js.map +1 -0
  62. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  63. package/dist/core/approvals/mcpSubjects.js +99 -0
  64. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  65. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  66. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  67. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  68. package/dist/core/approvals/toolHost.d.ts +46 -0
  69. package/dist/core/approvals/toolHost.js +108 -0
  70. package/dist/core/approvals/toolHost.js.map +1 -0
  71. package/dist/core/debugCapture.d.ts +75 -0
  72. package/dist/core/debugCapture.js +101 -0
  73. package/dist/core/debugCapture.js.map +1 -0
  74. package/dist/core/gthLeanAgentFactory.d.ts +9 -0
  75. package/dist/core/gthLeanAgentFactory.js +10 -0
  76. package/dist/core/gthLeanAgentFactory.js.map +1 -0
  77. package/dist/core/launchBanner.d.ts +120 -0
  78. package/dist/core/launchBanner.js +418 -0
  79. package/dist/core/launchBanner.js.map +1 -0
  80. package/dist/core/plainToolIndication.d.ts +15 -0
  81. package/dist/core/plainToolIndication.js +174 -0
  82. package/dist/core/plainToolIndication.js.map +1 -0
  83. package/dist/core/reasoningBlocks.d.ts +60 -0
  84. package/dist/core/reasoningBlocks.js +98 -0
  85. package/dist/core/reasoningBlocks.js.map +1 -0
  86. package/dist/core/refusal.d.ts +53 -0
  87. package/dist/core/refusal.js +133 -0
  88. package/dist/core/refusal.js.map +1 -0
  89. package/dist/core/runStats.d.ts +52 -0
  90. package/dist/core/runStats.js +118 -0
  91. package/dist/core/runStats.js.map +1 -0
  92. package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
  93. package/dist/core/shell/ShellCommandFailedError.js +68 -0
  94. package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
  95. package/dist/core/shell/abstention.d.ts +88 -0
  96. package/dist/core/shell/abstention.js +184 -0
  97. package/dist/core/shell/abstention.js.map +1 -0
  98. package/dist/core/shell/approvalCapture.d.ts +271 -0
  99. package/dist/core/shell/approvalCapture.js +108 -0
  100. package/dist/core/shell/approvalCapture.js.map +1 -0
  101. package/dist/core/shell/approvalStop.d.ts +84 -0
  102. package/dist/core/shell/approvalStop.js +119 -0
  103. package/dist/core/shell/approvalStop.js.map +1 -0
  104. package/dist/core/shell/arity.d.ts +6 -0
  105. package/dist/core/shell/arity.js +20 -6
  106. package/dist/core/shell/arity.js.map +1 -1
  107. package/dist/core/shell/denylist.d.ts +11 -0
  108. package/dist/core/shell/denylist.js +37 -0
  109. package/dist/core/shell/denylist.js.map +1 -0
  110. package/dist/core/shell/escalationSeverity.d.ts +141 -0
  111. package/dist/core/shell/escalationSeverity.js +89 -0
  112. package/dist/core/shell/escalationSeverity.js.map +1 -0
  113. package/dist/core/shell/framing.d.ts +190 -0
  114. package/dist/core/shell/framing.js +633 -0
  115. package/dist/core/shell/framing.js.map +1 -0
  116. package/dist/core/shell/hardline.d.ts +81 -0
  117. package/dist/core/shell/hardline.js +607 -0
  118. package/dist/core/shell/hardline.js.map +1 -0
  119. package/dist/core/shell/negotiation.d.ts +249 -0
  120. package/dist/core/shell/negotiation.js +355 -0
  121. package/dist/core/shell/negotiation.js.map +1 -0
  122. package/dist/core/shell/normalize.d.ts +44 -4
  123. package/dist/core/shell/normalize.js +61 -7
  124. package/dist/core/shell/normalize.js.map +1 -1
  125. package/dist/core/shell/openWorld.d.ts +263 -0
  126. package/dist/core/shell/openWorld.js +1188 -0
  127. package/dist/core/shell/openWorld.js.map +1 -0
  128. package/dist/core/shell/rater.d.ts +935 -0
  129. package/dist/core/shell/rater.js +1473 -0
  130. package/dist/core/shell/rater.js.map +1 -0
  131. package/dist/core/shell/raterModel.d.ts +41 -0
  132. package/dist/core/shell/raterModel.js +51 -0
  133. package/dist/core/shell/raterModel.js.map +1 -0
  134. package/dist/core/shell/rejection.d.ts +69 -0
  135. package/dist/core/shell/rejection.js +38 -0
  136. package/dist/core/shell/rejection.js.map +1 -0
  137. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  138. package/dist/core/toolCallRepair/grammar.js +116 -0
  139. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  140. package/dist/core/toolCallRepair/index.d.ts +2 -0
  141. package/dist/core/toolCallRepair/index.js +7 -0
  142. package/dist/core/toolCallRepair/index.js.map +1 -0
  143. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  144. package/dist/core/toolCallRepair/payload.js +341 -0
  145. package/dist/core/toolCallRepair/payload.js.map +1 -0
  146. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  147. package/dist/core/toolCallRepair/promote.js +90 -0
  148. package/dist/core/toolCallRepair/promote.js.map +1 -0
  149. package/dist/core/toolDisplay.d.ts +123 -0
  150. package/dist/core/toolDisplay.js +451 -0
  151. package/dist/core/toolDisplay.js.map +1 -0
  152. package/dist/core/toolOutputChannel.d.ts +95 -0
  153. package/dist/core/toolOutputChannel.js +165 -0
  154. package/dist/core/toolOutputChannel.js.map +1 -0
  155. package/dist/core/types.d.ts +294 -12
  156. package/dist/core/types.js.map +1 -1
  157. package/dist/history/historyFormat.d.ts +28 -0
  158. package/dist/history/historyFormat.js +127 -0
  159. package/dist/history/historyFormat.js.map +1 -0
  160. package/dist/history/historyStore.d.ts +198 -0
  161. package/dist/history/historyStore.js +482 -0
  162. package/dist/history/historyStore.js.map +1 -0
  163. package/dist/history/recordSession.d.ts +37 -0
  164. package/dist/history/recordSession.js +56 -0
  165. package/dist/history/recordSession.js.map +1 -0
  166. package/dist/index.d.ts +4 -0
  167. package/dist/index.js +4 -0
  168. package/dist/index.js.map +1 -1
  169. package/dist/providers/anthropic.d.ts +1 -1
  170. package/dist/providers/anthropic.js +5 -10
  171. package/dist/providers/anthropic.js.map +1 -1
  172. package/dist/providers/deepseek.d.ts +1 -1
  173. package/dist/providers/deepseek.js +5 -10
  174. package/dist/providers/deepseek.js.map +1 -1
  175. package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
  176. package/dist/providers/geminiSchemaSanitizer.js +347 -0
  177. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  178. package/dist/providers/geminiThinking.d.ts +52 -0
  179. package/dist/providers/geminiThinking.js +72 -0
  180. package/dist/providers/geminiThinking.js.map +1 -0
  181. package/dist/providers/google-genai.d.ts +1 -1
  182. package/dist/providers/google-genai.js +12 -11
  183. package/dist/providers/google-genai.js.map +1 -1
  184. package/dist/providers/groq.d.ts +1 -1
  185. package/dist/providers/groq.js +5 -10
  186. package/dist/providers/groq.js.map +1 -1
  187. package/dist/providers/huggingface.d.ts +25 -0
  188. package/dist/providers/huggingface.js +69 -0
  189. package/dist/providers/huggingface.js.map +1 -0
  190. package/dist/providers/modelCatalog.d.ts +109 -0
  191. package/dist/providers/modelCatalog.js +245 -0
  192. package/dist/providers/modelCatalog.js.map +1 -0
  193. package/dist/providers/modelDiscovery.d.ts +153 -6
  194. package/dist/providers/modelDiscovery.js +295 -41
  195. package/dist/providers/modelDiscovery.js.map +1 -1
  196. package/dist/providers/ollama.d.ts +19 -5
  197. package/dist/providers/ollama.js +60 -52
  198. package/dist/providers/ollama.js.map +1 -1
  199. package/dist/providers/openai.d.ts +1 -1
  200. package/dist/providers/openai.js +39 -10
  201. package/dist/providers/openai.js.map +1 -1
  202. package/dist/providers/openrouter.d.ts +4 -5
  203. package/dist/providers/openrouter.js +20 -35
  204. package/dist/providers/openrouter.js.map +1 -1
  205. package/dist/providers/vertexai.d.ts +1 -1
  206. package/dist/providers/vertexai.js +12 -11
  207. package/dist/providers/vertexai.js.map +1 -1
  208. package/dist/providers/xai.d.ts +1 -1
  209. package/dist/providers/xai.js +5 -10
  210. package/dist/providers/xai.js.map +1 -1
  211. package/dist/runtime/askStructured.d.ts +105 -0
  212. package/dist/runtime/askStructured.js +120 -0
  213. package/dist/runtime/askStructured.js.map +1 -0
  214. package/dist/runtime/conversation.d.ts +60 -0
  215. package/dist/runtime/conversation.js +151 -0
  216. package/dist/runtime/conversation.js.map +1 -0
  217. package/dist/runtime/singleShot.d.ts +25 -5
  218. package/dist/runtime/singleShot.js +95 -36
  219. package/dist/runtime/singleShot.js.map +1 -1
  220. package/dist/runtime/structuredOutput.d.ts +104 -0
  221. package/dist/runtime/structuredOutput.js +393 -0
  222. package/dist/runtime/structuredOutput.js.map +1 -0
  223. package/dist/utils/ProgressIndicator.d.ts +21 -0
  224. package/dist/utils/ProgressIndicator.js +30 -3
  225. package/dist/utils/ProgressIndicator.js.map +1 -1
  226. package/dist/utils/aiignoreUtils.js.map +1 -1
  227. package/dist/utils/binaryOutputUtils.js.map +1 -1
  228. package/dist/utils/consoleUtils.d.ts +52 -0
  229. package/dist/utils/consoleUtils.js +72 -2
  230. package/dist/utils/consoleUtils.js.map +1 -1
  231. package/dist/utils/crashHandler.d.ts +87 -0
  232. package/dist/utils/crashHandler.js +128 -0
  233. package/dist/utils/crashHandler.js.map +1 -0
  234. package/dist/utils/debugDump.d.ts +134 -0
  235. package/dist/utils/debugDump.js +381 -0
  236. package/dist/utils/debugDump.js.map +1 -0
  237. package/dist/utils/debugUtils.d.ts +13 -4
  238. package/dist/utils/debugUtils.js +36 -13
  239. package/dist/utils/debugUtils.js.map +1 -1
  240. package/dist/utils/displayWidth.d.ts +53 -0
  241. package/dist/utils/displayWidth.js +195 -0
  242. package/dist/utils/displayWidth.js.map +1 -0
  243. package/dist/utils/fileUtils.d.ts +24 -2
  244. package/dist/utils/fileUtils.js +54 -12
  245. package/dist/utils/fileUtils.js.map +1 -1
  246. package/dist/utils/llmUtils.d.ts +39 -8
  247. package/dist/utils/llmUtils.js +76 -8
  248. package/dist/utils/llmUtils.js.map +1 -1
  249. package/dist/utils/redactSecrets.d.ts +63 -0
  250. package/dist/utils/redactSecrets.js +286 -0
  251. package/dist/utils/redactSecrets.js.map +1 -0
  252. package/dist/utils/systemPromptNotes.d.ts +225 -0
  253. package/dist/utils/systemPromptNotes.js +340 -0
  254. package/dist/utils/systemPromptNotes.js.map +1 -0
  255. package/dist/utils/systemUtils.d.ts +49 -1
  256. package/dist/utils/systemUtils.js +67 -3
  257. package/dist/utils/systemUtils.js.map +1 -1
  258. package/dist/utils/toolMatching.d.ts +30 -0
  259. package/dist/utils/toolMatching.js +44 -0
  260. package/dist/utils/toolMatching.js.map +1 -0
  261. package/dist/utils/untrustedText.d.ts +66 -0
  262. package/dist/utils/untrustedText.js +80 -0
  263. package/dist/utils/untrustedText.js.map +1 -0
  264. package/package.json +22 -6
  265. package/schema/gsloth-config.schema.json +3134 -0
  266. package/dist/core/shell/allowlist.d.ts +0 -75
  267. package/dist/core/shell/allowlist.js +0 -187
  268. package/dist/core/shell/allowlist.js.map +0 -1
  269. package/dist/core/shell/judge.d.ts +0 -161
  270. package/dist/core/shell/judge.js +0 -261
  271. 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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"}