@gaunt-sloth/core 2.0.0-alpha.9 → 2.0.0-beta.0

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 (275) hide show
  1. package/.gsloth.review.md +2 -0
  2. package/README.md +71 -20
  3. package/dist/config/colour.d.ts +38 -0
  4. package/dist/config/colour.js +36 -0
  5. package/dist/config/colour.js.map +1 -0
  6. package/dist/config/configDiscovery.d.ts +79 -0
  7. package/dist/config/configDiscovery.js +80 -0
  8. package/dist/config/configDiscovery.js.map +1 -0
  9. package/dist/config/defaults.d.ts +20 -20
  10. package/dist/config/defaults.js +10 -8
  11. package/dist/config/defaults.js.map +1 -1
  12. package/dist/config/filesystem-tools.d.ts +41 -0
  13. package/dist/config/filesystem-tools.js +56 -0
  14. package/dist/config/filesystem-tools.js.map +1 -0
  15. package/dist/config/loader.d.ts +171 -19
  16. package/dist/config/loader.js +1054 -144
  17. package/dist/config/loader.js.map +1 -1
  18. package/dist/config/mouse.d.ts +50 -0
  19. package/dist/config/mouse.js +44 -0
  20. package/dist/config/mouse.js.map +1 -0
  21. package/dist/config/profiles.d.ts +68 -0
  22. package/dist/config/profiles.js +93 -0
  23. package/dist/config/profiles.js.map +1 -0
  24. package/dist/config/providerKeys.d.ts +69 -0
  25. package/dist/config/providerKeys.js +69 -0
  26. package/dist/config/providerKeys.js.map +1 -0
  27. package/dist/config/schema.d.ts +2671 -138
  28. package/dist/config/schema.js +1361 -85
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/config/shell-policy.d.ts +899 -111
  31. package/dist/config/shell-policy.js +800 -70
  32. package/dist/config/shell-policy.js.map +1 -1
  33. package/dist/config/tool-descriptions.d.ts +211 -0
  34. package/dist/config/tool-descriptions.js +272 -0
  35. package/dist/config/tool-descriptions.js.map +1 -0
  36. package/dist/config/types.d.ts +352 -41
  37. package/dist/config/types.js +1 -0
  38. package/dist/config/types.js.map +1 -1
  39. package/dist/config.d.ts +35 -1
  40. package/dist/config.js +16 -1
  41. package/dist/config.js.map +1 -1
  42. package/dist/constants.d.ts +28 -1
  43. package/dist/constants.js +28 -1
  44. package/dist/constants.js.map +1 -1
  45. package/dist/core/GthAbstractAgent.d.ts +166 -11
  46. package/dist/core/GthAbstractAgent.js +484 -45
  47. package/dist/core/GthAbstractAgent.js.map +1 -1
  48. package/dist/core/GthAgentRunner.d.ts +543 -57
  49. package/dist/core/GthAgentRunner.js +1494 -140
  50. package/dist/core/GthAgentRunner.js.map +1 -1
  51. package/dist/core/GthLangChainAgent.d.ts +117 -2
  52. package/dist/core/GthLangChainAgent.js +602 -29
  53. package/dist/core/GthLangChainAgent.js.map +1 -1
  54. package/dist/core/approvals/annotations.d.ts +122 -0
  55. package/dist/core/approvals/annotations.js +137 -0
  56. package/dist/core/approvals/annotations.js.map +1 -0
  57. package/dist/core/approvals/grants.d.ts +216 -0
  58. package/dist/core/approvals/grants.js +469 -0
  59. package/dist/core/approvals/grants.js.map +1 -0
  60. package/dist/core/approvals/matcher.d.ts +202 -0
  61. package/dist/core/approvals/matcher.js +267 -0
  62. package/dist/core/approvals/matcher.js.map +1 -0
  63. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  64. package/dist/core/approvals/mcpSubjects.js +99 -0
  65. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  66. package/dist/core/approvals/promptHeader.d.ts +28 -0
  67. package/dist/core/approvals/promptHeader.js +62 -0
  68. package/dist/core/approvals/promptHeader.js.map +1 -0
  69. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  70. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  71. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  72. package/dist/core/approvals/toolHost.d.ts +46 -0
  73. package/dist/core/approvals/toolHost.js +108 -0
  74. package/dist/core/approvals/toolHost.js.map +1 -0
  75. package/dist/core/debugCapture.d.ts +74 -0
  76. package/dist/core/debugCapture.js +100 -0
  77. package/dist/core/debugCapture.js.map +1 -0
  78. package/dist/core/gthLeanAgentFactory.d.ts +4 -4
  79. package/dist/core/gthLeanAgentFactory.js +4 -4
  80. package/dist/core/launchBanner.d.ts +127 -0
  81. package/dist/core/launchBanner.js +414 -0
  82. package/dist/core/launchBanner.js.map +1 -0
  83. package/dist/core/modelLabel.d.ts +19 -0
  84. package/dist/core/modelLabel.js +26 -0
  85. package/dist/core/modelLabel.js.map +1 -0
  86. package/dist/core/plainToolIndication.d.ts +15 -0
  87. package/dist/core/plainToolIndication.js +174 -0
  88. package/dist/core/plainToolIndication.js.map +1 -0
  89. package/dist/core/reasoningBlocks.d.ts +65 -0
  90. package/dist/core/reasoningBlocks.js +103 -0
  91. package/dist/core/reasoningBlocks.js.map +1 -0
  92. package/dist/core/refusal.d.ts +53 -0
  93. package/dist/core/refusal.js +133 -0
  94. package/dist/core/refusal.js.map +1 -0
  95. package/dist/core/runHeader.d.ts +38 -0
  96. package/dist/core/runHeader.js +42 -0
  97. package/dist/core/runHeader.js.map +1 -0
  98. package/dist/core/runStats.d.ts +14 -3
  99. package/dist/core/runStats.js +48 -3
  100. package/dist/core/runStats.js.map +1 -1
  101. package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
  102. package/dist/core/shell/ShellCommandFailedError.js +3 -4
  103. package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
  104. package/dist/core/shell/abstention.d.ts +88 -0
  105. package/dist/core/shell/abstention.js +184 -0
  106. package/dist/core/shell/abstention.js.map +1 -0
  107. package/dist/core/shell/approvalCapture.d.ts +271 -0
  108. package/dist/core/shell/approvalCapture.js +108 -0
  109. package/dist/core/shell/approvalCapture.js.map +1 -0
  110. package/dist/core/shell/approvalStop.d.ts +123 -0
  111. package/dist/core/shell/approvalStop.js +269 -0
  112. package/dist/core/shell/approvalStop.js.map +1 -0
  113. package/dist/core/shell/arity.d.ts +6 -0
  114. package/dist/core/shell/arity.js +20 -6
  115. package/dist/core/shell/arity.js.map +1 -1
  116. package/dist/core/shell/denylist.d.ts +11 -0
  117. package/dist/core/shell/denylist.js +37 -0
  118. package/dist/core/shell/denylist.js.map +1 -0
  119. package/dist/core/shell/escalationSeverity.d.ts +141 -0
  120. package/dist/core/shell/escalationSeverity.js +89 -0
  121. package/dist/core/shell/escalationSeverity.js.map +1 -0
  122. package/dist/core/shell/framing.d.ts +190 -0
  123. package/dist/core/shell/framing.js +633 -0
  124. package/dist/core/shell/framing.js.map +1 -0
  125. package/dist/core/shell/hardline.d.ts +103 -0
  126. package/dist/core/shell/hardline.js +780 -0
  127. package/dist/core/shell/hardline.js.map +1 -0
  128. package/dist/core/shell/negotiation.d.ts +328 -0
  129. package/dist/core/shell/negotiation.js +488 -0
  130. package/dist/core/shell/negotiation.js.map +1 -0
  131. package/dist/core/shell/normalize.d.ts +44 -4
  132. package/dist/core/shell/normalize.js +61 -7
  133. package/dist/core/shell/normalize.js.map +1 -1
  134. package/dist/core/shell/openWorld.d.ts +263 -0
  135. package/dist/core/shell/openWorld.js +1188 -0
  136. package/dist/core/shell/openWorld.js.map +1 -0
  137. package/dist/core/shell/rater.d.ts +873 -0
  138. package/dist/core/shell/rater.js +1454 -0
  139. package/dist/core/shell/rater.js.map +1 -0
  140. package/dist/core/shell/raterModel.d.ts +41 -0
  141. package/dist/core/shell/raterModel.js +51 -0
  142. package/dist/core/shell/raterModel.js.map +1 -0
  143. package/dist/core/shell/raterVocabulary.d.ts +121 -0
  144. package/dist/core/shell/raterVocabulary.js +116 -0
  145. package/dist/core/shell/raterVocabulary.js.map +1 -0
  146. package/dist/core/shell/rejection.d.ts +69 -0
  147. package/dist/core/shell/rejection.js +38 -0
  148. package/dist/core/shell/rejection.js.map +1 -0
  149. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  150. package/dist/core/toolCallRepair/grammar.js +116 -0
  151. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  152. package/dist/core/toolCallRepair/index.d.ts +2 -0
  153. package/dist/core/toolCallRepair/index.js +7 -0
  154. package/dist/core/toolCallRepair/index.js.map +1 -0
  155. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  156. package/dist/core/toolCallRepair/payload.js +341 -0
  157. package/dist/core/toolCallRepair/payload.js.map +1 -0
  158. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  159. package/dist/core/toolCallRepair/promote.js +90 -0
  160. package/dist/core/toolCallRepair/promote.js.map +1 -0
  161. package/dist/core/toolDisplay.d.ts +123 -0
  162. package/dist/core/toolDisplay.js +451 -0
  163. package/dist/core/toolDisplay.js.map +1 -0
  164. package/dist/core/toolOutputChannel.d.ts +95 -0
  165. package/dist/core/toolOutputChannel.js +165 -0
  166. package/dist/core/toolOutputChannel.js.map +1 -0
  167. package/dist/core/types.d.ts +349 -16
  168. package/dist/core/types.js.map +1 -1
  169. package/dist/history/historyFormat.d.ts +12 -3
  170. package/dist/history/historyFormat.js +50 -8
  171. package/dist/history/historyFormat.js.map +1 -1
  172. package/dist/history/historyStore.d.ts +77 -0
  173. package/dist/history/historyStore.js +173 -6
  174. package/dist/history/historyStore.js.map +1 -1
  175. package/dist/history/recordSession.d.ts +10 -1
  176. package/dist/history/recordSession.js +27 -0
  177. package/dist/history/recordSession.js.map +1 -1
  178. package/dist/providers/anthropic.js +12 -0
  179. package/dist/providers/anthropic.js.map +1 -1
  180. package/dist/providers/configurationPassthrough.d.ts +107 -0
  181. package/dist/providers/configurationPassthrough.js +148 -0
  182. package/dist/providers/configurationPassthrough.js.map +1 -0
  183. package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
  184. package/dist/providers/geminiSchemaSanitizer.js +347 -0
  185. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  186. package/dist/providers/geminiThinking.d.ts +60 -0
  187. package/dist/providers/geminiThinking.js +92 -0
  188. package/dist/providers/geminiThinking.js.map +1 -0
  189. package/dist/providers/google-genai.js +18 -1
  190. package/dist/providers/google-genai.js.map +1 -1
  191. package/dist/providers/groq.js +12 -0
  192. package/dist/providers/groq.js.map +1 -1
  193. package/dist/providers/huggingface.d.ts +25 -0
  194. package/dist/providers/huggingface.js +69 -0
  195. package/dist/providers/huggingface.js.map +1 -0
  196. package/dist/providers/modelCatalog.d.ts +109 -0
  197. package/dist/providers/modelCatalog.js +245 -0
  198. package/dist/providers/modelCatalog.js.map +1 -0
  199. package/dist/providers/modelDiscovery.d.ts +99 -5
  200. package/dist/providers/modelDiscovery.js +191 -35
  201. package/dist/providers/modelDiscovery.js.map +1 -1
  202. package/dist/providers/ollama.d.ts +18 -4
  203. package/dist/providers/ollama.js +67 -37
  204. package/dist/providers/ollama.js.map +1 -1
  205. package/dist/providers/openai.js +34 -0
  206. package/dist/providers/openai.js.map +1 -1
  207. package/dist/providers/openrouter.d.ts +26 -4
  208. package/dist/providers/openrouter.js +83 -26
  209. package/dist/providers/openrouter.js.map +1 -1
  210. package/dist/providers/vertexai.js +19 -1
  211. package/dist/providers/vertexai.js.map +1 -1
  212. package/dist/providers/xai.js +20 -0
  213. package/dist/providers/xai.js.map +1 -1
  214. package/dist/runtime/askStructured.d.ts +105 -0
  215. package/dist/runtime/askStructured.js +120 -0
  216. package/dist/runtime/askStructured.js.map +1 -0
  217. package/dist/runtime/conversation.d.ts +64 -0
  218. package/dist/runtime/conversation.js +171 -0
  219. package/dist/runtime/conversation.js.map +1 -0
  220. package/dist/runtime/singleShot.d.ts +37 -6
  221. package/dist/runtime/singleShot.js +113 -67
  222. package/dist/runtime/singleShot.js.map +1 -1
  223. package/dist/runtime/structuredOutput.d.ts +104 -0
  224. package/dist/runtime/structuredOutput.js +393 -0
  225. package/dist/runtime/structuredOutput.js.map +1 -0
  226. package/dist/utils/ProgressIndicator.d.ts +21 -0
  227. package/dist/utils/ProgressIndicator.js +30 -3
  228. package/dist/utils/ProgressIndicator.js.map +1 -1
  229. package/dist/utils/aiignoreUtils.js.map +1 -1
  230. package/dist/utils/binaryOutputUtils.js.map +1 -1
  231. package/dist/utils/consoleUtils.d.ts +95 -0
  232. package/dist/utils/consoleUtils.js +112 -2
  233. package/dist/utils/consoleUtils.js.map +1 -1
  234. package/dist/utils/crashHandler.d.ts +87 -0
  235. package/dist/utils/crashHandler.js +128 -0
  236. package/dist/utils/crashHandler.js.map +1 -0
  237. package/dist/utils/debugDump.d.ts +134 -0
  238. package/dist/utils/debugDump.js +381 -0
  239. package/dist/utils/debugDump.js.map +1 -0
  240. package/dist/utils/debugUtils.d.ts +13 -4
  241. package/dist/utils/debugUtils.js +36 -13
  242. package/dist/utils/debugUtils.js.map +1 -1
  243. package/dist/utils/displayWidth.d.ts +53 -0
  244. package/dist/utils/displayWidth.js +195 -0
  245. package/dist/utils/displayWidth.js.map +1 -0
  246. package/dist/utils/fileUtils.js.map +1 -1
  247. package/dist/utils/globalConfigUtils.d.ts +14 -2
  248. package/dist/utils/globalConfigUtils.js +22 -4
  249. package/dist/utils/globalConfigUtils.js.map +1 -1
  250. package/dist/utils/llmUtils.d.ts +39 -8
  251. package/dist/utils/llmUtils.js +76 -8
  252. package/dist/utils/llmUtils.js.map +1 -1
  253. package/dist/utils/redactSecrets.d.ts +63 -0
  254. package/dist/utils/redactSecrets.js +286 -0
  255. package/dist/utils/redactSecrets.js.map +1 -0
  256. package/dist/utils/systemPromptNotes.d.ts +222 -0
  257. package/dist/utils/systemPromptNotes.js +338 -0
  258. package/dist/utils/systemPromptNotes.js.map +1 -0
  259. package/dist/utils/systemUtils.d.ts +18 -1
  260. package/dist/utils/systemUtils.js +38 -3
  261. package/dist/utils/systemUtils.js.map +1 -1
  262. package/dist/utils/toolMatching.d.ts +30 -0
  263. package/dist/utils/toolMatching.js +44 -0
  264. package/dist/utils/toolMatching.js.map +1 -0
  265. package/dist/utils/untrustedText.d.ts +86 -0
  266. package/dist/utils/untrustedText.js +101 -0
  267. package/dist/utils/untrustedText.js.map +1 -0
  268. package/package.json +21 -7
  269. package/schema/gsloth-config.schema.json +1921 -326
  270. package/dist/core/shell/allowlist.d.ts +0 -75
  271. package/dist/core/shell/allowlist.js +0 -187
  272. package/dist/core/shell/allowlist.js.map +0 -1
  273. package/dist/core/shell/judge.d.ts +0 -161
  274. package/dist/core/shell/judge.js +0 -261
  275. package/dist/core/shell/judge.js.map +0 -1
