@kolisachint/hoocode-agent 0.5.42 → 0.5.44

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/core/settings-defaults.d.ts +1 -0
  3. package/dist/core/settings-defaults.d.ts.map +1 -1
  4. package/dist/core/settings-defaults.js +1 -0
  5. package/dist/core/settings-defaults.js.map +1 -1
  6. package/dist/core/settings-manager.d.ts +14 -0
  7. package/dist/core/settings-manager.d.ts.map +1 -1
  8. package/dist/core/settings-manager.js +15 -0
  9. package/dist/core/settings-manager.js.map +1 -1
  10. package/dist/core/settings-types.d.ts +3 -0
  11. package/dist/core/settings-types.d.ts.map +1 -1
  12. package/dist/core/settings-types.js.map +1 -1
  13. package/dist/core/tools/webfetch.d.ts +9 -0
  14. package/dist/core/tools/webfetch.d.ts.map +1 -1
  15. package/dist/core/tools/webfetch.js +52 -5
  16. package/dist/core/tools/webfetch.js.map +1 -1
  17. package/dist/core/tools/websearch.d.ts.map +1 -1
  18. package/dist/core/tools/websearch.js +8 -0
  19. package/dist/core/tools/websearch.js.map +1 -1
  20. package/dist/core/tools/webtools-shared.d.ts +83 -2
  21. package/dist/core/tools/webtools-shared.d.ts.map +1 -1
  22. package/dist/core/tools/webtools-shared.js +60 -2
  23. package/dist/core/tools/webtools-shared.js.map +1 -1
  24. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  25. package/dist/modes/interactive/components/settings-selector.js +11 -0
  26. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  27. package/dist/modes/interactive/interactive-mode.d.ts +6 -0
  28. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  29. package/dist/modes/interactive/interactive-mode.js +16 -0
  30. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  31. package/dist/modes/interactive/websearch-warning.d.ts +35 -0
  32. package/dist/modes/interactive/websearch-warning.d.ts.map +1 -0
  33. package/dist/modes/interactive/websearch-warning.js +40 -0
  34. package/dist/modes/interactive/websearch-warning.js.map +1 -0
  35. package/docs/settings.md +69 -2
  36. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  37. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  38. package/examples/extensions/sandbox/package.json +1 -1
  39. package/examples/extensions/with-deps/package.json +1 -1
  40. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.44] - 2026-08-31
4
+
5
+ ### Added
6
+
7
+ - `webfetch` takes an `offset`, so a long page can be read to the end.
8
+
9
+ With a `webtools` binary that reports paging offsets, a cut page comes back
10
+ with the window it covers and the offset that continues it, and passing that
11
+ offset back reads the next window. Windows tile the document exactly, so
12
+ nothing is skipped or repeated, and a whole document costs one budget per
13
+ window instead of one copy of the page per attempt.
14
+
15
+ Against an older binary the fields are absent and the note falls back to
16
+ advising a larger `maxTokens`, the only honest advice when there is no offset
17
+ to resume at. `--offset` is sent only when non-zero, so a read from the start
18
+ never passes a flag an older binary would reject.
19
+
20
+ - `webfetch` says when a page was cut off, and how to get the rest.
21
+
22
+ A fetch that overran its token budget came back with the binary's bare
23
+ `…[truncated]` marker: enough to tell the model something was missing,
24
+ nothing it could act on, so a long document was a dead end rather than a
25
+ first page. The result now carries the budget the cut was made at and the
26
+ ways past it, `details.truncated` records it, and the TUI marks the token
27
+ line `(truncated at N)` instead of showing a prefix and a complete page
28
+ identically.
29
+
30
+ The note names the *clamped* budget, not the requested one — advice to raise
31
+ `maxTokens` past the 25000 cap would point somewhere that changes nothing.
32
+
33
+ - `websearch` reports what its results cost, as `webfetch` already did.
34
+
35
+ Search is the one web tool with no token budget of its own — snippet length
36
+ is whatever the backend returns — so the estimate the binary already sends
37
+ back is the only thing that makes an expensive query visible before it is
38
+ already in context, and it is the number `maxResults` is tuned against. It
39
+ was being parsed into the result details and then dropped at render.
40
+
41
+ ## [0.5.43] - 2026-08-31
42
+
43
+ ### Added
44
+
45
+ - A startup notice when `websearch` is enabled with no search API key.
46
+
47
+ The tool works with no configuration at all, which is the problem: the keyless
48
+ default is scraped DuckDuckGo HTML, rate-limited hard and liable to fail
49
+ outright, and the failure arrives as a bad search rather than as a setup
50
+ problem. The TUI now says so once per session when `websearch` is active and
51
+ no keyed backend is configured, in two lines: what is wrong, and the
52
+ `BRAVE_API_KEY` / `TAVILY_API_KEY` that fixes it. The remaining backends and
53
+ the `webtools.search` block of `~/.hoocode/settings.json` — the same file the
54
+ `webtools` binary reads its own credentials from — are in the docs.
55
+
56
+ Detection mirrors the binary's own resolution order (env over settings file)
57
+ across Brave, Tavily and SearXNG, and never returns the credential itself, so
58
+ a key cannot reach the UI or a log. Pinning `duckduckgo` as the provider is
59
+ read as a deliberate choice and stays quiet, as does the new
60
+ `warnings.websearchApiKey` switch — `/settings` → Warnings → Web search API
61
+ key, beside the Anthropic extra-usage toggle it is modelled on.
62
+
3
63
  ## [0.5.42] - 2026-08-31
4
64
 
5
65
  ### Added
@@ -78,6 +78,7 @@ export declare const DEFAULT_SETTINGS: {
78
78
  };
79
79
  warnings: {
80
80
  anthropicExtraUsage: true;
81
+ websearchApiKey: true;
81
82
  };
82
83
  packages: never[];
83
84
  extensions: never[];
