@kolisachint/hoocode-agent 0.5.28 → 0.5.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/light.d.ts +9 -0
- package/dist/core/light.d.ts.map +1 -1
- package/dist/core/light.js +12 -4
- package/dist/core/light.js.map +1 -1
- package/dist/core/settings-manager.d.ts +8 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +12 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +6 -5
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +31 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +245 -51
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +43 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/plugins.md +3 -2
- package/docs/settings.md +64 -0
- package/docs/usage.md +3 -3
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -57,9 +57,9 @@ const LEARN_KEYS = new Set(LEARN_SETTINGS.map((setting) => setting.key));
|
|
|
57
57
|
* Preset list for a numeric row, guaranteed to contain the value in force.
|
|
58
58
|
*
|
|
59
59
|
* Without this a value set by hand in settings.json — say 45 days — is absent
|
|
60
|
-
* from the cycle, so the first keypress silently snaps it to the first preset
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* from the cycle, so the first keypress silently snaps it to the first preset,
|
|
61
|
+
* discarding a deliberate choice the pane never showed as unusual. Every numeric
|
|
62
|
+
* cycle row goes through this for that reason.
|
|
63
63
|
*/
|
|
64
64
|
function presetValues(presets, current) {
|
|
65
65
|
const all = presets.includes(current) ? presets : [...presets, current].sort((a, b) => a - b);
|
|
@@ -97,6 +97,73 @@ class WarningSettingsSubmenu extends Container {
|
|
|
97
97
|
this.settingsList.handleInput(data);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* The artifact platform targets, in the order the pane shows them. Each row is an
|
|
102
|
+
* independent on/off toggle because `--platform` (and the `platform` setting it
|
|
103
|
+
* mirrors) takes a list: emitting for two platforms at once is a supported shape.
|
|
104
|
+
* The tokens here are the canonical ones — the CLI aliases (`copilot`, `gh`,
|
|
105
|
+
* `native`) fold into these before anything reads them.
|
|
106
|
+
*/
|
|
107
|
+
const PLATFORM_ROWS = [
|
|
108
|
+
{
|
|
109
|
+
platform: "claude",
|
|
110
|
+
label: "claude",
|
|
111
|
+
description: "Claude Code layout: .claude/ scaffolds, and authored plugins drop into ~/.claude/skills/<id>/. The default when nothing is set.",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
platform: "github",
|
|
115
|
+
label: "github (copilot, gh)",
|
|
116
|
+
description: "Copilot layout: .github/ scaffolds, and authored plugins are produced under ~/.agents/publish/github/<id>/.",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
platform: "agents",
|
|
120
|
+
label: "agents (native)",
|
|
121
|
+
description: "Cross-vendor .agents/ layout. Scaffolds only - a plugin belongs to no marketplace in this layout, so plugin authoring ignores it.",
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
/** Row value for the platform setting: the selection, or the fallback when it is empty. */
|
|
125
|
+
function platformSummary(platforms) {
|
|
126
|
+
return platforms.length > 0 ? platforms.join(", ") : "default (claude)";
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Submenu for the session's artifact platform targets (the `platform` setting,
|
|
130
|
+
* same knob as `--platform`).
|
|
131
|
+
*
|
|
132
|
+
* A list rather than a cycle: the setting is a list, and the three tokens are not
|
|
133
|
+
* mutually exclusive. Turning everything off is legal and means "unset" — the
|
|
134
|
+
* per-consumer defaults come back (claude for plugins, .hoocode/ for scaffolds).
|
|
135
|
+
*/
|
|
136
|
+
class PlatformSubmenu extends Container {
|
|
137
|
+
settingsList;
|
|
138
|
+
selected;
|
|
139
|
+
constructor(platforms, onChange, onDone) {
|
|
140
|
+
super();
|
|
141
|
+
this.selected = new Set(platforms);
|
|
142
|
+
const items = PLATFORM_ROWS.map(({ platform, label, description }) => ({
|
|
143
|
+
id: platform,
|
|
144
|
+
label,
|
|
145
|
+
description,
|
|
146
|
+
currentValue: this.selected.has(platform) ? "on" : "off",
|
|
147
|
+
values: ["on", "off"],
|
|
148
|
+
}));
|
|
149
|
+
this.settingsList = new SettingsList(items, Math.min(items.length, 10), getSettingsListTheme(), (id, newValue) => {
|
|
150
|
+
const platform = id;
|
|
151
|
+
if (newValue === "on")
|
|
152
|
+
this.selected.add(platform);
|
|
153
|
+
else
|
|
154
|
+
this.selected.delete(platform);
|
|
155
|
+
onChange(this.ordered());
|
|
156
|
+
}, () => onDone(platformSummary(this.ordered())));
|
|
157
|
+
this.addChild(this.settingsList);
|
|
158
|
+
}
|
|
159
|
+
/** Selection in the pane's order, so the persisted list does not depend on click order. */
|
|
160
|
+
ordered() {
|
|
161
|
+
return PLATFORM_ROWS.map((row) => row.platform).filter((platform) => this.selected.has(platform));
|
|
162
|
+
}
|
|
163
|
+
handleInput(data) {
|
|
164
|
+
this.settingsList.handleInput(data);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
100
167
|
/**
|
|
101
168
|
* Submenu for tool availability. The first rows are group switches (web,
|
|
102
169
|
* semantic search) that decide whether a group's tools
|
|
@@ -130,6 +197,10 @@ class ToolsSubmenu extends Container {
|
|
|
130
197
|
? "Core tool. Disabling leaves the agent unable to perform this action in every session."
|
|
131
198
|
: "Disable to remove this tool from the agent this session and every future session.",
|
|
132
199
|
currentValue: tool.enabled ? "on" : "off",
|
|
200
|
+
// What the schema costs on every request, whether the tool is on or off:
|
|
201
|
+
// off, it is the price of turning it back on. This is the number that
|
|
202
|
+
// makes a tool worth disabling, so it belongs beside the switch.
|
|
203
|
+
valueSuffix: tool.tokens !== undefined ? `${tokenCount(tool.tokens)} tok/turn` : undefined,
|
|
133
204
|
values: ["on", "off"],
|
|
134
205
|
}));
|
|
135
206
|
const items = [...groupItems, ...toolItems];
|
|
@@ -168,52 +239,65 @@ function bytesToLabel(bytes) {
|
|
|
168
239
|
const match = TOOL_OUTPUT_BYTE_PRESETS.find(([, b]) => b === bytes);
|
|
169
240
|
return match ? match[0] : `${Math.round(bytes / 1024)} KB`;
|
|
170
241
|
}
|
|
242
|
+
/** Byte-cap labels to cycle through, including a hand-set cap that matches no preset. */
|
|
243
|
+
function byteLabels(current) {
|
|
244
|
+
const all = TOOL_OUTPUT_BYTE_PRESETS.some(([, bytes]) => bytes === current)
|
|
245
|
+
? [...TOOL_OUTPUT_BYTE_PRESETS]
|
|
246
|
+
: [...TOOL_OUTPUT_BYTE_PRESETS, [bytesToLabel(current), current]];
|
|
247
|
+
return all.sort((a, b) => a[1] - b[1]).map(([label]) => label);
|
|
248
|
+
}
|
|
171
249
|
/**
|
|
172
|
-
* Submenu for
|
|
173
|
-
*
|
|
174
|
-
*
|
|
250
|
+
* Submenu for what a tool result looks like: how much of it is rendered, and
|
|
251
|
+
* where it is truncated. The display level applies to the transcript at once;
|
|
252
|
+
* the caps feed the tool runtime, so they bind on future tool calls.
|
|
253
|
+
*
|
|
254
|
+
* Context GC is deliberately not here. It is not about a tool's output but about
|
|
255
|
+
* what stays in the outgoing context, which is the Context category's subject.
|
|
175
256
|
*/
|
|
176
257
|
class ToolSettingsSubmenu extends Container {
|
|
177
258
|
settingsList;
|
|
178
259
|
constructor(config, callbacks, onCancel) {
|
|
179
260
|
super();
|
|
180
261
|
const items = [
|
|
262
|
+
{
|
|
263
|
+
id: "tool-output-display",
|
|
264
|
+
label: "Display",
|
|
265
|
+
description: "How tool results render. 'standard': shown (expandable). 'collapsed': hidden. 'peek': hidden with a ▸ reveal caret (press the expand key to reveal).",
|
|
266
|
+
currentValue: config.toolOutputDisplay,
|
|
267
|
+
values: ["standard", "collapsed", "peek"],
|
|
268
|
+
},
|
|
181
269
|
{
|
|
182
270
|
id: "output-max-bytes",
|
|
183
|
-
label: "
|
|
271
|
+
label: "Max bytes",
|
|
184
272
|
description: "Byte cap on a single read/bash result before truncation. Applies to future tool calls.",
|
|
185
273
|
currentValue: bytesToLabel(config.toolOutputMaxBytes),
|
|
186
|
-
values:
|
|
274
|
+
values: byteLabels(config.toolOutputMaxBytes),
|
|
187
275
|
},
|
|
188
276
|
{
|
|
189
277
|
id: "output-max-lines",
|
|
190
|
-
label: "
|
|
278
|
+
label: "Max lines",
|
|
191
279
|
description: "Line cap on a single read/bash result before truncation. Applies to future tool calls.",
|
|
192
280
|
currentValue: String(config.toolOutputMaxLines),
|
|
193
|
-
values: [
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
id: "context-gc",
|
|
197
|
-
label: "Context GC",
|
|
198
|
-
description: "Stub superseded read results (files later edited/re-read) out of the outgoing context.",
|
|
199
|
-
currentValue: config.contextGc ? "true" : "false",
|
|
200
|
-
values: ["true", "false"],
|
|
281
|
+
values: presetValues([200, 400, 800, 1600, 3200], config.toolOutputMaxLines),
|
|
201
282
|
},
|
|
202
283
|
];
|
|
203
284
|
this.settingsList = new SettingsList(items, Math.min(items.length, 10), getSettingsListTheme(), (id, newValue) => {
|
|
204
285
|
switch (id) {
|
|
286
|
+
case "tool-output-display":
|
|
287
|
+
callbacks.onToolOutputDisplayChange(newValue);
|
|
288
|
+
break;
|
|
205
289
|
case "output-max-bytes": {
|
|
206
290
|
const preset = TOOL_OUTPUT_BYTE_PRESETS.find(([label]) => label === newValue);
|
|
207
|
-
|
|
208
|
-
|
|
291
|
+
// A hand-set cap has no preset entry; its label is "<n> KB" by
|
|
292
|
+
// construction, so read the number back out of it.
|
|
293
|
+
const bytes = preset ? preset[1] : Math.round(parseFloat(newValue) * 1024);
|
|
294
|
+
if (Number.isFinite(bytes) && bytes > 0)
|
|
295
|
+
callbacks.onToolOutputMaxBytesChange(bytes);
|
|
209
296
|
break;
|
|
210
297
|
}
|
|
211
298
|
case "output-max-lines":
|
|
212
299
|
callbacks.onToolOutputMaxLinesChange(parseInt(newValue, 10));
|
|
213
300
|
break;
|
|
214
|
-
case "context-gc":
|
|
215
|
-
callbacks.onContextGcChange(newValue === "true");
|
|
216
|
-
break;
|
|
217
301
|
}
|
|
218
302
|
}, onCancel);
|
|
219
303
|
this.addChild(this.settingsList);
|
|
@@ -343,16 +427,52 @@ class SelectSubmenu extends Container {
|
|
|
343
427
|
this.selectList.handleInput(data);
|
|
344
428
|
}
|
|
345
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Token counts, grouped and exact.
|
|
432
|
+
*
|
|
433
|
+
* `formatTokens` (2.7k) exists for the fixed-width chrome, where a count must
|
|
434
|
+
* never grow the box it sits in. This pane is the opposite case: the point of
|
|
435
|
+
* showing a number here is to compare it with another one, and "2.7k" hides the
|
|
436
|
+
* difference between the tool that costs 2,710 and the one that costs 2,749.
|
|
437
|
+
*/
|
|
438
|
+
function tokenCount(tokens) {
|
|
439
|
+
return tokens.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* The fixed per-turn cost, as one line under the pane.
|
|
443
|
+
*
|
|
444
|
+
* Every byte of system prompt and active tool schema is re-sent on every
|
|
445
|
+
* request, and the pane is where that number is decided - so the pane is where
|
|
446
|
+
* it should be visible. It reports the *live* session: a tool toggle applies at
|
|
447
|
+
* once and moves it, while a setting that only takes effect next session (a tool
|
|
448
|
+
* group, the light preset) leaves it alone until then.
|
|
449
|
+
*/
|
|
450
|
+
function formatSurfaceLine(surface) {
|
|
451
|
+
const tools = `${surface.tools.length} tool${surface.tools.length === 1 ? "" : "s"}`;
|
|
452
|
+
return (` Per-turn surface: ${tokenCount(surface.totalTokens)} tokens ` +
|
|
453
|
+
`${theme.fg("dim", `(${tokenCount(surface.systemPromptTokens)} system prompt + ${tokenCount(surface.toolSchemaTokens)} schemas, ${tools})`)}`);
|
|
454
|
+
}
|
|
346
455
|
/**
|
|
347
456
|
* Main settings selector component.
|
|
348
457
|
*/
|
|
349
458
|
export class SettingsSelectorComponent extends Container {
|
|
350
459
|
settingsList;
|
|
460
|
+
surfaceLine;
|
|
461
|
+
measureTokenSurface;
|
|
351
462
|
constructor(config, callbacks) {
|
|
352
463
|
super();
|
|
464
|
+
this.measureTokenSurface = config.measureTokenSurface;
|
|
353
465
|
const supportsImages = getCapabilities().images;
|
|
354
466
|
const followUpKey = keyDisplayText("app.message.followUp");
|
|
355
467
|
let currentWarnings = { ...config.warnings };
|
|
468
|
+
// A row writes the user settings file; a key the project file also sets is
|
|
469
|
+
// merged over it on the next session, so say so rather than letting the row
|
|
470
|
+
// look like it took.
|
|
471
|
+
const projectPinned = new Set(config.projectPinnedSettings);
|
|
472
|
+
const pinnedNote = (key) => projectPinned.has(key)
|
|
473
|
+
? ` This repo's .hoocode/settings.json sets ${key}, which overrides this row from the next session on.`
|
|
474
|
+
: "";
|
|
475
|
+
const initialSurface = config.measureTokenSurface?.();
|
|
356
476
|
const toolsOn = config.tools.filter((t) => t.enabled).length;
|
|
357
477
|
const toolsOff = config.tools.length - toolsOn;
|
|
358
478
|
const items = [
|
|
@@ -487,7 +607,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
487
607
|
label: "Image width",
|
|
488
608
|
description: "Preferred inline image width in terminal cells",
|
|
489
609
|
currentValue: String(config.imageWidthCells),
|
|
490
|
-
values: [
|
|
610
|
+
values: presetValues([60, 80, 120], config.imageWidthCells),
|
|
491
611
|
});
|
|
492
612
|
}
|
|
493
613
|
// Image auto-resize toggle (always available, affects both attached and read images)
|
|
@@ -507,8 +627,26 @@ export class SettingsSelectorComponent extends Container {
|
|
|
507
627
|
currentValue: config.blockImages ? "true" : "false",
|
|
508
628
|
values: ["true", "false"],
|
|
509
629
|
});
|
|
510
|
-
//
|
|
511
|
-
|
|
630
|
+
// Context GC (insert after block-images), a leaf the Context category picks up.
|
|
631
|
+
items.splice(items.findIndex((item) => item.id === "block-images") + 1, 0, {
|
|
632
|
+
id: "context-gc",
|
|
633
|
+
label: "Context GC",
|
|
634
|
+
description: "Stub superseded read results (files later edited or re-read) out of the outgoing context.",
|
|
635
|
+
currentValue: config.contextGc ? "true" : "false",
|
|
636
|
+
values: ["true", "false"],
|
|
637
|
+
});
|
|
638
|
+
// The light preset (insert after context GC). Read at startup to pick the
|
|
639
|
+
// tool set and the system prompt, so it lands on the next session.
|
|
640
|
+
const blockImagesIdx = items.findIndex((item) => item.id === "context-gc");
|
|
641
|
+
items.splice(blockImagesIdx + 1, 0, {
|
|
642
|
+
id: "light",
|
|
643
|
+
label: "Light preset",
|
|
644
|
+
description: "Low-token preset for small or local models: read/write/edit/bash only with stripped schemas, a terse system prompt, and no subagents/TodoWrite/skills/context files. Applies on the next session.",
|
|
645
|
+
currentValue: config.light ? "true" : "false",
|
|
646
|
+
values: ["true", "false"],
|
|
647
|
+
});
|
|
648
|
+
// Skill commands toggle (insert after the light preset)
|
|
649
|
+
const blockImagesIndex = items.findIndex((item) => item.id === "light");
|
|
512
650
|
items.splice(blockImagesIndex + 1, 0, {
|
|
513
651
|
id: "skill-commands",
|
|
514
652
|
label: "Skill commands",
|
|
@@ -516,19 +654,44 @@ export class SettingsSelectorComponent extends Container {
|
|
|
516
654
|
currentValue: config.enableSkillCommands ? "true" : "false",
|
|
517
655
|
values: ["true", "false"],
|
|
518
656
|
});
|
|
519
|
-
//
|
|
520
|
-
//
|
|
657
|
+
// The autonomous plugin system's master switch (insert after skill-commands).
|
|
658
|
+
// One flag for the lifecycle tools and the reuse nudge, so both flip together.
|
|
521
659
|
const skillCommandsIdx = items.findIndex((item) => item.id === "skill-commands");
|
|
522
660
|
items.splice(skillCommandsIdx + 1, 0, {
|
|
661
|
+
id: "plugin-tools",
|
|
662
|
+
label: "Plugin system",
|
|
663
|
+
description: `Autonomous plugin system: the lifecycle tools (SearchPlugins, InstallPlugin, ...), ProposePlugin, and the plugin-reuse nudge. Tools arrive on the next session; the nudge follows at once.${pinnedNote("enablePluginTools")}`,
|
|
664
|
+
currentValue: config.enablePluginTools ? "true" : "false",
|
|
665
|
+
values: ["true", "false"],
|
|
666
|
+
});
|
|
667
|
+
// Plugin install scope (insert after the master switch). Governs the
|
|
668
|
+
// autonomous InstallPlugin only — /plugin install asks per install.
|
|
669
|
+
const pluginToolsIdx = items.findIndex((item) => item.id === "plugin-tools");
|
|
670
|
+
items.splice(pluginToolsIdx + 1, 0, {
|
|
523
671
|
id: "plugin-install-scope",
|
|
524
672
|
label: "Plugin install scope",
|
|
525
673
|
description: "Where autonomous plugin installs go: user (~/.agents) or project (this repo, shared)",
|
|
526
674
|
currentValue: config.pluginInstallScope,
|
|
527
675
|
values: ["user", "project"],
|
|
528
676
|
});
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
677
|
+
// Artifact platform targets (insert after plugin-install-scope). Set once and
|
|
678
|
+
// it holds for every later session: it is the `platform` setting, which
|
|
679
|
+
// `--platform` overrides for a single run.
|
|
680
|
+
const pluginScopeIdx = items.findIndex((item) => item.id === "plugin-install-scope");
|
|
681
|
+
let currentPlatforms = [...config.platform];
|
|
682
|
+
items.splice(pluginScopeIdx + 1, 0, {
|
|
683
|
+
id: "platform",
|
|
684
|
+
label: "Platform",
|
|
685
|
+
description: `Vendor layout(s) hoocode writes artifacts in: authored plugins and the /new-skill //new-agent //new-command scaffolds.${pinnedNote("platform")}`,
|
|
686
|
+
currentValue: platformSummary(currentPlatforms),
|
|
687
|
+
submenu: (_currentValue, done) => new PlatformSubmenu(currentPlatforms, (platforms) => {
|
|
688
|
+
currentPlatforms = platforms;
|
|
689
|
+
callbacks.onPlatformChange(platforms);
|
|
690
|
+
}, (summary) => done(summary)),
|
|
691
|
+
});
|
|
692
|
+
// Hardware cursor toggle (insert after the platform row)
|
|
693
|
+
const platformIndex = items.findIndex((item) => item.id === "platform");
|
|
694
|
+
items.splice(platformIndex + 1, 0, {
|
|
532
695
|
id: "show-hardware-cursor",
|
|
533
696
|
label: "Show hardware cursor",
|
|
534
697
|
description: "Show the terminal cursor while still positioning it for IME support",
|
|
@@ -560,7 +723,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
560
723
|
label: "Autocomplete max items",
|
|
561
724
|
description: "Max visible items in autocomplete dropdown (3-20)",
|
|
562
725
|
currentValue: String(config.autocompleteMaxVisible),
|
|
563
|
-
values: [
|
|
726
|
+
values: presetValues([3, 5, 7, 10, 15, 20], config.autocompleteMaxVisible),
|
|
564
727
|
});
|
|
565
728
|
// Clear on shrink toggle (insert after autocomplete-max-visible)
|
|
566
729
|
const autocompleteIndex = items.findIndex((item) => item.id === "autocomplete-max-visible");
|
|
@@ -587,7 +750,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
587
750
|
label: "Voice silence window",
|
|
588
751
|
description: "Trailing-silence (ms) before voice capture auto-stops (300-10000). Env: VOICETOOLS_SILENCE_MS.",
|
|
589
752
|
currentValue: String(config.voiceSilenceMs),
|
|
590
|
-
values: [
|
|
753
|
+
values: presetValues([300, 500, 800, 1200, 2000, 3000, 5000, 8000, 10000], config.voiceSilenceMs),
|
|
591
754
|
});
|
|
592
755
|
// Webtools request timeout (insert after voice-silence-ms)
|
|
593
756
|
const voiceSilenceIndex = items.findIndex((item) => item.id === "voice-silence-ms");
|
|
@@ -596,7 +759,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
596
759
|
label: "Web tools timeout",
|
|
597
760
|
description: "Per-request timeout (secs) for webfetch/websearch (1-120). Env: HOOCODE_WEBTOOLS_TIMEOUT.",
|
|
598
761
|
currentValue: String(config.webtoolsTimeoutSecs),
|
|
599
|
-
values: [
|
|
762
|
+
values: presetValues([5, 10, 15, 30, 60, 120], config.webtoolsTimeoutSecs),
|
|
600
763
|
});
|
|
601
764
|
// The /learn thresholds, appended as leaf rows and gathered into their own
|
|
602
765
|
// category below. They are written to the user settings.json, which /learn
|
|
@@ -615,30 +778,28 @@ export class SettingsSelectorComponent extends Container {
|
|
|
615
778
|
{
|
|
616
779
|
id: "tools",
|
|
617
780
|
label: "Tools",
|
|
618
|
-
description: "Enable/disable tools and tool groups (web, semantic search). Changes persist across sessions.",
|
|
781
|
+
description: "Enable/disable tools and tool groups (web, semantic search), each priced by what its schema costs per turn. Changes persist across sessions.",
|
|
619
782
|
currentValue: toolsOff > 0 ? `${toolsOn} on · ${toolsOff} off` : `${toolsOn} on`,
|
|
620
|
-
|
|
783
|
+
valueSuffix: initialSurface ? `${tokenCount(initialSurface.toolSchemaTokens)} tok/turn` : undefined,
|
|
784
|
+
submenu: (_currentValue, done) => new ToolsSubmenu(config.tools, config.toolGroups, (name, enabled) => {
|
|
785
|
+
callbacks.onToolEnabledChange(name, enabled);
|
|
786
|
+
// Applied live by the host, so the surface below is already stale.
|
|
787
|
+
this.refreshTokenSurface();
|
|
788
|
+
}, (id, enabled) => callbacks.onToolGroupChange(id, enabled), () => done()),
|
|
621
789
|
},
|
|
622
790
|
{
|
|
623
|
-
id: "tool-output
|
|
624
|
-
label: "Tool output
|
|
625
|
-
description: "How
|
|
791
|
+
id: "tool-output",
|
|
792
|
+
label: "Tool output",
|
|
793
|
+
description: "How much of a tool result is rendered, and where it is truncated.",
|
|
626
794
|
currentValue: config.toolOutputDisplay,
|
|
627
|
-
values: ["standard", "collapsed", "peek"],
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
id: "tool-settings",
|
|
631
|
-
label: "Tool settings",
|
|
632
|
-
description: "Per-tool runtime settings: output truncation caps and context garbage collection.",
|
|
633
|
-
currentValue: "configure",
|
|
634
795
|
submenu: (_currentValue, done) => new ToolSettingsSubmenu({
|
|
796
|
+
toolOutputDisplay: config.toolOutputDisplay,
|
|
635
797
|
toolOutputMaxBytes: config.toolOutputMaxBytes,
|
|
636
798
|
toolOutputMaxLines: config.toolOutputMaxLines,
|
|
637
|
-
contextGc: config.contextGc,
|
|
638
799
|
}, {
|
|
800
|
+
onToolOutputDisplayChange: callbacks.onToolOutputDisplayChange,
|
|
639
801
|
onToolOutputMaxBytesChange: callbacks.onToolOutputMaxBytesChange,
|
|
640
802
|
onToolOutputMaxLinesChange: callbacks.onToolOutputMaxLinesChange,
|
|
641
|
-
onContextGcChange: callbacks.onContextGcChange,
|
|
642
803
|
}, () => done()),
|
|
643
804
|
},
|
|
644
805
|
];
|
|
@@ -660,9 +821,6 @@ export class SettingsSelectorComponent extends Container {
|
|
|
660
821
|
case "autocompact":
|
|
661
822
|
callbacks.onAutoCompactChange(newValue === "true");
|
|
662
823
|
break;
|
|
663
|
-
case "tool-output-display":
|
|
664
|
-
callbacks.onToolOutputDisplayChange(newValue);
|
|
665
|
-
break;
|
|
666
824
|
case "show-images":
|
|
667
825
|
callbacks.onShowImagesChange(newValue === "true");
|
|
668
826
|
break;
|
|
@@ -678,9 +836,19 @@ export class SettingsSelectorComponent extends Container {
|
|
|
678
836
|
case "skill-commands":
|
|
679
837
|
callbacks.onEnableSkillCommandsChange(newValue === "true");
|
|
680
838
|
break;
|
|
839
|
+
case "light":
|
|
840
|
+
callbacks.onLightChange(newValue === "true");
|
|
841
|
+
break;
|
|
842
|
+
case "plugin-tools":
|
|
843
|
+
callbacks.onEnablePluginToolsChange(newValue === "true");
|
|
844
|
+
break;
|
|
681
845
|
case "plugin-install-scope":
|
|
682
846
|
callbacks.onPluginInstallScopeChange(newValue);
|
|
683
847
|
break;
|
|
848
|
+
case "platform":
|
|
849
|
+
// Display only. Each toggle inside the submenu already applied itself;
|
|
850
|
+
// closing it hands back the summary purely to refresh this row's value.
|
|
851
|
+
break;
|
|
684
852
|
case "steering-mode":
|
|
685
853
|
callbacks.onSteeringModeChange(newValue);
|
|
686
854
|
break;
|
|
@@ -739,6 +907,10 @@ export class SettingsSelectorComponent extends Container {
|
|
|
739
907
|
callbacks.onLearnSettingChange(id, parseInt(newValue, 10));
|
|
740
908
|
break;
|
|
741
909
|
}
|
|
910
|
+
// Most rows leave the per-turn surface alone; the ones that do not (a tool
|
|
911
|
+
// toggle, anything that rebuilds the system prompt) move it immediately.
|
|
912
|
+
// Re-measuring after every change is cheaper than knowing which is which.
|
|
913
|
+
this.refreshTokenSurface();
|
|
742
914
|
};
|
|
743
915
|
// Partition the flat leaf settings into named category submenus so the
|
|
744
916
|
// top level stays short. `items` holds autocompact + every leaf setting.
|
|
@@ -751,12 +923,21 @@ export class SettingsSelectorComponent extends Container {
|
|
|
751
923
|
label,
|
|
752
924
|
description,
|
|
753
925
|
currentValue: `${members.length} setting${members.length === 1 ? "" : "s"}`,
|
|
926
|
+
// Categories shortened the top level but also hid every setting from
|
|
927
|
+
// its search: searching "theme" matched no category label. The member
|
|
928
|
+
// labels ride along as search text so the query lands on the category
|
|
929
|
+
// that holds the setting.
|
|
930
|
+
keywords: members.map((member) => member.label).join(" "),
|
|
754
931
|
submenu: (_currentValue, done) => new CategorySubmenu(members, applyChange, () => done()),
|
|
755
932
|
};
|
|
756
933
|
};
|
|
757
934
|
const topItems = [
|
|
758
|
-
...(byId.has("autocompact") ? [byId.get("autocompact")] : []),
|
|
759
935
|
...toolFlagGroup,
|
|
936
|
+
// What the model is sent, and how much of it. Auto-compact used to sit
|
|
937
|
+
// alone at the top level and context GC was filed under tool settings,
|
|
938
|
+
// which left the three settings that decide the token budget in three
|
|
939
|
+
// different places - with the light preset in none of them.
|
|
940
|
+
categoryRow("cat-context", "Context", "What the model is sent and how much of it: compaction, superseded reads, and the low-token preset.", ["autocompact", "context-gc", "light"]),
|
|
760
941
|
categoryRow("cat-behavior", "Behavior", "Agent and session behavior: steering, follow-up, thinking, escape, tree filter, transport.", ["steering-mode", "follow-up-mode", "thinking", "double-escape-action", "tree-filter-mode", "transport"]),
|
|
761
942
|
categoryRow("cat-interface", "Interface", "Appearance and editor: theme, thinking visibility, cursor, border, padding, autocomplete, terminal.", [
|
|
762
943
|
"theme",
|
|
@@ -768,6 +949,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
768
949
|
"clear-on-shrink",
|
|
769
950
|
"terminal-progress",
|
|
770
951
|
]),
|
|
952
|
+
// Artifact production. `plugin-install-scope` had no category and was
|
|
953
|
+
// therefore unreachable from the pane despite having a live callback.
|
|
954
|
+
categoryRow("cat-plugins", "Plugins", "The autonomous plugin system's master switch, the vendor layout hoocode writes, and where autonomous installs land.", ["plugin-tools", "platform", "plugin-install-scope"]),
|
|
771
955
|
categoryRow("cat-images", "Images", "Inline image rendering and resizing.", [
|
|
772
956
|
"show-images",
|
|
773
957
|
"image-width-cells",
|
|
@@ -792,8 +976,18 @@ export class SettingsSelectorComponent extends Container {
|
|
|
792
976
|
enableSearch: true,
|
|
793
977
|
});
|
|
794
978
|
this.addChild(this.settingsList);
|
|
979
|
+
if (initialSurface) {
|
|
980
|
+
this.surfaceLine = new Text(formatSurfaceLine(initialSurface), 0, 0);
|
|
981
|
+
this.addChild(this.surfaceLine);
|
|
982
|
+
}
|
|
795
983
|
this.addChild(new DynamicBorder());
|
|
796
984
|
}
|
|
985
|
+
/** Re-price the pane after a change that may have altered what each turn sends. */
|
|
986
|
+
refreshTokenSurface() {
|
|
987
|
+
if (!this.surfaceLine || !this.measureTokenSurface)
|
|
988
|
+
return;
|
|
989
|
+
this.surfaceLine.setText(formatSurfaceLine(this.measureTokenSurface()));
|
|
990
|
+
}
|
|
797
991
|
getSettingsList() {
|
|
798
992
|
return this.settingsList;
|
|
799
993
|
}
|