@kolisachint/hoocode-agent 0.5.28 → 0.5.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +4 -2
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/core/light.d.ts +9 -0
  6. package/dist/core/light.d.ts.map +1 -1
  7. package/dist/core/light.js +12 -4
  8. package/dist/core/light.js.map +1 -1
  9. package/dist/core/settings-manager.d.ts +8 -0
  10. package/dist/core/settings-manager.d.ts.map +1 -1
  11. package/dist/core/settings-manager.js +12 -0
  12. package/dist/core/settings-manager.js.map +1 -1
  13. package/dist/core/settings-types.d.ts.map +1 -1
  14. package/dist/core/settings-types.js.map +1 -1
  15. package/dist/main.d.ts.map +1 -1
  16. package/dist/main.js +6 -5
  17. package/dist/main.js.map +1 -1
  18. package/dist/modes/interactive/components/settings-selector.d.ts +31 -0
  19. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  20. package/dist/modes/interactive/components/settings-selector.js +245 -51
  21. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  22. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  23. package/dist/modes/interactive/interactive-mode.js +43 -1
  24. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  25. package/docs/plugins.md +3 -2
  26. package/docs/settings.md +64 -0
  27. package/docs/usage.md +3 -3
  28. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  29. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  30. package/examples/extensions/sandbox/package.json +1 -1
  31. package/examples/extensions/with-deps/package.json +1 -1
  32. package/package.json +4 -4
@@ -186,6 +186,14 @@ export declare class SettingsManager {
186
186
  setEnableTodoWrite(enabled: boolean): void;
187
187
  /** Raw `platform` tokens (string coerced to an array), or undefined when unset. */
188
188
  getPlatform(): string[] | undefined;
189
+ /**
190
+ * Persist the artifact platform targets. Always stored as an array (getPlatform
191
+ * accepts either shape), and an empty/undefined list removes the key so the
192
+ * per-consumer defaults apply again rather than an empty list being read as
193
+ * "target nothing". Global scope: the layout a machine writes is an
194
+ * environment-level choice, not a per-repo one.
195
+ */
196
+ setPlatform(platforms: readonly string[] | undefined): void;
189
197
  getEnablePluginTools(): boolean;
190
198
  setEnablePluginTools(enabled: boolean): void;
191
199
  /**
@@ -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\";\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/** How tool result bodies render in the TUI (collapsed / peek / standard). */\n\tgetToolOutputDisplay(): \"collapsed\" | \"peek\" | \"standard\" {\n\t\tconst v = this.settings.toolOutputDisplay;\n\t\treturn v === \"collapsed\" || v === \"peek\" || v === \"standard\" ? v : DEFAULT_SETTINGS.toolOutputDisplay!;\n\t}\n\n\tsetToolOutputDisplay(display: \"collapsed\" | \"peek\" | \"standard\"): void {\n\t\tthis.globalSettings.toolOutputDisplay = display;\n\t\tthis.markModified(\"toolOutputDisplay\");\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\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\";\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/** How tool result bodies render in the TUI (collapsed / peek / standard). */\n\tgetToolOutputDisplay(): \"collapsed\" | \"peek\" | \"standard\" {\n\t\tconst v = this.settings.toolOutputDisplay;\n\t\treturn v === \"collapsed\" || v === \"peek\" || v === \"standard\" ? v : DEFAULT_SETTINGS.toolOutputDisplay!;\n\t}\n\n\tsetToolOutputDisplay(display: \"collapsed\" | \"peek\" | \"standard\"): void {\n\t\tthis.globalSettings.toolOutputDisplay = display;\n\t\tthis.markModified(\"toolOutputDisplay\");\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"]}
@@ -769,6 +769,18 @@ export class SettingsManager {
769
769
  return undefined;
770
770
  return Array.isArray(value) ? value : [value];
771
771
  }
772
+ /**
773
+ * Persist the artifact platform targets. Always stored as an array (getPlatform
774
+ * accepts either shape), and an empty/undefined list removes the key so the
775
+ * per-consumer defaults apply again rather than an empty list being read as
776
+ * "target nothing". Global scope: the layout a machine writes is an
777
+ * environment-level choice, not a per-repo one.
778
+ */
779
+ setPlatform(platforms) {
780
+ this.globalSettings.platform = platforms && platforms.length > 0 ? [...platforms] : undefined;
781
+ this.markModified("platform");
782
+ this.save();
783
+ }
772
784
  getEnablePluginTools() {
773
785
  return this.settings.enablePluginTools ?? DEFAULT_SETTINGS.enablePluginTools;
774
786
  }