@gaunt-sloth/core 2.0.0-alpha.24 → 2.0.0-alpha.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/loader.d.ts +13 -6
- package/dist/config/loader.js +171 -28
- package/dist/config/loader.js.map +1 -1
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- package/dist/config/schema.d.ts +2372 -114
- package/dist/config/schema.js +845 -16
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +466 -116
- package/dist/config/shell-policy.js +273 -62
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +165 -0
- package/dist/config/tool-descriptions.js +229 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +36 -1
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +59 -1
- package/dist/core/GthAbstractAgent.js +72 -0
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +341 -58
- package/dist/core/GthAgentRunner.js +817 -149
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +16 -0
- package/dist/core/GthLangChainAgent.js +72 -2
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/annotations.d.ts +122 -0
- package/dist/core/approvals/annotations.js +137 -0
- package/dist/core/approvals/annotations.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +216 -0
- package/dist/core/approvals/grants.js +469 -0
- package/dist/core/approvals/grants.js.map +1 -0
- package/dist/core/approvals/matcher.d.ts +202 -0
- package/dist/core/approvals/matcher.js +267 -0
- package/dist/core/approvals/matcher.js.map +1 -0
- package/dist/core/approvals/mcpSubjects.d.ts +40 -0
- package/dist/core/approvals/mcpSubjects.js +99 -0
- package/dist/core/approvals/mcpSubjects.js.map +1 -0
- package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
- package/dist/core/approvals/toolAnnotationSources.js +277 -0
- package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
- package/dist/core/approvals/toolHost.d.ts +46 -0
- package/dist/core/approvals/toolHost.js +108 -0
- package/dist/core/approvals/toolHost.js.map +1 -0
- package/dist/core/launchBanner.d.ts +120 -0
- package/dist/core/launchBanner.js +399 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/plainToolIndication.js +13 -7
- package/dist/core/plainToolIndication.js.map +1 -1
- package/dist/core/shell/approvalStop.d.ts +75 -0
- package/dist/core/shell/approvalStop.js +108 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +41 -1
- package/dist/core/shell/normalize.js +58 -4
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +138 -0
- package/dist/core/shell/openWorld.js +523 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +560 -0
- package/dist/core/shell/rater.js +895 -0
- package/dist/core/shell/rater.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +41 -0
- package/dist/core/shell/raterModel.js +51 -0
- package/dist/core/shell/raterModel.js.map +1 -0
- package/dist/core/shell/rejection.d.ts +66 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/types.d.ts +71 -9
- package/dist/core/types.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +8 -7
- package/dist/runtime/askStructured.js +8 -7
- package/dist/runtime/askStructured.js.map +1 -1
- package/dist/runtime/conversation.js +89 -76
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.js +71 -60
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +30 -0
- package/dist/utils/consoleUtils.js +27 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/systemUtils.d.ts +17 -0
- package/dist/utils/systemUtils.js +17 -0
- package/dist/utils/systemUtils.js.map +1 -1
- package/package.json +5 -2
- package/schema/gsloth-config.schema.json +1216 -417
- package/dist/core/shell/allowlist.d.ts +0 -75
- package/dist/core/shell/allowlist.js +0 -187
- package/dist/core/shell/allowlist.js.map +0 -1
- package/dist/core/shell/judge.d.ts +0 -161
- package/dist/core/shell/judge.js +0 -261
- package/dist/core/shell/judge.js.map +0 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI-C40 — the resting face, under the name the animation layer refers to it by.
|
|
3
|
+
*
|
|
4
|
+
* Every frame below is the same five lines and the same 16-column field, so swapping one in changes
|
|
5
|
+
* nothing about the layout: the right column still starts at {@link RIGHT_COLUMN} and the
|
|
6
|
+
* truncation budgets are untouched. That invariant is what makes an animation a data change rather
|
|
7
|
+
* than a geometry change, and it is asserted per frame in the specs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RESTING_FACE: readonly [" ▄█▀▀▀▀▀▀▀▀█▄", "▄▀█▄█▀▀▀▀▀▀█▄█▀▄", "█ ▀█▄▀ ▀▄█▀ █", "▀▄▀▀ ██████ ▀▀▄▀", " ▀██████████▀"];
|
|
10
|
+
/** The animations a click can play. This list is the complete set. */
|
|
11
|
+
export declare const SLOTH_ANIMATIONS: readonly ['blink', 'nod', 'look-around', 'eyeroll'];
|
|
12
|
+
export type SlothAnimation = (typeof SLOTH_ANIMATIONS)[number];
|
|
13
|
+
/** One frame of an animation and how long it stays on screen. */
|
|
14
|
+
export interface SlothAnimationStep {
|
|
15
|
+
face: readonly string[];
|
|
16
|
+
holdMs: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The four sequences, as the frames shown AFTER the resting face.
|
|
20
|
+
*
|
|
21
|
+
* Playback always settles back on {@link RESTING_FACE} once the last step's hold elapses, so no
|
|
22
|
+
* sequence lists a trailing rest — but `nod` lists rest in the MIDDLE, because nodding twice means
|
|
23
|
+
* returning to level between the two dips. None of them loops, and a click plays exactly one.
|
|
24
|
+
*/
|
|
25
|
+
export declare const SLOTH_ANIMATION_STEPS: Record<SlothAnimation, readonly SlothAnimationStep[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Pick one animation at random. `random` is injectable so a test can pin the choice — the
|
|
28
|
+
* production call passes nothing and gets `Math.random`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pickSlothAnimation(random?: () => number): SlothAnimation;
|
|
31
|
+
/**
|
|
32
|
+
* Blank columns prepended to every art row (TUI-C36). One column of left margin, so the block does
|
|
33
|
+
* not sit flush against the edge of the terminal.
|
|
34
|
+
*/
|
|
35
|
+
export declare const LEFT_PAD = 1;
|
|
36
|
+
/**
|
|
37
|
+
* Column at which the right-hand column begins on EVERY row — the single number the whole layout
|
|
38
|
+
* and the colour split are expressed in terms of (margin + face + gutter = 22).
|
|
39
|
+
*/
|
|
40
|
+
export declare const RIGHT_COLUMN: number;
|
|
41
|
+
/** Column at which the version label begins on row 2 (right column + wordmark + gap = 44). */
|
|
42
|
+
export declare const VERSION_COLUMN: number;
|
|
43
|
+
/**
|
|
44
|
+
* Narrowest terminal that still gets the right column. The wordmark itself ends at column 40, so
|
|
45
|
+
* anything narrower than this leaves no usable room for the fields beside it; below the threshold
|
|
46
|
+
* the face prints alone (see the module docs on why wrapping is not an option).
|
|
47
|
+
*/
|
|
48
|
+
export declare const MIN_BANNER_COLUMNS: number;
|
|
49
|
+
/** The live values the banner reports, plus the terminal geometry it has to fit inside. */
|
|
50
|
+
export interface LaunchBannerInput {
|
|
51
|
+
/** Gaunt Sloth version, WITHOUT the `v` prefix (this module adds it). */
|
|
52
|
+
version?: string;
|
|
53
|
+
/** `config.modelDisplayName`. */
|
|
54
|
+
model?: string;
|
|
55
|
+
/** `config.modelProviderType`. */
|
|
56
|
+
provider?: string;
|
|
57
|
+
/** Working directory to report (`getProjectDir()`), before the home-prefix collapse. */
|
|
58
|
+
directory?: string;
|
|
59
|
+
/** The user's home dir; when `directory` sits under it the prefix collapses to `~`. */
|
|
60
|
+
homeDir?: string;
|
|
61
|
+
/** Live `stdout.columns`; `undefined` (non-TTY / tests) falls back to {@link DEFAULT_COLUMNS}. */
|
|
62
|
+
columns?: number;
|
|
63
|
+
/** Emit ANSI colour. Only {@link launchBannerText} reads this; Ink colours rows itself. */
|
|
64
|
+
colour?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* TUI-C40 — which face to draw. Defaults to {@link RESTING_FACE}, so every existing caller is
|
|
67
|
+
* unaffected and a plain launch is byte-identical to before. An animation frame differs only in
|
|
68
|
+
* these five lines: the geometry below reads the face's width from the constants, never from the
|
|
69
|
+
* strings, so a swapped frame cannot move the right column or change a truncation budget.
|
|
70
|
+
*/
|
|
71
|
+
face?: readonly string[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* One rendered banner row, pre-split at {@link RIGHT_COLUMN} so each surface can colour the two
|
|
75
|
+
* halves independently without re-deriving the geometry.
|
|
76
|
+
*/
|
|
77
|
+
export interface LaunchBannerRow {
|
|
78
|
+
/**
|
|
79
|
+
* Columns 0–21: the {@link LEFT_PAD} margin and the face, right-padded to {@link RIGHT_COLUMN}
|
|
80
|
+
* when this row has right-hand content (and left unpadded when it does not, so no row ends in
|
|
81
|
+
* trailing whitespace). Rendered in magenta — the margin is blank, so painting it is a no-op.
|
|
82
|
+
* Empty on the two blank padding rows, which carry nothing at all.
|
|
83
|
+
*/
|
|
84
|
+
face: string;
|
|
85
|
+
/** Columns 21+: wordmark, version, model/provider or directory. Default foreground, no colour. */
|
|
86
|
+
right: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Assemble the banner as {@link LaunchBannerRow}s: a blank row, the five art rows each pre-split at
|
|
90
|
+
* {@link RIGHT_COLUMN}, and a blank row. Pure — every input arrives as an argument, so the geometry
|
|
91
|
+
* is testable without a terminal, a config or a process.
|
|
92
|
+
*/
|
|
93
|
+
export declare function launchBannerRows(input: LaunchBannerInput): LaunchBannerRow[];
|
|
94
|
+
/**
|
|
95
|
+
* Render the banner as one multi-line string for the plain (readline) surface.
|
|
96
|
+
*
|
|
97
|
+
* Colour is the 16-colour `magenta` slot from {@link ANSI_COLORS} — the SAME slot the rest of the
|
|
98
|
+
* CLI uses — and never a 256-colour or 24-bit escape: the 16-colour slot picks up the violet from
|
|
99
|
+
* the user's own terminal theme, so the sloth looks native in both light and dark schemes. The
|
|
100
|
+
* right column carries no escape at all, not even dim.
|
|
101
|
+
*
|
|
102
|
+
* With `colour: false` the output contains ZERO escape sequences — not one dim wrapper, not one
|
|
103
|
+
* reset — so a monochrome session (`useColour: false`) stays genuinely clean. The caller decides;
|
|
104
|
+
* the plain surface passes `getUseColour()`, the TUI passes nothing and lets Ink's own
|
|
105
|
+
* colour-support detection make the same call.
|
|
106
|
+
*/
|
|
107
|
+
export declare function launchBannerText(input: LaunchBannerInput): string;
|
|
108
|
+
/** The process-resolved half of {@link LaunchBannerInput} — everything but the geometry. */
|
|
109
|
+
export type LaunchBannerFields = Pick<LaunchBannerInput, 'version' | 'model' | 'provider' | 'directory' | 'homeDir'>;
|
|
110
|
+
/**
|
|
111
|
+
* Read the live fields off the process, so both surfaces report the same three values from the same
|
|
112
|
+
* sources instead of each picking its own. `model`/`provider` come from the caller because only it
|
|
113
|
+
* has the resolved config (`config.modelDisplayName` / `config.modelProviderType`); the rest are
|
|
114
|
+
* process-global.
|
|
115
|
+
*
|
|
116
|
+
* Fail-soft, as in `debugDump`: `getSlothVersion()` reads the installed `package.json` through the
|
|
117
|
+
* install dir that the CLI entry point sets, and THROWS when nothing set it (an embedded caller, a
|
|
118
|
+
* unit test). A missing version drops one label — it must never stop a session from starting.
|
|
119
|
+
*/
|
|
120
|
+
export declare function launchBannerFields(model?: string, provider?: string): LaunchBannerFields;
|
|
@@ -0,0 +1,399 @@
|
|
|
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
|
+
* The model/provider and directory lines TRUNCATE to that budget: a clipped model id or path is
|
|
65
|
+
* still honest, it visibly ends in `…`. The directory is truncated from the LEFT
|
|
66
|
+
* (`…/dev/takahe`), because the leaf directory is the informative end; the model keeps its head and
|
|
67
|
+
* loses its tail.
|
|
68
|
+
*
|
|
69
|
+
* A VERSION that does not fit is dropped entirely instead ({@link fitOrDrop}) — never truncated.
|
|
70
|
+
* A clipped version number is not merely less informative, it is MISLEADING: `v2.0…` reads as a
|
|
71
|
+
* real, different version (2.0.1, say) rather than as a clipped `2.0.0-alpha.25`, and a wrong
|
|
72
|
+
* version in a bug report costs more than an absent one. The model/provider line already sets that
|
|
73
|
+
* drop-rather-than-mislead precedent by omitting itself when nothing resolves.
|
|
74
|
+
*
|
|
75
|
+
* Below {@link MIN_BANNER_COLUMNS} the right column cannot fit at all, so the face prints alone
|
|
76
|
+
* rather than wrapping the wordmark into rubble.
|
|
77
|
+
*/
|
|
78
|
+
import { homedir } from 'node:os';
|
|
79
|
+
import { ELLIPSIS } from '#src/core/toolDisplay.js';
|
|
80
|
+
import { ANSI_COLORS } from '#src/utils/consoleUtils.js';
|
|
81
|
+
import { getProjectDir, getSlothVersion } from '#src/utils/systemUtils.js';
|
|
82
|
+
/**
|
|
83
|
+
* The sloth face, columns 0–15. Lines 1 and 5 are 14 columns, lines 2–4 are 16 — the face field is
|
|
84
|
+
* 16 wide and the short lines simply do not reach the edge. Do not "tidy" the leading spaces: they
|
|
85
|
+
* are part of the art.
|
|
86
|
+
*/
|
|
87
|
+
const FACE_LINES = [
|
|
88
|
+
' ▄█▀▀▀▀▀▀▀▀█▄',
|
|
89
|
+
'▄▀█▄█▀▀▀▀▀▀█▄█▀▄',
|
|
90
|
+
'█ ▀█▄▀ ▀▄█▀ █',
|
|
91
|
+
'▀▄▀▀ ██████ ▀▀▄▀',
|
|
92
|
+
' ▀██████████▀',
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* TUI-C40 — the resting face, under the name the animation layer refers to it by.
|
|
96
|
+
*
|
|
97
|
+
* Every frame below is the same five lines and the same 16-column field, so swapping one in changes
|
|
98
|
+
* nothing about the layout: the right column still starts at {@link RIGHT_COLUMN} and the
|
|
99
|
+
* truncation budgets are untouched. That invariant is what makes an animation a data change rather
|
|
100
|
+
* than a geometry change, and it is asserted per frame in the specs.
|
|
101
|
+
*/
|
|
102
|
+
export const RESTING_FACE = FACE_LINES;
|
|
103
|
+
/**
|
|
104
|
+
* The extra faces, authored in takahē (`websites/ascii-art.md`) and transcribed here so the art has
|
|
105
|
+
* one home in this repo, exactly as the resting frame does. Names match the source file's blocks.
|
|
106
|
+
*/
|
|
107
|
+
const BLINK_HALF = [
|
|
108
|
+
' ▄█▀▀▀▀▀▀▀██▄',
|
|
109
|
+
'▄▀█▄█▀▀▀▀▀▀███▀▄',
|
|
110
|
+
'█ ▀█▄▀ ▀▄█▀ █',
|
|
111
|
+
'▀▄▀▀▀▐████▌▀▀▀▄▀',
|
|
112
|
+
' ▀██████████▀',
|
|
113
|
+
];
|
|
114
|
+
const BLINK_SHUT = [
|
|
115
|
+
' ▄█▀▀▀▀▀▀███▄',
|
|
116
|
+
'▄▀█▄█▀▀▀▀▀▀███▀▄',
|
|
117
|
+
'█ ▀█▄▀ ▀▄█▀ █',
|
|
118
|
+
'▀▄▀▀▀▐████▌ ▄▀',
|
|
119
|
+
' ▀██████████▀',
|
|
120
|
+
];
|
|
121
|
+
const NOD_DOWN = [
|
|
122
|
+
' ▄██████████▄',
|
|
123
|
+
'▄██▀▄▄▄▄▄▄▄▄▀██▄',
|
|
124
|
+
'█ ▀██ ▄ ▄ ██▀ █',
|
|
125
|
+
'█ ▄▄▀█▄▄▄▄█▀▄▄ █',
|
|
126
|
+
' ▄▄▄██████▄▄▄',
|
|
127
|
+
];
|
|
128
|
+
const LOOK_LEFT = [
|
|
129
|
+
' ▄██████████▄',
|
|
130
|
+
'▄██▀▄▄▄▄▄▄▄████▄',
|
|
131
|
+
'█ ▀██ ▄ ▄ ██▀ █',
|
|
132
|
+
'█ ▄▀█▄▄▄▄█▀ ▄ █',
|
|
133
|
+
' ▄▄▄██████▄▄▄',
|
|
134
|
+
];
|
|
135
|
+
const LOOK_RIGHT = [
|
|
136
|
+
' ▄██████████▄',
|
|
137
|
+
'▄████▄▄▄▄▄▄▄▀██▄',
|
|
138
|
+
'█ ▀██ ▄ ▄ ██▀ █',
|
|
139
|
+
'█ ▄ ▀█▄▄▄▄█▀▄ █',
|
|
140
|
+
' ▄▄▄██████▄▄▄',
|
|
141
|
+
];
|
|
142
|
+
const EYEROLL = [
|
|
143
|
+
' ▄█▀▀▀▀▀▀▀▀█▄',
|
|
144
|
+
'▄▀█▄█▀▀▀▀▀▀█▄█▀▄',
|
|
145
|
+
'█ ▀██▄▀ ▀▄██▀ █',
|
|
146
|
+
'▀▌ ▄ ▐████▌ ▄ ▐▀',
|
|
147
|
+
' ▀██████████▀',
|
|
148
|
+
];
|
|
149
|
+
/** The animations a click can play. This list is the complete set. */
|
|
150
|
+
export const SLOTH_ANIMATIONS = ['blink', 'nod', 'look-around', 'eyeroll'];
|
|
151
|
+
/**
|
|
152
|
+
* The four sequences, as the frames shown AFTER the resting face.
|
|
153
|
+
*
|
|
154
|
+
* Playback always settles back on {@link RESTING_FACE} once the last step's hold elapses, so no
|
|
155
|
+
* sequence lists a trailing rest — but `nod` lists rest in the MIDDLE, because nodding twice means
|
|
156
|
+
* returning to level between the two dips. None of them loops, and a click plays exactly one.
|
|
157
|
+
*/
|
|
158
|
+
export const SLOTH_ANIMATION_STEPS = {
|
|
159
|
+
// Quick, because a blink that lingers reads as a squint.
|
|
160
|
+
blink: [
|
|
161
|
+
{ face: BLINK_HALF, holdMs: 90 },
|
|
162
|
+
{ face: BLINK_SHUT, holdMs: 110 },
|
|
163
|
+
],
|
|
164
|
+
nod: [
|
|
165
|
+
{ face: NOD_DOWN, holdMs: 160 },
|
|
166
|
+
{ face: RESTING_FACE, holdMs: 130 },
|
|
167
|
+
{ face: NOD_DOWN, holdMs: 160 },
|
|
168
|
+
],
|
|
169
|
+
'look-around': [
|
|
170
|
+
{ face: LOOK_LEFT, holdMs: 190 },
|
|
171
|
+
{ face: LOOK_RIGHT, holdMs: 230 },
|
|
172
|
+
],
|
|
173
|
+
// The longest hold: the joke only lands if you get to see it.
|
|
174
|
+
eyeroll: [{ face: EYEROLL, holdMs: 300 }],
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Pick one animation at random. `random` is injectable so a test can pin the choice — the
|
|
178
|
+
* production call passes nothing and gets `Math.random`.
|
|
179
|
+
*/
|
|
180
|
+
export function pickSlothAnimation(random = Math.random) {
|
|
181
|
+
const index = Math.floor(random() * SLOTH_ANIMATIONS.length);
|
|
182
|
+
// Guard the boundary: a `random()` of exactly 1 (or a stub returning it) would index past the end.
|
|
183
|
+
return SLOTH_ANIMATIONS[Math.min(index, SLOTH_ANIMATIONS.length - 1)];
|
|
184
|
+
}
|
|
185
|
+
/** The `GAUNT SLOTH` wordmark, occupying rows 1–3 of the right column. */
|
|
186
|
+
const WORDMARK_LINES = [
|
|
187
|
+
'┏┓ ┏┓┓ ┓',
|
|
188
|
+
'┃┓┏┓┓┏┏┓╋ ┗┓┃┏┓╋┣┓',
|
|
189
|
+
'┗┛┗┻┗┻┛┗┗ ┗┛┗┗┛┗┛┗',
|
|
190
|
+
];
|
|
191
|
+
/** Widest wordmark line (19); rows 1 and 3 are padded to it so the version label lands square. */
|
|
192
|
+
const WORDMARK_WIDTH = Math.max(...WORDMARK_LINES.map((line) => [...line].length));
|
|
193
|
+
/** Blank columns between the wordmark and the version label. */
|
|
194
|
+
const VERSION_GAP = 3;
|
|
195
|
+
/**
|
|
196
|
+
* Blank columns prepended to every art row (TUI-C36). One column of left margin, so the block does
|
|
197
|
+
* not sit flush against the edge of the terminal.
|
|
198
|
+
*/
|
|
199
|
+
export const LEFT_PAD = 1;
|
|
200
|
+
/** The prepended margin itself, applied to art rows only — never to the blank padding rows. */
|
|
201
|
+
const PAD = ' '.repeat(LEFT_PAD);
|
|
202
|
+
/** Columns the face field occupies, after the margin. */
|
|
203
|
+
const FACE_WIDTH = 16;
|
|
204
|
+
/** Blank columns between the face field and the right column. */
|
|
205
|
+
const FACE_GAP = 5;
|
|
206
|
+
/**
|
|
207
|
+
* Column at which the right-hand column begins on EVERY row — the single number the whole layout
|
|
208
|
+
* and the colour split are expressed in terms of (margin + face + gutter = 22).
|
|
209
|
+
*/
|
|
210
|
+
export const RIGHT_COLUMN = LEFT_PAD + FACE_WIDTH + FACE_GAP;
|
|
211
|
+
/** Column at which the version label begins on row 2 (right column + wordmark + gap = 44). */
|
|
212
|
+
export const VERSION_COLUMN = RIGHT_COLUMN + WORDMARK_WIDTH + VERSION_GAP;
|
|
213
|
+
/**
|
|
214
|
+
* Field budget the narrowest banner still affords the right column — the widest wordmark line plus
|
|
215
|
+
* a few columns of field, which is what the original threshold of 45 amounted to at
|
|
216
|
+
* {@link RIGHT_COLUMN} 21.
|
|
217
|
+
*/
|
|
218
|
+
const MIN_FIELD_BUDGET = 24;
|
|
219
|
+
/**
|
|
220
|
+
* Narrowest terminal that still gets the right column. The wordmark itself ends at column 40, so
|
|
221
|
+
* anything narrower than this leaves no usable room for the fields beside it; below the threshold
|
|
222
|
+
* the face prints alone (see the module docs on why wrapping is not an option).
|
|
223
|
+
*/
|
|
224
|
+
export const MIN_BANNER_COLUMNS = RIGHT_COLUMN + MIN_FIELD_BUDGET;
|
|
225
|
+
/** Width assumed when the terminal width is unknown (non-TTY / tests) — as in `ruleWidth`. */
|
|
226
|
+
const DEFAULT_COLUMNS = 80;
|
|
227
|
+
/** Resolve the usable terminal width, mirroring `ruleWidth`'s unknown/non-finite handling. */
|
|
228
|
+
function resolveColumns(columns) {
|
|
229
|
+
const cols = typeof columns === 'number' && Number.isFinite(columns) ? columns : DEFAULT_COLUMNS;
|
|
230
|
+
return Math.floor(cols);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Truncate `text` to `budget` columns, losing the TAIL (`gemini-3.1-pro-experi…`). Returns
|
|
234
|
+
* `undefined` only when there is no budget at all; that guard keeps the function total (at
|
|
235
|
+
* `budget === 0` the slice below would otherwise drop a character and return a bare marker) and is
|
|
236
|
+
* not reachable from {@link launchBannerRows}, whose narrowest field budget is `45 - 21 = 24`.
|
|
237
|
+
*
|
|
238
|
+
* Slicing goes through `[...text]` rather than `String.slice` so a value containing an astral
|
|
239
|
+
* character (an emoji in a directory name) cannot be cut in half into a lone surrogate.
|
|
240
|
+
*/
|
|
241
|
+
function truncateTail(text, budget) {
|
|
242
|
+
if (budget <= 0)
|
|
243
|
+
return undefined;
|
|
244
|
+
const chars = [...text];
|
|
245
|
+
if (chars.length <= budget)
|
|
246
|
+
return text;
|
|
247
|
+
return chars.slice(0, budget - 1).join('') + ELLIPSIS;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Truncate `text` to `budget` columns, losing the HEAD (`…/dev/takahe`). Used for the directory,
|
|
251
|
+
* where the leaf is what the user needs and the ancestors are noise.
|
|
252
|
+
*/
|
|
253
|
+
function truncateHead(text, budget) {
|
|
254
|
+
if (budget <= 0)
|
|
255
|
+
return undefined;
|
|
256
|
+
const chars = [...text];
|
|
257
|
+
if (chars.length <= budget)
|
|
258
|
+
return text;
|
|
259
|
+
return ELLIPSIS + chars.slice(chars.length - (budget - 1)).join('');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* All-or-nothing: `text` if it fits `budget` columns in full, otherwise `undefined`. Used for the
|
|
263
|
+
* VERSION label, which must never be truncated — see the module docs on why a clipped version
|
|
264
|
+
* number is misleading rather than merely terse.
|
|
265
|
+
*/
|
|
266
|
+
function fitOrDrop(text, budget) {
|
|
267
|
+
return [...text].length <= budget ? text : undefined;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Collapse the home-directory prefix to `~`, the way a shell prompt does — a deep path spends its
|
|
271
|
+
* whole budget on `/Users/<name>` otherwise.
|
|
272
|
+
*
|
|
273
|
+
* Both separators are accepted deliberately: on Windows `homedir()` reads `%USERPROFILE%` and the
|
|
274
|
+
* path arrives back-slashed, and hard-coding POSIX `/` here is exactly the win32-only bug this repo
|
|
275
|
+
* keeps re-learning. The boundary check matters too — `/home/mar` must not collapse inside
|
|
276
|
+
* `/home/maria/x`.
|
|
277
|
+
*/
|
|
278
|
+
function collapseHomePrefix(directory, homeDir) {
|
|
279
|
+
if (!homeDir)
|
|
280
|
+
return directory;
|
|
281
|
+
if (directory === homeDir)
|
|
282
|
+
return '~';
|
|
283
|
+
if (!directory.startsWith(homeDir))
|
|
284
|
+
return directory;
|
|
285
|
+
const boundary = directory.charAt(homeDir.length);
|
|
286
|
+
if (boundary !== '/' && boundary !== '\\')
|
|
287
|
+
return directory;
|
|
288
|
+
return '~' + directory.slice(homeDir.length);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Build the model/provider line: `model (provider)` when both resolve, the one that resolved when
|
|
292
|
+
* only one does, and nothing at all when neither does — an empty `()` would look like a bug.
|
|
293
|
+
*/
|
|
294
|
+
function modelProviderLabel(model, provider) {
|
|
295
|
+
const m = model?.trim();
|
|
296
|
+
const p = provider?.trim();
|
|
297
|
+
if (m && p)
|
|
298
|
+
return `${m} (${p})`;
|
|
299
|
+
return m || p || undefined;
|
|
300
|
+
}
|
|
301
|
+
/** A padding row: no margin and no content, so it is an empty line on every surface. */
|
|
302
|
+
function blankRow() {
|
|
303
|
+
return { face: '', right: '' };
|
|
304
|
+
}
|
|
305
|
+
/** Wrap the five art rows in the blank row above and below that make the block a splash. */
|
|
306
|
+
function withPaddingRows(art) {
|
|
307
|
+
return [blankRow(), ...art, blankRow()];
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Assemble the banner as {@link LaunchBannerRow}s: a blank row, the five art rows each pre-split at
|
|
311
|
+
* {@link RIGHT_COLUMN}, and a blank row. Pure — every input arrives as an argument, so the geometry
|
|
312
|
+
* is testable without a terminal, a config or a process.
|
|
313
|
+
*/
|
|
314
|
+
export function launchBannerRows(input) {
|
|
315
|
+
const columns = resolveColumns(input.columns);
|
|
316
|
+
// TUI-C40 — the face is the one part a caller may swap; everything below is unchanged geometry.
|
|
317
|
+
const face = input.face ?? RESTING_FACE;
|
|
318
|
+
// Too narrow for the right column: the face alone, with no fields. It keeps its left margin and
|
|
319
|
+
// its blank rows — the padding is what makes it a splash, and it costs one column, not the
|
|
320
|
+
// gutter's five.
|
|
321
|
+
if (columns < MIN_BANNER_COLUMNS) {
|
|
322
|
+
return withPaddingRows(face.map((line) => ({ face: PAD + line, right: '' })));
|
|
323
|
+
}
|
|
324
|
+
// Every field is budgeted against ITS OWN start column, because the version starts further right
|
|
325
|
+
// than the other two. The version is the only fit-or-drop field; the other two truncate.
|
|
326
|
+
const fieldBudget = columns - RIGHT_COLUMN;
|
|
327
|
+
const version = input.version?.trim()
|
|
328
|
+
? fitOrDrop(`v${input.version.trim()}`, columns - VERSION_COLUMN)
|
|
329
|
+
: undefined;
|
|
330
|
+
const model = modelProviderLabel(input.model, input.provider);
|
|
331
|
+
const modelLine = model ? truncateTail(model, fieldBudget) : undefined;
|
|
332
|
+
const directory = input.directory?.trim()
|
|
333
|
+
? truncateHead(collapseHomePrefix(input.directory.trim(), input.homeDir), fieldBudget)
|
|
334
|
+
: undefined;
|
|
335
|
+
const rightLines = [
|
|
336
|
+
WORDMARK_LINES[0],
|
|
337
|
+
version
|
|
338
|
+
? WORDMARK_LINES[1].padEnd(WORDMARK_WIDTH) + ' '.repeat(VERSION_GAP) + version
|
|
339
|
+
: WORDMARK_LINES[1],
|
|
340
|
+
WORDMARK_LINES[2],
|
|
341
|
+
modelLine ?? '',
|
|
342
|
+
directory ?? '',
|
|
343
|
+
];
|
|
344
|
+
return withPaddingRows(face.map((line, index) => {
|
|
345
|
+
const right = rightLines[index] ?? '';
|
|
346
|
+
const padded = PAD + line;
|
|
347
|
+
// Pad the face out to the split column only when something follows it, so a row whose field
|
|
348
|
+
// was omitted (or truncated away) does not end in a run of spaces.
|
|
349
|
+
return { face: right ? padded.padEnd(RIGHT_COLUMN) : padded, right };
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Render the banner as one multi-line string for the plain (readline) surface.
|
|
354
|
+
*
|
|
355
|
+
* Colour is the 16-colour `magenta` slot from {@link ANSI_COLORS} — the SAME slot the rest of the
|
|
356
|
+
* CLI uses — and never a 256-colour or 24-bit escape: the 16-colour slot picks up the violet from
|
|
357
|
+
* the user's own terminal theme, so the sloth looks native in both light and dark schemes. The
|
|
358
|
+
* right column carries no escape at all, not even dim.
|
|
359
|
+
*
|
|
360
|
+
* With `colour: false` the output contains ZERO escape sequences — not one dim wrapper, not one
|
|
361
|
+
* reset — so a monochrome session (`useColour: false`) stays genuinely clean. The caller decides;
|
|
362
|
+
* the plain surface passes `getUseColour()`, the TUI passes nothing and lets Ink's own
|
|
363
|
+
* colour-support detection make the same call.
|
|
364
|
+
*/
|
|
365
|
+
export function launchBannerText(input) {
|
|
366
|
+
return launchBannerRows(input)
|
|
367
|
+
.map(({ face, right }) => {
|
|
368
|
+
// A padding row is an empty line and nothing else: wrapping it in the colour pair would put
|
|
369
|
+
// two escapes on a row that has no glyph to paint, which the colour-off guarantee and the
|
|
370
|
+
// one-pair-per-row assertion both read as a bug.
|
|
371
|
+
if (!face && !right)
|
|
372
|
+
return '';
|
|
373
|
+
return input.colour
|
|
374
|
+
? `${ANSI_COLORS.magenta}${face}${ANSI_COLORS.reset}${right}`
|
|
375
|
+
: face + right;
|
|
376
|
+
})
|
|
377
|
+
.join('\n');
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Read the live fields off the process, so both surfaces report the same three values from the same
|
|
381
|
+
* sources instead of each picking its own. `model`/`provider` come from the caller because only it
|
|
382
|
+
* has the resolved config (`config.modelDisplayName` / `config.modelProviderType`); the rest are
|
|
383
|
+
* process-global.
|
|
384
|
+
*
|
|
385
|
+
* Fail-soft, as in `debugDump`: `getSlothVersion()` reads the installed `package.json` through the
|
|
386
|
+
* install dir that the CLI entry point sets, and THROWS when nothing set it (an embedded caller, a
|
|
387
|
+
* unit test). A missing version drops one label — it must never stop a session from starting.
|
|
388
|
+
*/
|
|
389
|
+
export function launchBannerFields(model, provider) {
|
|
390
|
+
let version;
|
|
391
|
+
try {
|
|
392
|
+
version = getSlothVersion();
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
/* no install dir (embedded/test) — the banner simply shows no version */
|
|
396
|
+
}
|
|
397
|
+
return { version, model, provider, directory: getProjectDir(), homeDir: homedir() };
|
|
398
|
+
}
|
|
399
|
+
//# 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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"}
|
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
* Stream discipline (matches how the plain surface prints tool activity today): the block is
|
|
16
16
|
* emitted at INFO level through `displayToolIndication` — same stdout channel, same
|
|
17
17
|
* `consoleLevel` gate and session-log treatment as the existing tool notices — so scripted
|
|
18
|
-
* consumers that already silence INFO chatter silence this too. Colour is used
|
|
19
|
-
* `useColour`
|
|
20
|
-
*
|
|
18
|
+
* consumers that already silence INFO chatter silence this too. Colour is used exactly when the
|
|
19
|
+
* resolved `useColour` (the CFG-30 ladder in `config/colour.ts`) says so — TUI-C35 removed the
|
|
20
|
+
* local `&& stdout.isTTY` narrowing this module used to apply on top, which was redundant against
|
|
21
|
+
* the ladder's own rung-4 TTY auto-detection everywhere except `FORCE_COLOR` on a pipe, the one
|
|
22
|
+
* case that variable exists to serve. An ordinary piped run is therefore still clean monochrome
|
|
23
|
+
* (DL-7) — rung 4 decides that — with diff lines readable via their `+`/`-` prefixes.
|
|
21
24
|
*
|
|
22
25
|
* Live-output dedupe: shell-shaped results (`<COMMAND_OUTPUT>`) belong to tools whose child
|
|
23
26
|
* output ALREADY streamed raw via the channel's default sink, so those render with
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
import { AIMessage, AIMessageChunk, ToolMessage } from '@langchain/core/messages';
|
|
28
31
|
import { buildToolPreviewLines, getToolGlyph, isShellShapedResult, renderToolLineAnsi, summariseToolCall, } from '#src/core/toolDisplay.js';
|
|
29
32
|
import { displayToolIndication } from '#src/utils/consoleUtils.js';
|
|
30
|
-
import { getUseColour
|
|
33
|
+
import { getUseColour } from '#src/utils/systemUtils.js';
|
|
31
34
|
const INDENT = ' ';
|
|
32
35
|
/**
|
|
33
36
|
* Create the per-stream observer. State is scoped to one stream (one `agent.stream()` call);
|
|
@@ -59,9 +62,12 @@ export function createPlainToolIndication(emit = displayToolIndication) {
|
|
|
59
62
|
const name = tracked?.name || (typeof message.name === 'string' ? message.name : '') || '';
|
|
60
63
|
const result = typeof message.content === 'string' ? message.content : JSON.stringify(message.content);
|
|
61
64
|
const isError = message.status === 'error';
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
+
// TUI-C35 — colour is exactly what the resolved ladder says, with no local narrowing.
|
|
66
|
+
// This used to AND in `stdout.isTTY`, which was redundant in every case but one: rung 4 of
|
|
67
|
+
// `config/colour.ts` already auto-detects from stdout's TTY status, so an unconfigured piped
|
|
68
|
+
// run is monochrome either way. The one case it changed was `FORCE_COLOR` into a pipe — which
|
|
69
|
+
// it suppressed, defeating the only thing that variable is for.
|
|
70
|
+
const colour = getUseColour();
|
|
65
71
|
const statusGlyph = isError
|
|
66
72
|
? colour
|
|
67
73
|
? '\x1b[31m✗\x1b[0m'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plainToolIndication.js","sourceRoot":"","sources":["../../src/core/plainToolIndication.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plainToolIndication.js","sourceRoot":"","sources":["../../src/core/plainToolIndication.ts"],"names":[],"mappings":"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"}
|