@@ -1 +1 @@
1
- {"version":3,"file":"settings-defaults.d.ts","sourceRoot":"","sources":["../../src/core/settings-defaults.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuFT,CAAC","sourcesContent":["import type { Settings } from \"./settings-manager.js\";\n\nexport const DEFAULT_SETTINGS = {\n\ttransport: \"auto\",\n\tsteeringMode: \"one-at-a-time\",\n\tfollowUpMode: \"one-at-a-time\",\n\tcompaction: {\n\t\tenabled: true,\n\t\treserveTokens: 16384,\n\t\tkeepRecentTokens: 20000,\n\t\tmaxContextRatio: 0.75,\n\t},\n\ttoolOutput: {\n\t\tmaxBytes: 32 * 1024,\n\t\tmaxLines: 800,\n\t},\n\tcontextGc: {\n\t\tenabled: true,\n\t},\n\tvoice: {\n\t\tsilenceMs: 800,\n\t},\n\twebtools: {\n\t\ttimeoutSecs: 15,\n\t},\n\tdisabledTools: [],\n\ttoolOutputView: \"glance\",\n\tbranchSummary: {\n\t\treserveTokens: 16384,\n\t\tskipPrompt: false,\n\t},\n\tretry: {\n\t\tenabled: true,\n\t\tmaxRetries: 3,\n\t\tbaseDelayMs: 2000,\n\t\tprovider: {\n\t\t\tmaxRetryDelayMs: 60000,\n\t\t},\n\t},\n\thideThinkingBlock: false,\n\tquietStartup: false,\n\tcollapseChangelog: false,\n\tenableInstallTelemetry: true,\n\tenableSkillCommands: true,\n\tenableSubagent: true,\n\twarmSubagents: false,\n\tmaxSubagentDepth: 2,\n\tnestedSubagentConcurrency: 2,\n\tenableTodoWrite: true,\n\tenablePluginTools: false,\n\tpluginInstallScope: \"user\",\n\tdeferMcpSchemas: true,\n\tenableWebTools: false,\n\tenableEmbsearchTools: true,\n\tembsearchThresholdBytes: 0,\n\tlearnMaxSessions: 20,\n\tlearnMaxAgeDays: 30,\n\tlearnMinRepeats: 2,\n\tlearnMinRequestRepeats: 3,\n\tlearnMaxProposals: 8,\n\tlight: false,\n\tterminal: {\n\t\tshowImages: true,\n\t\timageWidthCells: 60,\n\t\tclearOnShrink: false,\n\t\tshowTerminalProgress: false,\n\t\tchimeOnTurnComplete: false,\n\t},\n\timages: {\n\t\tautoResize: true,\n\t\tblockImages: false,\n\t},\n\tdoubleEscapeAction: \"tree\",\n\ttreeFilterMode: \"default\",\n\teditorBorder: \"box\",\n\teditorPaddingX: 1,\n\tautocompleteMaxVisible: 5,\n\tmarkdown: {\n\t\tcodeBlockIndent: \" \",\n\t},\n\twarnings: {\n\t\tanthropicExtraUsage: true,\n\t},\n\tpackages: [],\n\textensions: [],\n\tskills: [],\n\tprompts: [],\n\tslashCommands: [],\n\tthemes: [],\n} satisfies Settings;\n"]}
1
+ {"version":3,"file":"settings-defaults.d.ts","sourceRoot":"","sources":["../../src/core/settings-defaults.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwFT,CAAC","sourcesContent":["import type { Settings } from \"./settings-manager.js\";\n\nexport const DEFAULT_SETTINGS = {\n\ttransport: \"auto\",\n\tsteeringMode: \"one-at-a-time\",\n\tfollowUpMode: \"one-at-a-time\",\n\tcompaction: {\n\t\tenabled: true,\n\t\treserveTokens: 16384,\n\t\tkeepRecentTokens: 20000,\n\t\tmaxContextRatio: 0.75,\n\t},\n\ttoolOutput: {\n\t\tmaxBytes: 32 * 1024,\n\t\tmaxLines: 800,\n\t},\n\tcontextGc: {\n\t\tenabled: true,\n\t},\n\tvoice: {\n\t\tsilenceMs: 800,\n\t},\n\twebtools: {\n\t\ttimeoutSecs: 15,\n\t},\n\tdisabledTools: [],\n\ttoolOutputView: \"glance\",\n\tbranchSummary: {\n\t\treserveTokens: 16384,\n\t\tskipPrompt: false,\n\t},\n\tretry: {\n\t\tenabled: true,\n\t\tmaxRetries: 3,\n\t\tbaseDelayMs: 2000,\n\t\tprovider: {\n\t\t\tmaxRetryDelayMs: 60000,\n\t\t},\n\t},\n\thideThinkingBlock: false,\n\tquietStartup: false,\n\tcollapseChangelog: false,\n\tenableInstallTelemetry: true,\n\tenableSkillCommands: true,\n\tenableSubagent: true,\n\twarmSubagents: false,\n\tmaxSubagentDepth: 2,\n\tnestedSubagentConcurrency: 2,\n\tenableTodoWrite: true,\n\tenablePluginTools: false,\n\tpluginInstallScope: \"user\",\n\tdeferMcpSchemas: true,\n\tenableWebTools: false,\n\tenableEmbsearchTools: true,\n\tembsearchThresholdBytes: 0,\n\tlearnMaxSessions: 20,\n\tlearnMaxAgeDays: 30,\n\tlearnMinRepeats: 2,\n\tlearnMinRequestRepeats: 3,\n\tlearnMaxProposals: 8,\n\tlight: false,\n\tterminal: {\n\t\tshowImages: true,\n\t\timageWidthCells: 60,\n\t\tclearOnShrink: false,\n\t\tshowTerminalProgress: false,\n\t\tchimeOnTurnComplete: false,\n\t},\n\timages: {\n\t\tautoResize: true,\n\t\tblockImages: false,\n\t},\n\tdoubleEscapeAction: \"tree\",\n\ttreeFilterMode: \"default\",\n\teditorBorder: \"box\",\n\teditorPaddingX: 1,\n\tautocompleteMaxVisible: 5,\n\tmarkdown: {\n\t\tcodeBlockIndent: \" \",\n\t},\n\twarnings: {\n\t\tanthropicExtraUsage: true,\n\t\twebsearchApiKey: true,\n\t},\n\tpackages: [],\n\textensions: [],\n\tskills: [],\n\tprompts: [],\n\tslashCommands: [],\n\tthemes: [],\n} satisfies Settings;\n"]}
@@ -78,6 +78,7 @@ export const DEFAULT_SETTINGS = {
78
78
  },
79
79
  warnings: {
80
80
  anthropicExtraUsage: true,
81
+ websearchApiKey: true,
81
82
  },
82
83
  packages: [],
83
84
  extensions: [],
@@ -1 +1 @@
1
- {"version":3,"file":"settings-defaults.js","sourceRoot":"","sources":["../../src/core/settings-defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC/B,SAAS,EAAE,MAAM;IACjB,YAAY,EAAE,eAAe;IAC7B,YAAY,EAAE,eAAe;IAC7B,UAAU,EAAE;QACX,OAAO,EAAE,IAAI;QACb,aAAa,EAAE,KAAK;QACpB,gBAAgB,EAAE,KAAK;QACvB,eAAe,EAAE,IAAI;KACrB;IACD,UAAU,EAAE;QACX,QAAQ,EAAE,EAAE,GAAG,IAAI;QACnB,QAAQ,EAAE,GAAG;KACb;IACD,SAAS,EAAE;QACV,OAAO,EAAE,IAAI;KACb;IACD,KAAK,EAAE;QACN,SAAS,EAAE,GAAG;KACd;IACD,QAAQ,EAAE;QACT,WAAW,EAAE,EAAE;KACf;IACD,aAAa,EAAE,EAAE;IACjB,cAAc,EAAE,QAAQ;IACxB,aAAa,EAAE;QACd,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE,KAAK;KACjB;IACD,KAAK,EAAE;QACN,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE;YACT,eAAe,EAAE,KAAK;SACtB;KACD;IACD,iBAAiB,EAAE,KAAK;IACxB,YAAY,EAAE,KAAK;IACnB,iBAAiB,EAAE,KAAK;IACxB,sBAAsB,EAAE,IAAI;IAC5B,mBAAmB,EAAE,IAAI;IACzB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,KAAK;IACpB,gBAAgB,EAAE,CAAC;IACnB,yBAAyB,EAAE,CAAC;IAC5B,eAAe,EAAE,IAAI;IACrB,iBAAiB,EAAE,KAAK;IACxB,kBAAkB,EAAE,MAAM;IAC1B,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,KAAK;IACrB,oBAAoB,EAAE,IAAI;IAC1B,uBAAuB,EAAE,CAAC;IAC1B,gBAAgB,EAAE,EAAE;IACpB,eAAe,EAAE,EAAE;IACnB,eAAe,EAAE,CAAC;IAClB,sBAAsB,EAAE,CAAC;IACzB,iBAAiB,EAAE,CAAC;IACpB,KAAK,EAAE,KAAK;IACZ,QAAQ,EAAE;QACT,UAAU,EAAE,IAAI;QAChB,eAAe,EAAE,EAAE;QACnB,aAAa,EAAE,KAAK;QACpB,oBAAoB,EAAE,KAAK;QAC3B,mBAAmB,EAAE,KAAK;KAC1B;IACD,MAAM,EAAE;QACP,UAAU,EAAE,IAAI;QAChB,WAAW,EAAE,KAAK;KAClB;IACD,kBAAkB,EAAE,MAAM;IAC1B,cAAc,EAAE,SAAS;IACzB,YAAY,EAAE,KAAK;IACnB,cAAc,EAAE,CAAC;IACjB,sBAAsB,EAAE,CAAC;IACzB,QAAQ,EAAE;QACT,eAAe,EAAE,IAAI;KACrB;IACD,QAAQ,EAAE;QACT,mBAAmB,EAAE,IAAI;KACzB;IACD,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,EAAE;IACV,OAAO,EAAE,EAAE;IACX,aAAa,EAAE,EAAE;IACjB,MAAM,EAAE,EAAE;CACS,CAAC","sourcesContent":["import type { Settings } from \"./settings-manager.js\";\n\nexport const DEFAULT_SETTINGS = {\n\ttransport: \"auto\",\n\tsteeringMode: \"one-at-a-time\",\n\tfollowUpMode: \"one-at-a-time\",\n\tcompaction: {\n\t\tenabled: true,\n\t\treserveTokens: 16384,\n\t\tkeepRecentTokens: 20000,\n\t\tmaxContextRatio: 0.75,\n\t},\n\ttoolOutput: {\n\t\tmaxBytes: 32 * 1024,\n\t\tmaxLines: 800,\n\t},\n\tcontextGc: {\n\t\tenabled: true,\n\t},\n\tvoice: {\n\t\tsilenceMs: 800,\n\t},\n\twebtools: {\n\t\ttimeoutSecs: 15,\n\t},\n\tdisabledTools: [],\n\ttoolOutputView: \"glance\",\n\tbranchSummary: {\n\t\treserveTokens: 16384,\n\t\tskipPrompt: false,\n\t},\n\tretry: {\n\t\tenabled: true,\n\t\tmaxRetries: 3,\n\t\tbaseDelayMs: 2000,\n\t\tprovider: {\n\t\t\tmaxRetryDelayMs: 60000,\n\t\t},\n\t},\n\thideThinkingBlock: false,\n\tquietStartup: false,\n\tcollapseChangelog: false,\n\tenableInstallTelemetry: true,\n\tenableSkillCommands: true,\n\tenableSubagent: true,\n\twarmSubagents: false,\n\tmaxSubagentDepth: 2,\n\tnestedSubagentConcurrency: 2,\n\tenableTodoWrite: true,\n\tenablePluginTools: false,\n\tpluginInstallScope: \"user\",\n\tdeferMcpSchemas: true,\n\tenableWebTools: false,\n\tenableEmbsearchTools: true,\n\tembsearchThresholdBytes: 0,\n\tlearnMaxSessions: 20,\n\tlearnMaxAgeDays: 30,\n\tlearnMinRepeats: 2,\n\tlearnMinRequestRepeats: 3,\n\tlearnMaxProposals: 8,\n\tlight: false,\n\tterminal: {\n\t\tshowImages: true,\n\t\timageWidthCells: 60,\n\t\tclearOnShrink: false,\n\t\tshowTerminalProgress: false,\n\t\tchimeOnTurnComplete: false,\n\t},\n\timages: {\n\t\tautoResize: true,\n\t\tblockImages: false,\n\t},\n\tdoubleEscapeAction: \"tree\",\n\ttreeFilterMode: \"default\",\n\teditorBorder: \"box\",\n\teditorPaddingX: 1,\n\tautocompleteMaxVisible: 5,\n\tmarkdown: {\n\t\tcodeBlockIndent: \" \",\n\t},\n\twarnings: {\n\t\tanthropicExtraUsage: true,\n\t},\n\tpackages: [],\n\textensions: [],\n\tskills: [],\n\tprompts: [],\n\tslashCommands: [],\n\tthemes: [],\n} satisfies Settings;\n"]}
1
+ {"version":3,"file":"settings-defaults.js","sourceRoot":"","sources":["../../src/core/settings-defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC/B,SAAS,EAAE,MAAM;IACjB,YAAY,EAAE,eAAe;IAC7B,YAAY,EAAE,eAAe;IAC7B,UAAU,EAAE;QACX,OAAO,EAAE,IAAI;QACb,aAAa,EAAE,KAAK;QACpB,gBAAgB,EAAE,KAAK;QACvB,eAAe,EAAE,IAAI;KACrB;IACD,UAAU,EAAE;QACX,QAAQ,EAAE,EAAE,GAAG,IAAI;QACnB,QAAQ,EAAE,GAAG;KACb;IACD,SAAS,EAAE;QACV,OAAO,EAAE,IAAI;KACb;IACD,KAAK,EAAE;QACN,SAAS,EAAE,GAAG;KACd;IACD,QAAQ,EAAE;QACT,WAAW,EAAE,EAAE;KACf;IACD,aAAa,EAAE,EAAE;IACjB,cAAc,EAAE,QAAQ;IACxB,aAAa,EAAE;QACd,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE,KAAK;KACjB;IACD,KAAK,EAAE;QACN,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE;YACT,eAAe,EAAE,KAAK;SACtB;KACD;IACD,iBAAiB,EAAE,KAAK;IACxB,YAAY,EAAE,KAAK;IACnB,iBAAiB,EAAE,KAAK;IACxB,sBAAsB,EAAE,IAAI;IAC5B,mBAAmB,EAAE,IAAI;IACzB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,KAAK;IACpB,gBAAgB,EAAE,CAAC;IACnB,yBAAyB,EAAE,CAAC;IAC5B,eAAe,EAAE,IAAI;IACrB,iBAAiB,EAAE,KAAK;IACxB,kBAAkB,EAAE,MAAM;IAC1B,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,KAAK;IACrB,oBAAoB,EAAE,IAAI;IAC1B,uBAAuB,EAAE,CAAC;IAC1B,gBAAgB,EAAE,EAAE;IACpB,eAAe,EAAE,EAAE;IACnB,eAAe,EAAE,CAAC;IAClB,sBAAsB,EAAE,CAAC;IACzB,iBAAiB,EAAE,CAAC;IACpB,KAAK,EAAE,KAAK;IACZ,QAAQ,EAAE;QACT,UAAU,EAAE,IAAI;QAChB,eAAe,EAAE,EAAE;QACnB,aAAa,EAAE,KAAK;QACpB,oBAAoB,EAAE,KAAK;QAC3B,mBAAmB,EAAE,KAAK;KAC1B;IACD,MAAM,EAAE;QACP,UAAU,EAAE,IAAI;QAChB,WAAW,EAAE,KAAK;KAClB;IACD,kBAAkB,EAAE,MAAM;IAC1B,cAAc,EAAE,SAAS;IACzB,YAAY,EAAE,KAAK;IACnB,cAAc,EAAE,CAAC;IACjB,sBAAsB,EAAE,CAAC;IACzB,QAAQ,EAAE;QACT,eAAe,EAAE,IAAI;KACrB;IACD,QAAQ,EAAE;QACT,mBAAmB,EAAE,IAAI;QACzB,eAAe,EAAE,IAAI;KACrB;IACD,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,EAAE;IACV,OAAO,EAAE,EAAE;IACX,aAAa,EAAE,EAAE;IACjB,MAAM,EAAE,EAAE;CACS,CAAC","sourcesContent":["import type { Settings } from \"./settings-manager.js\";\n\nexport const DEFAULT_SETTINGS = {\n\ttransport: \"auto\",\n\tsteeringMode: \"one-at-a-time\",\n\tfollowUpMode: \"one-at-a-time\",\n\tcompaction: {\n\t\tenabled: true,\n\t\treserveTokens: 16384,\n\t\tkeepRecentTokens: 20000,\n\t\tmaxContextRatio: 0.75,\n\t},\n\ttoolOutput: {\n\t\tmaxBytes: 32 * 1024,\n\t\tmaxLines: 800,\n\t},\n\tcontextGc: {\n\t\tenabled: true,\n\t},\n\tvoice: {\n\t\tsilenceMs: 800,\n\t},\n\twebtools: {\n\t\ttimeoutSecs: 15,\n\t},\n\tdisabledTools: [],\n\ttoolOutputView: \"glance\",\n\tbranchSummary: {\n\t\treserveTokens: 16384,\n\t\tskipPrompt: false,\n\t},\n\tretry: {\n\t\tenabled: true,\n\t\tmaxRetries: 3,\n\t\tbaseDelayMs: 2000,\n\t\tprovider: {\n\t\t\tmaxRetryDelayMs: 60000,\n\t\t},\n\t},\n\thideThinkingBlock: false,\n\tquietStartup: false,\n\tcollapseChangelog: false,\n\tenableInstallTelemetry: true,\n\tenableSkillCommands: true,\n\tenableSubagent: true,\n\twarmSubagents: false,\n\tmaxSubagentDepth: 2,\n\tnestedSubagentConcurrency: 2,\n\tenableTodoWrite: true,\n\tenablePluginTools: false,\n\tpluginInstallScope: \"user\",\n\tdeferMcpSchemas: true,\n\tenableWebTools: false,\n\tenableEmbsearchTools: true,\n\tembsearchThresholdBytes: 0,\n\tlearnMaxSessions: 20,\n\tlearnMaxAgeDays: 30,\n\tlearnMinRepeats: 2,\n\tlearnMinRequestRepeats: 3,\n\tlearnMaxProposals: 8,\n\tlight: false,\n\tterminal: {\n\t\tshowImages: true,\n\t\timageWidthCells: 60,\n\t\tclearOnShrink: false,\n\t\tshowTerminalProgress: false,\n\t\tchimeOnTurnComplete: false,\n\t},\n\timages: {\n\t\tautoResize: true,\n\t\tblockImages: false,\n\t},\n\tdoubleEscapeAction: \"tree\",\n\ttreeFilterMode: \"default\",\n\teditorBorder: \"box\",\n\teditorPaddingX: 1,\n\tautocompleteMaxVisible: 5,\n\tmarkdown: {\n\t\tcodeBlockIndent: \" \",\n\t},\n\twarnings: {\n\t\tanthropicExtraUsage: true,\n\t\twebsearchApiKey: true,\n\t},\n\tpackages: [],\n\textensions: [],\n\tskills: [],\n\tprompts: [],\n\tslashCommands: [],\n\tthemes: [],\n} satisfies Settings;\n"]}
@@ -1,6 +1,7 @@
1
1
  import { type SettingsError, type SettingsScope, type SettingsStorage } from "./settings-storage.js";
2
2
  import type { BranchSummarySettings, CompactionSettings, PackageSource, Settings, ThinkingBudgetsSettings, TransportSetting, WarningSettings } from "./settings-types.js";
3
3
  import { type ToolOutputView } from "./tool-output-view.js";
4
+ import type { WebtoolsSearchSettings } from "./tools/webtools-shared.js";
4
5
  /** The `/learn` thresholds, which are flat top-level numeric settings. */
5
6
  export type LearnSettingKey = "learnMaxSessions" | "learnMaxAgeDays" | "learnMinRepeats" | "learnMinRequestRepeats" | "learnMaxProposals";
6
7
  export type { SettingsError, SettingsScope, SettingsStorage };
@@ -115,6 +116,19 @@ export declare class SettingsManager {
115
116
  * non-finite settings value or malformed env falls through to the next tier.
116
117
  */
117
118
  getWebtoolsTimeoutSecs(): number;
119
+ /**
120
+ * The `webtools.search` block as the binary would read it (backend choice and
121
+ * credentials). hoocode only reads it, to tell whether `websearch` has a keyed
122
+ * provider; the binary itself resolves the actual key at call time.
123
+ *
124
+ * Deliberately the user-level file rather than the merged view: the binary
125
+ * reads only `~/.hoocode/settings.json`, so a key in a project's
126
+ * `.hoocode/settings.json` never reaches it, and treating one as configured
127
+ * would silence the warning for a search that still runs keyless. (Contrast
128
+ * {@link getWebtoolsTimeoutSecs}, which is hoocode's own setting forwarded as
129
+ * a flag, so there the merged value is the effective one.)
130
+ */
131
+ getWebtoolsSearch(): WebtoolsSearchSettings | undefined;
118
132
  setWebtoolsTimeoutSecs(secs: number): void;
119
133
  /** Tool names disabled from the TUI; removed from the agent in every session. */
120
134
  getDisabledTools(): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"settings-manager.d.ts","sourceRoot":"","sources":["../../src/core/settings-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from \"os\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\nimport { DEFAULT_SETTINGS } from \"./settings-defaults.js\";\nimport {\n\tFileSettingsStorage,\n\tInMemorySettingsStorage,\n\ttype SettingsError,\n\ttype SettingsScope,\n\ttype SettingsStorage,\n} from \"./settings-storage.js\";\nimport type {\n\tBranchSummarySettings,\n\tCompactionSettings,\n\tPackageSource,\n\tSettings,\n\tThinkingBudgetsSettings,\n\tTransportSetting,\n\tWarningSettings,\n} from \"./settings-types.js\";\nimport { isToolOutputView, LEGACY_TOOL_OUTPUT_VIEWS, type ToolOutputView } from \"./tool-output-view.js\";\n\n/** The `/learn` thresholds, which are flat top-level numeric settings. */\nexport type LearnSettingKey =\n\t| \"learnMaxSessions\"\n\t| \"learnMaxAgeDays\"\n\t| \"learnMinRepeats\"\n\t| \"learnMinRequestRepeats\"\n\t| \"learnMaxProposals\";\n\n/** Deep merge settings: project/overrides take precedence, nested objects merge recursively */\nfunction deepMergeSettings(base: Settings, overrides: Settings): Settings {\n\tconst result: Settings = { ...base };\n\n\tfor (const key of Object.keys(overrides) as (keyof Settings)[]) {\n\t\tconst overrideValue = overrides[key];\n\t\tconst baseValue = base[key];\n\n\t\tif (overrideValue === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// For nested objects, merge recursively\n\t\tif (\n\t\t\ttypeof overrideValue === \"object\" &&\n\t\t\toverrideValue !== null &&\n\t\t\t!Array.isArray(overrideValue) &&\n\t\t\ttypeof baseValue === \"object\" &&\n\t\t\tbaseValue !== null &&\n\t\t\t!Array.isArray(baseValue)\n\t\t) {\n\t\t\t(result as Record<string, unknown>)[key] = { ...baseValue, ...overrideValue };\n\t\t} else {\n\t\t\t// For primitives and arrays, override value wins\n\t\t\t(result as Record<string, unknown>)[key] = overrideValue;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport type { SettingsError, SettingsScope, SettingsStorage };\nexport type {\n\tBranchSummarySettings,\n\tCompactionSettings,\n\tImageSettings,\n\tPackageSource,\n\tRetrySettings,\n\tSettings,\n\tThinkingBudgetsSettings,\n\tTransportSetting,\n\tWarningSettings,\n} from \"./settings-types.js\";\n\nexport class SettingsManager {\n\tprivate storage: SettingsStorage;\n\tprivate globalSettings: Settings;\n\tprivate projectSettings: Settings;\n\tprivate settings: Settings;\n\tprivate modifiedFields = new Set<keyof Settings>(); // Track global fields modified during session\n\tprivate modifiedNestedFields = new Map<keyof Settings, Set<string>>(); // Track global nested field modifications\n\tprivate modifiedProjectFields = new Set<keyof Settings>(); // Track project fields modified during session\n\tprivate modifiedProjectNestedFields = new Map<keyof Settings, Set<string>>(); // Track project nested field modifications\n\tprivate globalSettingsLoadError: Error | null = null; // Track if global settings file had parse errors\n\tprivate projectSettingsLoadError: Error | null = null; // Track if project settings file had parse errors\n\tprivate writeQueue: Promise<void> = Promise.resolve();\n\tprivate errors: SettingsError[];\n\n\tprivate constructor(\n\t\tstorage: SettingsStorage,\n\t\tinitialGlobal: Settings,\n\t\tinitialProject: Settings,\n\t\tglobalLoadError: Error | null = null,\n\t\tprojectLoadError: Error | null = null,\n\t\tinitialErrors: SettingsError[] = [],\n\t) {\n\t\tthis.storage = storage;\n\t\tthis.globalSettings = initialGlobal;\n\t\tthis.projectSettings = initialProject;\n\t\tthis.globalSettingsLoadError = globalLoadError;\n\t\tthis.projectSettingsLoadError = projectLoadError;\n\t\tthis.errors = [...initialErrors];\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\t}\n\n\t/** Create a SettingsManager that loads from files */\n\tstatic create(cwd: string, agentDir: string = getAgentDir()): SettingsManager {\n\t\tconst storage = new FileSettingsStorage(cwd, agentDir);\n\t\treturn SettingsManager.fromStorage(storage);\n\t}\n\n\t/** Create a SettingsManager from an arbitrary storage backend */\n\tstatic fromStorage(storage: SettingsStorage): SettingsManager {\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(storage, \"global\");\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(storage, \"project\");\n\t\tconst initialErrors: SettingsError[] = [];\n\t\tif (globalLoad.error) {\n\t\t\tinitialErrors.push({ scope: \"global\", error: globalLoad.error });\n\t\t}\n\t\tif (projectLoad.error) {\n\t\t\tinitialErrors.push({ scope: \"project\", error: projectLoad.error });\n\t\t}\n\n\t\treturn new SettingsManager(\n\t\t\tstorage,\n\t\t\tglobalLoad.settings,\n\t\t\tprojectLoad.settings,\n\t\t\tglobalLoad.error,\n\t\t\tprojectLoad.error,\n\t\t\tinitialErrors,\n\t\t);\n\t}\n\n\t/** Create an in-memory SettingsManager (no file I/O) */\n\tstatic inMemory(settings: Partial<Settings> = {}): SettingsManager {\n\t\tconst storage = new InMemorySettingsStorage();\n\t\tconst initialSettings = SettingsManager.migrateSettings(structuredClone(settings) as Record<string, unknown>);\n\t\tstorage.withLock(\"global\", () => JSON.stringify(initialSettings, null, 2));\n\t\treturn SettingsManager.fromStorage(storage);\n\t}\n\n\tprivate static loadFromStorage(storage: SettingsStorage, scope: SettingsScope): Settings {\n\t\tlet content: string | undefined;\n\t\tstorage.withLock(scope, (current) => {\n\t\t\tcontent = current;\n\t\t\treturn undefined;\n\t\t});\n\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\tconst settings = JSON.parse(content);\n\t\treturn SettingsManager.migrateSettings(settings);\n\t}\n\n\tprivate static tryLoadFromStorage(\n\t\tstorage: SettingsStorage,\n\t\tscope: SettingsScope,\n\t): { settings: Settings; error: Error | null } {\n\t\ttry {\n\t\t\treturn { settings: SettingsManager.loadFromStorage(storage, scope), error: null };\n\t\t} catch (error) {\n\t\t\treturn { settings: {}, error: error as Error };\n\t\t}\n\t}\n\n\t/** Migrate old settings format to new format */\n\tprivate static migrateSettings(settings: Record<string, unknown>): Settings {\n\t\t// Migrate queueMode -> steeringMode\n\t\tif (\"queueMode\" in settings && !(\"steeringMode\" in settings)) {\n\t\t\tsettings.steeringMode = settings.queueMode;\n\t\t\tdelete settings.queueMode;\n\t\t}\n\n\t\t// Migrate legacy websockets boolean -> transport enum\n\t\tif (!(\"transport\" in settings) && typeof settings.websockets === \"boolean\") {\n\t\t\tsettings.transport = settings.websockets ? \"websocket\" : \"sse\";\n\t\t\tdelete settings.websockets;\n\t\t}\n\n\t\t// Migrate old skills object format to new array format\n\t\tif (\n\t\t\t\"skills\" in settings &&\n\t\t\ttypeof settings.skills === \"object\" &&\n\t\t\tsettings.skills !== null &&\n\t\t\t!Array.isArray(settings.skills)\n\t\t) {\n\t\t\tconst skillsSettings = settings.skills as {\n\t\t\t\tenableSkillCommands?: boolean;\n\t\t\t\tcustomDirectories?: unknown;\n\t\t\t};\n\t\t\tif (skillsSettings.enableSkillCommands !== undefined && settings.enableSkillCommands === undefined) {\n\t\t\t\tsettings.enableSkillCommands = skillsSettings.enableSkillCommands;\n\t\t\t}\n\t\t\tif (Array.isArray(skillsSettings.customDirectories) && skillsSettings.customDirectories.length > 0) {\n\t\t\t\tsettings.skills = skillsSettings.customDirectories;\n\t\t\t} else {\n\t\t\t\tdelete settings.skills;\n\t\t\t}\n\t\t}\n\n\t\t// Migrate retry.maxDelayMs -> retry.provider.maxRetryDelayMs\n\t\tif (\n\t\t\t\"retry\" in settings &&\n\t\t\ttypeof settings.retry === \"object\" &&\n\t\t\tsettings.retry !== null &&\n\t\t\t!Array.isArray(settings.retry)\n\t\t) {\n\t\t\tconst retrySettings = settings.retry as Record<string, unknown>;\n\t\t\tconst providerSettings =\n\t\t\t\ttypeof retrySettings.provider === \"object\" && retrySettings.provider !== null\n\t\t\t\t\t? (retrySettings.provider as Record<string, unknown>)\n\t\t\t\t\t: undefined;\n\t\t\tif (\n\t\t\t\ttypeof retrySettings.maxDelayMs === \"number\" &&\n\t\t\t\t(providerSettings?.maxRetryDelayMs === undefined || providerSettings?.maxRetryDelayMs === null)\n\t\t\t) {\n\t\t\t\tretrySettings.provider = {\n\t\t\t\t\t...(providerSettings ?? {}),\n\t\t\t\t\tmaxRetryDelayMs: retrySettings.maxDelayMs,\n\t\t\t\t};\n\t\t\t}\n\t\t\tdelete retrySettings.maxDelayMs;\n\t\t}\n\n\t\treturn settings as Settings;\n\t}\n\n\tgetGlobalSettings(): Settings {\n\t\treturn structuredClone(this.globalSettings);\n\t}\n\n\tgetProjectSettings(): Settings {\n\t\treturn structuredClone(this.projectSettings);\n\t}\n\n\tgetDefaultSettings(): Settings {\n\t\treturn structuredClone(DEFAULT_SETTINGS);\n\t}\n\n\tasync reload(): Promise<void> {\n\t\tawait this.writeQueue;\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(this.storage, \"global\");\n\t\tif (!globalLoad.error) {\n\t\t\tthis.globalSettings = globalLoad.settings;\n\t\t\tthis.globalSettingsLoadError = null;\n\t\t} else {\n\t\t\tthis.globalSettingsLoadError = globalLoad.error;\n\t\t\tthis.recordError(\"global\", globalLoad.error);\n\t\t}\n\n\t\tthis.modifiedFields.clear();\n\t\tthis.modifiedNestedFields.clear();\n\t\tthis.modifiedProjectFields.clear();\n\t\tthis.modifiedProjectNestedFields.clear();\n\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(this.storage, \"project\");\n\t\tif (!projectLoad.error) {\n\t\t\tthis.projectSettings = projectLoad.settings;\n\t\t\tthis.projectSettingsLoadError = null;\n\t\t} else {\n\t\t\tthis.projectSettingsLoadError = projectLoad.error;\n\t\t\tthis.recordError(\"project\", projectLoad.error);\n\t\t}\n\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\t}\n\n\t/** Apply additional overrides on top of current settings */\n\tapplyOverrides(overrides: Partial<Settings>): void {\n\t\tthis.settings = deepMergeSettings(this.settings, overrides);\n\t}\n\n\t/** Mark a global field as modified during this session */\n\tprivate markModified(field: keyof Settings, nestedKey?: string): void {\n\t\tthis.modifiedFields.add(field);\n\t\tif (nestedKey) {\n\t\t\tif (!this.modifiedNestedFields.has(field)) {\n\t\t\t\tthis.modifiedNestedFields.set(field, new Set());\n\t\t\t}\n\t\t\tthis.modifiedNestedFields.get(field)!.add(nestedKey);\n\t\t}\n\t}\n\n\t/** Mark a project field as modified during this session */\n\tprivate markProjectModified(field: keyof Settings, nestedKey?: string): void {\n\t\tthis.modifiedProjectFields.add(field);\n\t\tif (nestedKey) {\n\t\t\tif (!this.modifiedProjectNestedFields.has(field)) {\n\t\t\t\tthis.modifiedProjectNestedFields.set(field, new Set());\n\t\t\t}\n\t\t\tthis.modifiedProjectNestedFields.get(field)!.add(nestedKey);\n\t\t}\n\t}\n\n\tprivate recordError(scope: SettingsScope, error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push({ scope, error: normalizedError });\n\t}\n\n\tprivate clearModifiedScope(scope: SettingsScope): void {\n\t\tif (scope === \"global\") {\n\t\t\tthis.modifiedFields.clear();\n\t\t\tthis.modifiedNestedFields.clear();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.modifiedProjectFields.clear();\n\t\tthis.modifiedProjectNestedFields.clear();\n\t}\n\n\tprivate enqueueWrite(scope: SettingsScope, task: () => void): void {\n\t\tthis.writeQueue = this.writeQueue\n\t\t\t.then(() => {\n\t\t\t\ttask();\n\t\t\t\tthis.clearModifiedScope(scope);\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tthis.recordError(scope, error);\n\t\t\t});\n\t}\n\n\tprivate cloneModifiedNestedFields(source: Map<keyof Settings, Set<string>>): Map<keyof Settings, Set<string>> {\n\t\tconst snapshot = new Map<keyof Settings, Set<string>>();\n\t\tfor (const [key, value] of source.entries()) {\n\t\t\tsnapshot.set(key, new Set(value));\n\t\t}\n\t\treturn snapshot;\n\t}\n\n\tprivate persistScopedSettings(\n\t\tscope: SettingsScope,\n\t\tsnapshotSettings: Settings,\n\t\tmodifiedFields: Set<keyof Settings>,\n\t\tmodifiedNestedFields: Map<keyof Settings, Set<string>>,\n\t): void {\n\t\tthis.storage.withLock(scope, (current) => {\n\t\t\tconst currentFileSettings = current\n\t\t\t\t? SettingsManager.migrateSettings(JSON.parse(current) as Record<string, unknown>)\n\t\t\t\t: {};\n\t\t\tconst mergedSettings: Settings = { ...currentFileSettings };\n\t\t\tfor (const field of modifiedFields) {\n\t\t\t\tconst value = snapshotSettings[field];\n\t\t\t\tif (modifiedNestedFields.has(field) && typeof value === \"object\" && value !== null) {\n\t\t\t\t\tconst nestedModified = modifiedNestedFields.get(field)!;\n\t\t\t\t\tconst baseNested = (currentFileSettings[field] as Record<string, unknown>) ?? {};\n\t\t\t\t\tconst inMemoryNested = value as Record<string, unknown>;\n\t\t\t\t\tconst mergedNested = { ...baseNested };\n\t\t\t\t\tfor (const nestedKey of nestedModified) {\n\t\t\t\t\t\tmergedNested[nestedKey] = inMemoryNested[nestedKey];\n\t\t\t\t\t}\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = mergedNested;\n\t\t\t\t} else {\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn JSON.stringify(mergedSettings, null, 2);\n\t\t});\n\t}\n\n\tprivate save(): void {\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\n\t\tif (this.globalSettingsLoadError) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshotGlobalSettings = structuredClone(this.globalSettings);\n\t\tconst modifiedFields = new Set(this.modifiedFields);\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedNestedFields);\n\n\t\tthis.enqueueWrite(\"global\", () => {\n\t\t\tthis.persistScopedSettings(\"global\", snapshotGlobalSettings, modifiedFields, modifiedNestedFields);\n\t\t});\n\t}\n\n\tprivate saveProjectSettings(settings: Settings): void {\n\t\tthis.projectSettings = structuredClone(settings);\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\n\t\tif (this.projectSettingsLoadError) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshotProjectSettings = structuredClone(this.projectSettings);\n\t\tconst modifiedFields = new Set(this.modifiedProjectFields);\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedProjectNestedFields);\n\t\tthis.enqueueWrite(\"project\", () => {\n\t\t\tthis.persistScopedSettings(\"project\", snapshotProjectSettings, modifiedFields, modifiedNestedFields);\n\t\t});\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tawait this.writeQueue;\n\t}\n\n\tdrainErrors(): SettingsError[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\tgetLastChangelogVersion(): string | undefined {\n\t\treturn this.settings.lastChangelogVersion;\n\t}\n\n\tsetLastChangelogVersion(version: string): void {\n\t\tthis.globalSettings.lastChangelogVersion = version;\n\t\tthis.markModified(\"lastChangelogVersion\");\n\t\tthis.save();\n\t}\n\n\tgetSessionDir(): string | undefined {\n\t\tconst sessionDir = this.settings.sessionDir;\n\t\tif (!sessionDir) {\n\t\t\treturn sessionDir;\n\t\t}\n\t\tif (sessionDir === \"~\") {\n\t\t\treturn homedir();\n\t\t}\n\t\tif (sessionDir.startsWith(\"~/\")) {\n\t\t\treturn join(homedir(), sessionDir.slice(2));\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tsetDefaultProvider(provider: string): void {\n\t\tthis.globalSettings.defaultProvider = provider;\n\t\tthis.markModified(\"defaultProvider\");\n\t\tthis.save();\n\t}\n\n\tsetDefaultModel(modelId: string): void {\n\t\tthis.globalSettings.defaultModel = modelId;\n\t\tthis.markModified(\"defaultModel\");\n\t\tthis.save();\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.globalSettings.defaultProvider = provider;\n\t\tthis.globalSettings.defaultModel = modelId;\n\t\tthis.markModified(\"defaultProvider\");\n\t\tthis.markModified(\"defaultModel\");\n\t\tthis.save();\n\t}\n\n\tgetSteeringMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.settings.steeringMode || DEFAULT_SETTINGS.steeringMode!;\n\t}\n\n\tsetSteeringMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.globalSettings.steeringMode = mode;\n\t\tthis.markModified(\"steeringMode\");\n\t\tthis.save();\n\t}\n\n\tgetFollowUpMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.settings.followUpMode || DEFAULT_SETTINGS.followUpMode!;\n\t}\n\n\tsetFollowUpMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.globalSettings.followUpMode = mode;\n\t\tthis.markModified(\"followUpMode\");\n\t\tthis.save();\n\t}\n\n\tgetTheme(): string | undefined {\n\t\treturn this.settings.theme;\n\t}\n\n\tsetTheme(theme: string): void {\n\t\tthis.globalSettings.theme = theme;\n\t\tthis.markModified(\"theme\");\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | undefined {\n\t\treturn this.settings.defaultThinkingLevel;\n\t}\n\n\tsetDefaultThinkingLevel(level: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"): void {\n\t\tthis.globalSettings.defaultThinkingLevel = level;\n\t\tthis.markModified(\"defaultThinkingLevel\");\n\t\tthis.save();\n\t}\n\n\tgetTransport(): TransportSetting {\n\t\treturn this.settings.transport ?? DEFAULT_SETTINGS.transport!;\n\t}\n\n\tsetTransport(transport: TransportSetting): void {\n\t\tthis.globalSettings.transport = transport;\n\t\tthis.markModified(\"transport\");\n\t\tthis.save();\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_SETTINGS.compaction!.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tif (!this.globalSettings.compaction) {\n\t\t\tthis.globalSettings.compaction = {};\n\t\t}\n\t\tthis.globalSettings.compaction.enabled = enabled;\n\t\tthis.markModified(\"compaction\", \"enabled\");\n\t\tthis.save();\n\t}\n\n\tgetCompactionReserveTokens(): number {\n\t\treturn this.settings.compaction?.reserveTokens ?? DEFAULT_SETTINGS.compaction!.reserveTokens;\n\t}\n\n\tgetCompactionKeepRecentTokens(): number {\n\t\treturn this.settings.compaction?.keepRecentTokens ?? DEFAULT_SETTINGS.compaction!.keepRecentTokens;\n\t}\n\n\tgetCompactionSettings(): Required<CompactionSettings> {\n\t\treturn { ...DEFAULT_SETTINGS.compaction, ...this.settings.compaction };\n\t}\n\n\t/**\n\t * Window and threshold `/learn` mines sessions with.\n\t *\n\t * Each value falls back to its default unless it is a finite positive number,\n\t * so a typo (`0`, `-1`, a pasted string) cannot silently reduce the window to\n\t * nothing and make the command report \"no recent sessions\".\n\t */\n\tgetLearnSettings(): {\n\t\tmaxSessions: number;\n\t\tmaxAgeDays: number;\n\t\tminRepeats: number;\n\t\tminRequestRepeats: number;\n\t\tmaxProposals: number;\n\t} {\n\t\tconst positive = (value: unknown, fallback: number): number =>\n\t\t\ttypeof value === \"number\" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;\n\t\treturn {\n\t\t\tmaxSessions: positive(this.settings.learnMaxSessions, DEFAULT_SETTINGS.learnMaxSessions),\n\t\t\tmaxAgeDays: positive(this.settings.learnMaxAgeDays, DEFAULT_SETTINGS.learnMaxAgeDays),\n\t\t\tminRepeats: positive(this.settings.learnMinRepeats, DEFAULT_SETTINGS.learnMinRepeats),\n\t\t\tminRequestRepeats: positive(this.settings.learnMinRequestRepeats, DEFAULT_SETTINGS.learnMinRequestRepeats),\n\t\t\tmaxProposals: positive(this.settings.learnMaxProposals, DEFAULT_SETTINGS.learnMaxProposals),\n\t\t};\n\t}\n\n\t/** Configured `fast`/`standard`/`capable` tiers, for callers resolving a category. */\n\tgetModelCategories(): Settings[\"modelCategories\"] {\n\t\treturn this.settings.modelCategories ? structuredClone(this.settings.modelCategories) : undefined;\n\t}\n\n\t/**\n\t * Persist one `/learn` threshold to the user scope.\n\t *\n\t * One keyed setter rather than five identical ones: the settings are flat\n\t * top-level numbers that differ only in name, and the settings pane drives\n\t * them from a table. Values are floored to at least 1, matching the read\n\t * side's refusal to let a zero shrink the window to nothing.\n\t */\n\tsetLearnSetting(key: LearnSettingKey, value: number): void {\n\t\tconst safe = Number.isFinite(value) ? Math.max(1, Math.floor(value)) : DEFAULT_SETTINGS[key];\n\t\tthis.globalSettings[key] = safe;\n\t\tthis.markModified(key);\n\t\tthis.save();\n\t}\n\n\t/** Byte cap on a single read/bash tool result before truncation. Clamped to >= 1024. */\n\tgetToolOutputMaxBytes(): number {\n\t\tconst v = this.settings.toolOutput?.maxBytes ?? DEFAULT_SETTINGS.toolOutput!.maxBytes;\n\t\treturn Number.isFinite(v) && v >= 1024 ? Math.floor(v) : DEFAULT_SETTINGS.toolOutput!.maxBytes;\n\t}\n\n\tsetToolOutputMaxBytes(bytes: number): void {\n\t\tif (!this.globalSettings.toolOutput) {\n\t\t\tthis.globalSettings.toolOutput = {};\n\t\t}\n\t\tthis.globalSettings.toolOutput.maxBytes = Math.max(1024, Math.floor(bytes));\n\t\tthis.markModified(\"toolOutput\", \"maxBytes\");\n\t\tthis.save();\n\t}\n\n\t/** Line cap on a single read/bash tool result before truncation. Clamped to >= 1. */\n\tgetToolOutputMaxLines(): number {\n\t\tconst v = this.settings.toolOutput?.maxLines ?? DEFAULT_SETTINGS.toolOutput!.maxLines;\n\t\treturn Number.isFinite(v) && v >= 1 ? Math.floor(v) : DEFAULT_SETTINGS.toolOutput!.maxLines;\n\t}\n\n\tsetToolOutputMaxLines(lines: number): void {\n\t\tif (!this.globalSettings.toolOutput) {\n\t\t\tthis.globalSettings.toolOutput = {};\n\t\t}\n\t\tthis.globalSettings.toolOutput.maxLines = Math.max(1, Math.floor(lines));\n\t\tthis.markModified(\"toolOutput\", \"maxLines\");\n\t\tthis.save();\n\t}\n\n\t/** Whether to stub superseded read results out of the outgoing context. */\n\tgetContextGcEnabled(): boolean {\n\t\treturn this.settings.contextGc?.enabled ?? DEFAULT_SETTINGS.contextGc!.enabled;\n\t}\n\n\tsetContextGcEnabled(enabled: boolean): void {\n\t\tif (!this.globalSettings.contextGc) {\n\t\t\tthis.globalSettings.contextGc = {};\n\t\t}\n\t\tthis.globalSettings.contextGc.enabled = enabled;\n\t\tthis.markModified(\"contextGc\", \"enabled\");\n\t\tthis.save();\n\t}\n\n\t/** Trailing-silence window (ms) before voice capture auto-stops. Clamped to 300-10000. */\n\tgetVoiceSilenceMs(): number {\n\t\tconst fallback = DEFAULT_SETTINGS.voice!.silenceMs;\n\t\tconst v = this.settings.voice?.silenceMs ?? fallback;\n\t\treturn Number.isFinite(v) ? Math.min(10000, Math.max(300, Math.floor(v))) : fallback;\n\t}\n\n\tsetVoiceSilenceMs(ms: number): void {\n\t\tif (!this.globalSettings.voice) {\n\t\t\tthis.globalSettings.voice = {};\n\t\t}\n\t\tthis.globalSettings.voice.silenceMs = Math.min(10000, Math.max(300, Math.floor(ms)));\n\t\tthis.markModified(\"voice\", \"silenceMs\");\n\t\tthis.save();\n\t}\n\n\t/**\n\t * Per-request timeout (seconds) for the webfetch/websearch binary. Settings\n\t * takes precedence, then the `HOOCODE_WEBTOOLS_TIMEOUT` env var, then the\n\t * default 15 (mirrors {@link getClearOnShrink}). Clamped to 1-120; a\n\t * non-finite settings value or malformed env falls through to the next tier.\n\t */\n\tgetWebtoolsTimeoutSecs(): number {\n\t\tconst fallback = DEFAULT_SETTINGS.webtools!.timeoutSecs;\n\t\tconst clamp = (n: number): number => Math.min(120, Math.max(1, Math.floor(n)));\n\t\tconst configured = this.settings.webtools?.timeoutSecs;\n\t\tif (configured !== undefined && Number.isFinite(configured)) {\n\t\t\treturn clamp(configured);\n\t\t}\n\t\tconst envRaw = process.env.HOOCODE_WEBTOOLS_TIMEOUT?.trim();\n\t\tif (envRaw) {\n\t\t\tconst envValue = Number(envRaw);\n\t\t\tif (Number.isFinite(envValue) && envValue > 0) {\n\t\t\t\treturn clamp(envValue);\n\t\t\t}\n\t\t}\n\t\treturn fallback;\n\t}\n\n\tsetWebtoolsTimeoutSecs(secs: number): void {\n\t\tif (!this.globalSettings.webtools) {\n\t\t\tthis.globalSettings.webtools = {};\n\t\t}\n\t\tthis.globalSettings.webtools.timeoutSecs = Math.min(120, Math.max(1, Math.floor(secs)));\n\t\tthis.markModified(\"webtools\", \"timeoutSecs\");\n\t\tthis.save();\n\t}\n\n\t/** Tool names disabled from the TUI; removed from the agent in every session. */\n\tgetDisabledTools(): string[] {\n\t\treturn [...(this.settings.disabledTools ?? DEFAULT_SETTINGS.disabledTools!)];\n\t}\n\n\tsetDisabledTools(names: string[]): void {\n\t\tthis.globalSettings.disabledTools = [...names];\n\t\tthis.markModified(\"disabledTools\");\n\t\tthis.save();\n\t}\n\n\t/**\n\t * How much of a tool call the transcript shows (radar / glance / full).\n\t *\n\t * Settings files written before the dial existed carry the old three-value\n\t * `toolOutputDisplay` key. They are read here rather than rewritten on load,\n\t * so a downgrade keeps working until the user actually changes the setting.\n\t */\n\tgetToolOutputView(): ToolOutputView {\n\t\tconst view = this.settings.toolOutputView;\n\t\tif (isToolOutputView(view)) return view;\n\n\t\tconst legacy = (this.settings as { toolOutputDisplay?: unknown }).toolOutputDisplay;\n\t\tconst migrated = LEGACY_TOOL_OUTPUT_VIEWS[legacy as string];\n\t\treturn migrated ?? DEFAULT_SETTINGS.toolOutputView!;\n\t}\n\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.globalSettings.toolOutputView = view;\n\t\tthis.markModified(\"toolOutputView\");\n\t\tthis.save();\n\t}\n\n\t/** Persisted overrides for extension-registered flags (name -> value). */\n\tgetFlagOverrides(): Record<string, boolean | string> {\n\t\treturn { ...(this.settings.flags ?? {}) };\n\t}\n\n\tsetFlagOverride(name: string, value: boolean | string): void {\n\t\tif (!this.globalSettings.flags) {\n\t\t\tthis.globalSettings.flags = {};\n\t\t}\n\t\tthis.globalSettings.flags[name] = value;\n\t\tthis.markModified(\"flags\", name);\n\t\tthis.save();\n\t}\n\n\tclearFlagOverride(name: string): void {\n\t\tif (this.globalSettings.flags && name in this.globalSettings.flags) {\n\t\t\tdelete this.globalSettings.flags[name];\n\t\t\tthis.markModified(\"flags\", name);\n\t\t\tthis.save();\n\t\t}\n\t}\n\n\tgetBranchSummarySettings(): Required<BranchSummarySettings> {\n\t\treturn { ...DEFAULT_SETTINGS.branchSummary, ...this.settings.branchSummary };\n\t}\n\n\tgetBranchSummarySkipPrompt(): boolean {\n\t\treturn this.settings.branchSummary?.skipPrompt ?? DEFAULT_SETTINGS.branchSummary!.skipPrompt;\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_SETTINGS.retry!.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tif (!this.globalSettings.retry) {\n\t\t\tthis.globalSettings.retry = {};\n\t\t}\n\t\tthis.globalSettings.retry.enabled = enabled;\n\t\tthis.markModified(\"retry\", \"enabled\");\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): { enabled: boolean; maxRetries: number; baseDelayMs: number } {\n\t\treturn {\n\t\t\tenabled: this.getRetryEnabled(),\n\t\t\tmaxRetries: this.settings.retry?.maxRetries ?? DEFAULT_SETTINGS.retry!.maxRetries,\n\t\t\tbaseDelayMs: this.settings.retry?.baseDelayMs ?? DEFAULT_SETTINGS.retry!.baseDelayMs,\n\t\t};\n\t}\n\n\tgetProviderRetrySettings(): { timeoutMs?: number; maxRetries?: number; maxRetryDelayMs: number } {\n\t\treturn {\n\t\t\t...DEFAULT_SETTINGS.retry!.provider,\n\t\t\t...this.settings.retry?.provider,\n\t\t};\n\t}\n\n\tgetHideThinkingBlock(): boolean {\n\t\treturn this.settings.hideThinkingBlock ?? DEFAULT_SETTINGS.hideThinkingBlock!;\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.globalSettings.hideThinkingBlock = hide;\n\t\tthis.markModified(\"hideThinkingBlock\");\n\t\tthis.save();\n\t}\n\n\tgetShellPath(): string | undefined {\n\t\treturn this.settings.shellPath;\n\t}\n\n\tsetShellPath(path: string | undefined): void {\n\t\tthis.globalSettings.shellPath = path;\n\t\tthis.markModified(\"shellPath\");\n\t\tthis.save();\n\t}\n\n\tgetQuietStartup(): boolean {\n\t\treturn this.settings.quietStartup ?? DEFAULT_SETTINGS.quietStartup!;\n\t}\n\n\tsetQuietStartup(quiet: boolean): void {\n\t\tthis.globalSettings.quietStartup = quiet;\n\t\tthis.markModified(\"quietStartup\");\n\t\tthis.save();\n\t}\n\n\tgetShellCommandPrefix(): string | undefined {\n\t\treturn this.settings.shellCommandPrefix;\n\t}\n\n\tsetShellCommandPrefix(prefix: string | undefined): void {\n\t\tthis.globalSettings.shellCommandPrefix = prefix;\n\t\tthis.markModified(\"shellCommandPrefix\");\n\t\tthis.save();\n\t}\n\n\tgetNpmCommand(): string[] | undefined {\n\t\treturn this.settings.npmCommand ? [...this.settings.npmCommand] : undefined;\n\t}\n\n\tsetNpmCommand(command: string[] | undefined): void {\n\t\tthis.globalSettings.npmCommand = command ? [...command] : undefined;\n\t\tthis.markModified(\"npmCommand\");\n\t\tthis.save();\n\t}\n\n\tgetCollapseChangelog(): boolean {\n\t\treturn this.settings.collapseChangelog ?? DEFAULT_SETTINGS.collapseChangelog!;\n\t}\n\n\tsetCollapseChangelog(collapse: boolean): void {\n\t\tthis.globalSettings.collapseChangelog = collapse;\n\t\tthis.markModified(\"collapseChangelog\");\n\t\tthis.save();\n\t}\n\n\tgetEnableInstallTelemetry(): boolean {\n\t\treturn this.settings.enableInstallTelemetry ?? DEFAULT_SETTINGS.enableInstallTelemetry!;\n\t}\n\n\tsetEnableInstallTelemetry(enabled: boolean): void {\n\t\tthis.globalSettings.enableInstallTelemetry = enabled;\n\t\tthis.markModified(\"enableInstallTelemetry\");\n\t\tthis.save();\n\t}\n\n\tgetPackages(): PackageSource[] {\n\t\treturn [...(this.settings.packages ?? DEFAULT_SETTINGS.packages!)];\n\t}\n\n\tsetPackages(packages: PackageSource[]): void {\n\t\tthis.globalSettings.packages = packages;\n\t\tthis.markModified(\"packages\");\n\t\tthis.save();\n\t}\n\n\tsetProjectPackages(packages: PackageSource[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.packages = packages;\n\t\tthis.markProjectModified(\"packages\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetExtensionPaths(): string[] {\n\t\treturn [...(this.settings.extensions ?? DEFAULT_SETTINGS.extensions!)];\n\t}\n\n\tsetExtensionPaths(paths: string[]): void {\n\t\tthis.globalSettings.extensions = paths;\n\t\tthis.markModified(\"extensions\");\n\t\tthis.save();\n\t}\n\n\tsetProjectExtensionPaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.extensions = paths;\n\t\tthis.markProjectModified(\"extensions\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetSkillPaths(): string[] {\n\t\treturn [...(this.settings.skills ?? DEFAULT_SETTINGS.skills!)];\n\t}\n\n\tsetSkillPaths(paths: string[]): void {\n\t\tthis.globalSettings.skills = paths;\n\t\tthis.markModified(\"skills\");\n\t\tthis.save();\n\t}\n\n\tsetProjectSkillPaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.skills = paths;\n\t\tthis.markProjectModified(\"skills\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetPromptTemplatePaths(): string[] {\n\t\treturn [...(this.settings.prompts ?? DEFAULT_SETTINGS.prompts!)];\n\t}\n\n\tsetPromptTemplatePaths(paths: string[]): void {\n\t\tthis.globalSettings.prompts = paths;\n\t\tthis.markModified(\"prompts\");\n\t\tthis.save();\n\t}\n\n\tsetProjectPromptTemplatePaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.prompts = paths;\n\t\tthis.markProjectModified(\"prompts\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetSlashCommandPaths(): string[] {\n\t\treturn [...(this.settings.slashCommands ?? DEFAULT_SETTINGS.slashCommands!)];\n\t}\n\n\tsetSlashCommandPaths(paths: string[]): void {\n\t\tthis.globalSettings.slashCommands = paths;\n\t\tthis.markModified(\"slashCommands\");\n\t\tthis.save();\n\t}\n\n\tsetProjectSlashCommandPaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.slashCommands = paths;\n\t\tthis.markProjectModified(\"slashCommands\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetThemePaths(): string[] {\n\t\treturn [...(this.settings.themes ?? DEFAULT_SETTINGS.themes!)];\n\t}\n\n\tsetThemePaths(paths: string[]): void {\n\t\tthis.globalSettings.themes = paths;\n\t\tthis.markModified(\"themes\");\n\t\tthis.save();\n\t}\n\n\tsetProjectThemePaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.themes = paths;\n\t\tthis.markProjectModified(\"themes\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetEnableSkillCommands(): boolean {\n\t\treturn this.settings.enableSkillCommands ?? DEFAULT_SETTINGS.enableSkillCommands!;\n\t}\n\n\tsetEnableSkillCommands(enabled: boolean): void {\n\t\tthis.globalSettings.enableSkillCommands = enabled;\n\t\tthis.markModified(\"enableSkillCommands\");\n\t\tthis.save();\n\t}\n\n\tgetEnableSubagent(): boolean {\n\t\treturn this.settings.enableSubagent ?? DEFAULT_SETTINGS.enableSubagent!;\n\t}\n\n\tsetEnableSubagent(enabled: boolean): void {\n\t\tthis.globalSettings.enableSubagent = enabled;\n\t\tthis.markModified(\"enableSubagent\");\n\t\tthis.save();\n\t}\n\n\t/** Whether to dispatch eligible subagents on reused warm workers (experimental). */\n\tgetWarmSubagents(): boolean {\n\t\treturn this.settings.warmSubagents ?? DEFAULT_SETTINGS.warmSubagents!;\n\t}\n\n\tsetWarmSubagents(enabled: boolean): void {\n\t\tthis.globalSettings.warmSubagents = enabled;\n\t\tthis.markModified(\"warmSubagents\");\n\t\tthis.save();\n\t}\n\n\t/** Tree-wide subagent nesting cap. Clamped to >= 1 (delegation can never be disabled here). */\n\tgetMaxSubagentDepth(): number {\n\t\tconst v = this.settings.maxSubagentDepth ?? DEFAULT_SETTINGS.maxSubagentDepth!;\n\t\treturn Number.isFinite(v) && v >= 1 ? Math.floor(v) : 1;\n\t}\n\n\t/** Max concurrent subagents per pool at nesting depth >= 1. Clamped to >= 1. */\n\tgetNestedSubagentConcurrency(): number {\n\t\tconst v = this.settings.nestedSubagentConcurrency ?? DEFAULT_SETTINGS.nestedSubagentConcurrency!;\n\t\treturn Number.isFinite(v) && v >= 1 ? Math.floor(v) : 2;\n\t}\n\n\tgetEnableTodoWrite(): boolean {\n\t\treturn this.settings.enableTodoWrite ?? DEFAULT_SETTINGS.enableTodoWrite!;\n\t}\n\n\tsetEnableTodoWrite(enabled: boolean): void {\n\t\tthis.globalSettings.enableTodoWrite = enabled;\n\t\tthis.markModified(\"enableTodoWrite\");\n\t\tthis.save();\n\t}\n\n\t/** Raw `platform` tokens (string coerced to an array), or undefined when unset. */\n\tgetPlatform(): string[] | undefined {\n\t\tconst value = this.settings.platform;\n\t\tif (value === undefined) return undefined;\n\t\treturn Array.isArray(value) ? value : [value];\n\t}\n\n\t/**\n\t * Persist the artifact platform targets. Always stored as an array (getPlatform\n\t * accepts either shape), and an empty/undefined list removes the key so the\n\t * per-consumer defaults apply again rather than an empty list being read as\n\t * \"target nothing\". Global scope: the layout a machine writes is an\n\t * environment-level choice, not a per-repo one.\n\t */\n\tsetPlatform(platforms: readonly string[] | undefined): void {\n\t\tthis.globalSettings.platform = platforms && platforms.length > 0 ? [...platforms] : undefined;\n\t\tthis.markModified(\"platform\");\n\t\tthis.save();\n\t}\n\n\tgetEnablePluginTools(): boolean {\n\t\treturn this.settings.enablePluginTools ?? DEFAULT_SETTINGS.enablePluginTools!;\n\t}\n\n\tsetEnablePluginTools(enabled: boolean): void {\n\t\tthis.globalSettings.enablePluginTools = enabled;\n\t\tthis.markModified(\"enablePluginTools\");\n\t\tthis.save();\n\t}\n\n\t/**\n\t * Where an autonomous InstallPlugin puts a plugin. `/plugin install` asks the\n\t * human instead of reading this — the setting exists precisely for the path\n\t * where there is nobody to ask.\n\t */\n\tgetPluginInstallScope(): \"user\" | \"project\" {\n\t\tconst value = this.settings.pluginInstallScope;\n\t\treturn value === \"project\" ? \"project\" : DEFAULT_SETTINGS.pluginInstallScope!;\n\t}\n\n\tsetPluginInstallScope(scope: \"user\" | \"project\"): void {\n\t\tthis.globalSettings.pluginInstallScope = scope;\n\t\tthis.markModified(\"pluginInstallScope\");\n\t\tthis.save();\n\t}\n\n\tgetDeferMcpSchemas(): boolean {\n\t\treturn this.settings.deferMcpSchemas ?? DEFAULT_SETTINGS.deferMcpSchemas!;\n\t}\n\n\tsetDeferMcpSchemas(enabled: boolean): void {\n\t\tthis.globalSettings.deferMcpSchemas = enabled;\n\t\tthis.markModified(\"deferMcpSchemas\");\n\t\tthis.save();\n\t}\n\n\tgetEnableWebTools(): boolean {\n\t\treturn this.settings.enableWebTools ?? DEFAULT_SETTINGS.enableWebTools!;\n\t}\n\n\tsetEnableWebTools(enabled: boolean): void {\n\t\tthis.globalSettings.enableWebTools = enabled;\n\t\tthis.markModified(\"enableWebTools\");\n\t\tthis.save();\n\t}\n\n\tgetEnableEmbsearchTools(): boolean {\n\t\treturn this.settings.enableEmbsearchTools ?? DEFAULT_SETTINGS.enableEmbsearchTools!;\n\t}\n\n\tsetEnableEmbsearchTools(enabled: boolean): void {\n\t\tthis.globalSettings.enableEmbsearchTools = enabled;\n\t\tthis.markModified(\"enableEmbsearchTools\");\n\t\tthis.save();\n\t}\n\n\tgetEmbsearchBinaryPath(): string | undefined {\n\t\treturn this.settings.embsearchBinaryPath;\n\t}\n\n\tgetEmbsearchThresholdBytes(): number {\n\t\treturn this.settings.embsearchThresholdBytes ?? DEFAULT_SETTINGS.embsearchThresholdBytes!;\n\t}\n\n\tgetLight(): boolean {\n\t\treturn this.settings.light ?? DEFAULT_SETTINGS.light!;\n\t}\n\n\tsetLight(enabled: boolean): void {\n\t\tthis.globalSettings.light = enabled;\n\t\tthis.markModified(\"light\");\n\t\tthis.save();\n\t}\n\n\tgetThinkingBudgets(): ThinkingBudgetsSettings | undefined {\n\t\treturn this.settings.thinkingBudgets;\n\t}\n\n\tgetThinkingDisplay(): \"summarized\" | \"omitted\" | undefined {\n\t\treturn this.settings.thinkingDisplay;\n\t}\n\n\tgetShowImages(): boolean {\n\t\treturn this.settings.terminal?.showImages ?? DEFAULT_SETTINGS.terminal!.showImages;\n\t}\n\n\tsetShowImages(show: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.showImages = show;\n\t\tthis.markModified(\"terminal\", \"showImages\");\n\t\tthis.save();\n\t}\n\n\tgetImageWidthCells(): number {\n\t\tconst width = this.settings.terminal?.imageWidthCells ?? DEFAULT_SETTINGS.terminal!.imageWidthCells;\n\t\tif (typeof width !== \"number\" || !Number.isFinite(width)) {\n\t\t\treturn DEFAULT_SETTINGS.terminal!.imageWidthCells;\n\t\t}\n\t\treturn Math.max(1, Math.floor(width));\n\t}\n\n\tsetImageWidthCells(width: number): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.imageWidthCells = Math.max(1, Math.floor(width));\n\t\tthis.markModified(\"terminal\", \"imageWidthCells\");\n\t\tthis.save();\n\t}\n\n\tgetClearOnShrink(): boolean {\n\t\t// Settings takes precedence, then env var, then default false\n\t\tif (this.settings.terminal?.clearOnShrink !== undefined) {\n\t\t\treturn this.settings.terminal.clearOnShrink;\n\t\t}\n\t\treturn process.env.HOOCODE_CLEAR_ON_SHRINK === \"1\";\n\t}\n\n\tsetClearOnShrink(enabled: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.clearOnShrink = enabled;\n\t\tthis.markModified(\"terminal\", \"clearOnShrink\");\n\t\tthis.save();\n\t}\n\n\tgetShowTerminalProgress(): boolean {\n\t\treturn this.settings.terminal?.showTerminalProgress ?? DEFAULT_SETTINGS.terminal!.showTerminalProgress;\n\t}\n\n\tsetShowTerminalProgress(enabled: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.showTerminalProgress = enabled;\n\t\tthis.markModified(\"terminal\", \"showTerminalProgress\");\n\t\tthis.save();\n\t}\n\n\tgetChimeOnTurnComplete(): boolean {\n\t\treturn this.settings.terminal?.chimeOnTurnComplete ?? DEFAULT_SETTINGS.terminal!.chimeOnTurnComplete;\n\t}\n\n\tsetChimeOnTurnComplete(enabled: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.chimeOnTurnComplete = enabled;\n\t\tthis.markModified(\"terminal\", \"chimeOnTurnComplete\");\n\t\tthis.save();\n\t}\n\n\tgetImageAutoResize(): boolean {\n\t\treturn this.settings.images?.autoResize ?? DEFAULT_SETTINGS.images!.autoResize;\n\t}\n\n\tsetImageAutoResize(enabled: boolean): void {\n\t\tif (!this.globalSettings.images) {\n\t\t\tthis.globalSettings.images = {};\n\t\t}\n\t\tthis.globalSettings.images.autoResize = enabled;\n\t\tthis.markModified(\"images\", \"autoResize\");\n\t\tthis.save();\n\t}\n\n\tgetBlockImages(): boolean {\n\t\treturn this.settings.images?.blockImages ?? DEFAULT_SETTINGS.images!.blockImages;\n\t}\n\n\tsetBlockImages(blocked: boolean): void {\n\t\tif (!this.globalSettings.images) {\n\t\t\tthis.globalSettings.images = {};\n\t\t}\n\t\tthis.globalSettings.images.blockImages = blocked;\n\t\tthis.markModified(\"images\", \"blockImages\");\n\t\tthis.save();\n\t}\n\n\tgetEnabledModels(): string[] | undefined {\n\t\treturn this.settings.enabledModels;\n\t}\n\n\tsetEnabledModels(patterns: string[] | undefined): void {\n\t\tthis.globalSettings.enabledModels = patterns;\n\t\tthis.markModified(\"enabledModels\");\n\t\tthis.save();\n\t}\n\n\tgetDoubleEscapeAction(): \"fork\" | \"tree\" | \"none\" {\n\t\treturn this.settings.doubleEscapeAction ?? DEFAULT_SETTINGS.doubleEscapeAction!;\n\t}\n\n\tsetDoubleEscapeAction(action: \"fork\" | \"tree\" | \"none\"): void {\n\t\tthis.globalSettings.doubleEscapeAction = action;\n\t\tthis.markModified(\"doubleEscapeAction\");\n\t\tthis.save();\n\t}\n\n\tgetTreeFilterMode(): \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\" {\n\t\tconst mode = this.settings.treeFilterMode;\n\t\tconst valid = [\"default\", \"no-tools\", \"user-only\", \"labeled-only\", \"all\"];\n\t\treturn mode && valid.includes(mode) ? mode : DEFAULT_SETTINGS.treeFilterMode!;\n\t}\n\n\tsetTreeFilterMode(mode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\"): void {\n\t\tthis.globalSettings.treeFilterMode = mode;\n\t\tthis.markModified(\"treeFilterMode\");\n\t\tthis.save();\n\t}\n\n\tgetShowHardwareCursor(): boolean {\n\t\treturn this.settings.showHardwareCursor ?? process.env.HOOCODE_HARDWARE_CURSOR === \"1\";\n\t}\n\n\tsetShowHardwareCursor(enabled: boolean): void {\n\t\tthis.globalSettings.showHardwareCursor = enabled;\n\t\tthis.markModified(\"showHardwareCursor\");\n\t\tthis.save();\n\t}\n\n\tgetEditorBorder(): \"rule\" | \"box\" {\n\t\treturn this.settings.editorBorder ?? DEFAULT_SETTINGS.editorBorder!;\n\t}\n\n\tsetEditorBorder(border: \"rule\" | \"box\"): void {\n\t\tthis.globalSettings.editorBorder = border;\n\t\tthis.markModified(\"editorBorder\");\n\t\tthis.save();\n\t}\n\n\tgetEditorPaddingX(): number {\n\t\treturn this.settings.editorPaddingX ?? DEFAULT_SETTINGS.editorPaddingX!;\n\t}\n\n\tsetEditorPaddingX(padding: number): void {\n\t\tthis.globalSettings.editorPaddingX = Math.max(0, Math.min(3, Math.floor(padding)));\n\t\tthis.markModified(\"editorPaddingX\");\n\t\tthis.save();\n\t}\n\n\tgetAutocompleteMaxVisible(): number {\n\t\treturn this.settings.autocompleteMaxVisible ?? DEFAULT_SETTINGS.autocompleteMaxVisible!;\n\t}\n\n\tsetAutocompleteMaxVisible(maxVisible: number): void {\n\t\tthis.globalSettings.autocompleteMaxVisible = Math.max(3, Math.min(20, Math.floor(maxVisible)));\n\t\tthis.markModified(\"autocompleteMaxVisible\");\n\t\tthis.save();\n\t}\n\n\tgetCodeBlockIndent(): string {\n\t\treturn this.settings.markdown?.codeBlockIndent ?? DEFAULT_SETTINGS.markdown!.codeBlockIndent;\n\t}\n\n\tgetWarnings(): WarningSettings {\n\t\treturn { ...DEFAULT_SETTINGS.warnings, ...this.settings.warnings };\n\t}\n\n\tsetWarnings(warnings: WarningSettings): void {\n\t\tthis.globalSettings.warnings = { ...warnings };\n\t\tthis.markModified(\"warnings\");\n\t\tthis.save();\n\t}\n}\n"]}
1
+ {"version":3,"file":"settings-manager.d.ts","sourceRoot":"","sources":["../../src/core/settings-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from \"os\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\nimport { DEFAULT_SETTINGS } from \"./settings-defaults.js\";\nimport {\n\tFileSettingsStorage,\n\tInMemorySettingsStorage,\n\ttype SettingsError,\n\ttype SettingsScope,\n\ttype SettingsStorage,\n} from \"./settings-storage.js\";\nimport type {\n\tBranchSummarySettings,\n\tCompactionSettings,\n\tPackageSource,\n\tSettings,\n\tThinkingBudgetsSettings,\n\tTransportSetting,\n\tWarningSettings,\n} from \"./settings-types.js\";\nimport { isToolOutputView, LEGACY_TOOL_OUTPUT_VIEWS, type ToolOutputView } from \"./tool-output-view.js\";\nimport type { WebtoolsSearchSettings } from \"./tools/webtools-shared.js\";\n\n/** The `/learn` thresholds, which are flat top-level numeric settings. */\nexport type LearnSettingKey =\n\t| \"learnMaxSessions\"\n\t| \"learnMaxAgeDays\"\n\t| \"learnMinRepeats\"\n\t| \"learnMinRequestRepeats\"\n\t| \"learnMaxProposals\";\n\n/** Deep merge settings: project/overrides take precedence, nested objects merge recursively */\nfunction deepMergeSettings(base: Settings, overrides: Settings): Settings {\n\tconst result: Settings = { ...base };\n\n\tfor (const key of Object.keys(overrides) as (keyof Settings)[]) {\n\t\tconst overrideValue = overrides[key];\n\t\tconst baseValue = base[key];\n\n\t\tif (overrideValue === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// For nested objects, merge recursively\n\t\tif (\n\t\t\ttypeof overrideValue === \"object\" &&\n\t\t\toverrideValue !== null &&\n\t\t\t!Array.isArray(overrideValue) &&\n\t\t\ttypeof baseValue === \"object\" &&\n\t\t\tbaseValue !== null &&\n\t\t\t!Array.isArray(baseValue)\n\t\t) {\n\t\t\t(result as Record<string, unknown>)[key] = { ...baseValue, ...overrideValue };\n\t\t} else {\n\t\t\t// For primitives and arrays, override value wins\n\t\t\t(result as Record<string, unknown>)[key] = overrideValue;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport type { SettingsError, SettingsScope, SettingsStorage };\nexport type {\n\tBranchSummarySettings,\n\tCompactionSettings,\n\tImageSettings,\n\tPackageSource,\n\tRetrySettings,\n\tSettings,\n\tThinkingBudgetsSettings,\n\tTransportSetting,\n\tWarningSettings,\n} from \"./settings-types.js\";\n\nexport class SettingsManager {\n\tprivate storage: SettingsStorage;\n\tprivate globalSettings: Settings;\n\tprivate projectSettings: Settings;\n\tprivate settings: Settings;\n\tprivate modifiedFields = new Set<keyof Settings>(); // Track global fields modified during session\n\tprivate modifiedNestedFields = new Map<keyof Settings, Set<string>>(); // Track global nested field modifications\n\tprivate modifiedProjectFields = new Set<keyof Settings>(); // Track project fields modified during session\n\tprivate modifiedProjectNestedFields = new Map<keyof Settings, Set<string>>(); // Track project nested field modifications\n\tprivate globalSettingsLoadError: Error | null = null; // Track if global settings file had parse errors\n\tprivate projectSettingsLoadError: Error | null = null; // Track if project settings file had parse errors\n\tprivate writeQueue: Promise<void> = Promise.resolve();\n\tprivate errors: SettingsError[];\n\n\tprivate constructor(\n\t\tstorage: SettingsStorage,\n\t\tinitialGlobal: Settings,\n\t\tinitialProject: Settings,\n\t\tglobalLoadError: Error | null = null,\n\t\tprojectLoadError: Error | null = null,\n\t\tinitialErrors: SettingsError[] = [],\n\t) {\n\t\tthis.storage = storage;\n\t\tthis.globalSettings = initialGlobal;\n\t\tthis.projectSettings = initialProject;\n\t\tthis.globalSettingsLoadError = globalLoadError;\n\t\tthis.projectSettingsLoadError = projectLoadError;\n\t\tthis.errors = [...initialErrors];\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\t}\n\n\t/** Create a SettingsManager that loads from files */\n\tstatic create(cwd: string, agentDir: string = getAgentDir()): SettingsManager {\n\t\tconst storage = new FileSettingsStorage(cwd, agentDir);\n\t\treturn SettingsManager.fromStorage(storage);\n\t}\n\n\t/** Create a SettingsManager from an arbitrary storage backend */\n\tstatic fromStorage(storage: SettingsStorage): SettingsManager {\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(storage, \"global\");\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(storage, \"project\");\n\t\tconst initialErrors: SettingsError[] = [];\n\t\tif (globalLoad.error) {\n\t\t\tinitialErrors.push({ scope: \"global\", error: globalLoad.error });\n\t\t}\n\t\tif (projectLoad.error) {\n\t\t\tinitialErrors.push({ scope: \"project\", error: projectLoad.error });\n\t\t}\n\n\t\treturn new SettingsManager(\n\t\t\tstorage,\n\t\t\tglobalLoad.settings,\n\t\t\tprojectLoad.settings,\n\t\t\tglobalLoad.error,\n\t\t\tprojectLoad.error,\n\t\t\tinitialErrors,\n\t\t);\n\t}\n\n\t/** Create an in-memory SettingsManager (no file I/O) */\n\tstatic inMemory(settings: Partial<Settings> = {}): SettingsManager {\n\t\tconst storage = new InMemorySettingsStorage();\n\t\tconst initialSettings = SettingsManager.migrateSettings(structuredClone(settings) as Record<string, unknown>);\n\t\tstorage.withLock(\"global\", () => JSON.stringify(initialSettings, null, 2));\n\t\treturn SettingsManager.fromStorage(storage);\n\t}\n\n\tprivate static loadFromStorage(storage: SettingsStorage, scope: SettingsScope): Settings {\n\t\tlet content: string | undefined;\n\t\tstorage.withLock(scope, (current) => {\n\t\t\tcontent = current;\n\t\t\treturn undefined;\n\t\t});\n\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\tconst settings = JSON.parse(content);\n\t\treturn SettingsManager.migrateSettings(settings);\n\t}\n\n\tprivate static tryLoadFromStorage(\n\t\tstorage: SettingsStorage,\n\t\tscope: SettingsScope,\n\t): { settings: Settings; error: Error | null } {\n\t\ttry {\n\t\t\treturn { settings: SettingsManager.loadFromStorage(storage, scope), error: null };\n\t\t} catch (error) {\n\t\t\treturn { settings: {}, error: error as Error };\n\t\t}\n\t}\n\n\t/** Migrate old settings format to new format */\n\tprivate static migrateSettings(settings: Record<string, unknown>): Settings {\n\t\t// Migrate queueMode -> steeringMode\n\t\tif (\"queueMode\" in settings && !(\"steeringMode\" in settings)) {\n\t\t\tsettings.steeringMode = settings.queueMode;\n\t\t\tdelete settings.queueMode;\n\t\t}\n\n\t\t// Migrate legacy websockets boolean -> transport enum\n\t\tif (!(\"transport\" in settings) && typeof settings.websockets === \"boolean\") {\n\t\t\tsettings.transport = settings.websockets ? \"websocket\" : \"sse\";\n\t\t\tdelete settings.websockets;\n\t\t}\n\n\t\t// Migrate old skills object format to new array format\n\t\tif (\n\t\t\t\"skills\" in settings &&\n\t\t\ttypeof settings.skills === \"object\" &&\n\t\t\tsettings.skills !== null &&\n\t\t\t!Array.isArray(settings.skills)\n\t\t) {\n\t\t\tconst skillsSettings = settings.skills as {\n\t\t\t\tenableSkillCommands?: boolean;\n\t\t\t\tcustomDirectories?: unknown;\n\t\t\t};\n\t\t\tif (skillsSettings.enableSkillCommands !== undefined && settings.enableSkillCommands === undefined) {\n\t\t\t\tsettings.enableSkillCommands = skillsSettings.enableSkillCommands;\n\t\t\t}\n\t\t\tif (Array.isArray(skillsSettings.customDirectories) && skillsSettings.customDirectories.length > 0) {\n\t\t\t\tsettings.skills = skillsSettings.customDirectories;\n\t\t\t} else {\n\t\t\t\tdelete settings.skills;\n\t\t\t}\n\t\t}\n\n\t\t// Migrate retry.maxDelayMs -> retry.provider.maxRetryDelayMs\n\t\tif (\n\t\t\t\"retry\" in settings &&\n\t\t\ttypeof settings.retry === \"object\" &&\n\t\t\tsettings.retry !== null &&\n\t\t\t!Array.isArray(settings.retry)\n\t\t) {\n\t\t\tconst retrySettings = settings.retry as Record<string, unknown>;\n\t\t\tconst providerSettings =\n\t\t\t\ttypeof retrySettings.provider === \"object\" && retrySettings.provider !== null\n\t\t\t\t\t? (retrySettings.provider as Record<string, unknown>)\n\t\t\t\t\t: undefined;\n\t\t\tif (\n\t\t\t\ttypeof retrySettings.maxDelayMs === \"number\" &&\n\t\t\t\t(providerSettings?.maxRetryDelayMs === undefined || providerSettings?.maxRetryDelayMs === null)\n\t\t\t) {\n\t\t\t\tretrySettings.provider = {\n\t\t\t\t\t...(providerSettings ?? {}),\n\t\t\t\t\tmaxRetryDelayMs: retrySettings.maxDelayMs,\n\t\t\t\t};\n\t\t\t}\n\t\t\tdelete retrySettings.maxDelayMs;\n\t\t}\n\n\t\treturn settings as Settings;\n\t}\n\n\tgetGlobalSettings(): Settings {\n\t\treturn structuredClone(this.globalSettings);\n\t}\n\n\tgetProjectSettings(): Settings {\n\t\treturn structuredClone(this.projectSettings);\n\t}\n\n\tgetDefaultSettings(): Settings {\n\t\treturn structuredClone(DEFAULT_SETTINGS);\n\t}\n\n\tasync reload(): Promise<void> {\n\t\tawait this.writeQueue;\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(this.storage, \"global\");\n\t\tif (!globalLoad.error) {\n\t\t\tthis.globalSettings = globalLoad.settings;\n\t\t\tthis.globalSettingsLoadError = null;\n\t\t} else {\n\t\t\tthis.globalSettingsLoadError = globalLoad.error;\n\t\t\tthis.recordError(\"global\", globalLoad.error);\n\t\t}\n\n\t\tthis.modifiedFields.clear();\n\t\tthis.modifiedNestedFields.clear();\n\t\tthis.modifiedProjectFields.clear();\n\t\tthis.modifiedProjectNestedFields.clear();\n\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(this.storage, \"project\");\n\t\tif (!projectLoad.error) {\n\t\t\tthis.projectSettings = projectLoad.settings;\n\t\t\tthis.projectSettingsLoadError = null;\n\t\t} else {\n\t\t\tthis.projectSettingsLoadError = projectLoad.error;\n\t\t\tthis.recordError(\"project\", projectLoad.error);\n\t\t}\n\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\t}\n\n\t/** Apply additional overrides on top of current settings */\n\tapplyOverrides(overrides: Partial<Settings>): void {\n\t\tthis.settings = deepMergeSettings(this.settings, overrides);\n\t}\n\n\t/** Mark a global field as modified during this session */\n\tprivate markModified(field: keyof Settings, nestedKey?: string): void {\n\t\tthis.modifiedFields.add(field);\n\t\tif (nestedKey) {\n\t\t\tif (!this.modifiedNestedFields.has(field)) {\n\t\t\t\tthis.modifiedNestedFields.set(field, new Set());\n\t\t\t}\n\t\t\tthis.modifiedNestedFields.get(field)!.add(nestedKey);\n\t\t}\n\t}\n\n\t/** Mark a project field as modified during this session */\n\tprivate markProjectModified(field: keyof Settings, nestedKey?: string): void {\n\t\tthis.modifiedProjectFields.add(field);\n\t\tif (nestedKey) {\n\t\t\tif (!this.modifiedProjectNestedFields.has(field)) {\n\t\t\t\tthis.modifiedProjectNestedFields.set(field, new Set());\n\t\t\t}\n\t\t\tthis.modifiedProjectNestedFields.get(field)!.add(nestedKey);\n\t\t}\n\t}\n\n\tprivate recordError(scope: SettingsScope, error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push({ scope, error: normalizedError });\n\t}\n\n\tprivate clearModifiedScope(scope: SettingsScope): void {\n\t\tif (scope === \"global\") {\n\t\t\tthis.modifiedFields.clear();\n\t\t\tthis.modifiedNestedFields.clear();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.modifiedProjectFields.clear();\n\t\tthis.modifiedProjectNestedFields.clear();\n\t}\n\n\tprivate enqueueWrite(scope: SettingsScope, task: () => void): void {\n\t\tthis.writeQueue = this.writeQueue\n\t\t\t.then(() => {\n\t\t\t\ttask();\n\t\t\t\tthis.clearModifiedScope(scope);\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tthis.recordError(scope, error);\n\t\t\t});\n\t}\n\n\tprivate cloneModifiedNestedFields(source: Map<keyof Settings, Set<string>>): Map<keyof Settings, Set<string>> {\n\t\tconst snapshot = new Map<keyof Settings, Set<string>>();\n\t\tfor (const [key, value] of source.entries()) {\n\t\t\tsnapshot.set(key, new Set(value));\n\t\t}\n\t\treturn snapshot;\n\t}\n\n\tprivate persistScopedSettings(\n\t\tscope: SettingsScope,\n\t\tsnapshotSettings: Settings,\n\t\tmodifiedFields: Set<keyof Settings>,\n\t\tmodifiedNestedFields: Map<keyof Settings, Set<string>>,\n\t): void {\n\t\tthis.storage.withLock(scope, (current) => {\n\t\t\tconst currentFileSettings = current\n\t\t\t\t? SettingsManager.migrateSettings(JSON.parse(current) as Record<string, unknown>)\n\t\t\t\t: {};\n\t\t\tconst mergedSettings: Settings = { ...currentFileSettings };\n\t\t\tfor (const field of modifiedFields) {\n\t\t\t\tconst value = snapshotSettings[field];\n\t\t\t\tif (modifiedNestedFields.has(field) && typeof value === \"object\" && value !== null) {\n\t\t\t\t\tconst nestedModified = modifiedNestedFields.get(field)!;\n\t\t\t\t\tconst baseNested = (currentFileSettings[field] as Record<string, unknown>) ?? {};\n\t\t\t\t\tconst inMemoryNested = value as Record<string, unknown>;\n\t\t\t\t\tconst mergedNested = { ...baseNested };\n\t\t\t\t\tfor (const nestedKey of nestedModified) {\n\t\t\t\t\t\tmergedNested[nestedKey] = inMemoryNested[nestedKey];\n\t\t\t\t\t}\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = mergedNested;\n\t\t\t\t} else {\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn JSON.stringify(mergedSettings, null, 2);\n\t\t});\n\t}\n\n\tprivate save(): void {\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\n\t\tif (this.globalSettingsLoadError) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshotGlobalSettings = structuredClone(this.globalSettings);\n\t\tconst modifiedFields = new Set(this.modifiedFields);\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedNestedFields);\n\n\t\tthis.enqueueWrite(\"global\", () => {\n\t\t\tthis.persistScopedSettings(\"global\", snapshotGlobalSettings, modifiedFields, modifiedNestedFields);\n\t\t});\n\t}\n\n\tprivate saveProjectSettings(settings: Settings): void {\n\t\tthis.projectSettings = structuredClone(settings);\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\n\n\t\tif (this.projectSettingsLoadError) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshotProjectSettings = structuredClone(this.projectSettings);\n\t\tconst modifiedFields = new Set(this.modifiedProjectFields);\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedProjectNestedFields);\n\t\tthis.enqueueWrite(\"project\", () => {\n\t\t\tthis.persistScopedSettings(\"project\", snapshotProjectSettings, modifiedFields, modifiedNestedFields);\n\t\t});\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tawait this.writeQueue;\n\t}\n\n\tdrainErrors(): SettingsError[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\tgetLastChangelogVersion(): string | undefined {\n\t\treturn this.settings.lastChangelogVersion;\n\t}\n\n\tsetLastChangelogVersion(version: string): void {\n\t\tthis.globalSettings.lastChangelogVersion = version;\n\t\tthis.markModified(\"lastChangelogVersion\");\n\t\tthis.save();\n\t}\n\n\tgetSessionDir(): string | undefined {\n\t\tconst sessionDir = this.settings.sessionDir;\n\t\tif (!sessionDir) {\n\t\t\treturn sessionDir;\n\t\t}\n\t\tif (sessionDir === \"~\") {\n\t\t\treturn homedir();\n\t\t}\n\t\tif (sessionDir.startsWith(\"~/\")) {\n\t\t\treturn join(homedir(), sessionDir.slice(2));\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tsetDefaultProvider(provider: string): void {\n\t\tthis.globalSettings.defaultProvider = provider;\n\t\tthis.markModified(\"defaultProvider\");\n\t\tthis.save();\n\t}\n\n\tsetDefaultModel(modelId: string): void {\n\t\tthis.globalSettings.defaultModel = modelId;\n\t\tthis.markModified(\"defaultModel\");\n\t\tthis.save();\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.globalSettings.defaultProvider = provider;\n\t\tthis.globalSettings.defaultModel = modelId;\n\t\tthis.markModified(\"defaultProvider\");\n\t\tthis.markModified(\"defaultModel\");\n\t\tthis.save();\n\t}\n\n\tgetSteeringMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.settings.steeringMode || DEFAULT_SETTINGS.steeringMode!;\n\t}\n\n\tsetSteeringMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.globalSettings.steeringMode = mode;\n\t\tthis.markModified(\"steeringMode\");\n\t\tthis.save();\n\t}\n\n\tgetFollowUpMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.settings.followUpMode || DEFAULT_SETTINGS.followUpMode!;\n\t}\n\n\tsetFollowUpMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.globalSettings.followUpMode = mode;\n\t\tthis.markModified(\"followUpMode\");\n\t\tthis.save();\n\t}\n\n\tgetTheme(): string | undefined {\n\t\treturn this.settings.theme;\n\t}\n\n\tsetTheme(theme: string): void {\n\t\tthis.globalSettings.theme = theme;\n\t\tthis.markModified(\"theme\");\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | undefined {\n\t\treturn this.settings.defaultThinkingLevel;\n\t}\n\n\tsetDefaultThinkingLevel(level: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"): void {\n\t\tthis.globalSettings.defaultThinkingLevel = level;\n\t\tthis.markModified(\"defaultThinkingLevel\");\n\t\tthis.save();\n\t}\n\n\tgetTransport(): TransportSetting {\n\t\treturn this.settings.transport ?? DEFAULT_SETTINGS.transport!;\n\t}\n\n\tsetTransport(transport: TransportSetting): void {\n\t\tthis.globalSettings.transport = transport;\n\t\tthis.markModified(\"transport\");\n\t\tthis.save();\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_SETTINGS.compaction!.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tif (!this.globalSettings.compaction) {\n\t\t\tthis.globalSettings.compaction = {};\n\t\t}\n\t\tthis.globalSettings.compaction.enabled = enabled;\n\t\tthis.markModified(\"compaction\", \"enabled\");\n\t\tthis.save();\n\t}\n\n\tgetCompactionReserveTokens(): number {\n\t\treturn this.settings.compaction?.reserveTokens ?? DEFAULT_SETTINGS.compaction!.reserveTokens;\n\t}\n\n\tgetCompactionKeepRecentTokens(): number {\n\t\treturn this.settings.compaction?.keepRecentTokens ?? DEFAULT_SETTINGS.compaction!.keepRecentTokens;\n\t}\n\n\tgetCompactionSettings(): Required<CompactionSettings> {\n\t\treturn { ...DEFAULT_SETTINGS.compaction, ...this.settings.compaction };\n\t}\n\n\t/**\n\t * Window and threshold `/learn` mines sessions with.\n\t *\n\t * Each value falls back to its default unless it is a finite positive number,\n\t * so a typo (`0`, `-1`, a pasted string) cannot silently reduce the window to\n\t * nothing and make the command report \"no recent sessions\".\n\t */\n\tgetLearnSettings(): {\n\t\tmaxSessions: number;\n\t\tmaxAgeDays: number;\n\t\tminRepeats: number;\n\t\tminRequestRepeats: number;\n\t\tmaxProposals: number;\n\t} {\n\t\tconst positive = (value: unknown, fallback: number): number =>\n\t\t\ttypeof value === \"number\" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;\n\t\treturn {\n\t\t\tmaxSessions: positive(this.settings.learnMaxSessions, DEFAULT_SETTINGS.learnMaxSessions),\n\t\t\tmaxAgeDays: positive(this.settings.learnMaxAgeDays, DEFAULT_SETTINGS.learnMaxAgeDays),\n\t\t\tminRepeats: positive(this.settings.learnMinRepeats, DEFAULT_SETTINGS.learnMinRepeats),\n\t\t\tminRequestRepeats: positive(this.settings.learnMinRequestRepeats, DEFAULT_SETTINGS.learnMinRequestRepeats),\n\t\t\tmaxProposals: positive(this.settings.learnMaxProposals, DEFAULT_SETTINGS.learnMaxProposals),\n\t\t};\n\t}\n\n\t/** Configured `fast`/`standard`/`capable` tiers, for callers resolving a category. */\n\tgetModelCategories(): Settings[\"modelCategories\"] {\n\t\treturn this.settings.modelCategories ? structuredClone(this.settings.modelCategories) : undefined;\n\t}\n\n\t/**\n\t * Persist one `/learn` threshold to the user scope.\n\t *\n\t * One keyed setter rather than five identical ones: the settings are flat\n\t * top-level numbers that differ only in name, and the settings pane drives\n\t * them from a table. Values are floored to at least 1, matching the read\n\t * side's refusal to let a zero shrink the window to nothing.\n\t */\n\tsetLearnSetting(key: LearnSettingKey, value: number): void {\n\t\tconst safe = Number.isFinite(value) ? Math.max(1, Math.floor(value)) : DEFAULT_SETTINGS[key];\n\t\tthis.globalSettings[key] = safe;\n\t\tthis.markModified(key);\n\t\tthis.save();\n\t}\n\n\t/** Byte cap on a single read/bash tool result before truncation. Clamped to >= 1024. */\n\tgetToolOutputMaxBytes(): number {\n\t\tconst v = this.settings.toolOutput?.maxBytes ?? DEFAULT_SETTINGS.toolOutput!.maxBytes;\n\t\treturn Number.isFinite(v) && v >= 1024 ? Math.floor(v) : DEFAULT_SETTINGS.toolOutput!.maxBytes;\n\t}\n\n\tsetToolOutputMaxBytes(bytes: number): void {\n\t\tif (!this.globalSettings.toolOutput) {\n\t\t\tthis.globalSettings.toolOutput = {};\n\t\t}\n\t\tthis.globalSettings.toolOutput.maxBytes = Math.max(1024, Math.floor(bytes));\n\t\tthis.markModified(\"toolOutput\", \"maxBytes\");\n\t\tthis.save();\n\t}\n\n\t/** Line cap on a single read/bash tool result before truncation. Clamped to >= 1. */\n\tgetToolOutputMaxLines(): number {\n\t\tconst v = this.settings.toolOutput?.maxLines ?? DEFAULT_SETTINGS.toolOutput!.maxLines;\n\t\treturn Number.isFinite(v) && v >= 1 ? Math.floor(v) : DEFAULT_SETTINGS.toolOutput!.maxLines;\n\t}\n\n\tsetToolOutputMaxLines(lines: number): void {\n\t\tif (!this.globalSettings.toolOutput) {\n\t\t\tthis.globalSettings.toolOutput = {};\n\t\t}\n\t\tthis.globalSettings.toolOutput.maxLines = Math.max(1, Math.floor(lines));\n\t\tthis.markModified(\"toolOutput\", \"maxLines\");\n\t\tthis.save();\n\t}\n\n\t/** Whether to stub superseded read results out of the outgoing context. */\n\tgetContextGcEnabled(): boolean {\n\t\treturn this.settings.contextGc?.enabled ?? DEFAULT_SETTINGS.contextGc!.enabled;\n\t}\n\n\tsetContextGcEnabled(enabled: boolean): void {\n\t\tif (!this.globalSettings.contextGc) {\n\t\t\tthis.globalSettings.contextGc = {};\n\t\t}\n\t\tthis.globalSettings.contextGc.enabled = enabled;\n\t\tthis.markModified(\"contextGc\", \"enabled\");\n\t\tthis.save();\n\t}\n\n\t/** Trailing-silence window (ms) before voice capture auto-stops. Clamped to 300-10000. */\n\tgetVoiceSilenceMs(): number {\n\t\tconst fallback = DEFAULT_SETTINGS.voice!.silenceMs;\n\t\tconst v = this.settings.voice?.silenceMs ?? fallback;\n\t\treturn Number.isFinite(v) ? Math.min(10000, Math.max(300, Math.floor(v))) : fallback;\n\t}\n\n\tsetVoiceSilenceMs(ms: number): void {\n\t\tif (!this.globalSettings.voice) {\n\t\t\tthis.globalSettings.voice = {};\n\t\t}\n\t\tthis.globalSettings.voice.silenceMs = Math.min(10000, Math.max(300, Math.floor(ms)));\n\t\tthis.markModified(\"voice\", \"silenceMs\");\n\t\tthis.save();\n\t}\n\n\t/**\n\t * Per-request timeout (seconds) for the webfetch/websearch binary. Settings\n\t * takes precedence, then the `HOOCODE_WEBTOOLS_TIMEOUT` env var, then the\n\t * default 15 (mirrors {@link getClearOnShrink}). Clamped to 1-120; a\n\t * non-finite settings value or malformed env falls through to the next tier.\n\t */\n\tgetWebtoolsTimeoutSecs(): number {\n\t\tconst fallback = DEFAULT_SETTINGS.webtools!.timeoutSecs;\n\t\tconst clamp = (n: number): number => Math.min(120, Math.max(1, Math.floor(n)));\n\t\tconst configured = this.settings.webtools?.timeoutSecs;\n\t\tif (configured !== undefined && Number.isFinite(configured)) {\n\t\t\treturn clamp(configured);\n\t\t}\n\t\tconst envRaw = process.env.HOOCODE_WEBTOOLS_TIMEOUT?.trim();\n\t\tif (envRaw) {\n\t\t\tconst envValue = Number(envRaw);\n\t\t\tif (Number.isFinite(envValue) && envValue > 0) {\n\t\t\t\treturn clamp(envValue);\n\t\t\t}\n\t\t}\n\t\treturn fallback;\n\t}\n\n\t/**\n\t * The `webtools.search` block as the binary would read it (backend choice and\n\t * credentials). hoocode only reads it, to tell whether `websearch` has a keyed\n\t * provider; the binary itself resolves the actual key at call time.\n\t *\n\t * Deliberately the user-level file rather than the merged view: the binary\n\t * reads only `~/.hoocode/settings.json`, so a key in a project's\n\t * `.hoocode/settings.json` never reaches it, and treating one as configured\n\t * would silence the warning for a search that still runs keyless. (Contrast\n\t * {@link getWebtoolsTimeoutSecs}, which is hoocode's own setting forwarded as\n\t * a flag, so there the merged value is the effective one.)\n\t */\n\tgetWebtoolsSearch(): WebtoolsSearchSettings | undefined {\n\t\treturn this.globalSettings.webtools?.search;\n\t}\n\n\tsetWebtoolsTimeoutSecs(secs: number): void {\n\t\tif (!this.globalSettings.webtools) {\n\t\t\tthis.globalSettings.webtools = {};\n\t\t}\n\t\tthis.globalSettings.webtools.timeoutSecs = Math.min(120, Math.max(1, Math.floor(secs)));\n\t\tthis.markModified(\"webtools\", \"timeoutSecs\");\n\t\tthis.save();\n\t}\n\n\t/** Tool names disabled from the TUI; removed from the agent in every session. */\n\tgetDisabledTools(): string[] {\n\t\treturn [...(this.settings.disabledTools ?? DEFAULT_SETTINGS.disabledTools!)];\n\t}\n\n\tsetDisabledTools(names: string[]): void {\n\t\tthis.globalSettings.disabledTools = [...names];\n\t\tthis.markModified(\"disabledTools\");\n\t\tthis.save();\n\t}\n\n\t/**\n\t * How much of a tool call the transcript shows (radar / glance / full).\n\t *\n\t * Settings files written before the dial existed carry the old three-value\n\t * `toolOutputDisplay` key. They are read here rather than rewritten on load,\n\t * so a downgrade keeps working until the user actually changes the setting.\n\t */\n\tgetToolOutputView(): ToolOutputView {\n\t\tconst view = this.settings.toolOutputView;\n\t\tif (isToolOutputView(view)) return view;\n\n\t\tconst legacy = (this.settings as { toolOutputDisplay?: unknown }).toolOutputDisplay;\n\t\tconst migrated = LEGACY_TOOL_OUTPUT_VIEWS[legacy as string];\n\t\treturn migrated ?? DEFAULT_SETTINGS.toolOutputView!;\n\t}\n\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.globalSettings.toolOutputView = view;\n\t\tthis.markModified(\"toolOutputView\");\n\t\tthis.save();\n\t}\n\n\t/** Persisted overrides for extension-registered flags (name -> value). */\n\tgetFlagOverrides(): Record<string, boolean | string> {\n\t\treturn { ...(this.settings.flags ?? {}) };\n\t}\n\n\tsetFlagOverride(name: string, value: boolean | string): void {\n\t\tif (!this.globalSettings.flags) {\n\t\t\tthis.globalSettings.flags = {};\n\t\t}\n\t\tthis.globalSettings.flags[name] = value;\n\t\tthis.markModified(\"flags\", name);\n\t\tthis.save();\n\t}\n\n\tclearFlagOverride(name: string): void {\n\t\tif (this.globalSettings.flags && name in this.globalSettings.flags) {\n\t\t\tdelete this.globalSettings.flags[name];\n\t\t\tthis.markModified(\"flags\", name);\n\t\t\tthis.save();\n\t\t}\n\t}\n\n\tgetBranchSummarySettings(): Required<BranchSummarySettings> {\n\t\treturn { ...DEFAULT_SETTINGS.branchSummary, ...this.settings.branchSummary };\n\t}\n\n\tgetBranchSummarySkipPrompt(): boolean {\n\t\treturn this.settings.branchSummary?.skipPrompt ?? DEFAULT_SETTINGS.branchSummary!.skipPrompt;\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_SETTINGS.retry!.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tif (!this.globalSettings.retry) {\n\t\t\tthis.globalSettings.retry = {};\n\t\t}\n\t\tthis.globalSettings.retry.enabled = enabled;\n\t\tthis.markModified(\"retry\", \"enabled\");\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): { enabled: boolean; maxRetries: number; baseDelayMs: number } {\n\t\treturn {\n\t\t\tenabled: this.getRetryEnabled(),\n\t\t\tmaxRetries: this.settings.retry?.maxRetries ?? DEFAULT_SETTINGS.retry!.maxRetries,\n\t\t\tbaseDelayMs: this.settings.retry?.baseDelayMs ?? DEFAULT_SETTINGS.retry!.baseDelayMs,\n\t\t};\n\t}\n\n\tgetProviderRetrySettings(): { timeoutMs?: number; maxRetries?: number; maxRetryDelayMs: number } {\n\t\treturn {\n\t\t\t...DEFAULT_SETTINGS.retry!.provider,\n\t\t\t...this.settings.retry?.provider,\n\t\t};\n\t}\n\n\tgetHideThinkingBlock(): boolean {\n\t\treturn this.settings.hideThinkingBlock ?? DEFAULT_SETTINGS.hideThinkingBlock!;\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.globalSettings.hideThinkingBlock = hide;\n\t\tthis.markModified(\"hideThinkingBlock\");\n\t\tthis.save();\n\t}\n\n\tgetShellPath(): string | undefined {\n\t\treturn this.settings.shellPath;\n\t}\n\n\tsetShellPath(path: string | undefined): void {\n\t\tthis.globalSettings.shellPath = path;\n\t\tthis.markModified(\"shellPath\");\n\t\tthis.save();\n\t}\n\n\tgetQuietStartup(): boolean {\n\t\treturn this.settings.quietStartup ?? DEFAULT_SETTINGS.quietStartup!;\n\t}\n\n\tsetQuietStartup(quiet: boolean): void {\n\t\tthis.globalSettings.quietStartup = quiet;\n\t\tthis.markModified(\"quietStartup\");\n\t\tthis.save();\n\t}\n\n\tgetShellCommandPrefix(): string | undefined {\n\t\treturn this.settings.shellCommandPrefix;\n\t}\n\n\tsetShellCommandPrefix(prefix: string | undefined): void {\n\t\tthis.globalSettings.shellCommandPrefix = prefix;\n\t\tthis.markModified(\"shellCommandPrefix\");\n\t\tthis.save();\n\t}\n\n\tgetNpmCommand(): string[] | undefined {\n\t\treturn this.settings.npmCommand ? [...this.settings.npmCommand] : undefined;\n\t}\n\n\tsetNpmCommand(command: string[] | undefined): void {\n\t\tthis.globalSettings.npmCommand = command ? [...command] : undefined;\n\t\tthis.markModified(\"npmCommand\");\n\t\tthis.save();\n\t}\n\n\tgetCollapseChangelog(): boolean {\n\t\treturn this.settings.collapseChangelog ?? DEFAULT_SETTINGS.collapseChangelog!;\n\t}\n\n\tsetCollapseChangelog(collapse: boolean): void {\n\t\tthis.globalSettings.collapseChangelog = collapse;\n\t\tthis.markModified(\"collapseChangelog\");\n\t\tthis.save();\n\t}\n\n\tgetEnableInstallTelemetry(): boolean {\n\t\treturn this.settings.enableInstallTelemetry ?? DEFAULT_SETTINGS.enableInstallTelemetry!;\n\t}\n\n\tsetEnableInstallTelemetry(enabled: boolean): void {\n\t\tthis.globalSettings.enableInstallTelemetry = enabled;\n\t\tthis.markModified(\"enableInstallTelemetry\");\n\t\tthis.save();\n\t}\n\n\tgetPackages(): PackageSource[] {\n\t\treturn [...(this.settings.packages ?? DEFAULT_SETTINGS.packages!)];\n\t}\n\n\tsetPackages(packages: PackageSource[]): void {\n\t\tthis.globalSettings.packages = packages;\n\t\tthis.markModified(\"packages\");\n\t\tthis.save();\n\t}\n\n\tsetProjectPackages(packages: PackageSource[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.packages = packages;\n\t\tthis.markProjectModified(\"packages\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetExtensionPaths(): string[] {\n\t\treturn [...(this.settings.extensions ?? DEFAULT_SETTINGS.extensions!)];\n\t}\n\n\tsetExtensionPaths(paths: string[]): void {\n\t\tthis.globalSettings.extensions = paths;\n\t\tthis.markModified(\"extensions\");\n\t\tthis.save();\n\t}\n\n\tsetProjectExtensionPaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.extensions = paths;\n\t\tthis.markProjectModified(\"extensions\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetSkillPaths(): string[] {\n\t\treturn [...(this.settings.skills ?? DEFAULT_SETTINGS.skills!)];\n\t}\n\n\tsetSkillPaths(paths: string[]): void {\n\t\tthis.globalSettings.skills = paths;\n\t\tthis.markModified(\"skills\");\n\t\tthis.save();\n\t}\n\n\tsetProjectSkillPaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.skills = paths;\n\t\tthis.markProjectModified(\"skills\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetPromptTemplatePaths(): string[] {\n\t\treturn [...(this.settings.prompts ?? DEFAULT_SETTINGS.prompts!)];\n\t}\n\n\tsetPromptTemplatePaths(paths: string[]): void {\n\t\tthis.globalSettings.prompts = paths;\n\t\tthis.markModified(\"prompts\");\n\t\tthis.save();\n\t}\n\n\tsetProjectPromptTemplatePaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.prompts = paths;\n\t\tthis.markProjectModified(\"prompts\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetSlashCommandPaths(): string[] {\n\t\treturn [...(this.settings.slashCommands ?? DEFAULT_SETTINGS.slashCommands!)];\n\t}\n\n\tsetSlashCommandPaths(paths: string[]): void {\n\t\tthis.globalSettings.slashCommands = paths;\n\t\tthis.markModified(\"slashCommands\");\n\t\tthis.save();\n\t}\n\n\tsetProjectSlashCommandPaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.slashCommands = paths;\n\t\tthis.markProjectModified(\"slashCommands\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetThemePaths(): string[] {\n\t\treturn [...(this.settings.themes ?? DEFAULT_SETTINGS.themes!)];\n\t}\n\n\tsetThemePaths(paths: string[]): void {\n\t\tthis.globalSettings.themes = paths;\n\t\tthis.markModified(\"themes\");\n\t\tthis.save();\n\t}\n\n\tsetProjectThemePaths(paths: string[]): void {\n\t\tconst projectSettings = structuredClone(this.projectSettings);\n\t\tprojectSettings.themes = paths;\n\t\tthis.markProjectModified(\"themes\");\n\t\tthis.saveProjectSettings(projectSettings);\n\t}\n\n\tgetEnableSkillCommands(): boolean {\n\t\treturn this.settings.enableSkillCommands ?? DEFAULT_SETTINGS.enableSkillCommands!;\n\t}\n\n\tsetEnableSkillCommands(enabled: boolean): void {\n\t\tthis.globalSettings.enableSkillCommands = enabled;\n\t\tthis.markModified(\"enableSkillCommands\");\n\t\tthis.save();\n\t}\n\n\tgetEnableSubagent(): boolean {\n\t\treturn this.settings.enableSubagent ?? DEFAULT_SETTINGS.enableSubagent!;\n\t}\n\n\tsetEnableSubagent(enabled: boolean): void {\n\t\tthis.globalSettings.enableSubagent = enabled;\n\t\tthis.markModified(\"enableSubagent\");\n\t\tthis.save();\n\t}\n\n\t/** Whether to dispatch eligible subagents on reused warm workers (experimental). */\n\tgetWarmSubagents(): boolean {\n\t\treturn this.settings.warmSubagents ?? DEFAULT_SETTINGS.warmSubagents!;\n\t}\n\n\tsetWarmSubagents(enabled: boolean): void {\n\t\tthis.globalSettings.warmSubagents = enabled;\n\t\tthis.markModified(\"warmSubagents\");\n\t\tthis.save();\n\t}\n\n\t/** Tree-wide subagent nesting cap. Clamped to >= 1 (delegation can never be disabled here). */\n\tgetMaxSubagentDepth(): number {\n\t\tconst v = this.settings.maxSubagentDepth ?? DEFAULT_SETTINGS.maxSubagentDepth!;\n\t\treturn Number.isFinite(v) && v >= 1 ? Math.floor(v) : 1;\n\t}\n\n\t/** Max concurrent subagents per pool at nesting depth >= 1. Clamped to >= 1. */\n\tgetNestedSubagentConcurrency(): number {\n\t\tconst v = this.settings.nestedSubagentConcurrency ?? DEFAULT_SETTINGS.nestedSubagentConcurrency!;\n\t\treturn Number.isFinite(v) && v >= 1 ? Math.floor(v) : 2;\n\t}\n\n\tgetEnableTodoWrite(): boolean {\n\t\treturn this.settings.enableTodoWrite ?? DEFAULT_SETTINGS.enableTodoWrite!;\n\t}\n\n\tsetEnableTodoWrite(enabled: boolean): void {\n\t\tthis.globalSettings.enableTodoWrite = enabled;\n\t\tthis.markModified(\"enableTodoWrite\");\n\t\tthis.save();\n\t}\n\n\t/** Raw `platform` tokens (string coerced to an array), or undefined when unset. */\n\tgetPlatform(): string[] | undefined {\n\t\tconst value = this.settings.platform;\n\t\tif (value === undefined) return undefined;\n\t\treturn Array.isArray(value) ? value : [value];\n\t}\n\n\t/**\n\t * Persist the artifact platform targets. Always stored as an array (getPlatform\n\t * accepts either shape), and an empty/undefined list removes the key so the\n\t * per-consumer defaults apply again rather than an empty list being read as\n\t * \"target nothing\". Global scope: the layout a machine writes is an\n\t * environment-level choice, not a per-repo one.\n\t */\n\tsetPlatform(platforms: readonly string[] | undefined): void {\n\t\tthis.globalSettings.platform = platforms && platforms.length > 0 ? [...platforms] : undefined;\n\t\tthis.markModified(\"platform\");\n\t\tthis.save();\n\t}\n\n\tgetEnablePluginTools(): boolean {\n\t\treturn this.settings.enablePluginTools ?? DEFAULT_SETTINGS.enablePluginTools!;\n\t}\n\n\tsetEnablePluginTools(enabled: boolean): void {\n\t\tthis.globalSettings.enablePluginTools = enabled;\n\t\tthis.markModified(\"enablePluginTools\");\n\t\tthis.save();\n\t}\n\n\t/**\n\t * Where an autonomous InstallPlugin puts a plugin. `/plugin install` asks the\n\t * human instead of reading this — the setting exists precisely for the path\n\t * where there is nobody to ask.\n\t */\n\tgetPluginInstallScope(): \"user\" | \"project\" {\n\t\tconst value = this.settings.pluginInstallScope;\n\t\treturn value === \"project\" ? \"project\" : DEFAULT_SETTINGS.pluginInstallScope!;\n\t}\n\n\tsetPluginInstallScope(scope: \"user\" | \"project\"): void {\n\t\tthis.globalSettings.pluginInstallScope = scope;\n\t\tthis.markModified(\"pluginInstallScope\");\n\t\tthis.save();\n\t}\n\n\tgetDeferMcpSchemas(): boolean {\n\t\treturn this.settings.deferMcpSchemas ?? DEFAULT_SETTINGS.deferMcpSchemas!;\n\t}\n\n\tsetDeferMcpSchemas(enabled: boolean): void {\n\t\tthis.globalSettings.deferMcpSchemas = enabled;\n\t\tthis.markModified(\"deferMcpSchemas\");\n\t\tthis.save();\n\t}\n\n\tgetEnableWebTools(): boolean {\n\t\treturn this.settings.enableWebTools ?? DEFAULT_SETTINGS.enableWebTools!;\n\t}\n\n\tsetEnableWebTools(enabled: boolean): void {\n\t\tthis.globalSettings.enableWebTools = enabled;\n\t\tthis.markModified(\"enableWebTools\");\n\t\tthis.save();\n\t}\n\n\tgetEnableEmbsearchTools(): boolean {\n\t\treturn this.settings.enableEmbsearchTools ?? DEFAULT_SETTINGS.enableEmbsearchTools!;\n\t}\n\n\tsetEnableEmbsearchTools(enabled: boolean): void {\n\t\tthis.globalSettings.enableEmbsearchTools = enabled;\n\t\tthis.markModified(\"enableEmbsearchTools\");\n\t\tthis.save();\n\t}\n\n\tgetEmbsearchBinaryPath(): string | undefined {\n\t\treturn this.settings.embsearchBinaryPath;\n\t}\n\n\tgetEmbsearchThresholdBytes(): number {\n\t\treturn this.settings.embsearchThresholdBytes ?? DEFAULT_SETTINGS.embsearchThresholdBytes!;\n\t}\n\n\tgetLight(): boolean {\n\t\treturn this.settings.light ?? DEFAULT_SETTINGS.light!;\n\t}\n\n\tsetLight(enabled: boolean): void {\n\t\tthis.globalSettings.light = enabled;\n\t\tthis.markModified(\"light\");\n\t\tthis.save();\n\t}\n\n\tgetThinkingBudgets(): ThinkingBudgetsSettings | undefined {\n\t\treturn this.settings.thinkingBudgets;\n\t}\n\n\tgetThinkingDisplay(): \"summarized\" | \"omitted\" | undefined {\n\t\treturn this.settings.thinkingDisplay;\n\t}\n\n\tgetShowImages(): boolean {\n\t\treturn this.settings.terminal?.showImages ?? DEFAULT_SETTINGS.terminal!.showImages;\n\t}\n\n\tsetShowImages(show: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.showImages = show;\n\t\tthis.markModified(\"terminal\", \"showImages\");\n\t\tthis.save();\n\t}\n\n\tgetImageWidthCells(): number {\n\t\tconst width = this.settings.terminal?.imageWidthCells ?? DEFAULT_SETTINGS.terminal!.imageWidthCells;\n\t\tif (typeof width !== \"number\" || !Number.isFinite(width)) {\n\t\t\treturn DEFAULT_SETTINGS.terminal!.imageWidthCells;\n\t\t}\n\t\treturn Math.max(1, Math.floor(width));\n\t}\n\n\tsetImageWidthCells(width: number): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.imageWidthCells = Math.max(1, Math.floor(width));\n\t\tthis.markModified(\"terminal\", \"imageWidthCells\");\n\t\tthis.save();\n\t}\n\n\tgetClearOnShrink(): boolean {\n\t\t// Settings takes precedence, then env var, then default false\n\t\tif (this.settings.terminal?.clearOnShrink !== undefined) {\n\t\t\treturn this.settings.terminal.clearOnShrink;\n\t\t}\n\t\treturn process.env.HOOCODE_CLEAR_ON_SHRINK === \"1\";\n\t}\n\n\tsetClearOnShrink(enabled: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.clearOnShrink = enabled;\n\t\tthis.markModified(\"terminal\", \"clearOnShrink\");\n\t\tthis.save();\n\t}\n\n\tgetShowTerminalProgress(): boolean {\n\t\treturn this.settings.terminal?.showTerminalProgress ?? DEFAULT_SETTINGS.terminal!.showTerminalProgress;\n\t}\n\n\tsetShowTerminalProgress(enabled: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.showTerminalProgress = enabled;\n\t\tthis.markModified(\"terminal\", \"showTerminalProgress\");\n\t\tthis.save();\n\t}\n\n\tgetChimeOnTurnComplete(): boolean {\n\t\treturn this.settings.terminal?.chimeOnTurnComplete ?? DEFAULT_SETTINGS.terminal!.chimeOnTurnComplete;\n\t}\n\n\tsetChimeOnTurnComplete(enabled: boolean): void {\n\t\tif (!this.globalSettings.terminal) {\n\t\t\tthis.globalSettings.terminal = {};\n\t\t}\n\t\tthis.globalSettings.terminal.chimeOnTurnComplete = enabled;\n\t\tthis.markModified(\"terminal\", \"chimeOnTurnComplete\");\n\t\tthis.save();\n\t}\n\n\tgetImageAutoResize(): boolean {\n\t\treturn this.settings.images?.autoResize ?? DEFAULT_SETTINGS.images!.autoResize;\n\t}\n\n\tsetImageAutoResize(enabled: boolean): void {\n\t\tif (!this.globalSettings.images) {\n\t\t\tthis.globalSettings.images = {};\n\t\t}\n\t\tthis.globalSettings.images.autoResize = enabled;\n\t\tthis.markModified(\"images\", \"autoResize\");\n\t\tthis.save();\n\t}\n\n\tgetBlockImages(): boolean {\n\t\treturn this.settings.images?.blockImages ?? DEFAULT_SETTINGS.images!.blockImages;\n\t}\n\n\tsetBlockImages(blocked: boolean): void {\n\t\tif (!this.globalSettings.images) {\n\t\t\tthis.globalSettings.images = {};\n\t\t}\n\t\tthis.globalSettings.images.blockImages = blocked;\n\t\tthis.markModified(\"images\", \"blockImages\");\n\t\tthis.save();\n\t}\n\n\tgetEnabledModels(): string[] | undefined {\n\t\treturn this.settings.enabledModels;\n\t}\n\n\tsetEnabledModels(patterns: string[] | undefined): void {\n\t\tthis.globalSettings.enabledModels = patterns;\n\t\tthis.markModified(\"enabledModels\");\n\t\tthis.save();\n\t}\n\n\tgetDoubleEscapeAction(): \"fork\" | \"tree\" | \"none\" {\n\t\treturn this.settings.doubleEscapeAction ?? DEFAULT_SETTINGS.doubleEscapeAction!;\n\t}\n\n\tsetDoubleEscapeAction(action: \"fork\" | \"tree\" | \"none\"): void {\n\t\tthis.globalSettings.doubleEscapeAction = action;\n\t\tthis.markModified(\"doubleEscapeAction\");\n\t\tthis.save();\n\t}\n\n\tgetTreeFilterMode(): \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\" {\n\t\tconst mode = this.settings.treeFilterMode;\n\t\tconst valid = [\"default\", \"no-tools\", \"user-only\", \"labeled-only\", \"all\"];\n\t\treturn mode && valid.includes(mode) ? mode : DEFAULT_SETTINGS.treeFilterMode!;\n\t}\n\n\tsetTreeFilterMode(mode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\"): void {\n\t\tthis.globalSettings.treeFilterMode = mode;\n\t\tthis.markModified(\"treeFilterMode\");\n\t\tthis.save();\n\t}\n\n\tgetShowHardwareCursor(): boolean {\n\t\treturn this.settings.showHardwareCursor ?? process.env.HOOCODE_HARDWARE_CURSOR === \"1\";\n\t}\n\n\tsetShowHardwareCursor(enabled: boolean): void {\n\t\tthis.globalSettings.showHardwareCursor = enabled;\n\t\tthis.markModified(\"showHardwareCursor\");\n\t\tthis.save();\n\t}\n\n\tgetEditorBorder(): \"rule\" | \"box\" {\n\t\treturn this.settings.editorBorder ?? DEFAULT_SETTINGS.editorBorder!;\n\t}\n\n\tsetEditorBorder(border: \"rule\" | \"box\"): void {\n\t\tthis.globalSettings.editorBorder = border;\n\t\tthis.markModified(\"editorBorder\");\n\t\tthis.save();\n\t}\n\n\tgetEditorPaddingX(): number {\n\t\treturn this.settings.editorPaddingX ?? DEFAULT_SETTINGS.editorPaddingX!;\n\t}\n\n\tsetEditorPaddingX(padding: number): void {\n\t\tthis.globalSettings.editorPaddingX = Math.max(0, Math.min(3, Math.floor(padding)));\n\t\tthis.markModified(\"editorPaddingX\");\n\t\tthis.save();\n\t}\n\n\tgetAutocompleteMaxVisible(): number {\n\t\treturn this.settings.autocompleteMaxVisible ?? DEFAULT_SETTINGS.autocompleteMaxVisible!;\n\t}\n\n\tsetAutocompleteMaxVisible(maxVisible: number): void {\n\t\tthis.globalSettings.autocompleteMaxVisible = Math.max(3, Math.min(20, Math.floor(maxVisible)));\n\t\tthis.markModified(\"autocompleteMaxVisible\");\n\t\tthis.save();\n\t}\n\n\tgetCodeBlockIndent(): string {\n\t\treturn this.settings.markdown?.codeBlockIndent ?? DEFAULT_SETTINGS.markdown!.codeBlockIndent;\n\t}\n\n\tgetWarnings(): WarningSettings {\n\t\treturn { ...DEFAULT_SETTINGS.warnings, ...this.settings.warnings };\n\t}\n\n\tsetWarnings(warnings: WarningSettings): void {\n\t\tthis.globalSettings.warnings = { ...warnings };\n\t\tthis.markModified(\"warnings\");\n\t\tthis.save();\n\t}\n}\n"]}
@@ -504,6 +504,21 @@ export class SettingsManager {
504
504
  }
505
505
  return fallback;
506
506
  }
507
+ /**
508
+ * The `webtools.search` block as the binary would read it (backend choice and
509
+ * credentials). hoocode only reads it, to tell whether `websearch` has a keyed
510
+ * provider; the binary itself resolves the actual key at call time.
511
+ *
512
+ * Deliberately the user-level file rather than the merged view: the binary
513
+ * reads only `~/.hoocode/settings.json`, so a key in a project's
514
+ * `.hoocode/settings.json` never reaches it, and treating one as configured
515
+ * would silence the warning for a search that still runs keyless. (Contrast
516
+ * {@link getWebtoolsTimeoutSecs}, which is hoocode's own setting forwarded as
517
+ * a flag, so there the merged value is the effective one.)
518
+ */
519
+ getWebtoolsSearch() {
520
+ return this.globalSettings.webtools?.search;
521
+ }
507
522
  setWebtoolsTimeoutSecs(secs) {
508
523
  if (!this.globalSettings.webtools) {
509
524
  this.globalSettings.webtools = {};