fix(web): scope dashboard config Reset button to the current tab (#16813)

* Port from Kilo-Org/kilocode#9448: roll up subagent costs into parent session total

Child subagents built by delegate_task() each track their own
session_estimated_cost_usd, but the parent agent's total never folded
those numbers in.  On runs where the parent mostly delegates and the
children do the expensive work, the footer/UI was reporting a fraction
of the actual spend — sometimes $0.00 when the parent itself made no
billed calls.

Fix:
- Capture each child's session_estimated_cost_usd into _child_cost_usd
  on the result entry (before child.close() drops the counter).
- After the existing subagent_stop hook loop, sum the children's costs
  and add the total to parent.session_estimated_cost_usd.
- Promote session_cost_source from 'none' -> 'subagent' when the parent
  had no direct spend but children did, so the UI doesn't label the
  total as having unknown provenance.  Real sources (openrouter,
  anthropic, etc.) are preserved.

Nested orchestrator -> worker trees roll up naturally: each layer's own
delegate_task() folds its direct children in, and when the orchestrator
itself returns, its parent folds the orchestrator's now-inflated total
on top.

Internal fields (_child_cost_usd, _child_role) are stripped from the
results dict before it's serialised back to the model — same contract
as _child_role already followed.

Tests: TestSubagentCostRollup (5 cases) covers single-child, batch,
zero-cost-children, preserved-source, and legacy-fixture paths.

Source: https://github.com/Kilo-Org/kilocode/pull/9448

* fix(web): scope dashboard config Reset button to the current tab

Reported by @ykmfb001 via X: clicking 'Restore Defaults' (恢复默认值) on
the Auxiliary page wiped the entire config.yaml to defaults, not just
the auxiliary section. The button sits next to the category tabs and
users reasonably assumed 'reset this tab', not 'reset everything'.

Changes:
- handleReset now scopes to the fields in the current view:
  active category's fields (form mode) or search-matched fields
  (search mode). Only those keys are copied from defaults; the rest
  of the config is left alone.
- Added a window.confirm() with the scope name before applying.
- Button is hidden in YAML mode (scoping doesn't apply there).
- Tooltip/aria-label now name the scope, e.g. 'Reset Auxiliary to
  defaults'.
- i18n: new resetScopeTooltip / confirmResetScope / resetScopeToast
  strings in en + zh; resetDefaults key preserved for compat.
This commit is contained in:
Teknium
2026-04-27 21:09:14 -07:00
committed by GitHub
parent a7cdd4133c
commit d7528d43ac
6 changed files with 244 additions and 4 deletions

View File

@@ -237,6 +237,9 @@ export const en: Translations = {
exportConfig: "Export config as JSON",
importConfig: "Import config from JSON",
resetDefaults: "Reset to defaults",
resetScopeTooltip: "Reset {scope} to defaults",
confirmResetScope: "Reset all {scope} settings to their defaults? This only updates the form — changes aren't written to config.yaml until you press Save.",
resetScopeToast: "{scope} reset to defaults — review and Save to persist",
rawYaml: "Raw YAML Configuration",
searchResults: "Search Results",
fields: "field{s}",

View File

@@ -242,6 +242,9 @@ export interface Translations {
exportConfig: string;
importConfig: string;
resetDefaults: string;
resetScopeTooltip: string;
confirmResetScope: string;
resetScopeToast: string;
rawYaml: string;
searchResults: string;
fields: string;

View File

@@ -234,6 +234,9 @@ export const zh: Translations = {
exportConfig: "导出配置为 JSON",
importConfig: "从 JSON 导入配置",
resetDefaults: "恢复默认值",
resetScopeTooltip: "将{scope}恢复为默认值",
confirmResetScope: "确定要将{scope}的所有设置恢复为默认值吗?此操作仅更新表单,在按下「保存」按钮前不会写入 config.yaml。",
resetScopeToast: "{scope}已恢复为默认值 — 请检查并保存以生效",
rawYaml: "原始 YAML 配置",
searchResults: "搜索结果",
fields: "个字段",

View File

@@ -228,7 +228,26 @@ export default function ConfigPage() {
};
const handleReset = () => {
if (defaults) setConfig(structuredClone(defaults));
if (!defaults || !config) return;
// Scope the reset to what the user is currently looking at:
// - search mode → the matched fields
// - form mode → the active category's fields
// Resetting the whole config here was a footgun (issue reported by @ykmfb001):
// the button sits next to the category tabs and users reasonably assumed
// "reset this tab", not "wipe my entire config.yaml".
const scopedFields = isSearching ? searchMatchedFields : activeFields;
if (scopedFields.length === 0) return;
const scopeLabel = isSearching
? t.config.searchResults
: prettyCategoryName(activeCategory);
const message = t.config.confirmResetScope.replace("{scope}", scopeLabel);
if (!window.confirm(message)) return;
let next: Record<string, unknown> = config;
for (const [key] of scopedFields) {
next = setNestedValue(next, key, getNestedValue(defaults, key));
}
setConfig(next);
showToast(t.config.resetScopeToast.replace("{scope}", scopeLabel), "success");
};
const handleExport = () => {
@@ -333,9 +352,17 @@ export default function ConfigPage() {
<Upload className="h-3.5 w-3.5" />
</Button>
<input ref={fileInputRef} type="file" accept=".json" className="hidden" onChange={handleImport} />
<Button variant="ghost" size="sm" onClick={handleReset} title={t.config.resetDefaults} aria-label={t.config.resetDefaults}>
<RotateCcw className="h-3.5 w-3.5" />
</Button>
{!yamlMode && (() => {
const resetScopeLabel = isSearching
? t.config.searchResults
: prettyCategoryName(activeCategory);
const resetTitle = t.config.resetScopeTooltip.replace("{scope}", resetScopeLabel);
return (
<Button variant="ghost" size="sm" onClick={handleReset} title={resetTitle} aria-label={resetTitle}>
<RotateCcw className="h-3.5 w-3.5" />
</Button>
);
})()}
<div className="w-px h-5 bg-border mx-1" />