@kolisachint/hoocode-agent 0.5.29 → 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 +24 -0
- 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/modes/interactive/components/settings-selector.d.ts +20 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +105 -35
- 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 +21 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/settings.md +21 -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
|
@@ -197,6 +197,10 @@ class ToolsSubmenu extends Container {
|
|
|
197
197
|
? "Core tool. Disabling leaves the agent unable to perform this action in every session."
|
|
198
198
|
: "Disable to remove this tool from the agent this session and every future session.",
|
|
199
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,
|
|
200
204
|
values: ["on", "off"],
|
|
201
205
|
}));
|
|
202
206
|
const items = [...groupItems, ...toolItems];
|
|
@@ -243,39 +247,45 @@ function byteLabels(current) {
|
|
|
243
247
|
return all.sort((a, b) => a[1] - b[1]).map(([label]) => label);
|
|
244
248
|
}
|
|
245
249
|
/**
|
|
246
|
-
* Submenu for
|
|
247
|
-
*
|
|
248
|
-
*
|
|
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.
|
|
249
256
|
*/
|
|
250
257
|
class ToolSettingsSubmenu extends Container {
|
|
251
258
|
settingsList;
|
|
252
259
|
constructor(config, callbacks, onCancel) {
|
|
253
260
|
super();
|
|
254
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
|
+
},
|
|
255
269
|
{
|
|
256
270
|
id: "output-max-bytes",
|
|
257
|
-
label: "
|
|
271
|
+
label: "Max bytes",
|
|
258
272
|
description: "Byte cap on a single read/bash result before truncation. Applies to future tool calls.",
|
|
259
273
|
currentValue: bytesToLabel(config.toolOutputMaxBytes),
|
|
260
274
|
values: byteLabels(config.toolOutputMaxBytes),
|
|
261
275
|
},
|
|
262
276
|
{
|
|
263
277
|
id: "output-max-lines",
|
|
264
|
-
label: "
|
|
278
|
+
label: "Max lines",
|
|
265
279
|
description: "Line cap on a single read/bash result before truncation. Applies to future tool calls.",
|
|
266
280
|
currentValue: String(config.toolOutputMaxLines),
|
|
267
281
|
values: presetValues([200, 400, 800, 1600, 3200], config.toolOutputMaxLines),
|
|
268
282
|
},
|
|
269
|
-
{
|
|
270
|
-
id: "context-gc",
|
|
271
|
-
label: "Context GC",
|
|
272
|
-
description: "Stub superseded read results (files later edited/re-read) out of the outgoing context.",
|
|
273
|
-
currentValue: config.contextGc ? "true" : "false",
|
|
274
|
-
values: ["true", "false"],
|
|
275
|
-
},
|
|
276
283
|
];
|
|
277
284
|
this.settingsList = new SettingsList(items, Math.min(items.length, 10), getSettingsListTheme(), (id, newValue) => {
|
|
278
285
|
switch (id) {
|
|
286
|
+
case "tool-output-display":
|
|
287
|
+
callbacks.onToolOutputDisplayChange(newValue);
|
|
288
|
+
break;
|
|
279
289
|
case "output-max-bytes": {
|
|
280
290
|
const preset = TOOL_OUTPUT_BYTE_PRESETS.find(([label]) => label === newValue);
|
|
281
291
|
// A hand-set cap has no preset entry; its label is "<n> KB" by
|
|
@@ -288,9 +298,6 @@ class ToolSettingsSubmenu extends Container {
|
|
|
288
298
|
case "output-max-lines":
|
|
289
299
|
callbacks.onToolOutputMaxLinesChange(parseInt(newValue, 10));
|
|
290
300
|
break;
|
|
291
|
-
case "context-gc":
|
|
292
|
-
callbacks.onContextGcChange(newValue === "true");
|
|
293
|
-
break;
|
|
294
301
|
}
|
|
295
302
|
}, onCancel);
|
|
296
303
|
this.addChild(this.settingsList);
|
|
@@ -420,13 +427,41 @@ class SelectSubmenu extends Container {
|
|
|
420
427
|
this.selectList.handleInput(data);
|
|
421
428
|
}
|
|
422
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
|
+
}
|
|
423
455
|
/**
|
|
424
456
|
* Main settings selector component.
|
|
425
457
|
*/
|
|
426
458
|
export class SettingsSelectorComponent extends Container {
|
|
427
459
|
settingsList;
|
|
460
|
+
surfaceLine;
|
|
461
|
+
measureTokenSurface;
|
|
428
462
|
constructor(config, callbacks) {
|
|
429
463
|
super();
|
|
464
|
+
this.measureTokenSurface = config.measureTokenSurface;
|
|
430
465
|
const supportsImages = getCapabilities().images;
|
|
431
466
|
const followUpKey = keyDisplayText("app.message.followUp");
|
|
432
467
|
let currentWarnings = { ...config.warnings };
|
|
@@ -437,6 +472,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
437
472
|
const pinnedNote = (key) => projectPinned.has(key)
|
|
438
473
|
? ` This repo's .hoocode/settings.json sets ${key}, which overrides this row from the next session on.`
|
|
439
474
|
: "";
|
|
475
|
+
const initialSurface = config.measureTokenSurface?.();
|
|
440
476
|
const toolsOn = config.tools.filter((t) => t.enabled).length;
|
|
441
477
|
const toolsOff = config.tools.length - toolsOn;
|
|
442
478
|
const items = [
|
|
@@ -591,8 +627,26 @@ export class SettingsSelectorComponent extends Container {
|
|
|
591
627
|
currentValue: config.blockImages ? "true" : "false",
|
|
592
628
|
values: ["true", "false"],
|
|
593
629
|
});
|
|
594
|
-
//
|
|
595
|
-
|
|
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");
|
|
596
650
|
items.splice(blockImagesIndex + 1, 0, {
|
|
597
651
|
id: "skill-commands",
|
|
598
652
|
label: "Skill commands",
|
|
@@ -724,30 +778,28 @@ export class SettingsSelectorComponent extends Container {
|
|
|
724
778
|
{
|
|
725
779
|
id: "tools",
|
|
726
780
|
label: "Tools",
|
|
727
|
-
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.",
|
|
728
782
|
currentValue: toolsOff > 0 ? `${toolsOn} on · ${toolsOff} off` : `${toolsOn} on`,
|
|
729
|
-
|
|
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()),
|
|
730
789
|
},
|
|
731
790
|
{
|
|
732
|
-
id: "tool-output
|
|
733
|
-
label: "Tool output
|
|
734
|
-
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.",
|
|
735
794
|
currentValue: config.toolOutputDisplay,
|
|
736
|
-
values: ["standard", "collapsed", "peek"],
|
|
737
|
-
},
|
|
738
|
-
{
|
|
739
|
-
id: "tool-settings",
|
|
740
|
-
label: "Tool settings",
|
|
741
|
-
description: "Per-tool runtime settings: output truncation caps and context garbage collection.",
|
|
742
|
-
currentValue: "configure",
|
|
743
795
|
submenu: (_currentValue, done) => new ToolSettingsSubmenu({
|
|
796
|
+
toolOutputDisplay: config.toolOutputDisplay,
|
|
744
797
|
toolOutputMaxBytes: config.toolOutputMaxBytes,
|
|
745
798
|
toolOutputMaxLines: config.toolOutputMaxLines,
|
|
746
|
-
contextGc: config.contextGc,
|
|
747
799
|
}, {
|
|
800
|
+
onToolOutputDisplayChange: callbacks.onToolOutputDisplayChange,
|
|
748
801
|
onToolOutputMaxBytesChange: callbacks.onToolOutputMaxBytesChange,
|
|
749
802
|
onToolOutputMaxLinesChange: callbacks.onToolOutputMaxLinesChange,
|
|
750
|
-
onContextGcChange: callbacks.onContextGcChange,
|
|
751
803
|
}, () => done()),
|
|
752
804
|
},
|
|
753
805
|
];
|
|
@@ -769,9 +821,6 @@ export class SettingsSelectorComponent extends Container {
|
|
|
769
821
|
case "autocompact":
|
|
770
822
|
callbacks.onAutoCompactChange(newValue === "true");
|
|
771
823
|
break;
|
|
772
|
-
case "tool-output-display":
|
|
773
|
-
callbacks.onToolOutputDisplayChange(newValue);
|
|
774
|
-
break;
|
|
775
824
|
case "show-images":
|
|
776
825
|
callbacks.onShowImagesChange(newValue === "true");
|
|
777
826
|
break;
|
|
@@ -787,6 +836,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
787
836
|
case "skill-commands":
|
|
788
837
|
callbacks.onEnableSkillCommandsChange(newValue === "true");
|
|
789
838
|
break;
|
|
839
|
+
case "light":
|
|
840
|
+
callbacks.onLightChange(newValue === "true");
|
|
841
|
+
break;
|
|
790
842
|
case "plugin-tools":
|
|
791
843
|
callbacks.onEnablePluginToolsChange(newValue === "true");
|
|
792
844
|
break;
|
|
@@ -855,6 +907,10 @@ export class SettingsSelectorComponent extends Container {
|
|
|
855
907
|
callbacks.onLearnSettingChange(id, parseInt(newValue, 10));
|
|
856
908
|
break;
|
|
857
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();
|
|
858
914
|
};
|
|
859
915
|
// Partition the flat leaf settings into named category submenus so the
|
|
860
916
|
// top level stays short. `items` holds autocompact + every leaf setting.
|
|
@@ -876,8 +932,12 @@ export class SettingsSelectorComponent extends Container {
|
|
|
876
932
|
};
|
|
877
933
|
};
|
|
878
934
|
const topItems = [
|
|
879
|
-
...(byId.has("autocompact") ? [byId.get("autocompact")] : []),
|
|
880
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"]),
|
|
881
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"]),
|
|
882
942
|
categoryRow("cat-interface", "Interface", "Appearance and editor: theme, thinking visibility, cursor, border, padding, autocomplete, terminal.", [
|
|
883
943
|
"theme",
|
|
@@ -916,8 +976,18 @@ export class SettingsSelectorComponent extends Container {
|
|
|
916
976
|
enableSearch: true,
|
|
917
977
|
});
|
|
918
978
|
this.addChild(this.settingsList);
|
|
979
|
+
if (initialSurface) {
|
|
980
|
+
this.surfaceLine = new Text(formatSurfaceLine(initialSurface), 0, 0);
|
|
981
|
+
this.addChild(this.surfaceLine);
|
|
982
|
+
}
|
|
919
983
|
this.addChild(new DynamicBorder());
|
|
920
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
|
+
}
|
|
921
991
|
getSettingsList() {
|
|
922
992
|
return this.settingsList;
|
|
923
993
|
}
|