@@ -0,0 +1,414 @@
1
+ /**
2
+ * @module launchBanner
3
+ * TUI-C33 — the welcoming ASCII-art launch banner shown at the top of an INTERACTIVE session
4
+ * (`gth chat` / `gth code`): a sloth face on the left, the `GAUNT SLOTH` wordmark plus three live
5
+ * fields (version, model/provider, working directory) on the right.
6
+ *
7
+ *
8
+ * ▄█▀▀▀▀▀▀▀▀█▄ ┏┓ ┏┓┓ ┓
9
+ * ▄▀█▄█▀▀▀▀▀▀█▄█▀▄ ┃┓┏┓┓┏┏┓╋ ┗┓┃┏┓╋┣┓ v2.0.0-alpha.25
10
+ * █ ▀█▄▀ ▀▄█▀ █ ┗┛┗┻┗┻┛┗┗ ┗┛┗┗┛┗┛┗
11
+ * ▀▄▀▀ ██████ ▀▀▄▀ gemini-3.1-pro (google-genai)
12
+ * ▀██████████▀ ~/dev/takahe
13
+ *
14
+ * It is deliberately NOT emitted by the one-shot verbs (`ask`, `review`, `pr`, `get`, `eval`,
15
+ * `batch`, `api`, `init`): their stdout is piped into files, diffs and CI, where a banner is
16
+ * corruption rather than welcome.
17
+ *
18
+ * ## Why this module is pure, and split in two halves
19
+ *
20
+ * The banner has to render IDENTICALLY on two surfaces that share no rendering code — the Ink TUI
21
+ * (`packages/app/src/tui/components/LaunchBanner.tsx`) and the plain `--no-tui` readline session
22
+ * (`packages/agent/src/modules/interactiveSessionModule.ts`). So all the geometry lives here as
23
+ * pure functions of `{ version, model, provider, directory, homeDir, columns, colour }`
24
+ * ({@link launchBannerRows} / {@link launchBannerText}), and the surfaces differ only in how they
25
+ * EMIT: one string through `displayLaunchBanner`, or one `<Text>` per row. That is the same split
26
+ * `Rule.tsx` uses (pure `ruleWidth` + a thin React component), including its
27
+ * `columns === undefined → 80` convention for a non-TTY / test stdout.
28
+ *
29
+ * The one impure function is {@link launchBannerFields} at the bottom, which reads the live
30
+ * version / project dir / home dir. It is shared for the same reason: both surfaces must read the
31
+ * same sources rather than each inventing its own.
32
+ *
33
+ * ## Why the right column is a slice at a fixed column, not a glyph classifier
34
+ *
35
+ * Colouring is "columns 0–20 are magenta, columns 21+ are default foreground" — a per-line split at
36
+ * {@link RIGHT_COLUMN}. It is emphatically NOT "is this character a block element": the wordmark is
37
+ * box-drawing, the face is block elements, and the moment a field value contained a `▀` (or the art
38
+ * gained a non-block glyph) a classifier would mis-colour it. A fixed column cannot.
39
+ *
40
+ * The face is 16 columns wide and the right column starts at {@link RIGHT_COLUMN}, leaving a
41
+ * 5-column gutter. The version label sits three columns after the widest wordmark line, i.e. at
42
+ * {@link VERSION_COLUMN}.
43
+ *
44
+ * ## Why the padding is part of the layout rather than of either surface
45
+ *
46
+ * TUI-C36 — the block is a splash, so it gets breathing room: one blank row above, one blank row
47
+ * below, and a {@link LEFT_PAD}-column left margin on every art row. That padding lives HERE, with
48
+ * the rest of the geometry, for the same reason the columns do — the Ink surface and the readline
49
+ * surface must show the same block, and a margin added by one renderer is a margin the other one
50
+ * lacks. The blank rows are genuinely empty (no pad, no escape): a padding row that carried
51
+ * trailing spaces would be visible the moment anything selected or copied it.
52
+ *
53
+ * The pad column shifts every column constant by exactly one, which is why they are all derived
54
+ * from {@link LEFT_PAD} rather than written as literals. The field budgets are expressed against
55
+ * those constants (`columns - RIGHT_COLUMN`), so they absorb the shift without changing.
56
+ *
57
+ * ## Why every dynamic field is bounded, and why the version is the one that is DROPPED
58
+ *
59
+ * A wrapped line SHATTERS the art: the continuation starts back at column 0 and collides with the
60
+ * face, so the sloth grows an extra row of model id. Wrapping is therefore not allowed to happen at
61
+ * all, and each field is bounded by what is left of the terminal on its own line — `columns - 21`
62
+ * for the model/provider and directory lines, `columns - 43` for the version.
63
+ *
64
+ * Those budgets are TERMINAL COLUMNS, and every measurement and cut in this module goes through
65
+ * `#src/utils/displayWidth.js` for that reason. A CJK ideograph or an emoji is one code point in
66
+ * two columns, so a `.length`-style count would let a path or model id containing either measure
67
+ * short, slip past its budget and wrap — the very failure above.
68
+ *
69
+ * The model/provider and directory lines TRUNCATE to that budget: a clipped model id or path is
70
+ * still honest, it visibly ends in `…`. The directory is truncated from the LEFT
71
+ * (`…/dev/takahe`), because the leaf directory is the informative end; the model keeps its head and
72
+ * loses its tail.
73
+ *
74
+ * A VERSION that does not fit is dropped entirely instead ({@link fitOrDrop}) — never truncated.
75
+ * A clipped version number is not merely less informative, it is MISLEADING: `v2.0…` reads as a
76
+ * real, different version (2.0.1, say) rather than as a clipped `2.0.0-alpha.25`, and a wrong
77
+ * version in a bug report costs more than an absent one. The model/provider line already sets that
78
+ * drop-rather-than-mislead precedent by omitting itself when nothing resolves.
79
+ *
80
+ * Below {@link MIN_BANNER_COLUMNS} the right column cannot fit at all, so the face prints alone
81
+ * rather than wrapping the wordmark into rubble.
82
+ */
83
+ import { homedir } from 'node:os';
84
+ import { ELLIPSIS, ELLIPSIS_WIDTH } from '#src/core/toolDisplay.js';
85
+ import { modelProviderLabel } from '#src/core/modelLabel.js';
86
+ import { ANSI_COLORS } from '#src/utils/consoleUtils.js';
87
+ import { displayWidth, sliceEndToWidth, sliceToWidth } from '#src/utils/displayWidth.js';
88
+ import { getProjectDir, getSlothVersion } from '#src/utils/systemUtils.js';
89
+ /**
90
+ * The sloth face, columns 0–15. Lines 1 and 5 are 14 columns, lines 2–4 are 16 — the face field is
91
+ * 16 wide and the short lines simply do not reach the edge. Do not "tidy" the leading spaces: they
92
+ * are part of the art.
93
+ */
94
+ const FACE_LINES = [
95
+ ' ▄█▀▀▀▀▀▀▀▀█▄',
96
+ '▄▀█▄█▀▀▀▀▀▀█▄█▀▄',
97
+ '█ ▀█▄▀ ▀▄█▀ █',
98
+ '▀▄▀▀ ██████ ▀▀▄▀',
99
+ ' ▀██████████▀',
100
+ ];
101
+ /**
102
+ * TUI-C40 — the resting face, under the name the animation layer refers to it by.
103
+ *
104
+ * Every frame below is the same five lines and the same 16-column field, so swapping one in changes
105
+ * nothing about the layout: the right column still starts at {@link RIGHT_COLUMN} and the
106
+ * truncation budgets are untouched. That invariant is what makes an animation a data change rather
107
+ * than a geometry change, and it is asserted per frame in the specs.
108
+ */
109
+ export const RESTING_FACE = FACE_LINES;
110
+ /**
111
+ * The extra faces, authored in takahē (`websites/ascii-art.md`) and transcribed here so the art has
112
+ * one home in this repo, exactly as the resting frame does. Names match the source file's blocks.
113
+ */
114
+ const BLINK_HALF = [
115
+ ' ▄█▀▀▀▀▀▀▀██▄',
116
+ '▄▀█▄█▀▀▀▀▀▀███▀▄',
117
+ '█ ▀█▄▀ ▀▄█▀ █',
118
+ '▀▄▀▀▀▐████▌▀▀▀▄▀',
119
+ ' ▀██████████▀',
120
+ ];
121
+ const BLINK_SHUT = [
122
+ ' ▄█▀▀▀▀▀▀███▄',
123
+ '▄▀█▄█▀▀▀▀▀▀███▀▄',
124
+ '█ ▀█▄▀ ▀▄█▀ █',
125
+ '▀▄▀▀▀▐████▌ ▄▀',
126
+ ' ▀██████████▀',
127
+ ];
128
+ const NOD_DOWN = [
129
+ ' ▄██████████▄',
130
+ '▄██▀▄▄▄▄▄▄▄▄▀██▄',
131
+ '█ ▀██ ▄ ▄ ██▀ █',
132
+ '█ ▄▄▀█▄▄▄▄█▀▄▄ █',
133
+ ' ▄▄▄██████▄▄▄',
134
+ ];
135
+ const LOOK_LEFT = [
136
+ ' ▄██████████▄',
137
+ '▄██▀▄▄▄▄▄▄▄████▄',
138
+ '█ ▀██ ▄ ▄ ██▀ █',
139
+ '█ ▄▀█▄▄▄▄█▀ ▄ █',
140
+ ' ▄▄▄██████▄▄▄',
141
+ ];
142
+ const LOOK_RIGHT = [
143
+ ' ▄██████████▄',
144
+ '▄████▄▄▄▄▄▄▄▀██▄',
145
+ '█ ▀██ ▄ ▄ ██▀ █',
146
+ '█ ▄ ▀█▄▄▄▄█▀▄ █',
147
+ ' ▄▄▄██████▄▄▄',
148
+ ];
149
+ const EYEROLL = [
150
+ ' ▄█▀▀▀▀▀▀▀▀█▄',
151
+ '▄▀█▄█▀▀▀▀▀▀█▄█▀▄',
152
+ '█ ▀██▄▀ ▀▄██▀ █',
153
+ '▀▌ ▄ ▐████▌ ▄ ▐▀',
154
+ ' ▀██████████▀',
155
+ ];
156
+ /** The animations a click can play. This list is the complete set. */
157
+ export const SLOTH_ANIMATIONS = ['blink', 'nod', 'look-around', 'eyeroll'];
158
+ /**
159
+ * The four sequences, as the frames shown AFTER the resting face.
160
+ *
161
+ * Playback always settles back on {@link RESTING_FACE} once the last step's hold elapses, so no
162
+ * sequence lists a trailing rest — but `nod` lists rest in the MIDDLE, because nodding twice means
163
+ * returning to level between the two dips. None of them loops, and a click plays exactly one.
164
+ */
165
+ export const SLOTH_ANIMATION_STEPS = {
166
+ // Quick, because a blink that lingers reads as a squint.
167
+ blink: [
168
+ { face: BLINK_HALF, holdMs: 90 },
169
+ { face: BLINK_SHUT, holdMs: 110 },
170
+ ],
171
+ nod: [
172
+ { face: NOD_DOWN, holdMs: 160 },
173
+ { face: RESTING_FACE, holdMs: 130 },
174
+ { face: NOD_DOWN, holdMs: 160 },
175
+ ],
176
+ 'look-around': [
177
+ { face: LOOK_LEFT, holdMs: 190 },
178
+ { face: LOOK_RIGHT, holdMs: 230 },
179
+ ],
180
+ // The longest hold: the joke only lands if you get to see it.
181
+ eyeroll: [{ face: EYEROLL, holdMs: 300 }],
182
+ };
183
+ /**
184
+ * Pick one animation at random. `random` is injectable so a test can pin the choice — the
185
+ * production call passes nothing and gets `Math.random`.
186
+ */
187
+ export function pickSlothAnimation(random = Math.random) {
188
+ const index = Math.floor(random() * SLOTH_ANIMATIONS.length);
189
+ // Guard the boundary: a `random()` of exactly 1 (or a stub returning it) would index past the end.
190
+ return SLOTH_ANIMATIONS[Math.min(index, SLOTH_ANIMATIONS.length - 1)];
191
+ }
192
+ /** The `GAUNT SLOTH` wordmark, occupying rows 1–3 of the right column. */
193
+ const WORDMARK_LINES = [
194
+ '┏┓ ┏┓┓ ┓',
195
+ '┃┓┏┓┓┏┏┓╋ ┗┓┃┏┓╋┣┓',
196
+ '┗┛┗┻┗┻┛┗┗ ┗┛┗┗┛┗┛┗',
197
+ ];
198
+ /** Widest wordmark line (19); rows 1 and 3 are padded to it so the version label lands square. */
199
+ const WORDMARK_WIDTH = Math.max(...WORDMARK_LINES.map((line) => displayWidth(line)));
200
+ /** Blank columns between the wordmark and the version label. */
201
+ const VERSION_GAP = 3;
202
+ /**
203
+ * Blank columns prepended to every art row (TUI-C36). One column of left margin, so the block does
204
+ * not sit flush against the edge of the terminal.
205
+ */
206
+ export const LEFT_PAD = 1;
207
+ /** The prepended margin itself, applied to art rows only — never to the blank padding rows. */
208
+ const PAD = ' '.repeat(LEFT_PAD);
209
+ /** Columns the face field occupies, after the margin. */
210
+ const FACE_WIDTH = 16;
211
+ /** Blank columns between the face field and the right column. */
212
+ const FACE_GAP = 5;
213
+ /**
214
+ * Column at which the right-hand column begins on EVERY row — the single number the whole layout
215
+ * and the colour split are expressed in terms of (margin + face + gutter = 22).
216
+ */
217
+ export const RIGHT_COLUMN = LEFT_PAD + FACE_WIDTH + FACE_GAP;
218
+ /** Column at which the version label begins on row 2 (right column + wordmark + gap = 44). */
219
+ export const VERSION_COLUMN = RIGHT_COLUMN + WORDMARK_WIDTH + VERSION_GAP;
220
+ /**
221
+ * Field budget the narrowest banner still affords the right column — the widest wordmark line plus
222
+ * a few columns of field, which is what the original threshold of 45 amounted to at
223
+ * {@link RIGHT_COLUMN} 21.
224
+ */
225
+ const MIN_FIELD_BUDGET = 24;
226
+ /**
227
+ * Narrowest terminal that still gets the right column. The wordmark itself ends at column 40, so
228
+ * anything narrower than this leaves no usable room for the fields beside it; below the threshold
229
+ * the face prints alone (see the module docs on why wrapping is not an option).
230
+ */
231
+ export const MIN_BANNER_COLUMNS = RIGHT_COLUMN + MIN_FIELD_BUDGET;
232
+ /** Width assumed when the terminal width is unknown (non-TTY / tests) — as in `ruleWidth`. */
233
+ const DEFAULT_COLUMNS = 80;
234
+ /**
235
+ * Right-pad `text` to `width` TERMINAL COLUMNS. `String.prototype.padEnd` counts UTF-16 units, so
236
+ * a field holding a wide glyph would be filled one column too far for each of them — and since the
237
+ * split column is the single number this whole layout is expressed in terms of, that shifts the
238
+ * right column and pushes the row past the terminal. Text already at or past the width is returned
239
+ * untouched, exactly as `padEnd` leaves it.
240
+ */
241
+ function padToWidth(text, width) {
242
+ return text + ' '.repeat(Math.max(0, width - displayWidth(text)));
243
+ }
244
+ /** Resolve the usable terminal width, mirroring `ruleWidth`'s unknown/non-finite handling. */
245
+ function resolveColumns(columns) {
246
+ const cols = typeof columns === 'number' && Number.isFinite(columns) ? columns : DEFAULT_COLUMNS;
247
+ return Math.floor(cols);
248
+ }
249
+ /**
250
+ * Truncate `text` to `budget` columns, losing the TAIL (`gemini-3.1-pro-experi…`). Returns
251
+ * `undefined` only when there is no budget at all; that guard keeps the function total (at
252
+ * `budget === 0` the slice below would otherwise return a bare marker) and is not reachable from
253
+ * {@link launchBannerRows}, whose narrowest field budget is `46 - 22 = 24`.
254
+ *
255
+ * `budget` is TERMINAL COLUMNS, and so is every measurement here: measuring and slicing both go
256
+ * through the shared width primitive, which counts a CJK ideograph or an emoji as the two columns
257
+ * it draws and cuts only between whole grapheme clusters. A code-point count would let such a
258
+ * value measure short, escape its budget and wrap into the face; a code-point slice would cut a
259
+ * cluster in half.
260
+ */
261
+ function truncateTail(text, budget) {
262
+ if (budget <= 0)
263
+ return undefined;
264
+ if (displayWidth(text) <= budget)
265
+ return text;
266
+ return sliceToWidth(text, budget - ELLIPSIS_WIDTH) + ELLIPSIS;
267
+ }
268
+ /**
269
+ * Truncate `text` to `budget` columns, losing the HEAD (`…/dev/takahe`). Used for the directory,
270
+ * where the leaf is what the user needs and the ancestors are noise. Columns, not code points —
271
+ * see {@link truncateTail}.
272
+ */
273
+ function truncateHead(text, budget) {
274
+ if (budget <= 0)
275
+ return undefined;
276
+ if (displayWidth(text) <= budget)
277
+ return text;
278
+ return ELLIPSIS + sliceEndToWidth(text, budget - ELLIPSIS_WIDTH);
279
+ }
280
+ /**
281
+ * All-or-nothing: `text` if it fits `budget` columns in full, otherwise `undefined`. Used for the
282
+ * VERSION label, which must never be truncated — see the module docs on why a clipped version
283
+ * number is misleading rather than merely terse. "Fits" is measured in columns, so a wide glyph
284
+ * cannot buy its way in on a short code-point count and push the row past the terminal.
285
+ */
286
+ function fitOrDrop(text, budget) {
287
+ return displayWidth(text) <= budget ? text : undefined;
288
+ }
289
+ /**
290
+ * Collapse the home-directory prefix to `~`, the way a shell prompt does — a deep path spends its
291
+ * whole budget on `/Users/<name>` otherwise.
292
+ *
293
+ * Both separators are accepted deliberately: on Windows `homedir()` reads `%USERPROFILE%` and the
294
+ * path arrives back-slashed, and hard-coding POSIX `/` here is exactly the win32-only bug this repo
295
+ * keeps re-learning. The boundary check matters too — `/home/mar` must not collapse inside
296
+ * `/home/maria/x`.
297
+ */
298
+ function collapseHomePrefix(directory, homeDir) {
299
+ if (!homeDir)
300
+ return directory;
301
+ if (directory === homeDir)
302
+ return '~';
303
+ if (!directory.startsWith(homeDir))
304
+ return directory;
305
+ const boundary = directory.charAt(homeDir.length);
306
+ if (boundary !== '/' && boundary !== '\\')
307
+ return directory;
308
+ return '~' + directory.slice(homeDir.length);
309
+ }
310
+ /**
311
+ * The shared `model (provider)` spelling, re-exported so this module stays the documented home of
312
+ * the banner's fields. It lives in `modelLabel.ts` because the agent renders it too and must not
313
+ * import this module's console/filesystem graph to do so.
314
+ */
315
+ export { modelProviderLabel };
316
+ /** A padding row: no margin and no content, so it is an empty line on every surface. */
317
+ function blankRow() {
318
+ return { face: '', right: '' };
319
+ }
320
+ /** Wrap the five art rows in the blank row above and below that make the block a splash. */
321
+ function withPaddingRows(art) {
322
+ return [blankRow(), ...art, blankRow()];
323
+ }
324
+ /**
325
+ * Assemble the banner as {@link LaunchBannerRow}s: a blank row, the five art rows each pre-split at
326
+ * {@link RIGHT_COLUMN}, and a blank row. Pure — every input arrives as an argument, so the geometry
327
+ * is testable without a terminal, a config or a process.
328
+ */
329
+ export function launchBannerRows(input) {
330
+ const columns = resolveColumns(input.columns);
331
+ // TUI-C40 — the face is the one part a caller may swap; everything below is unchanged geometry.
332
+ const face = input.face ?? RESTING_FACE;
333
+ // Too narrow for the right column: the face alone, with no fields. It keeps its left margin and
334
+ // its blank rows — the padding is what makes it a splash, and it costs one column, not the
335
+ // gutter's five.
336
+ if (columns < MIN_BANNER_COLUMNS) {
337
+ return withPaddingRows(face.map((line) => ({ face: PAD + line, right: '' })));
338
+ }
339
+ // Every field is budgeted against ITS OWN start column, because the version starts further right
340
+ // than the other two. The version is the only fit-or-drop field; the other two truncate.
341
+ const fieldBudget = columns - RIGHT_COLUMN;
342
+ const version = input.version?.trim()
343
+ ? fitOrDrop(`v${input.version.trim()}`, columns - VERSION_COLUMN)
344
+ : undefined;
345
+ const model = modelProviderLabel(input.model, input.provider);
346
+ const modelLine = model ? truncateTail(model, fieldBudget) : undefined;
347
+ const directory = input.directory?.trim()
348
+ ? truncateHead(collapseHomePrefix(input.directory.trim(), input.homeDir), fieldBudget)
349
+ : undefined;
350
+ const rightLines = [
351
+ WORDMARK_LINES[0],
352
+ version
353
+ ? padToWidth(WORDMARK_LINES[1], WORDMARK_WIDTH) + ' '.repeat(VERSION_GAP) + version
354
+ : WORDMARK_LINES[1],
355
+ WORDMARK_LINES[2],
356
+ modelLine ?? '',
357
+ directory ?? '',
358
+ ];
359
+ return withPaddingRows(face.map((line, index) => {
360
+ const right = rightLines[index] ?? '';
361
+ const padded = PAD + line;
362
+ // Pad the face out to the split column only when something follows it, so a row whose field
363
+ // was omitted (or truncated away) does not end in a run of spaces.
364
+ return { face: right ? padToWidth(padded, RIGHT_COLUMN) : padded, right };
365
+ }));
366
+ }
367
+ /**
368
+ * Render the banner as one multi-line string for the plain (readline) surface.
369
+ *
370
+ * Colour is the 16-colour `magenta` slot from {@link ANSI_COLORS} — the SAME slot the rest of the
371
+ * CLI uses — and never a 256-colour or 24-bit escape: the 16-colour slot picks up the violet from
372
+ * the user's own terminal theme, so the sloth looks native in both light and dark schemes. The
373
+ * right column carries no escape at all, not even dim.
374
+ *
375
+ * With `colour: false` the output contains ZERO escape sequences — not one dim wrapper, not one
376
+ * reset — so a monochrome session (`useColour: false`) stays genuinely clean. The caller decides;
377
+ * the plain surface passes `getUseColour()`, the TUI passes nothing and lets Ink's own
378
+ * colour-support detection make the same call.
379
+ */
380
+ export function launchBannerText(input) {
381
+ return launchBannerRows(input)
382
+ .map(({ face, right }) => {
383
+ // A padding row is an empty line and nothing else: wrapping it in the colour pair would put
384
+ // two escapes on a row that has no glyph to paint, which the colour-off guarantee and the
385
+ // one-pair-per-row assertion both read as a bug.
386
+ if (!face && !right)
387
+ return '';
388
+ return input.colour
389
+ ? `${ANSI_COLORS.magenta}${face}${ANSI_COLORS.reset}${right}`
390
+ : face + right;
391
+ })
392
+ .join('\n');
393
+ }
394
+ /**
395
+ * Read the live fields off the process, so both surfaces report the same three values from the same
396
+ * sources instead of each picking its own. `model`/`provider` come from the caller because only it
397
+ * has the resolved config (`config.modelDisplayName` / `config.modelProviderType`); the rest are
398
+ * process-global.
399
+ *
400
+ * Fail-soft, as in `debugDump`: `getSlothVersion()` reads the installed `package.json` through the
401
+ * install dir that the CLI entry point sets, and THROWS when nothing set it (an embedded caller, a
402
+ * unit test). A missing version drops one label — it must never stop a session from starting.
403
+ */
404
+ export function launchBannerFields(model, provider) {
405
+ let version;
406
+ try {
407
+ version = getSlothVersion();
408
+ }
409
+ catch {
410
+ /* no install dir (embedded/test) — the banner simply shows no version */
411
+ }
412
+ return { version, model, provider, directory: getProjectDir(), homeDir: homedir() };
413
+ }
414
+ //# sourceMappingURL=launchBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchBanner.js","sourceRoot":"","sources":["../../src/core/launchBanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @module modelLabel
3
+ * The ONE spelling of "which model served this run", on its own so every surface can reach it.
4
+ *
5
+ * It is a leaf with **no imports at all**, and that is the point: the agent emits the compact run
6
+ * header from `GthAbstractAgent`, and pulling in the launch banner — which reaches console and
7
+ * filesystem helpers — just to format two strings would drag that graph into the agent's module
8
+ * load. A second local `${model} (${provider})` would avoid the import and is exactly what must not
9
+ * happen (DL-6): that is how one surface ends up saying `google-genai:gemini` where its neighbour
10
+ * says `gemini (google-genai)`.
11
+ */
12
+ /**
13
+ * Build the model/provider label: `model (provider)` when both resolve, the one that resolved when
14
+ * only one does, and nothing at all when neither does — an empty `()` would look like a bug.
15
+ *
16
+ * Its three renderings today are the launch banner, the review document's attribution line, and the
17
+ * `compact` run header.
18
+ */
19
+ export declare function modelProviderLabel(model?: string, provider?: string): string | undefined;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @module modelLabel
3
+ * The ONE spelling of "which model served this run", on its own so every surface can reach it.
4
+ *
5
+ * It is a leaf with **no imports at all**, and that is the point: the agent emits the compact run
6
+ * header from `GthAbstractAgent`, and pulling in the launch banner — which reaches console and
7
+ * filesystem helpers — just to format two strings would drag that graph into the agent's module
8
+ * load. A second local `${model} (${provider})` would avoid the import and is exactly what must not
9
+ * happen (DL-6): that is how one surface ends up saying `google-genai:gemini` where its neighbour
10
+ * says `gemini (google-genai)`.
11
+ */
12
+ /**
13
+ * Build the model/provider label: `model (provider)` when both resolve, the one that resolved when
14
+ * only one does, and nothing at all when neither does — an empty `()` would look like a bug.
15
+ *
16
+ * Its three renderings today are the launch banner, the review document's attribution line, and the
17
+ * `compact` run header.
18
+ */
19
+ export function modelProviderLabel(model, provider) {
20
+ const m = model?.trim();
21
+ const p = provider?.trim();
22
+ if (m && p)
23
+ return `${m} (${p})`;
24
+ return m || p || undefined;
25
+ }
26
+ //# sourceMappingURL=modelLabel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelLabel.js","sourceRoot":"","sources":["../../src/core/modelLabel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc,EAAE,QAAiB;IAClE,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IACxB,MAAM,CAAC,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;IACjC,OAAO,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface PlainToolIndicationObserver {
2
+ /** Feed every streamed message/chunk (the string path's existing loop) through this. */
3
+ observe(chunk: unknown): void;
4
+ }
5
+ /**
6
+ * Create the per-stream observer. State is scoped to one stream (one `agent.stream()` call);
7
+ * tool_call deltas are accumulated from `tool_call_chunks` (keyed by the provider's chunk
8
+ * `index`, which restarts per LLM round — the map is flushed into the by-id map whenever a
9
+ * `ToolMessage` arrives, mirroring `processEventStream`'s reset-per-round). Deliberately does
10
+ * NOT `concat()` whole `AIMessageChunk`s: only the tool-call slices are needed, which also
11
+ * sidesteps the TUI-C29 `__raw_response` aggregation-growth trap entirely.
12
+ *
13
+ * `emit` is injectable for tests; production uses the INFO-level `displayToolIndication`.
14
+ */
15
+ export declare function createPlainToolIndication(emit?: (text: string) => void): PlainToolIndicationObserver;