pi-minimalist 0.1.2 → 0.2.2
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 +26 -5
- package/index.ts +13 -35
- package/package.json +1 -1
- package/src/bridge.ts +76 -55
- package/src/components.ts +97 -32
- package/src/config-file.ts +26 -81
- package/src/config-ui.ts +42 -34
- package/src/config.ts +59 -37
- package/src/core-patch.ts +167 -178
- package/src/row.ts +44 -28
- package/src/run-grouping.ts +98 -52
- package/src/thinking.ts +5 -8
- package/src/tool-renderer.ts +21 -10
- package/src/tools.ts +57 -42
package/src/config-file.ts
CHANGED
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* WHY settings.json AND NOT OUR OWN FILE
|
|
5
5
|
* --------------------------------------
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* be read back. settings.json is where a Pi user already looks, it is what
|
|
10
|
-
* `pi-env` reads, and Pi tolerates unknown top-level keys (the published
|
|
11
|
-
* `pi-powerline-footer` extension does exactly this with its `powerline` key).
|
|
6
|
+
* It is where a Pi user already looks, it is what `pi-env` reads, and Pi
|
|
7
|
+
* tolerates unknown top-level keys (the published `pi-powerline-footer`
|
|
8
|
+
* extension does exactly this with its `powerline` key).
|
|
12
9
|
*
|
|
13
10
|
* SCOPE
|
|
14
11
|
* defaults < global agent settings.json (PI_CODING_AGENT_DIR if set).
|
|
@@ -29,6 +26,11 @@ import {
|
|
|
29
26
|
DEFAULTS,
|
|
30
27
|
DEFAULT_TOKENS,
|
|
31
28
|
GLYPH_PRESETS,
|
|
29
|
+
FRESH_PRESET,
|
|
30
|
+
LOOK_KEYS,
|
|
31
|
+
PRESETS,
|
|
32
|
+
isFresh,
|
|
33
|
+
isPreset,
|
|
32
34
|
SETTINGS_KEY,
|
|
33
35
|
type BasicSettings,
|
|
34
36
|
type Glyphs,
|
|
@@ -52,11 +54,6 @@ export function globalSettingsPath(env = process.env): string {
|
|
|
52
54
|
return join(agentDir(env), "settings.json");
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
/** Legacy state file written by the removed `/quiet` command. */
|
|
56
|
-
export function legacyQuietPath(env = process.env): string {
|
|
57
|
-
return join(agentDir(env), "pi-minimalist.json");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
57
|
type Json = Record<string, unknown>;
|
|
61
58
|
|
|
62
59
|
function isRecord(value: unknown): value is Json {
|
|
@@ -70,59 +67,12 @@ function isRecord(value: unknown): value is Json {
|
|
|
70
67
|
/**
|
|
71
68
|
* Remove `//` and block comments that are outside string literals.
|
|
72
69
|
*
|
|
73
|
-
* Pi bundles `strip-json-comments` but does not re-export it to extensions
|
|
74
|
-
*
|
|
75
|
-
*
|
|
70
|
+
* Pi bundles `strip-json-comments` but does not re-export it to extensions. The
|
|
71
|
+
* regex matches a whole string first (escapes included) and keeps it, so `//`
|
|
72
|
+
* inside a URL or path never starts a comment.
|
|
76
73
|
*/
|
|
77
74
|
export function stripJsonComments(text: string): string {
|
|
78
|
-
|
|
79
|
-
let inString = false;
|
|
80
|
-
let escaped = false;
|
|
81
|
-
let comment: "line" | "block" | undefined;
|
|
82
|
-
|
|
83
|
-
for (let i = 0; i < text.length; i++) {
|
|
84
|
-
const char = text[i];
|
|
85
|
-
const next = text[i + 1];
|
|
86
|
-
|
|
87
|
-
if (comment === "line") {
|
|
88
|
-
if (char === "\n") {
|
|
89
|
-
comment = undefined;
|
|
90
|
-
out += char;
|
|
91
|
-
}
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (comment === "block") {
|
|
95
|
-
if (char === "*" && next === "/") {
|
|
96
|
-
comment = undefined;
|
|
97
|
-
i++;
|
|
98
|
-
}
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (inString) {
|
|
102
|
-
out += char;
|
|
103
|
-
if (escaped) escaped = false;
|
|
104
|
-
else if (char === "\\") escaped = true;
|
|
105
|
-
else if (char === '"') inString = false;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (char === '"') {
|
|
109
|
-
inString = true;
|
|
110
|
-
out += char;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (char === "/" && next === "/") {
|
|
114
|
-
comment = "line";
|
|
115
|
-
i++;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (char === "/" && next === "*") {
|
|
119
|
-
comment = "block";
|
|
120
|
-
i++;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
out += char;
|
|
124
|
-
}
|
|
125
|
-
return out;
|
|
75
|
+
return text.replace(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_, string) => string ?? "");
|
|
126
76
|
}
|
|
127
77
|
|
|
128
78
|
/** True when a file carries comments, so writing it back would destroy them. */
|
|
@@ -153,8 +103,10 @@ function readSettingsFile(path: string): Json {
|
|
|
153
103
|
* this is hand-edited input.
|
|
154
104
|
*/
|
|
155
105
|
function coerce(raw: unknown, into: Settings): Settings {
|
|
156
|
-
if (!isRecord(raw)) return into;
|
|
157
106
|
const out = { ...into };
|
|
107
|
+
// A user with no basic key has never configured anything: they get the default preset.
|
|
108
|
+
if (isFresh(raw)) out.preset = FRESH_PRESET;
|
|
109
|
+
if (!isRecord(raw)) return out;
|
|
158
110
|
|
|
159
111
|
for (const key of BASIC_KEYS) {
|
|
160
112
|
if (typeof DEFAULT_BASIC[key] === "boolean" && typeof raw[key] === "boolean") {
|
|
@@ -169,6 +121,7 @@ function coerce(raw: unknown, into: Settings): Settings {
|
|
|
169
121
|
if (Array.isArray(raw.excludeTools)) {
|
|
170
122
|
out.excludeTools = raw.excludeTools.filter((name): name is string => typeof name === "string");
|
|
171
123
|
}
|
|
124
|
+
if (isPreset(raw.preset)) out.preset = raw.preset;
|
|
172
125
|
if (raw.glyphStyle === "unicode" || raw.glyphStyle === "ascii") {
|
|
173
126
|
out.glyphStyle = raw.glyphStyle;
|
|
174
127
|
}
|
|
@@ -183,9 +136,6 @@ function coerce(raw: unknown, into: Settings): Settings {
|
|
|
183
136
|
// therefore the honest boundary of what this function can check.
|
|
184
137
|
out.tokens = pickStrings(raw.tokens, Object.keys(DEFAULT_TOKENS) as (keyof Tokens)[]) as Partial<Tokens>;
|
|
185
138
|
}
|
|
186
|
-
if (typeof raw.maxDetailChars === "number" && raw.maxDetailChars > 0) {
|
|
187
|
-
out.maxDetailChars = Math.floor(raw.maxDetailChars);
|
|
188
|
-
}
|
|
189
139
|
return out;
|
|
190
140
|
}
|
|
191
141
|
|
|
@@ -205,21 +155,14 @@ export function loadSettings(env = process.env): Settings {
|
|
|
205
155
|
}
|
|
206
156
|
|
|
207
157
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* explicit new setting always wins and a stale legacy file cannot resurrect a
|
|
212
|
-
* preference the user has since changed.
|
|
158
|
+
* Look keys to write when the user switches to `custom` for the first time: the
|
|
159
|
+
* `lite` look, so "custom" starts from something known. Undefined when the file
|
|
160
|
+
* already holds any of the user's own look keys (they are kept untouched).
|
|
213
161
|
*/
|
|
214
|
-
export function
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const legacy = JSON.parse(readFileSync(legacyQuietPath(env), "utf8"));
|
|
219
|
-
return isRecord(legacy) && legacy.quiet === true ? true : undefined;
|
|
220
|
-
} catch {
|
|
221
|
-
return undefined;
|
|
222
|
-
}
|
|
162
|
+
export function customSeed(env = process.env): Record<string, boolean> | undefined {
|
|
163
|
+
const block = readSettingsFile(globalSettingsPath(env))[SETTINGS_KEY];
|
|
164
|
+
const own = isRecord(block) && LOOK_KEYS.some((key) => key in block);
|
|
165
|
+
return own ? undefined : PRESETS.lite;
|
|
223
166
|
}
|
|
224
167
|
|
|
225
168
|
// ---------------------------------------------------------------------------
|
|
@@ -232,7 +175,7 @@ export type SaveResult =
|
|
|
232
175
|
|
|
233
176
|
/**
|
|
234
177
|
* Persist only the supplied editor-managed settings into the global agent file.
|
|
235
|
-
* A normal edit passes one key
|
|
178
|
+
* A normal edit passes one key.
|
|
236
179
|
* Hand-written `glyphs`/`tokens`/`excludeTools` values survive untouched.
|
|
237
180
|
*
|
|
238
181
|
* REFUSES to write a file containing comments. `JSON.stringify` would silently
|
|
@@ -263,6 +206,8 @@ export function saveBasicSettings(settings: Partial<BasicSettings>, env = proces
|
|
|
263
206
|
}
|
|
264
207
|
const existing = isRecord(root[SETTINGS_KEY]) ? root[SETTINGS_KEY] : {};
|
|
265
208
|
const merged: Json = { ...existing };
|
|
209
|
+
// The first write must pin the fresh-install preset, or the file would stop looking fresh and become `custom`.
|
|
210
|
+
if (isFresh(existing)) merged.preset = FRESH_PRESET;
|
|
266
211
|
for (const key of BASIC_KEYS) {
|
|
267
212
|
if (settings[key] !== undefined) merged[key] = settings[key];
|
|
268
213
|
}
|
package/src/config-ui.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import { truncateToWidth, type AutocompleteItem, type Component, type SettingItem, type SettingsList, type SettingsListTheme } from "@earendil-works/pi-tui";
|
|
4
4
|
import {
|
|
5
5
|
BASIC_KEYS,
|
|
6
|
-
|
|
6
|
+
LOOK_KEYS,
|
|
7
|
+
PRESET_NAMES,
|
|
8
|
+
isPreset,
|
|
7
9
|
type BasicKey,
|
|
8
10
|
type BasicSettings,
|
|
9
11
|
type Config,
|
|
@@ -20,6 +22,11 @@ type Field = {
|
|
|
20
22
|
|
|
21
23
|
/** Label and hover description for every basic setting, in display order. */
|
|
22
24
|
export const FIELDS: Field[] = [
|
|
25
|
+
{
|
|
26
|
+
key: "preset",
|
|
27
|
+
label: "Preset",
|
|
28
|
+
description: "off = native Pi, lite = one-line rows, full = + groups and thinking rows, max = + collapsed earlier activity. custom uses your own values. A preset only sets the four look rows; your other settings are kept.",
|
|
29
|
+
},
|
|
23
30
|
{
|
|
24
31
|
key: "compactToolRows",
|
|
25
32
|
label: "Compact tool rows",
|
|
@@ -35,12 +42,6 @@ export const FIELDS: Field[] = [
|
|
|
35
42
|
label: "Collapse earlier activity",
|
|
36
43
|
description: "Keep the latest assistant reply visible; replace earlier text, thinking, and tool calls with one summary.",
|
|
37
44
|
},
|
|
38
|
-
{
|
|
39
|
-
key: "foldActivityOnFinalAnswer",
|
|
40
|
-
label: " Collapse when final answer starts",
|
|
41
|
-
description: "Keep work visible until OpenAI starts its final answer, then collapse the earlier activity.",
|
|
42
|
-
activityOption: true,
|
|
43
|
-
},
|
|
44
45
|
{
|
|
45
46
|
key: "activitySummary",
|
|
46
47
|
label: " Activity summary",
|
|
@@ -83,18 +84,27 @@ type InputComponent = Component & { handleInput(data: string): void };
|
|
|
83
84
|
type SettingsListConstructor = new (...args: ConstructorParameters<typeof SettingsList>) => SettingsList;
|
|
84
85
|
type BasicValue = BasicSettings[BasicKey];
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
const CHOICES: Partial<Record<BasicKey, string[]>> = {
|
|
88
|
+
preset: [...PRESET_NAMES],
|
|
89
|
+
activitySummary: ["elapsed time", "tools used"],
|
|
90
|
+
glyphStyle: ["Unicode", "ASCII"],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const isLook = (key: string) => (LOOK_KEYS as readonly string[]).includes(key);
|
|
94
|
+
|
|
95
|
+
/** Conditional rows plus their display values. `dim` greys the look rows a preset owns. */
|
|
96
|
+
export function items(settings: Settings, dim?: (text: string) => string): SettingItem[] {
|
|
88
97
|
return FIELDS.filter((field) => !field.activityOption || settings.foldIntermediateActivity).map(
|
|
89
98
|
({ key, label, description }) => {
|
|
90
|
-
const value = settings[key];
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
99
|
+
const value = displayValue(settings[key]);
|
|
100
|
+
// SettingsList has no disabled rows, so a locked row shows its dimmed value
|
|
101
|
+
// as its ONLY value: Enter "cycles" to the same text and nothing changes.
|
|
102
|
+
if (dim && isLook(key)) {
|
|
103
|
+
const shown = dim(value);
|
|
104
|
+
const hint = `Set by the '${settings.preset}' preset. Switch Preset to 'custom' to edit (your custom values are kept).`;
|
|
105
|
+
return { id: key, label, description: hint, currentValue: shown, values: [shown] };
|
|
106
|
+
}
|
|
107
|
+
return { id: key, label, description, currentValue: value, values: CHOICES[key] ?? ["on", "off"] };
|
|
98
108
|
},
|
|
99
109
|
);
|
|
100
110
|
}
|
|
@@ -104,39 +114,35 @@ export type ConfigScreenDeps = {
|
|
|
104
114
|
theme: SettingsListTheme;
|
|
105
115
|
config: Config;
|
|
106
116
|
onChange: (key: BasicKey, value: BasicValue) => void;
|
|
107
|
-
onReset: () => void;
|
|
108
117
|
onClose: () => void;
|
|
109
118
|
};
|
|
110
119
|
|
|
111
120
|
/** Build a live screen; toggling prose folding immediately adds/removes its two child rows. */
|
|
112
121
|
export function createConfigScreen(deps: ConfigScreenDeps): InputComponent {
|
|
113
|
-
const { SettingsList, theme, config, onChange,
|
|
122
|
+
const { SettingsList, theme, config, onChange, onClose } = deps;
|
|
114
123
|
let list: SettingsList;
|
|
124
|
+
let locked = false;
|
|
115
125
|
|
|
116
126
|
const build = () => {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
id: "restoreDefaults",
|
|
121
|
-
label: "Restore defaults",
|
|
122
|
-
description: "Reset the settings in this editor, including Symbols. Custom glyphs, colours, and excluded tools are kept.",
|
|
123
|
-
currentValue: "Enter to restore",
|
|
124
|
-
values: ["Restore"],
|
|
125
|
-
},
|
|
126
|
-
];
|
|
127
|
+
const preset = config.all().preset;
|
|
128
|
+
locked = preset !== "custom";
|
|
129
|
+
const rows = items(config.all(), locked ? theme.hint : undefined);
|
|
127
130
|
return new SettingsList(
|
|
128
131
|
rows,
|
|
129
132
|
rows.length,
|
|
130
133
|
theme,
|
|
131
134
|
(id, displayValue) => {
|
|
132
|
-
if (id
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
if (!isBasicKey(id)) return;
|
|
136
|
+
if (id === "preset") {
|
|
137
|
+
const value = isPreset(displayValue) ? displayValue : "custom";
|
|
138
|
+
config.set(id, value);
|
|
139
|
+
onChange(id, value);
|
|
135
140
|
list = build();
|
|
136
141
|
list.selectItem(id);
|
|
137
142
|
return;
|
|
138
143
|
}
|
|
139
|
-
|
|
144
|
+
// Locked rows only echo their own text; do nothing (their description explains).
|
|
145
|
+
if (locked && isLook(id)) return;
|
|
140
146
|
if (id === "activitySummary") {
|
|
141
147
|
const value = displayValue === "tools used" ? "tools" : "elapsed";
|
|
142
148
|
config.set(id, value);
|
|
@@ -176,6 +182,7 @@ function isBasicKey(id: string): id is BasicKey {
|
|
|
176
182
|
}
|
|
177
183
|
|
|
178
184
|
function displayValue(value: BasicValue): string {
|
|
185
|
+
if (isPreset(value)) return value;
|
|
179
186
|
if (value === "elapsed") return "elapsed time";
|
|
180
187
|
if (value === "tools") return "tools used";
|
|
181
188
|
if (value === "unicode") return "Unicode";
|
|
@@ -199,7 +206,8 @@ export function summary(settings: Settings): string {
|
|
|
199
206
|
const width = Math.max(...visible.map((field) => field.label.length));
|
|
200
207
|
return [
|
|
201
208
|
...visible.map(({ key, label }) => {
|
|
202
|
-
|
|
209
|
+
const dimmed = settings.preset !== "custom" && isLook(key) ? " (from preset)" : "";
|
|
210
|
+
return ` ${label.padEnd(width)} ${displayValue(settings[key])}${dimmed}`;
|
|
203
211
|
}),
|
|
204
212
|
"",
|
|
205
213
|
" /minimalist change these",
|
package/src/config.ts
CHANGED
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
* are readable on their own.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
// ThemeBg is not exported publicly, so recover it from Theme.bg's own signature
|
|
13
|
-
// rather than importing a deep dist path (which is not a package export).
|
|
14
|
-
type ThemeBg = Parameters<Theme["bg"]>[0];
|
|
10
|
+
import type { ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
15
11
|
|
|
16
12
|
/** Top-level key inside Pi's settings.json. */
|
|
17
13
|
export const SETTINGS_KEY = "minimalist";
|
|
@@ -41,6 +37,9 @@ export type Glyphs = {
|
|
|
41
37
|
|
|
42
38
|
export type GlyphStyle = "unicode" | "ascii";
|
|
43
39
|
|
|
40
|
+
/** Marker on the header of an opened run. Not user-configurable. */
|
|
41
|
+
export const OPEN_MARKER: Record<GlyphStyle, string> = { unicode: "▾", ascii: "v" };
|
|
42
|
+
|
|
44
43
|
export const GLYPH_PRESETS: Record<GlyphStyle, Glyphs> = {
|
|
45
44
|
unicode: { done: "✓", failed: "✗", running: "•", queued: "›", gutter: "▌", timer: "⏱", count: "×" },
|
|
46
45
|
// Every ASCII glyph is exactly one column wide, so rows stay aligned in fonts
|
|
@@ -56,9 +55,8 @@ export const GLYPH_PRESETS: Record<GlyphStyle, Glyphs> = {
|
|
|
56
55
|
* Which Pi theme token paints each part of a row.
|
|
57
56
|
*
|
|
58
57
|
* Overridable, but the defaults are deliberate: the action word is a LABEL, not
|
|
59
|
-
* a warning, so it uses `success` green even for mutating tools
|
|
60
|
-
*
|
|
61
|
-
* prose".
|
|
58
|
+
* a warning, so it uses `success` green even for mutating tools (orange would
|
|
59
|
+
* collide with "highlighted prose").
|
|
62
60
|
*/
|
|
63
61
|
export type Tokens = {
|
|
64
62
|
label: ThemeColor;
|
|
@@ -66,7 +64,6 @@ export type Tokens = {
|
|
|
66
64
|
thinking: ThemeColor;
|
|
67
65
|
error: ThemeColor;
|
|
68
66
|
outputGutter: ThemeColor;
|
|
69
|
-
activeBg: ThemeBg;
|
|
70
67
|
};
|
|
71
68
|
|
|
72
69
|
export const DEFAULT_TOKENS: Tokens = {
|
|
@@ -75,7 +72,6 @@ export const DEFAULT_TOKENS: Tokens = {
|
|
|
75
72
|
thinking: "thinkingText",
|
|
76
73
|
error: "error",
|
|
77
74
|
outputGutter: "borderMuted",
|
|
78
|
-
activeBg: "toolPendingBg",
|
|
79
75
|
};
|
|
80
76
|
|
|
81
77
|
// ---------------------------------------------------------------------------
|
|
@@ -85,15 +81,34 @@ export const DEFAULT_TOKENS: Tokens = {
|
|
|
85
81
|
/** Settings `/minimalist` can edit: visible effect, no vocabulary needed. */
|
|
86
82
|
export type ActivitySummary = "elapsed" | "tools";
|
|
87
83
|
|
|
84
|
+
export const PRESET_NAMES = ["off", "lite", "full", "max", "custom"] as const;
|
|
85
|
+
export type Preset = (typeof PRESET_NAMES)[number];
|
|
86
|
+
|
|
87
|
+
export function isPreset(value: unknown): value is Preset {
|
|
88
|
+
return (PRESET_NAMES as readonly unknown[]).includes(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The only settings a preset names. Every other setting stays the user's own. */
|
|
92
|
+
export const LOOK_KEYS = ["compactToolRows", "groupToolRuns", "foldIntermediateActivity", "thinkingAsToolCall"] as const;
|
|
93
|
+
export type LookKey = (typeof LOOK_KEYS)[number];
|
|
94
|
+
|
|
95
|
+
/** Preset of a user who has never configured anything (no basic key in the `minimalist` block). */
|
|
96
|
+
export const FRESH_PRESET: Preset = "full";
|
|
97
|
+
|
|
98
|
+
/** True when the `minimalist` block holds no basic key, so the user never configured anything. */
|
|
99
|
+
export function isFresh(block: unknown): boolean {
|
|
100
|
+
return typeof block !== "object" || block === null || !BASIC_KEYS.some((key) => key in block);
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
export type BasicSettings = {
|
|
104
|
+
/** A named bundle of the four look settings below. `custom` uses the user's own values for all of them. */
|
|
105
|
+
preset: Preset;
|
|
89
106
|
/** Master switch. Off restores Pi's native tool cards entirely. */
|
|
90
107
|
compactToolRows: boolean;
|
|
91
108
|
/** Fold adjacent finished rows into one summary line. */
|
|
92
109
|
groupToolRuns: boolean;
|
|
93
110
|
/** Replace all but the latest assistant prose block with one summary row. */
|
|
94
111
|
foldIntermediateActivity: boolean;
|
|
95
|
-
/** Keep commentary visible until OpenAI starts its final-answer message. */
|
|
96
|
-
foldActivityOnFinalAnswer: boolean;
|
|
97
112
|
/** What replaces the activity hidden before the latest prose block. */
|
|
98
113
|
activitySummary: ActivitySummary;
|
|
99
114
|
/** Draw the left gutter bar. */
|
|
@@ -116,21 +131,15 @@ export type AdvancedSettings = {
|
|
|
116
131
|
excludeTools: string[];
|
|
117
132
|
glyphs: Partial<Glyphs>;
|
|
118
133
|
tokens: Partial<Tokens>;
|
|
119
|
-
/**
|
|
120
|
-
* Sanity cap on detail text length. NOT a display limit — rows truncate at the
|
|
121
|
-
* real viewport width. This only stops a multi-megabyte heredoc from being
|
|
122
|
-
* whitespace-collapsed, colored and measured on every repaint.
|
|
123
|
-
*/
|
|
124
|
-
maxDetailChars: number;
|
|
125
134
|
};
|
|
126
135
|
|
|
127
136
|
export type Settings = BasicSettings & AdvancedSettings;
|
|
128
137
|
|
|
129
138
|
export const DEFAULT_BASIC: BasicSettings = {
|
|
139
|
+
preset: "custom",
|
|
130
140
|
compactToolRows: true,
|
|
131
141
|
groupToolRuns: true,
|
|
132
142
|
foldIntermediateActivity: false,
|
|
133
|
-
foldActivityOnFinalAnswer: false,
|
|
134
143
|
activitySummary: "elapsed",
|
|
135
144
|
gutter: true,
|
|
136
145
|
timer: true,
|
|
@@ -140,15 +149,24 @@ export const DEFAULT_BASIC: BasicSettings = {
|
|
|
140
149
|
glyphStyle: "unicode",
|
|
141
150
|
};
|
|
142
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The four look settings each non-custom preset forces. A preset is a LAYER
|
|
154
|
+
* applied on read (see `Config`); it never writes the user's own keys.
|
|
155
|
+
*/
|
|
156
|
+
export const PRESETS: Record<Exclude<Preset, "custom">, Record<LookKey, boolean>> = {
|
|
157
|
+
off: { compactToolRows: false, groupToolRuns: false, foldIntermediateActivity: false, thinkingAsToolCall: false },
|
|
158
|
+
lite: { compactToolRows: true, groupToolRuns: false, foldIntermediateActivity: false, thinkingAsToolCall: false },
|
|
159
|
+
full: { compactToolRows: true, groupToolRuns: true, foldIntermediateActivity: false, thinkingAsToolCall: true },
|
|
160
|
+
max: { compactToolRows: true, groupToolRuns: true, foldIntermediateActivity: true, thinkingAsToolCall: true },
|
|
161
|
+
};
|
|
162
|
+
|
|
143
163
|
export const DEFAULT_ADVANCED: AdvancedSettings = {
|
|
144
164
|
// `subagent` ships a renderer showing run id, state and a ctrl+o hint that one
|
|
145
|
-
// line cannot carry. Excluded by NAME rather than by
|
|
146
|
-
// have its own renderer?" rule, which silently
|
|
147
|
-
// and made the behavior impossible to discover from the UI.
|
|
165
|
+
// line cannot carry. Excluded by NAME rather than by a "does this tool
|
|
166
|
+
// have its own renderer?" rule, which would silently exempt every third-party tool.
|
|
148
167
|
excludeTools: ["subagent"],
|
|
149
168
|
glyphs: {},
|
|
150
169
|
tokens: {},
|
|
151
|
-
maxDetailChars: 4000,
|
|
152
170
|
};
|
|
153
171
|
|
|
154
172
|
export const DEFAULTS: Settings = { ...DEFAULT_BASIC, ...DEFAULT_ADVANCED };
|
|
@@ -167,6 +185,11 @@ export type BasicKey = keyof BasicSettings;
|
|
|
167
185
|
// Live configuration
|
|
168
186
|
// ---------------------------------------------------------------------------
|
|
169
187
|
|
|
188
|
+
/** defaults < the user's own keys < the preset's four look keys (none for `custom`). */
|
|
189
|
+
function layer(user: Settings): Settings {
|
|
190
|
+
return user.preset === "custom" ? user : { ...user, ...PRESETS[user.preset] };
|
|
191
|
+
}
|
|
192
|
+
|
|
170
193
|
/**
|
|
171
194
|
* The live settings object.
|
|
172
195
|
*
|
|
@@ -175,7 +198,12 @@ export type BasicKey = keyof BasicSettings;
|
|
|
175
198
|
* and no restart.
|
|
176
199
|
*/
|
|
177
200
|
export class Config {
|
|
201
|
+
/** The user's own values (what settings.json says), before any preset. */
|
|
178
202
|
private settings: Settings;
|
|
203
|
+
/** What the rest of the extension sees: `settings` with the preset layered on. */
|
|
204
|
+
private get view(): Settings {
|
|
205
|
+
return layer(this.settings);
|
|
206
|
+
}
|
|
179
207
|
|
|
180
208
|
/**
|
|
181
209
|
* Values changed in this session that are NOT in settings.json.
|
|
@@ -192,9 +220,10 @@ export class Config {
|
|
|
192
220
|
}
|
|
193
221
|
|
|
194
222
|
get<K extends keyof Settings>(key: K): Settings[K] {
|
|
195
|
-
return this.
|
|
223
|
+
return this.view[key];
|
|
196
224
|
}
|
|
197
225
|
|
|
226
|
+
/** Writes the user's own value; a non-custom preset keeps shadowing it until `preset` is custom. */
|
|
198
227
|
set<K extends keyof Settings>(key: K, value: Settings[K]): void {
|
|
199
228
|
this.settings = { ...this.settings, [key]: value };
|
|
200
229
|
}
|
|
@@ -211,7 +240,7 @@ export class Config {
|
|
|
211
240
|
}
|
|
212
241
|
|
|
213
242
|
all(): Settings {
|
|
214
|
-
return this.
|
|
243
|
+
return this.view;
|
|
215
244
|
}
|
|
216
245
|
|
|
217
246
|
/** Adopt freshly read settings, re-applying anything held only in memory. */
|
|
@@ -221,26 +250,19 @@ export class Config {
|
|
|
221
250
|
|
|
222
251
|
/** Preset chosen by `glyphStyle`, then per-glyph overrides. */
|
|
223
252
|
glyphs(): Glyphs {
|
|
224
|
-
return { ...GLYPH_PRESETS[this.
|
|
253
|
+
return { ...GLYPH_PRESETS[this.view.glyphStyle], ...this.view.glyphs };
|
|
225
254
|
}
|
|
226
255
|
|
|
227
256
|
/** Default token map plus overrides. */
|
|
228
257
|
tokens(): Tokens {
|
|
229
|
-
return { ...DEFAULT_TOKENS, ...this.
|
|
258
|
+
return { ...DEFAULT_TOKENS, ...this.view.tokens };
|
|
230
259
|
}
|
|
231
260
|
|
|
232
261
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* The exclusion list is the ONLY exemption, so the master switch is checked
|
|
236
|
-
* alongside it wherever claiming happens.
|
|
262
|
+
* True when this tool should be drawn as a compact row. The exclusion list is
|
|
263
|
+
* the ONLY exemption, so the master switch is checked alongside it.
|
|
237
264
|
*/
|
|
238
|
-
isExcluded(toolName: string): boolean {
|
|
239
|
-
return this.settings.excludeTools.includes(toolName);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** True when this tool should be drawn as a compact row. */
|
|
243
265
|
compacts(toolName: string): boolean {
|
|
244
|
-
return this.
|
|
266
|
+
return this.view.compactToolRows && !this.view.excludeTools.includes(toolName);
|
|
245
267
|
}
|
|
246
268
|
}
|