@kolisachint/hoocode-agent 0.5.18 → 0.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +179 -0
- package/dist/core/learn/audit.d.ts +136 -0
- package/dist/core/learn/audit.d.ts.map +1 -0
- package/dist/core/learn/audit.js +316 -0
- package/dist/core/learn/audit.js.map +1 -0
- package/dist/core/learn/cache.d.ts.map +1 -1
- package/dist/core/learn/cache.js +14 -2
- package/dist/core/learn/cache.js.map +1 -1
- package/dist/core/learn/cluster.d.ts +78 -0
- package/dist/core/learn/cluster.d.ts.map +1 -0
- package/dist/core/learn/cluster.js +184 -0
- package/dist/core/learn/cluster.js.map +1 -0
- package/dist/core/learn/coverage.d.ts.map +1 -1
- package/dist/core/learn/coverage.js +2 -0
- package/dist/core/learn/coverage.js.map +1 -1
- package/dist/core/learn/digest.d.ts +12 -0
- package/dist/core/learn/digest.d.ts.map +1 -1
- package/dist/core/learn/digest.js +86 -14
- package/dist/core/learn/digest.js.map +1 -1
- package/dist/core/learn/extract.d.ts +39 -4
- package/dist/core/learn/extract.d.ts.map +1 -1
- package/dist/core/learn/extract.js +170 -34
- package/dist/core/learn/extract.js.map +1 -1
- package/dist/core/learn/mine.d.ts +78 -23
- package/dist/core/learn/mine.d.ts.map +1 -1
- package/dist/core/learn/mine.js +142 -37
- package/dist/core/learn/mine.js.map +1 -1
- package/dist/core/learn/reduce.d.ts +19 -8
- package/dist/core/learn/reduce.d.ts.map +1 -1
- package/dist/core/learn/reduce.js +69 -13
- package/dist/core/learn/reduce.js.map +1 -1
- package/dist/core/learn/state.d.ts +11 -18
- package/dist/core/learn/state.d.ts.map +1 -1
- package/dist/core/learn/state.js +23 -34
- package/dist/core/learn/state.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +1 -1
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +1 -1
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -1
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/extensions/core/learn.d.ts.map +1 -1
- package/dist/extensions/core/learn.js +128 -73
- package/dist/extensions/core/learn.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/settings.md +9 -6
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learn.js","sourceRoot":"","sources":["../../../src/extensions/core/learn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/learn` — promote what recent sessions actually taught into durable rules\n * and skills.\n *\n * The command is a thin shell on purpose. It runs the mining pipeline over\n * session transcripts on disk, renders the ranked result, and injects it as a\n * follow-up message; every judgement after that belongs to the model, which can\n * read the repo and phrase a rule far better than a heuristic can.\n *\n * Reading transcripts from disk rather than the live context is what makes this\n * work: the on-disk history survives compaction, and it spans past sessions, so\n * \"you have said this in five separate sessions\" is available as a number\n * instead of a guess. That number is the whole reason the command exists.\n *\n * The pipeline reads every transcript with a model rather than pre-filtering\n * with regexes, which costs real tokens on a cold cache. That price is stated\n * before it is paid, never inferred: a run with sessions to read asks first.\n *\n * Follows /grill in modes.ts: no session switch, no mode change, no config\n * write — just a follow-up message. Writes to AGENTS.md happen through ordinary\n * edit tools, so the existing permission prompt is the approval step and no\n * separate picker is needed.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { CONFIG_DIR_NAME, getHooCodeDir } from \"../../config.js\";\nimport { loadProjectContextFiles } from \"../../core/context-files.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport type { CoverageJudge } from \"../../core/learn/coverage.js\";\nimport { createLlmCoverageJudge } from \"../../core/learn/coverage.js\";\nimport { isEmptyDigest, renderLearnDigest } from \"../../core/learn/digest.js\";\nimport {\n\tbuildCoverageIndex,\n\ttype LearnDigest,\n\tmineLearnDigest,\n\tplanMining,\n\ttype SessionScanReport,\n\tscanSessions,\n} from \"../../core/learn/extract.js\";\nimport type { Miner } from \"../../core/learn/mine.js\";\nimport { chunkCharsForModel, createLlmMiner } from \"../../core/learn/mine.js\";\nimport {\n\tgetLearnStatePath,\n\treadLearnState,\n\trecordSurfaced,\n\tsummarizeLearnState,\n\twriteLearnState,\n} from \"../../core/learn/state.js\";\nimport { resolveModelCategory } from \"../../core/model-categories.js\";\nimport { getSessionDirPath } from \"../../core/session-manager.js\";\nimport { SettingsManager } from \"../../core/settings-manager.js\";\nimport { startupProgress } from \"../../core/startup-progress.js\";\n\n/** Guards against double-registration when default extensions load more than once. */\nconst REGISTERED = Symbol.for(\"hoocode.learn.registered\");\n\n/** User-scope destination offered for personal rules that travel across repos. */\nconst USER_SCOPE_PATH = join(homedir(), \".agents\", \"AGENTS.md\");\n\n/** Footer key for the mining progress bar. */\nconst PROGRESS_KEY = \"learn-mining\";\n\n/** Escape, the way a raw terminal delivers it. */\nconst ESCAPE = \"\\x1b\";\n\n/**\n * Sessions that can be read without asking first.\n *\n * A run that has one or two new transcripts to read is the normal daily case\n * and interrupting it to confirm a trivial cost is noise. Beyond this the run\n * is a backfill — onboarding to an existing repo, or a first run — and the\n * reader should get to decide before it starts.\n */\nconst CONFIRM_ABOVE_PENDING = 3;\n\n/** Render a home-relative path the way the user would type it. */\nfunction displayPath(path: string): string {\n\tconst home = homedir();\n\treturn path.startsWith(home) ? `~${path.slice(home.length)}` : path;\n}\n\nfunction shortDate(iso: string | undefined): string {\n\tif (!iso) return \"unknown\";\n\tconst date = new Date(iso);\n\treturn Number.isNaN(date.getTime()) ? \"unknown\" : date.toISOString().slice(0, 10);\n}\n\n/** The settings keys `/learn` reads, paired with the values in force right now. */\ntype LearnWindow = ReturnType<SettingsManager[\"getLearnSettings\"]>;\n\nconst SETTING_KEYS: Array<{ key: keyof LearnWindow; setting: string; note: string }> = [\n\t{ key: \"maxSessions\", setting: \"learnMaxSessions\", note: \"recent sessions scanned\" },\n\t{ key: \"maxAgeDays\", setting: \"learnMaxAgeDays\", note: \"ignore sessions older than this, in days\" },\n\t{ key: \"minRepeats\", setting: \"learnMinRepeats\", note: \"times a directive must recur to be proposed\" },\n\t{\n\t\tkey: \"minWorkflowRepeats\",\n\t\tsetting: \"learnMinWorkflowRepeats\",\n\t\tnote: \"repeats before a tool sequence is proposed\",\n\t},\n\t{ key: \"maxProposals\", setting: \"learnMaxProposals\", note: \"cap on each list in the digest\" },\n];\n\n/**\n * Where the knobs live, and what they are set to.\n *\n * `/learn` has its settings and no UI, so until this existed the only way to\n * find them was to already know they were in `settings.json`. Every message that\n * reports a disappointing result names a threshold, so every one of them ends\n * with these lines.\n */\nfunction settingsPathLines(ctx: ExtensionCommandContext, agentDir: string): string[] {\n\treturn [\n\t\t\"Settings — edit either file, no restart needed\",\n\t\t` user ${displayPath(join(agentDir, \"settings.json\"))}`,\n\t\t` project ${displayPath(join(ctx.cwd, CONFIG_DIR_NAME, \"settings.json\"))} (wins where both set a key)`,\n\t];\n}\n\nfunction settingsLines(ctx: ExtensionCommandContext, agentDir: string, window: LearnWindow): string[] {\n\tconst lines = settingsPathLines(ctx, agentDir);\n\tfor (const { key, setting, note } of SETTING_KEYS) {\n\t\tlines.push(` ${setting.padEnd(24)} ${String(window[key] ?? \"—\").padStart(3)} ${note}`);\n\t}\n\treturn lines;\n}\n\n/**\n * The directory whose name keys this cwd's bookmark.\n *\n * Derived from the cwd, never from the live session manager. An in-memory\n * session (`--no-session`) reports an empty session directory, which used to key\n * every such run to the same nameless state file, and a shared custom\n * `sessionDir` used to make two unrelated projects share one bookmark. The cwd\n * is what \"per directory\" means here, so the cwd is what it is keyed on.\n */\nfunction stateKeyDir(ctx: ExtensionCommandContext, agentDir: string): string {\n\treturn getSessionDirPath(ctx.cwd, agentDir);\n}\n\n/** Run the directory scan without mining anything, for the reports that only need counts. */\nfunction sessionScanPreview(ctx: ExtensionCommandContext, agentDir: string, window: LearnWindow): SessionScanReport {\n\treturn scanSessions({\n\t\tcwd: ctx.cwd,\n\t\tagentDir,\n\t\tsessionDir: ctx.sessionManager.getSessionDir(),\n\t\tmaxSessions: window.maxSessions,\n\t\tmaxAgeDays: window.maxAgeDays,\n\t});\n}\n\n/** Where sessions were looked for, and what was passed over — the \"why nothing?\" answer. */\nfunction scanLines(scan: SessionScanReport, window: LearnWindow): string[] {\n\tconst lines: string[] = [\"Looked in\"];\n\tfor (const dir of scan.dirs) {\n\t\tconst missing = scan.missingDirs.includes(dir) ? \" (does not exist)\" : \"\";\n\t\tlines.push(` ${displayPath(dir)}${missing}`);\n\t}\n\tlines.push(`Found ${scan.files} session file(s)`);\n\n\tconst skips: string[] = [];\n\tif (scan.tooOld > 0) skips.push(`${scan.tooOld} older than ${window.maxAgeDays} days (learnMaxAgeDays)`);\n\tif (scan.otherCwd > 0) skips.push(`${scan.otherCwd} recorded a different working directory`);\n\tif (scan.overLimit > 0) skips.push(`${scan.overLimit} beyond the newest ${window.maxSessions} (learnMaxSessions)`);\n\tif (scan.unreadable > 0) skips.push(`${scan.unreadable} empty or unreadable`);\n\tfor (const skip of skips) lines.push(` skipped: ${skip}`);\n\treturn lines;\n}\n\n/**\n * Explain an empty scan rather than asserting there is no history.\n *\n * The old single sentence was wrong as often as it was right: sessions existed,\n * they were simply all outside the window or recorded under another path. Naming\n * the directory searched and the reason each file was passed over turns a dead\n * end into something the reader can fix.\n */\nfunction reportNoSessions(ctx: ExtensionCommandContext, agentDir: string, digest: LearnDigest, window: LearnWindow) {\n\tconst lines: string[] = [];\n\tlines.push(\n\t\tdigest.scan.files === 0\n\t\t\t? \"/learn found no session transcripts for this directory.\"\n\t\t\t: \"/learn found session transcripts, but none inside the current window.\",\n\t);\n\tlines.push(\"\");\n\tlines.push(...scanLines(digest.scan, window));\n\tlines.push(\"\");\n\tlines.push(...settingsLines(ctx, agentDir, window));\n\tctx.ui.notify(lines.join(\"\\n\"), \"warning\");\n}\n\n/**\n * The model that reads transcripts.\n *\n * This is the one call in the pipeline that reads *everything*, so it wants the\n * cheapest capable model rather than the session's. That question already has an\n * answer in this codebase — the `fast` model category, which subagents use for\n * exactly this kind of bulk read — so it is reused rather than reinvented.\n * `settings.modelCategories.fast` wins when set; otherwise the tier is derived\n * from the user's available models, and nothing here is provider-specific.\n *\n * Falls back to the session model when the tier resolves to nothing or to a\n * model the registry cannot find, since a mis-set tier should not take the\n * command out entirely.\n */\nfunction resolveMinerModel(ctx: ExtensionCommandContext, settings: SettingsManager): Model<Api> | undefined {\n\tconst ref = resolveModelCategory(\n\t\t\"fast\",\n\t\t{\n\t\t\tmodelCategories: settings.getModelCategories(),\n\t\t\tdefaultProvider: settings.getDefaultProvider(),\n\t\t\tdefaultModel: settings.getDefaultModel(),\n\t\t},\n\t\tctx.modelRegistry.getAvailable(),\n\t);\n\tif (!ref) return ctx.model;\n\n\tconst slash = ref.indexOf(\"/\");\n\tconst found = slash > 0 ? ctx.modelRegistry.find(ref.slice(0, slash), ref.slice(slash + 1)) : undefined;\n\treturn found ?? ctx.model;\n}\n\n/** Build the two model-backed stages, or report why they cannot be built. */\nasync function buildPipeline(\n\tctx: ExtensionCommandContext,\n\tsettings: SettingsManager,\n): Promise<{ miner: Miner; coverageJudge: CoverageJudge; model: Model<Api> } | { error: string }> {\n\tconst model = resolveMinerModel(ctx, settings);\n\tif (!model) {\n\t\treturn {\n\t\t\terror: \"/learn reads session transcripts with a model, and no model is selected. Pick one with /model, then run /learn again.\",\n\t\t};\n\t}\n\n\tconst auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);\n\tif (!auth.ok) {\n\t\treturn { error: `/learn could not authenticate ${model.provider}/${model.id}: ${auth.error}` };\n\t}\n\n\tconst deps = { model, apiKey: auth.apiKey, headers: auth.headers };\n\treturn { miner: createLlmMiner(deps), coverageJudge: createLlmCoverageJudge(deps), model };\n}\n\n/**\n * What this run still owes the model.\n *\n * Delegated to `planMining` so the number quoted by the confirmation prompt\n * comes from the same session selection the run will use — same window, same\n * cwd check, same de-duplication.\n */\nfunction pendingWork(ctx: ExtensionCommandContext, agentDir: string, window: LearnWindow) {\n\treturn planMining({\n\t\tcwd: ctx.cwd,\n\t\tagentDir,\n\t\tsessionDir: ctx.sessionManager.getSessionDir(),\n\t\tmaxSessions: window.maxSessions,\n\t\tmaxAgeDays: window.maxAgeDays,\n\t});\n}\n\n/**\n * `/learn stats` — what became of past proposals.\n *\n * Reads the state file and recomputes coverage; it does not re-mine sessions,\n * so it costs one small model call and answers a different question than a\n * normal run: not \"what should I write down\" but \"is this command earning its\n * place\".\n */\nasync function reportStats(ctx: ExtensionCommandContext): Promise<void> {\n\tconst agentDir = getHooCodeDir();\n\tconst settings = SettingsManager.create(ctx.cwd, agentDir);\n\tconst window = settings.getLearnSettings();\n\tconst statePath = getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir));\n\tconst state = readLearnState(statePath);\n\n\tif (Object.keys(state.surfaced).length === 0) {\n\t\t// Nothing on record means `/learn` has never proposed anything here — which\n\t\t// is as likely to be \"it never found any sessions\" as \"you never ran it\", so\n\t\t// point at both the sessions it can see and the knobs that gate them.\n\t\tconst lines = [\"No /learn history for this directory yet — nothing has been proposed here.\"];\n\t\tlines.push(` State file ${displayPath(statePath)} (not created yet)`);\n\t\tlines.push(\"\");\n\t\tlines.push(...scanLines(sessionScanPreview(ctx, agentDir, window), window));\n\t\tlines.push(\"\");\n\t\tlines.push(...settingsPathLines(ctx, agentDir));\n\t\tlines.push(\" Run /learn settings for the thresholds in force.\");\n\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\t\treturn;\n\t}\n\n\tconst coverage = buildCoverageIndex({ cwd: ctx.cwd, agentDir });\n\n\t// Coverage is a model judgement now, so it is resolved once for every stored\n\t// label in a single batch and then answered from the resulting set. Asking\n\t// per item would be one call per proposal ever made in this directory.\n\tconst labels = Object.entries(state.surfaced)\n\t\t.filter(([key]) => key.startsWith(\"directive:\"))\n\t\t// Entries recorded before the wording was kept fall back to the slug, which\n\t\t// is a weaker question but still a answerable one.\n\t\t.map(([key, item]) => ({\n\t\t\tlabel: key.slice(\"directive:\".length),\n\t\t\ttext: item.text || key.slice(\"directive:\".length),\n\t\t}));\n\n\tconst covered = new Set<string>();\n\tif (labels.length > 0) {\n\t\tconst pipeline = await buildPipeline(ctx, settings);\n\t\tif (!(\"error\" in pipeline)) {\n\t\t\ttry {\n\t\t\t\tconst verdicts = await pipeline.coverageJudge(labels, coverage);\n\t\t\t\tfor (const [label, match] of verdicts) {\n\t\t\t\t\tif (match.rule || match.skill) covered.add(label);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Adoption is reported as zero rather than failing the report; the\n\t\t\t\t// caveat below already tells the reader not to over-read the number.\n\t\t\t}\n\t\t}\n\t}\n\n\tconst stats = summarizeLearnState(state, (label) => covered.has(label));\n\n\tconst contextTokens = loadProjectContextFiles({ cwd: ctx.cwd, agentDir }).agentsFiles.reduce(\n\t\t(sum, file) => sum + (file.tokens ?? 0),\n\t\t0,\n\t);\n\n\tconst lines: string[] = [];\n\tlines.push(`/learn history for this directory — ${shortDate(stats.earliest)} to ${shortDate(stats.latest)}`);\n\tlines.push(\n\t\t` Proposals shown ${stats.total} (${stats.directives} directive, ${stats.fixes} fix, ${stats.workflows} workflow)`,\n\t);\n\tif (stats.lastRun) lines.push(` Last run ${shortDate(stats.lastRun)}`);\n\tlines.push(\"\");\n\n\tif (stats.open === 0) {\n\t\tlines.push(\"No directive proposals yet, so there is nothing to measure adoption against.\");\n\t} else {\n\t\tconst rate = Math.round((stats.adopted / stats.open) * 100);\n\t\tlines.push(\"Directive adoption — the only category with a coverage signal\");\n\t\tlines.push(` Written down ${stats.adopted} of ${stats.open} (${rate}%)`);\n\t\tlines.push(` Passed over ${stats.declined}`);\n\t\tlines.push(\"\");\n\t\t// Without this the number invites the wrong conclusion. Adoption is a proxy\n\t\t// for usefulness, and a proposal correctly rejected as not durable counts\n\t\t// against it exactly like a junk one — so near-100% means the bar is too\n\t\t// low, not that the extractor is perfect.\n\t\tlines.push(\" A very high rate means the bar is too low, not that every proposal was good.\");\n\t\tlines.push(\" Near zero means the miner is proposing the wrong things.\");\n\t}\n\n\tlines.push(\"\");\n\tlines.push(`Context files ~${contextTokens} tokens, re-sent every request`);\n\tlines.push(`State file ${displayPath(statePath)}`);\n\tlines.push(\"\");\n\tlines.push(...settingsPathLines(ctx, agentDir));\n\tlines.push(\" Run /learn settings for the thresholds in force.\");\n\n\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n}\n\n/** `/learn settings` — the knobs, their current values, and the files to set them in. */\nfunction reportSettings(ctx: ExtensionCommandContext): void {\n\tconst agentDir = getHooCodeDir();\n\tconst settings = SettingsManager.create(ctx.cwd, agentDir);\n\tconst window = settings.getLearnSettings();\n\tconst lines = settingsLines(ctx, agentDir, window);\n\n\t// The reading model is not a `/learn` setting — it is the shared `fast` tier,\n\t// so name it here rather than leaving the reader to guess which model is\n\t// about to read their history, and point at the setting that changes it.\n\tconst model = resolveMinerModel(ctx, settings);\n\tlines.push(\n\t\t` reads transcripts with ${model ? `${model.provider}/${model.id}` : \"no model selected\"}` +\n\t\t\t` (the \\`fast\\` tier — set modelCategories.fast to change it)`,\n\t);\n\tif (model) {\n\t\tlines.push(\n\t\t\t` ${Math.round(chunkCharsForModel(model) / 1000)}k characters per call, from its ${model.contextWindow} token window`,\n\t\t);\n\t}\n\n\tlines.push(\"\");\n\tlines.push(...scanLines(sessionScanPreview(ctx, agentDir, window), window));\n\tlines.push(`State file ${displayPath(getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir)))}`);\n\tconst { pending } = pendingWork(ctx, agentDir, window);\n\tlines.push(\n\t\tpending === 0\n\t\t\t? \"All sessions in the window are already mined; the next /learn costs one small coverage call.\"\n\t\t\t: `${pending} session(s) in the window still need reading, roughly one call each.`,\n\t);\n\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n}\n\nexport function setupLearn(pi: ExtensionAPI): void {\n\tconst guarded = pi as unknown as Record<symbol, boolean>;\n\tif (guarded[REGISTERED]) return;\n\tguarded[REGISTERED] = true;\n\n\tpi.registerCommand(\"learn\", {\n\t\tdescription: \"Mine recent sessions for durable rules and skills. Usage: /learn [all|stats|settings]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t(\n\t\t\t\t[\n\t\t\t\t\t{ value: \"all\", label: \"re-propose everything\" },\n\t\t\t\t\t{ value: \"stats\", label: \"what happened to past proposals\" },\n\t\t\t\t\t{ value: \"settings\", label: \"where sessions are read from, and the knobs\" },\n\t\t\t\t] as const\n\t\t\t)\n\t\t\t\t.filter((option) => option.value.startsWith(prefix))\n\t\t\t\t.map((option) => ({ value: option.value, label: option.label })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst argument = args.trim().toLowerCase();\n\t\t\tif (argument && argument !== \"all\" && argument !== \"stats\" && argument !== \"settings\") {\n\t\t\t\tctx.ui.notify(\"Usage: /learn [all|stats|settings]\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (argument === \"stats\") {\n\t\t\t\tawait reportStats(ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (argument === \"settings\") {\n\t\t\t\treportSettings(ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst ignoreState = argument === \"all\";\n\n\t\t\t// Read per-invocation so a settings edit takes effect without a reload,\n\t\t\t// and so a project settings.json can narrow the window for one repo.\n\t\t\tconst agentDir = getHooCodeDir();\n\t\t\tconst settings = SettingsManager.create(ctx.cwd, agentDir);\n\t\t\tconst window = settings.getLearnSettings();\n\t\t\tconst statePath = getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir));\n\n\t\t\tconst pipeline = await buildPipeline(ctx, settings);\n\t\t\tif (\"error\" in pipeline) {\n\t\t\t\tctx.ui.notify(pipeline.error, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State the price before charging it. A first run in a busy repo reads\n\t\t\t// every transcript in the window, which is the expensive path by design\n\t\t\t// — but it should never be a surprise, and the cache means it is paid\n\t\t\t// once rather than on every run.\n\t\t\tconst { pending } = pendingWork(ctx, agentDir, window);\n\t\t\tif (pending > CONFIRM_ABOVE_PENDING) {\n\t\t\t\tconst proceed = await ctx.ui.confirm(\n\t\t\t\t\t\"Read session transcripts?\",\n\t\t\t\t\t`${pending} session(s) have not been read yet. /learn reads each one with a model ` +\n\t\t\t\t\t\t`(${pipeline.model.provider}/${pipeline.model.id}) and caches the result, so this cost is paid once ` +\n\t\t\t\t\t\t`per session. Later runs reuse it.`,\n\t\t\t\t);\n\t\t\t\tif (!proceed) {\n\t\t\t\t\tctx.ui.notify(\"/learn cancelled — nothing was read.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// A backfill can run for minutes across dozens of transcripts, and the\n\t\t\t// agent is idle throughout — so `ctx.signal` is undefined and there is no\n\t\t\t// ambient way out. Escape gets one.\n\t\t\tconst controller = new AbortController();\n\t\t\tconst unsubscribe = ctx.ui.onTerminalInput((data) => {\n\t\t\t\tif (data !== ESCAPE) return undefined;\n\t\t\t\tcontroller.abort();\n\t\t\t\treturn { consume: true };\n\t\t\t});\n\n\t\t\tlet digest: LearnDigest;\n\t\t\ttry {\n\t\t\t\tdigest = await mineLearnDigest({\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\t// Searched in addition to the per-cwd default directory, so a session\n\t\t\t\t\t// manager pointing elsewhere (`--session`, a custom `sessionDir`, or\n\t\t\t\t\t// an in-memory session reporting none at all) cannot hide the history.\n\t\t\t\t\tsessionDir: ctx.sessionManager.getSessionDir(),\n\t\t\t\t\tmaxSessions: window.maxSessions,\n\t\t\t\t\tmaxAgeDays: window.maxAgeDays,\n\t\t\t\t\tminRepeats: window.minRepeats,\n\t\t\t\t\tminWorkflowRepeats: window.minWorkflowRepeats,\n\t\t\t\t\tmaxProposals: window.maxProposals,\n\t\t\t\t\tstate: readLearnState(statePath),\n\t\t\t\t\tignoreState,\n\t\t\t\t\tminer: pipeline.miner,\n\t\t\t\t\tcoverageJudge: pipeline.coverageJudge,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\tonProgress: ({ done, total, cached }) => {\n\t\t\t\t\t\t// The same footer bar the semantic index uses. Cached sessions are\n\t\t\t\t\t\t// counted as done because they are: the bar measures progress\n\t\t\t\t\t\t// through the window, not money spent, and a run that is mostly\n\t\t\t\t\t\t// cache should look nearly finished from the start.\n\t\t\t\t\t\tstartupProgress.set({\n\t\t\t\t\t\t\tkey: PROGRESS_KEY,\n\t\t\t\t\t\t\tkind: \"work\",\n\t\t\t\t\t\t\tlabel:\n\t\t\t\t\t\t\t\tcached > 0\n\t\t\t\t\t\t\t\t\t? `Reading sessions (${cached} cached) — esc to stop`\n\t\t\t\t\t\t\t\t\t: \"Reading sessions — esc to stop\",\n\t\t\t\t\t\t\tdone,\n\t\t\t\t\t\t\ttotal,\n\t\t\t\t\t\t\tunit: \"sessions\",\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(`/learn could not read session history: ${error}`, \"error\");\n\t\t\t\treturn;\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tstartupProgress.remove(PROGRESS_KEY);\n\t\t\t}\n\n\t\t\t// A cancelled run counted only part of the window, so its numbers are not\n\t\t\t// merely incomplete — they are low. Showing them would be misleading and\n\t\t\t// bookmarking them would hide those items on the next, complete run.\n\t\t\t// Everything read so far is cached, so stopping costs nothing but time.\n\t\t\tif (digest.aborted) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`/learn stopped — ${digest.mining.mined} session(s) were read and cached, so resuming picks up where this left off.`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (digest.scannedSessions === 0) {\n\t\t\t\treportNoSessions(ctx, agentDir, digest, window);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isEmptyDigest(digest)) {\n\t\t\t\tconst lines: string[] = [];\n\t\t\t\tlines.push(\n\t\t\t\t\tdigest.suppressed > 0\n\t\t\t\t\t\t? `Read ${digest.scannedSessions} session(s) — nothing new since last time (${digest.suppressed} already shown). Run /learn all to see them again.`\n\t\t\t\t\t\t: `Read ${digest.scannedSessions} session(s) — nothing repeated often enough to be worth a rule yet.`,\n\t\t\t\t);\n\t\t\t\tif (digest.suppressed === 0) {\n\t\t\t\t\t// The thresholds are the reason a scan with real sessions in it came\n\t\t\t\t\t// back empty, so this is the moment they are worth knowing about.\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t\tlines.push(...settingsLines(ctx, agentDir, window));\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst counts = [\n\t\t\t\tdigest.directives.length > 0 ? `${digest.directives.length} directive(s)` : undefined,\n\t\t\t\tdigest.fixes.length > 0 ? `${digest.fixes.length} fix(es)` : undefined,\n\t\t\t\tdigest.workflows.length > 0 ? `${digest.workflows.length} workflow(s)` : undefined,\n\t\t\t].filter((part): part is string => !!part);\n\t\t\tconst held = digest.suppressed > 0 ? `, ${digest.suppressed} held back` : \"\";\n\t\t\tctx.ui.notify(\n\t\t\t\t`Mined ${digest.scannedSessions} session(s) (${digest.mining.mined} read, ${digest.mining.cached} cached): ${counts.join(\", \")}${held}.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\n\t\t\t// Record before delivering: what matters is that these were put in front\n\t\t\t// of the user, which is true whether or not they act on the digest.\n\t\t\twriteLearnState(statePath, recordSurfaced(readLearnState(statePath), digest.surfaced));\n\n\t\t\tpi.sendUserMessage(\n\t\t\t\trenderLearnDigest(digest, {\n\t\t\t\t\tuserScopePath: displayPath(USER_SCOPE_PATH),\n\t\t\t\t\tmode: ignoreState ? \"all\" : \"incremental\",\n\t\t\t\t}),\n\t\t\t\t{ deliverAs: \"followUp\" },\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"learn.js","sourceRoot":"","sources":["../../../src/extensions/core/learn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/learn` — promote what recent sessions actually taught into durable rules\n * and skills.\n *\n * The command is a thin shell on purpose. It runs the mining pipeline over\n * session transcripts on disk, renders the ranked result, and injects it as a\n * follow-up message; every judgement after that belongs to the model, which can\n * read the repo and phrase a rule far better than a heuristic can.\n *\n * Reading transcripts from disk rather than the live context is what makes this\n * work: the on-disk history survives compaction, and it spans past sessions, so\n * \"you have said this in five separate sessions\" is available as a number\n * instead of a guess. That number is the whole reason the command exists.\n *\n * The pipeline reads every transcript with a model rather than pre-filtering\n * with regexes, which costs real tokens on a cold cache. That price is stated\n * before it is paid, never inferred: a run with sessions to read asks first.\n *\n * Follows /grill in modes.ts: no session switch, no mode change, no config\n * write — just a follow-up message. Writes to AGENTS.md happen through ordinary\n * edit tools, so the existing permission prompt is the approval step and no\n * separate picker is needed.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { CONFIG_DIR_NAME, getHooCodeDir } from \"../../config.js\";\nimport { loadProjectContextFiles } from \"../../core/context-files.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { auditContextFiles, staleTokens } from \"../../core/learn/audit.js\";\nimport type { Clusterer } from \"../../core/learn/cluster.js\";\nimport { createLlmClusterer } from \"../../core/learn/cluster.js\";\nimport type { CoverageJudge } from \"../../core/learn/coverage.js\";\nimport { createLlmCoverageJudge } from \"../../core/learn/coverage.js\";\nimport { isEmptyDigest, renderAuditReport, renderLearnDigest } from \"../../core/learn/digest.js\";\nimport {\n\ttype LearnDigest,\n\tmineLearnDigest,\n\tplanMining,\n\ttype SessionScanReport,\n\tscanSessions,\n} from \"../../core/learn/extract.js\";\nimport type { Miner } from \"../../core/learn/mine.js\";\nimport { chunkCharsForModel, createLlmMiner, replayFingerprints } from \"../../core/learn/mine.js\";\nimport {\n\tgetLearnStatePath,\n\treadLearnState,\n\trecordSurfaced,\n\tsummarizeLearnState,\n\twriteLearnState,\n} from \"../../core/learn/state.js\";\nimport { resolveModelCategory } from \"../../core/model-categories.js\";\n\nimport { getSessionDirPath } from \"../../core/session-manager.js\";\nimport { SettingsManager } from \"../../core/settings-manager.js\";\nimport { startupProgress } from \"../../core/startup-progress.js\";\n\n/** Guards against double-registration when default extensions load more than once. */\nconst REGISTERED = Symbol.for(\"hoocode.learn.registered\");\n\n/** User-scope destination offered for personal rules that travel across repos. */\nconst USER_SCOPE_PATH = join(homedir(), \".agents\", \"AGENTS.md\");\n\n/** Footer key for the mining progress bar. */\nconst PROGRESS_KEY = \"learn-mining\";\n\n/** Escape, the way a raw terminal delivers it. */\nconst ESCAPE = \"\\x1b\";\n\n/**\n * Sessions that can be read without asking first.\n *\n * A run that has one or two new transcripts to read is the normal daily case\n * and interrupting it to confirm a trivial cost is noise. Beyond this the run\n * is a backfill — onboarding to an existing repo, or a first run — and the\n * reader should get to decide before it starts.\n */\nconst CONFIRM_ABOVE_PENDING = 3;\n\n/** Render a home-relative path the way the user would type it. */\nfunction displayPath(path: string): string {\n\tconst home = homedir();\n\treturn path.startsWith(home) ? `~${path.slice(home.length)}` : path;\n}\n\nfunction shortDate(iso: string | undefined): string {\n\tif (!iso) return \"unknown\";\n\tconst date = new Date(iso);\n\treturn Number.isNaN(date.getTime()) ? \"unknown\" : date.toISOString().slice(0, 10);\n}\n\n/** The settings keys `/learn` reads, paired with the values in force right now. */\ntype LearnWindow = ReturnType<SettingsManager[\"getLearnSettings\"]>;\n\nconst SETTING_KEYS: Array<{ key: keyof LearnWindow; setting: string; note: string }> = [\n\t{ key: \"maxSessions\", setting: \"learnMaxSessions\", note: \"recent sessions scanned\" },\n\t{ key: \"maxAgeDays\", setting: \"learnMaxAgeDays\", note: \"ignore sessions older than this, in days\" },\n\t{ key: \"minRepeats\", setting: \"learnMinRepeats\", note: \"times a directive must recur to be proposed\" },\n\t{\n\t\tkey: \"minRequestRepeats\",\n\t\tsetting: \"learnMinRequestRepeats\",\n\t\tnote: \"repeats before a tool sequence is proposed\",\n\t},\n\t{ key: \"maxProposals\", setting: \"learnMaxProposals\", note: \"cap on each list in the digest\" },\n];\n\n/**\n * Where the knobs live, and what they are set to.\n *\n * `/learn` has its settings and no UI, so until this existed the only way to\n * find them was to already know they were in `settings.json`. Every message that\n * reports a disappointing result names a threshold, so every one of them ends\n * with these lines.\n */\nfunction settingsPathLines(ctx: ExtensionCommandContext, agentDir: string): string[] {\n\treturn [\n\t\t\"Settings — edit either file, no restart needed\",\n\t\t` user ${displayPath(join(agentDir, \"settings.json\"))}`,\n\t\t` project ${displayPath(join(ctx.cwd, CONFIG_DIR_NAME, \"settings.json\"))} (wins where both set a key)`,\n\t];\n}\n\nfunction settingsLines(ctx: ExtensionCommandContext, agentDir: string, window: LearnWindow): string[] {\n\tconst lines = settingsPathLines(ctx, agentDir);\n\tfor (const { key, setting, note } of SETTING_KEYS) {\n\t\tlines.push(` ${setting.padEnd(24)} ${String(window[key] ?? \"—\").padStart(3)} ${note}`);\n\t}\n\treturn lines;\n}\n\n/**\n * The directory whose name keys this cwd's bookmark.\n *\n * Derived from the cwd, never from the live session manager. An in-memory\n * session (`--no-session`) reports an empty session directory, which used to key\n * every such run to the same nameless state file, and a shared custom\n * `sessionDir` used to make two unrelated projects share one bookmark. The cwd\n * is what \"per directory\" means here, so the cwd is what it is keyed on.\n */\nfunction stateKeyDir(ctx: ExtensionCommandContext, agentDir: string): string {\n\treturn getSessionDirPath(ctx.cwd, agentDir);\n}\n\n/** Run the directory scan without mining anything, for the reports that only need counts. */\nfunction sessionScanPreview(ctx: ExtensionCommandContext, agentDir: string, window: LearnWindow): SessionScanReport {\n\treturn scanSessions({\n\t\tcwd: ctx.cwd,\n\t\tagentDir,\n\t\tsessionDir: ctx.sessionManager.getSessionDir(),\n\t\tmaxSessions: window.maxSessions,\n\t\tmaxAgeDays: window.maxAgeDays,\n\t});\n}\n\n/** Where sessions were looked for, and what was passed over — the \"why nothing?\" answer. */\nfunction scanLines(scan: SessionScanReport, window: LearnWindow): string[] {\n\tconst lines: string[] = [\"Looked in\"];\n\tfor (const dir of scan.dirs) {\n\t\tconst missing = scan.missingDirs.includes(dir) ? \" (does not exist)\" : \"\";\n\t\tlines.push(` ${displayPath(dir)}${missing}`);\n\t}\n\tlines.push(`Found ${scan.files} session file(s)`);\n\n\tconst skips: string[] = [];\n\tif (scan.tooOld > 0) skips.push(`${scan.tooOld} older than ${window.maxAgeDays} days (learnMaxAgeDays)`);\n\tif (scan.otherCwd > 0) skips.push(`${scan.otherCwd} recorded a different working directory`);\n\tif (scan.overLimit > 0) skips.push(`${scan.overLimit} beyond the newest ${window.maxSessions} (learnMaxSessions)`);\n\tif (scan.unreadable > 0) skips.push(`${scan.unreadable} empty or unreadable`);\n\tfor (const skip of skips) lines.push(` skipped: ${skip}`);\n\treturn lines;\n}\n\n/**\n * Explain an empty scan rather than asserting there is no history.\n *\n * The old single sentence was wrong as often as it was right: sessions existed,\n * they were simply all outside the window or recorded under another path. Naming\n * the directory searched and the reason each file was passed over turns a dead\n * end into something the reader can fix.\n */\nfunction reportNoSessions(ctx: ExtensionCommandContext, agentDir: string, digest: LearnDigest, window: LearnWindow) {\n\tconst lines: string[] = [];\n\tlines.push(\n\t\tdigest.scan.files === 0\n\t\t\t? \"/learn found no session transcripts for this directory.\"\n\t\t\t: \"/learn found session transcripts, but none inside the current window.\",\n\t);\n\tlines.push(\"\");\n\tlines.push(...scanLines(digest.scan, window));\n\tlines.push(\"\");\n\tlines.push(...settingsLines(ctx, agentDir, window));\n\tctx.ui.notify(lines.join(\"\\n\"), \"warning\");\n}\n\n/**\n * The model that reads transcripts.\n *\n * This is the one call in the pipeline that reads *everything*, so it wants the\n * cheapest capable model rather than the session's. That question already has an\n * answer in this codebase — the `fast` model category, which subagents use for\n * exactly this kind of bulk read — so it is reused rather than reinvented.\n * `settings.modelCategories.fast` wins when set; otherwise the tier is derived\n * from the user's available models, and nothing here is provider-specific.\n *\n * Falls back to the session model when the tier resolves to nothing or to a\n * model the registry cannot find, since a mis-set tier should not take the\n * command out entirely.\n */\nfunction resolveMinerModel(ctx: ExtensionCommandContext, settings: SettingsManager): Model<Api> | undefined {\n\tconst ref = resolveModelCategory(\n\t\t\"fast\",\n\t\t{\n\t\t\tmodelCategories: settings.getModelCategories(),\n\t\t\tdefaultProvider: settings.getDefaultProvider(),\n\t\t\tdefaultModel: settings.getDefaultModel(),\n\t\t},\n\t\tctx.modelRegistry.getAvailable(),\n\t);\n\tif (!ref) return ctx.model;\n\n\tconst slash = ref.indexOf(\"/\");\n\tconst found = slash > 0 ? ctx.modelRegistry.find(ref.slice(0, slash), ref.slice(slash + 1)) : undefined;\n\treturn found ?? ctx.model;\n}\n\n/**\n * Literal runs from the slash commands in force, so the miner can tell a\n * command body replaying itself from something the user typed.\n *\n * Read from the session's own command list rather than re-deriving the search\n * path: which directories are scanned, in which order, and which flags disable\n * them is a precedence list that lives in one place and would drift the moment\n * it lived in two.\n */\nfunction loadReplayFingerprints(pi: ExtensionAPI): string[] {\n\tconst bodies: Array<{ content: string }> = [];\n\tfor (const command of pi.getCommands()) {\n\t\tconst path = command.sourceInfo?.path;\n\t\t// A built-in has no file behind it, and nothing to replay.\n\t\tif (!path || !existsSync(path)) continue;\n\t\ttry {\n\t\t\tbodies.push({ content: readFileSync(path, \"utf-8\") });\n\t\t} catch {\n\t\t\t// Unreadable command file: one fewer fingerprint, not a failed run.\n\t\t}\n\t}\n\treturn replayFingerprints(bodies);\n}\n\n/** Build the two model-backed stages, or report why they cannot be built. */\nasync function buildPipeline(\n\tctx: ExtensionCommandContext,\n\tsettings: SettingsManager,\n\t/** Empty for callers that only need the coverage judge; mining wants the real set. */\n\tfingerprints: string[] = [],\n): Promise<\n\t{ miner: Miner; clusterer: Clusterer; coverageJudge: CoverageJudge; model: Model<Api> } | { error: string }\n> {\n\tconst model = resolveMinerModel(ctx, settings);\n\tif (!model) {\n\t\treturn {\n\t\t\terror: \"/learn reads session transcripts with a model, and no model is selected. Pick one with /model, then run /learn again.\",\n\t\t};\n\t}\n\n\tconst auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);\n\tif (!auth.ok) {\n\t\treturn { error: `/learn could not authenticate ${model.provider}/${model.id}: ${auth.error}` };\n\t}\n\n\tconst deps = {\n\t\tmodel,\n\t\tapiKey: auth.apiKey,\n\t\theaders: auth.headers,\n\t\treplayFingerprints: fingerprints,\n\t};\n\treturn {\n\t\tminer: createLlmMiner(deps),\n\t\tclusterer: createLlmClusterer(deps),\n\t\tcoverageJudge: createLlmCoverageJudge(deps),\n\t\tmodel,\n\t};\n}\n\n/**\n * What this run still owes the model.\n *\n * Delegated to `planMining` so the number quoted by the confirmation prompt\n * comes from the same session selection the run will use — same window, same\n * cwd check, same de-duplication.\n */\nfunction pendingWork(ctx: ExtensionCommandContext, agentDir: string, window: LearnWindow) {\n\treturn planMining({\n\t\tcwd: ctx.cwd,\n\t\tagentDir,\n\t\tsessionDir: ctx.sessionManager.getSessionDir(),\n\t\tmaxSessions: window.maxSessions,\n\t\tmaxAgeDays: window.maxAgeDays,\n\t});\n}\n\n/**\n * `/learn stats` — what has been proposed here, and what it costs.\n *\n * Reads the state file and the context files. No model call: this used to\n * re-judge coverage and report an \"adoption rate\", which was unreliable in both\n * directions and shipped with two disclaimers explaining how not to misread it.\n * The honest version of the question it was trying to answer — is the\n * always-loaded surface growing — is a number the filesystem can answer exactly.\n */\nfunction reportStats(ctx: ExtensionCommandContext): void {\n\tconst agentDir = getHooCodeDir();\n\tconst settings = SettingsManager.create(ctx.cwd, agentDir);\n\tconst window = settings.getLearnSettings();\n\tconst statePath = getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir));\n\tconst state = readLearnState(statePath);\n\n\tif (Object.keys(state.surfaced).length === 0) {\n\t\t// Nothing on record means `/learn` has never proposed anything here — which\n\t\t// is as likely to be \"it never found any sessions\" as \"you never ran it\", so\n\t\t// point at both the sessions it can see and the knobs that gate them.\n\t\tconst lines = [\"No /learn history for this directory yet — nothing has been proposed here.\"];\n\t\tlines.push(` State file ${displayPath(statePath)} (not created yet)`);\n\t\tlines.push(\"\");\n\t\tlines.push(...scanLines(sessionScanPreview(ctx, agentDir, window), window));\n\t\tlines.push(\"\");\n\t\tlines.push(...settingsPathLines(ctx, agentDir));\n\t\tlines.push(\" Run /learn settings for the thresholds in force.\");\n\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\t\treturn;\n\t}\n\n\tconst stats = summarizeLearnState(state);\n\n\tconst contextFiles = loadProjectContextFiles({ cwd: ctx.cwd, agentDir }).agentsFiles;\n\tconst contextTokens = contextFiles.reduce((sum, file) => sum + (file.tokens ?? 0), 0);\n\n\tconst lines: string[] = [];\n\tlines.push(`/learn history for this directory — ${shortDate(stats.earliest)} to ${shortDate(stats.latest)}`);\n\tlines.push(\n\t\t` Proposals shown ${stats.total} (${stats.directives} directive, ${stats.fixes} fix, ${stats.requests} request)`,\n\t);\n\tif (stats.lastRun) lines.push(` Last run ${shortDate(stats.lastRun)}`);\n\tlines.push(\"\");\n\n\t// The one number worth watching, and the only one here that is exact. Mining\n\t// can only push it up; `/learn stale` is what pushes it down.\n\tlines.push(`Always-loaded cost ~${contextTokens} tokens across ${contextFiles.length} context file(s)`);\n\tfor (const file of contextFiles) {\n\t\tlines.push(` ~${file.tokens ?? 0} ${displayPath(file.path)}`);\n\t}\n\tlines.push(\" Run /learn stale to find lines naming something that no longer exists.\");\n\tlines.push(\"\");\n\tlines.push(`State file ${displayPath(statePath)}`);\n\tlines.push(\"\");\n\tlines.push(...settingsPathLines(ctx, agentDir));\n\tlines.push(\" Run /learn settings for the thresholds in force.\");\n\n\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n}\n\n/**\n * `/learn stale` — which lines in the context files name something that is gone.\n *\n * The mining path can only propose additions, so this is the only half of the\n * command that moves the always-loaded token surface down. It is deterministic\n * and costs nothing, which is what makes it the half worth running often; the\n * findings go to the model only when there are some, so a clean audit is free.\n */\nfunction reportAudit(pi: ExtensionAPI, ctx: ExtensionCommandContext): void {\n\tconst agentDir = getHooCodeDir();\n\tconst { agentsFiles } = loadProjectContextFiles({ cwd: ctx.cwd, agentDir });\n\n\tif (agentsFiles.length === 0) {\n\t\tctx.ui.notify(\"/learn stale found no context files to check (no AGENTS.md or CLAUDE.md is in force).\", \"warning\");\n\t\treturn;\n\t}\n\n\tconst report = auditContextFiles({ cwd: ctx.cwd, files: agentsFiles });\n\n\tif (report.files.length === 0) {\n\t\tconst lines = [\"/learn stale checked nothing — every context file in force is outside this working tree.\"];\n\t\tfor (const path of report.skippedFiles) lines.push(` ${displayPath(path)}`);\n\t\tlines.push(\"A rule written in a user-scope file names paths in whatever repo it was written for, not this one.\");\n\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\t\treturn;\n\t}\n\n\tconst totalTokens = report.files.reduce((sum, file) => sum + file.tokens, 0);\n\tif (report.stale.length === 0) {\n\t\tctx.ui.notify(\n\t\t\t`/learn stale — ${report.checked} referent(s) in ${report.files.length} context file(s) all resolve. ` +\n\t\t\t\t`~${totalTokens} tokens, re-sent every request.`,\n\t\t\t\"info\",\n\t\t);\n\t\treturn;\n\t}\n\n\tctx.ui.notify(\n\t\t`/learn stale — ${report.stale.length} of ${report.checked} referent(s) do not resolve ` +\n\t\t\t`(~${staleTokens(report)} of ~${totalTokens} always-loaded tokens).`,\n\t\t\"info\",\n\t);\n\tpi.sendUserMessage(renderAuditReport(report), { deliverAs: \"followUp\" });\n}\n\n/** `/learn settings` — the knobs, their current values, and the files to set them in. */\nfunction reportSettings(ctx: ExtensionCommandContext): void {\n\tconst agentDir = getHooCodeDir();\n\tconst settings = SettingsManager.create(ctx.cwd, agentDir);\n\tconst window = settings.getLearnSettings();\n\tconst lines = settingsLines(ctx, agentDir, window);\n\n\t// The reading model is not a `/learn` setting — it is the shared `fast` tier,\n\t// so name it here rather than leaving the reader to guess which model is\n\t// about to read their history, and point at the setting that changes it.\n\tconst model = resolveMinerModel(ctx, settings);\n\tlines.push(\n\t\t` reads transcripts with ${model ? `${model.provider}/${model.id}` : \"no model selected\"}` +\n\t\t\t` (the \\`fast\\` tier — set modelCategories.fast to change it)`,\n\t);\n\tif (model) {\n\t\tlines.push(\n\t\t\t` ${Math.round(chunkCharsForModel(model) / 1000)}k characters per call, from its ${model.contextWindow} token window`,\n\t\t);\n\t}\n\n\tlines.push(\"\");\n\tlines.push(...scanLines(sessionScanPreview(ctx, agentDir, window), window));\n\tlines.push(`State file ${displayPath(getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir)))}`);\n\tconst { pending } = pendingWork(ctx, agentDir, window);\n\tlines.push(\n\t\tpending === 0\n\t\t\t? \"All sessions in the window are already mined; the next /learn costs one small coverage call.\"\n\t\t\t: `${pending} session(s) in the window still need reading, roughly one call each.`,\n\t);\n\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n}\n\nexport function setupLearn(pi: ExtensionAPI): void {\n\tconst guarded = pi as unknown as Record<symbol, boolean>;\n\tif (guarded[REGISTERED]) return;\n\tguarded[REGISTERED] = true;\n\n\tpi.registerCommand(\"learn\", {\n\t\tdescription: \"Mine recent sessions for durable rules and skills. Usage: /learn [all|stale|stats|settings]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t(\n\t\t\t\t[\n\t\t\t\t\t{ value: \"all\", label: \"re-propose everything\" },\n\t\t\t\t\t{ value: \"stale\", label: \"context-file lines naming something that is gone\" },\n\t\t\t\t\t{ value: \"stats\", label: \"what happened to past proposals\" },\n\t\t\t\t\t{ value: \"settings\", label: \"where sessions are read from, and the knobs\" },\n\t\t\t\t] as const\n\t\t\t)\n\t\t\t\t.filter((option) => option.value.startsWith(prefix))\n\t\t\t\t.map((option) => ({ value: option.value, label: option.label })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst argument = args.trim().toLowerCase();\n\t\t\tif (argument && ![\"all\", \"stale\", \"stats\", \"settings\"].includes(argument)) {\n\t\t\t\tctx.ui.notify(\"Usage: /learn [all|stale|stats|settings]\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (argument === \"stale\") {\n\t\t\t\treportAudit(pi, ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (argument === \"stats\") {\n\t\t\t\treportStats(ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (argument === \"settings\") {\n\t\t\t\treportSettings(ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst ignoreState = argument === \"all\";\n\n\t\t\t// Read per-invocation so a settings edit takes effect without a reload,\n\t\t\t// and so a project settings.json can narrow the window for one repo.\n\t\t\tconst agentDir = getHooCodeDir();\n\t\t\tconst settings = SettingsManager.create(ctx.cwd, agentDir);\n\t\t\tconst window = settings.getLearnSettings();\n\t\t\tconst statePath = getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir));\n\n\t\t\tconst pipeline = await buildPipeline(ctx, settings, loadReplayFingerprints(pi));\n\t\t\tif (\"error\" in pipeline) {\n\t\t\t\tctx.ui.notify(pipeline.error, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State the price before charging it. A first run in a busy repo reads\n\t\t\t// every transcript in the window, which is the expensive path by design\n\t\t\t// — but it should never be a surprise, and the cache means it is paid\n\t\t\t// once rather than on every run.\n\t\t\tconst { pending } = pendingWork(ctx, agentDir, window);\n\t\t\tif (pending > CONFIRM_ABOVE_PENDING) {\n\t\t\t\tconst proceed = await ctx.ui.confirm(\n\t\t\t\t\t\"Read session transcripts?\",\n\t\t\t\t\t`${pending} session(s) have not been read yet. /learn reads each one with a model ` +\n\t\t\t\t\t\t`(${pipeline.model.provider}/${pipeline.model.id}) and caches the result, so this cost is paid once ` +\n\t\t\t\t\t\t`per session. Later runs reuse it.`,\n\t\t\t\t);\n\t\t\t\tif (!proceed) {\n\t\t\t\t\tctx.ui.notify(\"/learn cancelled — nothing was read.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// A backfill can run for minutes across dozens of transcripts, and the\n\t\t\t// agent is idle throughout — so `ctx.signal` is undefined and there is no\n\t\t\t// ambient way out. Escape gets one.\n\t\t\tconst controller = new AbortController();\n\t\t\tconst unsubscribe = ctx.ui.onTerminalInput((data) => {\n\t\t\t\tif (data !== ESCAPE) return undefined;\n\t\t\t\tcontroller.abort();\n\t\t\t\treturn { consume: true };\n\t\t\t});\n\n\t\t\tlet digest: LearnDigest;\n\t\t\ttry {\n\t\t\t\tdigest = await mineLearnDigest({\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\t// Searched in addition to the per-cwd default directory, so a session\n\t\t\t\t\t// manager pointing elsewhere (`--session`, a custom `sessionDir`, or\n\t\t\t\t\t// an in-memory session reporting none at all) cannot hide the history.\n\t\t\t\t\tsessionDir: ctx.sessionManager.getSessionDir(),\n\t\t\t\t\tmaxSessions: window.maxSessions,\n\t\t\t\t\tmaxAgeDays: window.maxAgeDays,\n\t\t\t\t\tminRepeats: window.minRepeats,\n\t\t\t\t\tminRequestRepeats: window.minRequestRepeats,\n\t\t\t\t\tmaxProposals: window.maxProposals,\n\t\t\t\t\tstate: readLearnState(statePath),\n\t\t\t\t\tignoreState,\n\t\t\t\t\tminer: pipeline.miner,\n\t\t\t\t\tclusterer: pipeline.clusterer,\n\t\t\t\t\tcoverageJudge: pipeline.coverageJudge,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\tonProgress: ({ done, total, cached }) => {\n\t\t\t\t\t\t// The same footer bar the semantic index uses. Cached sessions are\n\t\t\t\t\t\t// counted as done because they are: the bar measures progress\n\t\t\t\t\t\t// through the window, not money spent, and a run that is mostly\n\t\t\t\t\t\t// cache should look nearly finished from the start.\n\t\t\t\t\t\tstartupProgress.set({\n\t\t\t\t\t\t\tkey: PROGRESS_KEY,\n\t\t\t\t\t\t\tkind: \"work\",\n\t\t\t\t\t\t\tlabel:\n\t\t\t\t\t\t\t\tcached > 0\n\t\t\t\t\t\t\t\t\t? `Reading sessions (${cached} cached) — esc to stop`\n\t\t\t\t\t\t\t\t\t: \"Reading sessions — esc to stop\",\n\t\t\t\t\t\t\tdone,\n\t\t\t\t\t\t\ttotal,\n\t\t\t\t\t\t\tunit: \"sessions\",\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(`/learn could not read session history: ${error}`, \"error\");\n\t\t\t\treturn;\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tstartupProgress.remove(PROGRESS_KEY);\n\t\t\t}\n\n\t\t\t// A cancelled run counted only part of the window, so its numbers are not\n\t\t\t// merely incomplete — they are low. Showing them would be misleading and\n\t\t\t// bookmarking them would hide those items on the next, complete run.\n\t\t\t// Everything read so far is cached, so stopping costs nothing but time.\n\t\t\tif (digest.aborted) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`/learn stopped — ${digest.mining.mined} session(s) were read and cached, so resuming picks up where this left off.`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (digest.scannedSessions === 0) {\n\t\t\t\treportNoSessions(ctx, agentDir, digest, window);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isEmptyDigest(digest)) {\n\t\t\t\tconst lines: string[] = [];\n\t\t\t\tlines.push(\n\t\t\t\t\tdigest.suppressed > 0\n\t\t\t\t\t\t? `Read ${digest.scannedSessions} session(s) — nothing new since last time (${digest.suppressed} already shown). Run /learn all to see them again.`\n\t\t\t\t\t\t: `Read ${digest.scannedSessions} session(s) — nothing repeated often enough to be worth a rule yet.`,\n\t\t\t\t);\n\t\t\t\tif (digest.suppressed === 0) {\n\t\t\t\t\t// Which of the two empty results this is. \"Nothing was said\" and \"a\n\t\t\t\t\t// lot was said and none of it repeated\" read identically otherwise,\n\t\t\t\t\t// and they point at completely different knobs.\n\t\t\t\t\tlines.push(\n\t\t\t\t\t\t` ${digest.funnel.candidates} occurrence(s) → ${digest.funnel.points} distinct point(s) → ` +\n\t\t\t\t\t\t\t`${digest.funnel.belowThreshold} below the repeat threshold`,\n\t\t\t\t\t);\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t\tlines.push(...settingsLines(ctx, agentDir, window));\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst counts = [\n\t\t\t\tdigest.directives.length > 0 ? `${digest.directives.length} directive(s)` : undefined,\n\t\t\t\tdigest.fixes.length > 0 ? `${digest.fixes.length} fix(es)` : undefined,\n\t\t\t\tdigest.requests.length > 0 ? `${digest.requests.length} request(s)` : undefined,\n\t\t\t].filter((part): part is string => !!part);\n\t\t\tconst held = digest.suppressed > 0 ? `, ${digest.suppressed} held back` : \"\";\n\t\t\tconst cut = digest.cut > 0 ? `, ${digest.cut} cut to fit the cap` : \"\";\n\t\t\tctx.ui.notify(\n\t\t\t\t`Mined ${digest.scannedSessions} session(s) (${digest.mining.mined} read, ${digest.mining.cached} cached): ${counts.join(\", \")}${held}${cut}.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\n\t\t\t// Record before delivering: what matters is that these were put in front\n\t\t\t// of the user, which is true whether or not they act on the digest.\n\t\t\t//\n\t\t\t// Unless coverage could not be read. The bookmark stores whether an item\n\t\t\t// was already written down when it was shown, and that is what later tells\n\t\t\t// an adopted proposal from one passed over. Recording a guess as a reading\n\t\t\t// would have a later run tell the user they passed on something they were\n\t\t\t// never shown. Skipping costs one round of re-proposing.\n\t\t\tif (!digest.coverageFailed) {\n\t\t\t\twriteLearnState(statePath, recordSurfaced(readLearnState(statePath), digest.surfaced));\n\t\t\t}\n\n\t\t\tpi.sendUserMessage(\n\t\t\t\trenderLearnDigest(digest, {\n\t\t\t\t\tuserScopePath: displayPath(USER_SCOPE_PATH),\n\t\t\t\t\tmode: ignoreState ? \"all\" : \"incremental\",\n\t\t\t\t}),\n\t\t\t\t{ deliverAs: \"followUp\" },\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport type { Transport } from \"@kolisachint/hoocode-ai\";\nimport {\n\tContainer,\n\tgetCapabilities,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@kolisachint/hoocode-tui\";\nimport type { LearnSettingKey, WarningSettings } from \"../../../core/settings-manager.js\";\nimport { getSelectListTheme, getSettingsListTheme, getThemeDescription, theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyDisplayText } from \"./keybinding-hints.js\";\n\nconst SETTINGS_SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\ninterface ToolToggleInfo {\n\t/** Tool name (e.g. \"read\", \"bash\"). */\n\tname: string;\n\t/** Whether the tool is currently enabled (not in the persisted disabled set). */\n\tenabled: boolean;\n}\n\ninterface FlagInfo {\n\t/** Flag name (without the leading --). */\n\tname: string;\n\tdescription?: string;\n\ttype: \"boolean\" | \"string\";\n\t/** Current effective value. */\n\tvalue: boolean | string;\n}\n\ninterface ToolGroupInfo {\n\t/** Group identifier (e.g. \"web\", \"embsearch\"). */\n\tid: string;\n\tlabel: string;\n\tdescription: string;\n\t/** Whether the group is currently enabled (its tools are available). */\n\tenabled: boolean;\n}\n\nexport interface SettingsConfig {\n\tautoCompact: boolean;\n\ttools: ToolToggleInfo[];\n\ttoolGroups: ToolGroupInfo[];\n\tflags: FlagInfo[];\n\ttoolOutputDisplay: \"collapsed\" | \"peek\" | \"standard\";\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n\tshowImages: boolean;\n\timageWidthCells: number;\n\tautoResizeImages: boolean;\n\tblockImages: boolean;\n\tenableSkillCommands: boolean;\n\tpluginInstallScope: \"user\" | \"project\";\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\ttransport: Transport;\n\tthinkingLevel: ThinkingLevel;\n\tavailableThinkingLevels: ThinkingLevel[];\n\tcurrentTheme: string;\n\tavailableThemes: string[];\n\thideThinkingBlock: boolean;\n\tcollapseChangelog: boolean;\n\tenableInstallTelemetry: boolean;\n\tdoubleEscapeAction: \"fork\" | \"tree\" | \"none\";\n\ttreeFilterMode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\";\n\tshowHardwareCursor: boolean;\n\teditorBorder: \"rule\" | \"box\";\n\teditorPaddingX: number;\n\tautocompleteMaxVisible: number;\n\tquietStartup: boolean;\n\tclearOnShrink: boolean;\n\tshowTerminalProgress: boolean;\n\twarnings: WarningSettings;\n\tvoiceSilenceMs: number;\n\twebtoolsTimeoutSecs: number;\n\tlearn: Record<LearnSettingKey, number>;\n}\n\nexport interface SettingsCallbacks {\n\tonAutoCompactChange: (enabled: boolean) => void;\n\tonToolEnabledChange: (name: string, enabled: boolean) => void;\n\tonToolGroupChange: (id: string, enabled: boolean) => void;\n\tonToolOutputDisplayChange: (level: \"collapsed\" | \"peek\" | \"standard\") => void;\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n\tonFlagChange: (name: string, value: boolean | string) => void;\n\tonShowImagesChange: (enabled: boolean) => void;\n\tonImageWidthCellsChange: (width: number) => void;\n\tonAutoResizeImagesChange: (enabled: boolean) => void;\n\tonBlockImagesChange: (blocked: boolean) => void;\n\tonEnableSkillCommandsChange: (enabled: boolean) => void;\n\tonPluginInstallScopeChange: (scope: \"user\" | \"project\") => void;\n\tonSteeringModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonFollowUpModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonTransportChange: (transport: Transport) => void;\n\tonThinkingLevelChange: (level: ThinkingLevel) => void;\n\tonThemeChange: (theme: string) => void;\n\tonThemePreview?: (theme: string) => void;\n\tonHideThinkingBlockChange: (hidden: boolean) => void;\n\tonCollapseChangelogChange: (collapsed: boolean) => void;\n\tonEnableInstallTelemetryChange: (enabled: boolean) => void;\n\tonDoubleEscapeActionChange: (action: \"fork\" | \"tree\" | \"none\") => void;\n\tonTreeFilterModeChange: (mode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\") => void;\n\tonShowHardwareCursorChange: (enabled: boolean) => void;\n\tonEditorBorderChange: (border: \"rule\" | \"box\") => void;\n\tonEditorPaddingXChange: (padding: number) => void;\n\tonAutocompleteMaxVisibleChange: (maxVisible: number) => void;\n\tonQuietStartupChange: (enabled: boolean) => void;\n\tonClearOnShrinkChange: (enabled: boolean) => void;\n\tonShowTerminalProgressChange: (enabled: boolean) => void;\n\tonWarningsChange: (warnings: WarningSettings) => void;\n\tonVoiceSilenceMsChange: (ms: number) => void;\n\tonWebtoolsTimeoutSecsChange: (secs: number) => void;\n\tonLearnSettingChange: (key: LearnSettingKey, value: number) => void;\n\tonCancel: () => void;\n}\n\n/**\n * The `/learn` thresholds as pane rows: the presets to cycle through, and what\n * each one buys. Kept as a table because all five are the same shape — a\n * positive integer with a handful of sensible values — and the pane, the change\n * handler and the category row all read from it rather than repeating the list.\n */\nconst LEARN_SETTINGS: ReadonlyArray<{\n\tkey: LearnSettingKey;\n\tlabel: string;\n\tdescription: string;\n\tpresets: number[];\n}> = [\n\t{\n\t\tkey: \"learnMaxSessions\",\n\t\tlabel: \"Sessions scanned\",\n\t\tdescription: \"How many recent sessions in this directory /learn mines. Raise it on a repo you touch rarely.\",\n\t\tpresets: [10, 20, 30, 50, 100],\n\t},\n\t{\n\t\tkey: \"learnMaxAgeDays\",\n\t\tlabel: \"Session age limit\",\n\t\tdescription: \"Ignore sessions older than this many days. A pattern that stopped is not a rule.\",\n\t\tpresets: [7, 14, 30, 60, 90, 180],\n\t},\n\t{\n\t\tkey: \"learnMinRepeats\",\n\t\tlabel: \"Directive repeats\",\n\t\tdescription:\n\t\t\t\"Times a directive must recur before it is proposed. The signal/noise dial: raise it for fewer, better-evidenced proposals.\",\n\t\tpresets: [2, 3, 4, 5],\n\t},\n\t{\n\t\tkey: \"learnMinWorkflowRepeats\",\n\t\tlabel: \"Workflow repeats\",\n\t\tdescription: \"Non-overlapping repeats a tool sequence needs before it is proposed as a skill.\",\n\t\tpresets: [2, 3, 4, 5, 6],\n\t},\n\t{\n\t\tkey: \"learnMaxProposals\",\n\t\tlabel: \"Max proposals\",\n\t\tdescription: \"Cap on each list in the digest. Every proposal costs the model context.\",\n\t\tpresets: [3, 5, 8, 12, 20],\n\t},\n];\n\nconst LEARN_KEYS: ReadonlySet<string> = new Set(LEARN_SETTINGS.map((setting) => setting.key));\n\n/**\n * Preset list for a numeric row, guaranteed to contain the value in force.\n *\n * Without this a value set by hand in settings.json — say 45 days — is absent\n * from the cycle, so the first keypress silently snaps it to the first preset.\n * These particular settings gate whether `/learn` finds anything at all, so a\n * stray keystroke narrowing the window is exactly the surprise to avoid.\n */\nfunction presetValues(presets: number[], current: number): string[] {\n\tconst all = presets.includes(current) ? presets : [...presets, current].sort((a, b) => a - b);\n\treturn all.map(String);\n}\n\n/**\n * A submenu component for selecting from a list of options.\n */\nclass WarningSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate state: WarningSettings;\n\n\tconstructor(warnings: WarningSettings, onChange: (warnings: WarningSettings) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tthis.state = { ...warnings };\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"anthropic-extra-usage\",\n\t\t\t\tlabel: \"Anthropic extra usage\",\n\t\t\t\tdescription: \"Warn when Anthropic subscription auth may use paid extra usage\",\n\t\t\t\tcurrentValue: (this.state.anthropicExtraUsage ?? true) ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"anthropic-extra-usage\":\n\t\t\t\t\t\tthis.state = { ...this.state, anthropicExtraUsage: newValue === \"true\" };\n\t\t\t\t\t\tonChange({ ...this.state });\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Submenu for tool availability. The first rows are group switches (web,\n * semantic search) that decide whether a group's tools\n * exist at all — this is the same master switch that governs, e.g., the\n * webfetch/websearch tools. Below them are per-tool on/off toggles for the\n * tools that are currently available.\n *\n * Group switches change tool availability and apply on the next session;\n * per-tool toggles apply live and persist. A minimum core (read/bash/edit/\n * write) is guarded so the agent can never be left with no way to act.\n */\nclass ToolsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate enabled: Map<string, boolean>;\n\tprivate static readonly CORE = new Set([\"read\", \"bash\", \"edit\", \"write\"]);\n\tprivate static readonly GROUP_PREFIX = \"group:\";\n\n\tconstructor(\n\t\ttools: ToolToggleInfo[],\n\t\tgroups: ToolGroupInfo[],\n\t\tonChange: (name: string, enabled: boolean) => void,\n\t\tonGroupChange: (id: string, enabled: boolean) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.enabled = new Map(tools.map((t) => [t.name, t.enabled]));\n\n\t\tconst groupItems: SettingItem[] = groups.map((group) => ({\n\t\t\tid: `${ToolsSubmenu.GROUP_PREFIX}${group.id}`,\n\t\t\tlabel: `[group] ${group.label}`,\n\t\t\tdescription: `${group.description} Governs whether these tools exist; applies on the next session.`,\n\t\t\tcurrentValue: group.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst toolItems: SettingItem[] = tools.map((tool) => ({\n\t\t\tid: tool.name,\n\t\t\tlabel: tool.name,\n\t\t\tdescription: ToolsSubmenu.CORE.has(tool.name)\n\t\t\t\t? \"Core tool. Disabling leaves the agent unable to perform this action in every session.\"\n\t\t\t\t: \"Disable to remove this tool from the agent this session and every future session.\",\n\t\t\tcurrentValue: tool.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst items = [...groupItems, ...toolItems];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tconst wantEnabled = newValue === \"on\";\n\t\t\t\tif (id.startsWith(ToolsSubmenu.GROUP_PREFIX)) {\n\t\t\t\t\tonGroupChange(id.slice(ToolsSubmenu.GROUP_PREFIX.length), wantEnabled);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Guard: never let the last core tool be turned off.\n\t\t\t\tif (!wantEnabled && ToolsSubmenu.CORE.has(id)) {\n\t\t\t\t\tconst remainingCore = [...ToolsSubmenu.CORE].filter((n) => n !== id && this.enabled.get(n));\n\t\t\t\t\tif (remainingCore.length === 0) {\n\t\t\t\t\t\tthis.settingsList.updateValue(id, \"on\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.enabled.set(id, wantEnabled);\n\t\t\t\tonChange(id, wantEnabled);\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Byte-cap presets shown as human labels; mapped back to raw byte counts. */\nconst TOOL_OUTPUT_BYTE_PRESETS: ReadonlyArray<[label: string, bytes: number]> = [\n\t[\"8 KB\", 8 * 1024],\n\t[\"16 KB\", 16 * 1024],\n\t[\"32 KB\", 32 * 1024],\n\t[\"64 KB\", 64 * 1024],\n\t[\"128 KB\", 128 * 1024],\n];\n\nfunction bytesToLabel(bytes: number): string {\n\tconst match = TOOL_OUTPUT_BYTE_PRESETS.find(([, b]) => b === bytes);\n\treturn match ? match[0] : `${Math.round(bytes / 1024)} KB`;\n}\n\ninterface ToolSettingsConfig {\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n}\n\ninterface ToolSettingsCallbacks {\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n}\n\n/**\n * Submenu for per-tool runtime settings. These feed the tool runtime the next\n * time it is built (next session / rebuild), so changes apply to future tool\n * calls rather than retroactively.\n */\nclass ToolSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: ToolSettingsConfig, callbacks: ToolSettingsCallbacks, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"output-max-bytes\",\n\t\t\t\tlabel: \"Output max bytes\",\n\t\t\t\tdescription: \"Byte cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: bytesToLabel(config.toolOutputMaxBytes),\n\t\t\t\tvalues: TOOL_OUTPUT_BYTE_PRESETS.map(([label]) => label),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"output-max-lines\",\n\t\t\t\tlabel: \"Output max lines\",\n\t\t\t\tdescription: \"Line cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: String(config.toolOutputMaxLines),\n\t\t\t\tvalues: [\"200\", \"400\", \"800\", \"1600\", \"3200\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"context-gc\",\n\t\t\t\tlabel: \"Context GC\",\n\t\t\t\tdescription: \"Stub superseded read results (files later edited/re-read) out of the outgoing context.\",\n\t\t\t\tcurrentValue: config.contextGc ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"output-max-bytes\": {\n\t\t\t\t\t\tconst preset = TOOL_OUTPUT_BYTE_PRESETS.find(([label]) => label === newValue);\n\t\t\t\t\t\tif (preset) callbacks.onToolOutputMaxBytesChange(preset[1]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"output-max-lines\":\n\t\t\t\t\t\tcallbacks.onToolOutputMaxLinesChange(parseInt(newValue, 10));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"context-gc\":\n\t\t\t\t\t\tcallbacks.onContextGcChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Single-line text editor for a string flag value. */\nclass FlagStringEditSubmenu extends Container {\n\tprivate input: Input;\n\n\tconstructor(flagName: string, currentValue: string, done: (value?: string) => void) {\n\t\tsuper();\n\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", `Flag: --${flagName}`)), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"muted\", \"Enter a value · Enter to save · Esc to cancel\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.input = new Input();\n\t\tthis.input.setValue(currentValue);\n\t\tthis.input.onSubmit = (value: string) => done(value);\n\t\tthis.input.onEscape = () => done();\n\t\tthis.addChild(this.input);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.input.handleInput(data);\n\t}\n}\n\n/**\n * Submenu listing extension-registered flags. Boolean flags toggle on/off;\n * string flags open a text editor. Changes persist to settings.json and are\n * applied live best-effort — extensions that read a flag only at load time\n * pick up the new value on the next launch.\n */\nclass FlagsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(flags: FlagInfo[], onChange: (name: string, value: boolean | string) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = flags.map((flag) => {\n\t\t\tconst baseDescription = flag.description ?? \"Extension-registered flag.\";\n\t\t\tconst description = `${baseDescription} Persists across sessions; some flags need a restart to fully apply.`;\n\t\t\tif (flag.type === \"boolean\") {\n\t\t\t\treturn {\n\t\t\t\t\tid: flag.name,\n\t\t\t\t\tlabel: flag.name,\n\t\t\t\t\tdescription,\n\t\t\t\t\tcurrentValue: flag.value ? \"on\" : \"off\",\n\t\t\t\t\tvalues: [\"on\", \"off\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: flag.name,\n\t\t\t\tlabel: flag.name,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: String(flag.value ?? \"\"),\n\t\t\t\tsubmenu: (currentValue, done) => new FlagStringEditSubmenu(flag.name, currentValue, done),\n\t\t\t};\n\t\t});\n\n\t\tconst typeByName = new Map(flags.map((f) => [f.name, f.type]));\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tif (typeByName.get(id) === \"boolean\") {\n\t\t\t\t\tonChange(id, newValue === \"on\");\n\t\t\t\t} else {\n\t\t\t\t\tonChange(id, newValue);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Generic submenu holding a subset of leaf settings under a category label.\n * Shares the parent's change handler so cycle rows behave exactly as they did\n * when flat; nested submenu rows (theme, thinking, warnings) keep their own\n * factories.\n */\nclass CategorySubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(items: SettingItem[], onChange: (id: string, newValue: string) => void, onCancel: () => void) {\n\t\tsuper();\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tonChange,\n\t\t\tonCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\nclass SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t) {\n\t\tsuper();\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = new SelectList(\n\t\t\toptions,\n\t\t\tMath.min(options.length, 10),\n\t\t\tgetSelectListTheme(),\n\t\t\tSETTINGS_SUBMENU_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Pre-select current value\n\t\tconst currentIndex = options.findIndex((o) => o.value === currentValue);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value);\n\t\t};\n\n\t\tthis.selectList.onCancel = onCancel;\n\n\t\tif (onSelectionChange) {\n\t\t\tthis.selectList.onSelectionChange = (item) => {\n\t\t\t\tonSelectionChange(item.value);\n\t\t\t};\n\t\t}\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \" Enter to select · Esc to go back\"), 0, 0));\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.selectList.handleInput(data);\n\t}\n}\n\n/**\n * Main settings selector component.\n */\nexport class SettingsSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: SettingsConfig, callbacks: SettingsCallbacks) {\n\t\tsuper();\n\n\t\tconst supportsImages = getCapabilities().images;\n\t\tconst followUpKey = keyDisplayText(\"app.message.followUp\");\n\t\tlet currentWarnings = { ...config.warnings };\n\n\t\tconst toolsOn = config.tools.filter((t) => t.enabled).length;\n\t\tconst toolsOff = config.tools.length - toolsOn;\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"autocompact\",\n\t\t\t\tlabel: \"Auto-compact\",\n\t\t\t\tdescription: \"Automatically compact context when it gets too large\",\n\t\t\t\tcurrentValue: config.autoCompact ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"steering-mode\",\n\t\t\t\tlabel: \"Steering mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.steeringMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"follow-up-mode\",\n\t\t\t\tlabel: \"Follow-up mode\",\n\t\t\t\tdescription: `${followUpKey} queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.`,\n\t\t\t\tcurrentValue: config.followUpMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"transport\",\n\t\t\t\tlabel: \"Transport\",\n\t\t\t\tdescription: \"Preferred transport for providers that support multiple transports\",\n\t\t\t\tcurrentValue: config.transport,\n\t\t\t\tvalues: [\"sse\", \"websocket\", \"websocket-cached\", \"auto\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"hide-thinking\",\n\t\t\t\tlabel: \"Hide thinking\",\n\t\t\t\tdescription: \"Hide thinking blocks in assistant responses\",\n\t\t\t\tcurrentValue: config.hideThinkingBlock ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"collapse-changelog\",\n\t\t\t\tlabel: \"Collapse changelog\",\n\t\t\t\tdescription: \"Show condensed changelog after updates\",\n\t\t\t\tcurrentValue: config.collapseChangelog ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"quiet-startup\",\n\t\t\t\tlabel: \"Quiet startup\",\n\t\t\t\tdescription: \"Disable verbose printing at startup\",\n\t\t\t\tcurrentValue: config.quietStartup ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"install-telemetry\",\n\t\t\t\tlabel: \"Install telemetry\",\n\t\t\t\tdescription: \"Send an anonymous version/update ping after changelog-detected updates\",\n\t\t\t\tcurrentValue: config.enableInstallTelemetry ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"double-escape-action\",\n\t\t\t\tlabel: \"Double-escape action\",\n\t\t\t\tdescription: \"Action when pressing Escape twice with empty editor\",\n\t\t\t\tcurrentValue: config.doubleEscapeAction,\n\t\t\t\tvalues: [\"tree\", \"fork\", \"none\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tree-filter-mode\",\n\t\t\t\tlabel: \"Tree filter mode\",\n\t\t\t\tdescription: \"Default filter when opening /tree\",\n\t\t\t\tcurrentValue: config.treeFilterMode,\n\t\t\t\tvalues: [\"default\", \"no-tools\", \"user-only\", \"labeled-only\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"warnings\",\n\t\t\t\tlabel: \"Warnings\",\n\t\t\t\tdescription: \"Enable or disable individual warnings\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew WarningSettingsSubmenu(\n\t\t\t\t\t\tcurrentWarnings,\n\t\t\t\t\t\t(warnings) => {\n\t\t\t\t\t\t\tcurrentWarnings = warnings;\n\t\t\t\t\t\t\tcallbacks.onWarningsChange(warnings);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"thinking\",\n\t\t\t\tlabel: \"Thinking level\",\n\t\t\t\tdescription: \"Reasoning depth for thinking-capable models\",\n\t\t\t\tcurrentValue: config.thinkingLevel,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Thinking Level\",\n\t\t\t\t\t\t\"Select reasoning depth for thinking-capable models\",\n\t\t\t\t\t\tconfig.availableThinkingLevels.map((level) => ({\n\t\t\t\t\t\t\tvalue: level,\n\t\t\t\t\t\t\tlabel: level,\n\t\t\t\t\t\t\tdescription: THINKING_DESCRIPTIONS[level],\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThinkingLevelChange(value as ThinkingLevel);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"theme\",\n\t\t\t\tlabel: \"Theme\",\n\t\t\t\tdescription: \"Color theme for the interface\",\n\t\t\t\tcurrentValue: config.currentTheme,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Theme\",\n\t\t\t\t\t\t\"Select color theme\",\n\t\t\t\t\t\tconfig.availableThemes.map((t) => ({\n\t\t\t\t\t\t\tvalue: t,\n\t\t\t\t\t\t\tlabel: t,\n\t\t\t\t\t\t\tdescription: getThemeDescription(t),\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThemeChange(value);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t// Restore original theme on cancel\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(currentValue);\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\t// Preview theme on selection change\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\n\t\t// Only show image toggle if terminal supports it\n\t\tif (supportsImages) {\n\t\t\t// Insert after autocompact\n\t\t\titems.splice(1, 0, {\n\t\t\t\tid: \"show-images\",\n\t\t\t\tlabel: \"Show images\",\n\t\t\t\tdescription: \"Render images inline in terminal\",\n\t\t\t\tcurrentValue: config.showImages ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t});\n\t\t\titems.splice(2, 0, {\n\t\t\t\tid: \"image-width-cells\",\n\t\t\t\tlabel: \"Image width\",\n\t\t\t\tdescription: \"Preferred inline image width in terminal cells\",\n\t\t\t\tcurrentValue: String(config.imageWidthCells),\n\t\t\t\tvalues: [\"60\", \"80\", \"120\"],\n\t\t\t});\n\t\t}\n\n\t\t// Image auto-resize toggle (always available, affects both attached and read images)\n\t\titems.splice(supportsImages ? 3 : 1, 0, {\n\t\t\tid: \"auto-resize-images\",\n\t\t\tlabel: \"Auto-resize images\",\n\t\t\tdescription: \"Resize large images to 2000x2000 max for better model compatibility\",\n\t\t\tcurrentValue: config.autoResizeImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Block images toggle (always available, insert after auto-resize-images)\n\t\tconst autoResizeIndex = items.findIndex((item) => item.id === \"auto-resize-images\");\n\t\titems.splice(autoResizeIndex + 1, 0, {\n\t\t\tid: \"block-images\",\n\t\t\tlabel: \"Block images\",\n\t\t\tdescription: \"Prevent images from being sent to LLM providers\",\n\t\t\tcurrentValue: config.blockImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Skill commands toggle (insert after block-images)\n\t\tconst blockImagesIndex = items.findIndex((item) => item.id === \"block-images\");\n\t\titems.splice(blockImagesIndex + 1, 0, {\n\t\t\tid: \"skill-commands\",\n\t\t\tlabel: \"Skill commands\",\n\t\t\tdescription: \"Register skills as /skill:name commands\",\n\t\t\tcurrentValue: config.enableSkillCommands ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Plugin install scope (insert after skill-commands). Governs the\n\t\t// autonomous InstallPlugin only — /plugin install asks per install.\n\t\tconst skillCommandsIdx = items.findIndex((item) => item.id === \"skill-commands\");\n\t\titems.splice(skillCommandsIdx + 1, 0, {\n\t\t\tid: \"plugin-install-scope\",\n\t\t\tlabel: \"Plugin install scope\",\n\t\t\tdescription: \"Where autonomous plugin installs go: user (~/.agents) or project (this repo, shared)\",\n\t\t\tcurrentValue: config.pluginInstallScope,\n\t\t\tvalues: [\"user\", \"project\"],\n\t\t});\n\n\t\t// Hardware cursor toggle (insert after plugin-install-scope)\n\t\tconst skillCommandsIndex = items.findIndex((item) => item.id === \"plugin-install-scope\");\n\t\titems.splice(skillCommandsIndex + 1, 0, {\n\t\t\tid: \"show-hardware-cursor\",\n\t\t\tlabel: \"Show hardware cursor\",\n\t\t\tdescription: \"Show the terminal cursor while still positioning it for IME support\",\n\t\t\tcurrentValue: config.showHardwareCursor ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Editor border toggle (insert after show-hardware-cursor)\n\t\tconst hardwareCursorIndex = items.findIndex((item) => item.id === \"show-hardware-cursor\");\n\t\titems.splice(hardwareCursorIndex + 1, 0, {\n\t\t\tid: \"editor-border\",\n\t\t\tlabel: \"Editor border\",\n\t\t\tdescription: \"Box draws side borders, rule draws horizontal lines only\",\n\t\t\tcurrentValue: config.editorBorder,\n\t\t\tvalues: [\"box\", \"rule\"],\n\t\t});\n\n\t\t// Editor padding toggle (insert after editor-border)\n\t\tconst editorBorderIndex = items.findIndex((item) => item.id === \"editor-border\");\n\t\titems.splice(editorBorderIndex + 1, 0, {\n\t\t\tid: \"editor-padding\",\n\t\t\tlabel: \"Editor padding\",\n\t\t\tdescription: \"Horizontal padding for input editor (0-3)\",\n\t\t\tcurrentValue: String(config.editorPaddingX),\n\t\t\tvalues: [\"0\", \"1\", \"2\", \"3\"],\n\t\t});\n\n\t\t// Autocomplete max visible toggle (insert after editor-padding)\n\t\tconst editorPaddingIndex = items.findIndex((item) => item.id === \"editor-padding\");\n\t\titems.splice(editorPaddingIndex + 1, 0, {\n\t\t\tid: \"autocomplete-max-visible\",\n\t\t\tlabel: \"Autocomplete max items\",\n\t\t\tdescription: \"Max visible items in autocomplete dropdown (3-20)\",\n\t\t\tcurrentValue: String(config.autocompleteMaxVisible),\n\t\t\tvalues: [\"3\", \"5\", \"7\", \"10\", \"15\", \"20\"],\n\t\t});\n\n\t\t// Clear on shrink toggle (insert after autocomplete-max-visible)\n\t\tconst autocompleteIndex = items.findIndex((item) => item.id === \"autocomplete-max-visible\");\n\t\titems.splice(autocompleteIndex + 1, 0, {\n\t\t\tid: \"clear-on-shrink\",\n\t\t\tlabel: \"Clear on shrink\",\n\t\t\tdescription: \"Clear empty rows when content shrinks (may cause flicker)\",\n\t\t\tcurrentValue: config.clearOnShrink ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Terminal progress toggle (insert after clear-on-shrink)\n\t\tconst clearOnShrinkIndex = items.findIndex((item) => item.id === \"clear-on-shrink\");\n\t\titems.splice(clearOnShrinkIndex + 1, 0, {\n\t\t\tid: \"terminal-progress\",\n\t\t\tlabel: \"Terminal progress\",\n\t\t\tdescription: \"Show OSC 9;4 progress indicators in the terminal tab bar\",\n\t\t\tcurrentValue: config.showTerminalProgress ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Voice silence window (insert after terminal-progress)\n\t\tconst terminalProgressIndex = items.findIndex((item) => item.id === \"terminal-progress\");\n\t\titems.splice(terminalProgressIndex + 1, 0, {\n\t\t\tid: \"voice-silence-ms\",\n\t\t\tlabel: \"Voice silence window\",\n\t\t\tdescription: \"Trailing-silence (ms) before voice capture auto-stops (300-10000). Env: VOICETOOLS_SILENCE_MS.\",\n\t\t\tcurrentValue: String(config.voiceSilenceMs),\n\t\t\tvalues: [\"300\", \"500\", \"800\", \"1200\", \"2000\", \"3000\", \"5000\", \"8000\", \"10000\"],\n\t\t});\n\n\t\t// Webtools request timeout (insert after voice-silence-ms)\n\t\tconst voiceSilenceIndex = items.findIndex((item) => item.id === \"voice-silence-ms\");\n\t\titems.splice(voiceSilenceIndex + 1, 0, {\n\t\t\tid: \"webtools-timeout-secs\",\n\t\t\tlabel: \"Web tools timeout\",\n\t\t\tdescription: \"Per-request timeout (secs) for webfetch/websearch (1-120). Env: HOOCODE_WEBTOOLS_TIMEOUT.\",\n\t\t\tcurrentValue: String(config.webtoolsTimeoutSecs),\n\t\t\tvalues: [\"5\", \"10\", \"15\", \"30\", \"60\", \"120\"],\n\t\t});\n\n\t\t// The /learn thresholds, appended as leaf rows and gathered into their own\n\t\t// category below. They are written to the user settings.json, which /learn\n\t\t// re-reads on every invocation, so a change here applies to the next run.\n\t\tconst webtoolsIndex = items.findIndex((item) => item.id === \"webtools-timeout-secs\");\n\t\titems.splice(\n\t\t\twebtoolsIndex + 1,\n\t\t\t0,\n\t\t\t...LEARN_SETTINGS.map(({ key, label, description, presets }) => ({\n\t\t\t\tid: key,\n\t\t\t\tlabel,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: String(config.learn[key]),\n\t\t\t\tvalues: presetValues(presets, config.learn[key]),\n\t\t\t})),\n\t\t);\n\n\t\t// Keep the tool/flag controls together as one block near the top, inserted\n\t\t// after the image/terminal splices above so they aren't leapfrogged.\n\t\tconst toolFlagGroup: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"tools\",\n\t\t\t\tlabel: \"Tools\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enable/disable tools and tool groups (web, semantic search). Changes persist across sessions.\",\n\t\t\t\tcurrentValue: toolsOff > 0 ? `${toolsOn} on · ${toolsOff} off` : `${toolsOn} on`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolsSubmenu(\n\t\t\t\t\t\tconfig.tools,\n\t\t\t\t\t\tconfig.toolGroups,\n\t\t\t\t\t\t(name, enabled) => callbacks.onToolEnabledChange(name, enabled),\n\t\t\t\t\t\t(id, enabled) => callbacks.onToolGroupChange(id, enabled),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-output-display\",\n\t\t\t\tlabel: \"Tool output display\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"How tool results render. 'standard': shown (expandable). 'collapsed': hidden. 'peek': hidden with a ▸ reveal caret (press the expand key to reveal).\",\n\t\t\t\tcurrentValue: config.toolOutputDisplay,\n\t\t\t\tvalues: [\"standard\", \"collapsed\", \"peek\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-settings\",\n\t\t\t\tlabel: \"Tool settings\",\n\t\t\t\tdescription: \"Per-tool runtime settings: output truncation caps and context garbage collection.\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolSettingsSubmenu(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttoolOutputMaxBytes: config.toolOutputMaxBytes,\n\t\t\t\t\t\t\ttoolOutputMaxLines: config.toolOutputMaxLines,\n\t\t\t\t\t\t\tcontextGc: config.contextGc,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tonToolOutputMaxBytesChange: callbacks.onToolOutputMaxBytesChange,\n\t\t\t\t\t\t\tonToolOutputMaxLinesChange: callbacks.onToolOutputMaxLinesChange,\n\t\t\t\t\t\t\tonContextGcChange: callbacks.onContextGcChange,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\t\tif (config.flags.length > 0) {\n\t\t\ttoolFlagGroup.push({\n\t\t\t\tid: \"flags\",\n\t\t\t\tlabel: \"Flags\",\n\t\t\t\tdescription: \"Set flags registered by extensions. Changes persist across sessions.\",\n\t\t\t\tcurrentValue: `${config.flags.length} flag${config.flags.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew FlagsSubmenu(\n\t\t\t\t\t\tconfig.flags,\n\t\t\t\t\t\t(name, value) => callbacks.onFlagChange(name, value),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\t// Add borders\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Shared change handler for every leaf (cycle) setting; used by the\n\t\t// top-level list and each category submenu.\n\t\tconst applyChange = (id: string, newValue: string): void => {\n\t\t\tswitch (id) {\n\t\t\t\tcase \"autocompact\":\n\t\t\t\t\tcallbacks.onAutoCompactChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tool-output-display\":\n\t\t\t\t\tcallbacks.onToolOutputDisplayChange(newValue as \"collapsed\" | \"peek\" | \"standard\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-images\":\n\t\t\t\t\tcallbacks.onShowImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"image-width-cells\":\n\t\t\t\t\tcallbacks.onImageWidthCellsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"auto-resize-images\":\n\t\t\t\t\tcallbacks.onAutoResizeImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"block-images\":\n\t\t\t\t\tcallbacks.onBlockImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skill-commands\":\n\t\t\t\t\tcallbacks.onEnableSkillCommandsChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"plugin-install-scope\":\n\t\t\t\t\tcallbacks.onPluginInstallScopeChange(newValue as \"user\" | \"project\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"steering-mode\":\n\t\t\t\t\tcallbacks.onSteeringModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"follow-up-mode\":\n\t\t\t\t\tcallbacks.onFollowUpModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"transport\":\n\t\t\t\t\tcallbacks.onTransportChange(newValue as Transport);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"hide-thinking\":\n\t\t\t\t\tcallbacks.onHideThinkingBlockChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"collapse-changelog\":\n\t\t\t\t\tcallbacks.onCollapseChangelogChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"quiet-startup\":\n\t\t\t\t\tcallbacks.onQuietStartupChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"install-telemetry\":\n\t\t\t\t\tcallbacks.onEnableInstallTelemetryChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"double-escape-action\":\n\t\t\t\t\tcallbacks.onDoubleEscapeActionChange(newValue as \"fork\" | \"tree\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tree-filter-mode\":\n\t\t\t\t\tcallbacks.onTreeFilterModeChange(\n\t\t\t\t\t\tnewValue as \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\",\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-hardware-cursor\":\n\t\t\t\t\tcallbacks.onShowHardwareCursorChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-border\":\n\t\t\t\t\tcallbacks.onEditorBorderChange(newValue as \"rule\" | \"box\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-padding\":\n\t\t\t\t\tcallbacks.onEditorPaddingXChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"autocomplete-max-visible\":\n\t\t\t\t\tcallbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"clear-on-shrink\":\n\t\t\t\t\tcallbacks.onClearOnShrinkChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"terminal-progress\":\n\t\t\t\t\tcallbacks.onShowTerminalProgressChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"voice-silence-ms\":\n\t\t\t\t\tcallbacks.onVoiceSilenceMsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"webtools-timeout-secs\":\n\t\t\t\t\tcallbacks.onWebtoolsTimeoutSecsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t// The /learn rows are keyed by their settings.json name, so they need\n\t\t\t\t\t// no case of their own — the id is the key to write.\n\t\t\t\t\tif (LEARN_KEYS.has(id)) callbacks.onLearnSettingChange(id as LearnSettingKey, parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\n\t\t// Partition the flat leaf settings into named category submenus so the\n\t\t// top level stays short. `items` holds autocompact + every leaf setting.\n\t\tconst byId = new Map(items.map((item) => [item.id, item] as const));\n\t\tconst pick = (ids: string[]): SettingItem[] =>\n\t\t\tids.map((id) => byId.get(id)).filter((item): item is SettingItem => item !== undefined);\n\t\tconst categoryRow = (id: string, label: string, description: string, ids: string[]): SettingItem => {\n\t\t\tconst members = pick(ids);\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tlabel,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: `${members.length} setting${members.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) => new CategorySubmenu(members, applyChange, () => done()),\n\t\t\t};\n\t\t};\n\n\t\tconst topItems: SettingItem[] = [\n\t\t\t...(byId.has(\"autocompact\") ? [byId.get(\"autocompact\")!] : []),\n\t\t\t...toolFlagGroup,\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-behavior\",\n\t\t\t\t\"Behavior\",\n\t\t\t\t\"Agent and session behavior: steering, follow-up, thinking, escape, tree filter, transport.\",\n\t\t\t\t[\"steering-mode\", \"follow-up-mode\", \"thinking\", \"double-escape-action\", \"tree-filter-mode\", \"transport\"],\n\t\t\t),\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-interface\",\n\t\t\t\t\"Interface\",\n\t\t\t\t\"Appearance and editor: theme, thinking visibility, cursor, border, padding, autocomplete, terminal.\",\n\t\t\t\t[\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"hide-thinking\",\n\t\t\t\t\t\"show-hardware-cursor\",\n\t\t\t\t\t\"editor-border\",\n\t\t\t\t\t\"editor-padding\",\n\t\t\t\t\t\"autocomplete-max-visible\",\n\t\t\t\t\t\"clear-on-shrink\",\n\t\t\t\t\t\"terminal-progress\",\n\t\t\t\t],\n\t\t\t),\n\t\t\tcategoryRow(\"cat-images\", \"Images\", \"Inline image rendering and resizing.\", [\n\t\t\t\t\"show-images\",\n\t\t\t\t\"image-width-cells\",\n\t\t\t\t\"auto-resize-images\",\n\t\t\t\t\"block-images\",\n\t\t\t]),\n\t\t\t// Top level rather than folded into Advanced: these are the thresholds\n\t\t\t// that decide whether /learn finds anything, and burying them is what\n\t\t\t// made them undiscoverable in the first place.\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-learn\",\n\t\t\t\t\"Learning\",\n\t\t\t\t\"Thresholds /learn mines sessions with: how far back to look, and how often something must repeat.\",\n\t\t\t\tLEARN_SETTINGS.map((setting) => setting.key),\n\t\t\t),\n\t\t\tcategoryRow(\"cat-advanced\", \"Advanced\", \"Startup, telemetry, skills, warnings, voice, and web tools.\", [\n\t\t\t\t\"quiet-startup\",\n\t\t\t\t\"collapse-changelog\",\n\t\t\t\t\"install-telemetry\",\n\t\t\t\t\"skill-commands\",\n\t\t\t\t\"warnings\",\n\t\t\t\t\"voice-silence-ms\",\n\t\t\t\t\"webtools-timeout-secs\",\n\t\t\t]),\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\ttopItems,\n\t\t\tMath.min(topItems.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tapplyChange,\n\t\t\tcallbacks.onCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"settings-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport type { Transport } from \"@kolisachint/hoocode-ai\";\nimport {\n\tContainer,\n\tgetCapabilities,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@kolisachint/hoocode-tui\";\nimport type { LearnSettingKey, WarningSettings } from \"../../../core/settings-manager.js\";\nimport { getSelectListTheme, getSettingsListTheme, getThemeDescription, theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyDisplayText } from \"./keybinding-hints.js\";\n\nconst SETTINGS_SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\ninterface ToolToggleInfo {\n\t/** Tool name (e.g. \"read\", \"bash\"). */\n\tname: string;\n\t/** Whether the tool is currently enabled (not in the persisted disabled set). */\n\tenabled: boolean;\n}\n\ninterface FlagInfo {\n\t/** Flag name (without the leading --). */\n\tname: string;\n\tdescription?: string;\n\ttype: \"boolean\" | \"string\";\n\t/** Current effective value. */\n\tvalue: boolean | string;\n}\n\ninterface ToolGroupInfo {\n\t/** Group identifier (e.g. \"web\", \"embsearch\"). */\n\tid: string;\n\tlabel: string;\n\tdescription: string;\n\t/** Whether the group is currently enabled (its tools are available). */\n\tenabled: boolean;\n}\n\nexport interface SettingsConfig {\n\tautoCompact: boolean;\n\ttools: ToolToggleInfo[];\n\ttoolGroups: ToolGroupInfo[];\n\tflags: FlagInfo[];\n\ttoolOutputDisplay: \"collapsed\" | \"peek\" | \"standard\";\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n\tshowImages: boolean;\n\timageWidthCells: number;\n\tautoResizeImages: boolean;\n\tblockImages: boolean;\n\tenableSkillCommands: boolean;\n\tpluginInstallScope: \"user\" | \"project\";\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\ttransport: Transport;\n\tthinkingLevel: ThinkingLevel;\n\tavailableThinkingLevels: ThinkingLevel[];\n\tcurrentTheme: string;\n\tavailableThemes: string[];\n\thideThinkingBlock: boolean;\n\tcollapseChangelog: boolean;\n\tenableInstallTelemetry: boolean;\n\tdoubleEscapeAction: \"fork\" | \"tree\" | \"none\";\n\ttreeFilterMode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\";\n\tshowHardwareCursor: boolean;\n\teditorBorder: \"rule\" | \"box\";\n\teditorPaddingX: number;\n\tautocompleteMaxVisible: number;\n\tquietStartup: boolean;\n\tclearOnShrink: boolean;\n\tshowTerminalProgress: boolean;\n\twarnings: WarningSettings;\n\tvoiceSilenceMs: number;\n\twebtoolsTimeoutSecs: number;\n\tlearn: Record<LearnSettingKey, number>;\n}\n\nexport interface SettingsCallbacks {\n\tonAutoCompactChange: (enabled: boolean) => void;\n\tonToolEnabledChange: (name: string, enabled: boolean) => void;\n\tonToolGroupChange: (id: string, enabled: boolean) => void;\n\tonToolOutputDisplayChange: (level: \"collapsed\" | \"peek\" | \"standard\") => void;\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n\tonFlagChange: (name: string, value: boolean | string) => void;\n\tonShowImagesChange: (enabled: boolean) => void;\n\tonImageWidthCellsChange: (width: number) => void;\n\tonAutoResizeImagesChange: (enabled: boolean) => void;\n\tonBlockImagesChange: (blocked: boolean) => void;\n\tonEnableSkillCommandsChange: (enabled: boolean) => void;\n\tonPluginInstallScopeChange: (scope: \"user\" | \"project\") => void;\n\tonSteeringModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonFollowUpModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonTransportChange: (transport: Transport) => void;\n\tonThinkingLevelChange: (level: ThinkingLevel) => void;\n\tonThemeChange: (theme: string) => void;\n\tonThemePreview?: (theme: string) => void;\n\tonHideThinkingBlockChange: (hidden: boolean) => void;\n\tonCollapseChangelogChange: (collapsed: boolean) => void;\n\tonEnableInstallTelemetryChange: (enabled: boolean) => void;\n\tonDoubleEscapeActionChange: (action: \"fork\" | \"tree\" | \"none\") => void;\n\tonTreeFilterModeChange: (mode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\") => void;\n\tonShowHardwareCursorChange: (enabled: boolean) => void;\n\tonEditorBorderChange: (border: \"rule\" | \"box\") => void;\n\tonEditorPaddingXChange: (padding: number) => void;\n\tonAutocompleteMaxVisibleChange: (maxVisible: number) => void;\n\tonQuietStartupChange: (enabled: boolean) => void;\n\tonClearOnShrinkChange: (enabled: boolean) => void;\n\tonShowTerminalProgressChange: (enabled: boolean) => void;\n\tonWarningsChange: (warnings: WarningSettings) => void;\n\tonVoiceSilenceMsChange: (ms: number) => void;\n\tonWebtoolsTimeoutSecsChange: (secs: number) => void;\n\tonLearnSettingChange: (key: LearnSettingKey, value: number) => void;\n\tonCancel: () => void;\n}\n\n/**\n * The `/learn` thresholds as pane rows: the presets to cycle through, and what\n * each one buys. Kept as a table because all five are the same shape — a\n * positive integer with a handful of sensible values — and the pane, the change\n * handler and the category row all read from it rather than repeating the list.\n */\nconst LEARN_SETTINGS: ReadonlyArray<{\n\tkey: LearnSettingKey;\n\tlabel: string;\n\tdescription: string;\n\tpresets: number[];\n}> = [\n\t{\n\t\tkey: \"learnMaxSessions\",\n\t\tlabel: \"Sessions scanned\",\n\t\tdescription: \"How many recent sessions in this directory /learn mines. Raise it on a repo you touch rarely.\",\n\t\tpresets: [10, 20, 30, 50, 100],\n\t},\n\t{\n\t\tkey: \"learnMaxAgeDays\",\n\t\tlabel: \"Session age limit\",\n\t\tdescription: \"Ignore sessions older than this many days. A pattern that stopped is not a rule.\",\n\t\tpresets: [7, 14, 30, 60, 90, 180],\n\t},\n\t{\n\t\tkey: \"learnMinRepeats\",\n\t\tlabel: \"Directive repeats\",\n\t\tdescription:\n\t\t\t\"Times a directive must recur before it is proposed. The signal/noise dial: raise it for fewer, better-evidenced proposals.\",\n\t\tpresets: [2, 3, 4, 5],\n\t},\n\t{\n\t\tkey: \"learnMinRequestRepeats\",\n\t\tlabel: \"Workflow repeats\",\n\t\tdescription: \"Non-overlapping repeats a tool sequence needs before it is proposed as a skill.\",\n\t\tpresets: [2, 3, 4, 5, 6],\n\t},\n\t{\n\t\tkey: \"learnMaxProposals\",\n\t\tlabel: \"Max proposals\",\n\t\tdescription: \"Cap on each list in the digest. Every proposal costs the model context.\",\n\t\tpresets: [3, 5, 8, 12, 20],\n\t},\n];\n\nconst LEARN_KEYS: ReadonlySet<string> = new Set(LEARN_SETTINGS.map((setting) => setting.key));\n\n/**\n * Preset list for a numeric row, guaranteed to contain the value in force.\n *\n * Without this a value set by hand in settings.json — say 45 days — is absent\n * from the cycle, so the first keypress silently snaps it to the first preset.\n * These particular settings gate whether `/learn` finds anything at all, so a\n * stray keystroke narrowing the window is exactly the surprise to avoid.\n */\nfunction presetValues(presets: number[], current: number): string[] {\n\tconst all = presets.includes(current) ? presets : [...presets, current].sort((a, b) => a - b);\n\treturn all.map(String);\n}\n\n/**\n * A submenu component for selecting from a list of options.\n */\nclass WarningSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate state: WarningSettings;\n\n\tconstructor(warnings: WarningSettings, onChange: (warnings: WarningSettings) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tthis.state = { ...warnings };\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"anthropic-extra-usage\",\n\t\t\t\tlabel: \"Anthropic extra usage\",\n\t\t\t\tdescription: \"Warn when Anthropic subscription auth may use paid extra usage\",\n\t\t\t\tcurrentValue: (this.state.anthropicExtraUsage ?? true) ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"anthropic-extra-usage\":\n\t\t\t\t\t\tthis.state = { ...this.state, anthropicExtraUsage: newValue === \"true\" };\n\t\t\t\t\t\tonChange({ ...this.state });\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Submenu for tool availability. The first rows are group switches (web,\n * semantic search) that decide whether a group's tools\n * exist at all — this is the same master switch that governs, e.g., the\n * webfetch/websearch tools. Below them are per-tool on/off toggles for the\n * tools that are currently available.\n *\n * Group switches change tool availability and apply on the next session;\n * per-tool toggles apply live and persist. A minimum core (read/bash/edit/\n * write) is guarded so the agent can never be left with no way to act.\n */\nclass ToolsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate enabled: Map<string, boolean>;\n\tprivate static readonly CORE = new Set([\"read\", \"bash\", \"edit\", \"write\"]);\n\tprivate static readonly GROUP_PREFIX = \"group:\";\n\n\tconstructor(\n\t\ttools: ToolToggleInfo[],\n\t\tgroups: ToolGroupInfo[],\n\t\tonChange: (name: string, enabled: boolean) => void,\n\t\tonGroupChange: (id: string, enabled: boolean) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.enabled = new Map(tools.map((t) => [t.name, t.enabled]));\n\n\t\tconst groupItems: SettingItem[] = groups.map((group) => ({\n\t\t\tid: `${ToolsSubmenu.GROUP_PREFIX}${group.id}`,\n\t\t\tlabel: `[group] ${group.label}`,\n\t\t\tdescription: `${group.description} Governs whether these tools exist; applies on the next session.`,\n\t\t\tcurrentValue: group.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst toolItems: SettingItem[] = tools.map((tool) => ({\n\t\t\tid: tool.name,\n\t\t\tlabel: tool.name,\n\t\t\tdescription: ToolsSubmenu.CORE.has(tool.name)\n\t\t\t\t? \"Core tool. Disabling leaves the agent unable to perform this action in every session.\"\n\t\t\t\t: \"Disable to remove this tool from the agent this session and every future session.\",\n\t\t\tcurrentValue: tool.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst items = [...groupItems, ...toolItems];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tconst wantEnabled = newValue === \"on\";\n\t\t\t\tif (id.startsWith(ToolsSubmenu.GROUP_PREFIX)) {\n\t\t\t\t\tonGroupChange(id.slice(ToolsSubmenu.GROUP_PREFIX.length), wantEnabled);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Guard: never let the last core tool be turned off.\n\t\t\t\tif (!wantEnabled && ToolsSubmenu.CORE.has(id)) {\n\t\t\t\t\tconst remainingCore = [...ToolsSubmenu.CORE].filter((n) => n !== id && this.enabled.get(n));\n\t\t\t\t\tif (remainingCore.length === 0) {\n\t\t\t\t\t\tthis.settingsList.updateValue(id, \"on\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.enabled.set(id, wantEnabled);\n\t\t\t\tonChange(id, wantEnabled);\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Byte-cap presets shown as human labels; mapped back to raw byte counts. */\nconst TOOL_OUTPUT_BYTE_PRESETS: ReadonlyArray<[label: string, bytes: number]> = [\n\t[\"8 KB\", 8 * 1024],\n\t[\"16 KB\", 16 * 1024],\n\t[\"32 KB\", 32 * 1024],\n\t[\"64 KB\", 64 * 1024],\n\t[\"128 KB\", 128 * 1024],\n];\n\nfunction bytesToLabel(bytes: number): string {\n\tconst match = TOOL_OUTPUT_BYTE_PRESETS.find(([, b]) => b === bytes);\n\treturn match ? match[0] : `${Math.round(bytes / 1024)} KB`;\n}\n\ninterface ToolSettingsConfig {\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n}\n\ninterface ToolSettingsCallbacks {\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n}\n\n/**\n * Submenu for per-tool runtime settings. These feed the tool runtime the next\n * time it is built (next session / rebuild), so changes apply to future tool\n * calls rather than retroactively.\n */\nclass ToolSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: ToolSettingsConfig, callbacks: ToolSettingsCallbacks, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"output-max-bytes\",\n\t\t\t\tlabel: \"Output max bytes\",\n\t\t\t\tdescription: \"Byte cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: bytesToLabel(config.toolOutputMaxBytes),\n\t\t\t\tvalues: TOOL_OUTPUT_BYTE_PRESETS.map(([label]) => label),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"output-max-lines\",\n\t\t\t\tlabel: \"Output max lines\",\n\t\t\t\tdescription: \"Line cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: String(config.toolOutputMaxLines),\n\t\t\t\tvalues: [\"200\", \"400\", \"800\", \"1600\", \"3200\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"context-gc\",\n\t\t\t\tlabel: \"Context GC\",\n\t\t\t\tdescription: \"Stub superseded read results (files later edited/re-read) out of the outgoing context.\",\n\t\t\t\tcurrentValue: config.contextGc ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"output-max-bytes\": {\n\t\t\t\t\t\tconst preset = TOOL_OUTPUT_BYTE_PRESETS.find(([label]) => label === newValue);\n\t\t\t\t\t\tif (preset) callbacks.onToolOutputMaxBytesChange(preset[1]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"output-max-lines\":\n\t\t\t\t\t\tcallbacks.onToolOutputMaxLinesChange(parseInt(newValue, 10));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"context-gc\":\n\t\t\t\t\t\tcallbacks.onContextGcChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Single-line text editor for a string flag value. */\nclass FlagStringEditSubmenu extends Container {\n\tprivate input: Input;\n\n\tconstructor(flagName: string, currentValue: string, done: (value?: string) => void) {\n\t\tsuper();\n\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", `Flag: --${flagName}`)), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"muted\", \"Enter a value · Enter to save · Esc to cancel\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.input = new Input();\n\t\tthis.input.setValue(currentValue);\n\t\tthis.input.onSubmit = (value: string) => done(value);\n\t\tthis.input.onEscape = () => done();\n\t\tthis.addChild(this.input);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.input.handleInput(data);\n\t}\n}\n\n/**\n * Submenu listing extension-registered flags. Boolean flags toggle on/off;\n * string flags open a text editor. Changes persist to settings.json and are\n * applied live best-effort — extensions that read a flag only at load time\n * pick up the new value on the next launch.\n */\nclass FlagsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(flags: FlagInfo[], onChange: (name: string, value: boolean | string) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = flags.map((flag) => {\n\t\t\tconst baseDescription = flag.description ?? \"Extension-registered flag.\";\n\t\t\tconst description = `${baseDescription} Persists across sessions; some flags need a restart to fully apply.`;\n\t\t\tif (flag.type === \"boolean\") {\n\t\t\t\treturn {\n\t\t\t\t\tid: flag.name,\n\t\t\t\t\tlabel: flag.name,\n\t\t\t\t\tdescription,\n\t\t\t\t\tcurrentValue: flag.value ? \"on\" : \"off\",\n\t\t\t\t\tvalues: [\"on\", \"off\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: flag.name,\n\t\t\t\tlabel: flag.name,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: String(flag.value ?? \"\"),\n\t\t\t\tsubmenu: (currentValue, done) => new FlagStringEditSubmenu(flag.name, currentValue, done),\n\t\t\t};\n\t\t});\n\n\t\tconst typeByName = new Map(flags.map((f) => [f.name, f.type]));\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tif (typeByName.get(id) === \"boolean\") {\n\t\t\t\t\tonChange(id, newValue === \"on\");\n\t\t\t\t} else {\n\t\t\t\t\tonChange(id, newValue);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Generic submenu holding a subset of leaf settings under a category label.\n * Shares the parent's change handler so cycle rows behave exactly as they did\n * when flat; nested submenu rows (theme, thinking, warnings) keep their own\n * factories.\n */\nclass CategorySubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(items: SettingItem[], onChange: (id: string, newValue: string) => void, onCancel: () => void) {\n\t\tsuper();\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tonChange,\n\t\t\tonCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\nclass SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t) {\n\t\tsuper();\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = new SelectList(\n\t\t\toptions,\n\t\t\tMath.min(options.length, 10),\n\t\t\tgetSelectListTheme(),\n\t\t\tSETTINGS_SUBMENU_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Pre-select current value\n\t\tconst currentIndex = options.findIndex((o) => o.value === currentValue);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value);\n\t\t};\n\n\t\tthis.selectList.onCancel = onCancel;\n\n\t\tif (onSelectionChange) {\n\t\t\tthis.selectList.onSelectionChange = (item) => {\n\t\t\t\tonSelectionChange(item.value);\n\t\t\t};\n\t\t}\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \" Enter to select · Esc to go back\"), 0, 0));\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.selectList.handleInput(data);\n\t}\n}\n\n/**\n * Main settings selector component.\n */\nexport class SettingsSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: SettingsConfig, callbacks: SettingsCallbacks) {\n\t\tsuper();\n\n\t\tconst supportsImages = getCapabilities().images;\n\t\tconst followUpKey = keyDisplayText(\"app.message.followUp\");\n\t\tlet currentWarnings = { ...config.warnings };\n\n\t\tconst toolsOn = config.tools.filter((t) => t.enabled).length;\n\t\tconst toolsOff = config.tools.length - toolsOn;\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"autocompact\",\n\t\t\t\tlabel: \"Auto-compact\",\n\t\t\t\tdescription: \"Automatically compact context when it gets too large\",\n\t\t\t\tcurrentValue: config.autoCompact ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"steering-mode\",\n\t\t\t\tlabel: \"Steering mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.steeringMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"follow-up-mode\",\n\t\t\t\tlabel: \"Follow-up mode\",\n\t\t\t\tdescription: `${followUpKey} queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.`,\n\t\t\t\tcurrentValue: config.followUpMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"transport\",\n\t\t\t\tlabel: \"Transport\",\n\t\t\t\tdescription: \"Preferred transport for providers that support multiple transports\",\n\t\t\t\tcurrentValue: config.transport,\n\t\t\t\tvalues: [\"sse\", \"websocket\", \"websocket-cached\", \"auto\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"hide-thinking\",\n\t\t\t\tlabel: \"Hide thinking\",\n\t\t\t\tdescription: \"Hide thinking blocks in assistant responses\",\n\t\t\t\tcurrentValue: config.hideThinkingBlock ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"collapse-changelog\",\n\t\t\t\tlabel: \"Collapse changelog\",\n\t\t\t\tdescription: \"Show condensed changelog after updates\",\n\t\t\t\tcurrentValue: config.collapseChangelog ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"quiet-startup\",\n\t\t\t\tlabel: \"Quiet startup\",\n\t\t\t\tdescription: \"Disable verbose printing at startup\",\n\t\t\t\tcurrentValue: config.quietStartup ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"install-telemetry\",\n\t\t\t\tlabel: \"Install telemetry\",\n\t\t\t\tdescription: \"Send an anonymous version/update ping after changelog-detected updates\",\n\t\t\t\tcurrentValue: config.enableInstallTelemetry ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"double-escape-action\",\n\t\t\t\tlabel: \"Double-escape action\",\n\t\t\t\tdescription: \"Action when pressing Escape twice with empty editor\",\n\t\t\t\tcurrentValue: config.doubleEscapeAction,\n\t\t\t\tvalues: [\"tree\", \"fork\", \"none\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tree-filter-mode\",\n\t\t\t\tlabel: \"Tree filter mode\",\n\t\t\t\tdescription: \"Default filter when opening /tree\",\n\t\t\t\tcurrentValue: config.treeFilterMode,\n\t\t\t\tvalues: [\"default\", \"no-tools\", \"user-only\", \"labeled-only\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"warnings\",\n\t\t\t\tlabel: \"Warnings\",\n\t\t\t\tdescription: \"Enable or disable individual warnings\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew WarningSettingsSubmenu(\n\t\t\t\t\t\tcurrentWarnings,\n\t\t\t\t\t\t(warnings) => {\n\t\t\t\t\t\t\tcurrentWarnings = warnings;\n\t\t\t\t\t\t\tcallbacks.onWarningsChange(warnings);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"thinking\",\n\t\t\t\tlabel: \"Thinking level\",\n\t\t\t\tdescription: \"Reasoning depth for thinking-capable models\",\n\t\t\t\tcurrentValue: config.thinkingLevel,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Thinking Level\",\n\t\t\t\t\t\t\"Select reasoning depth for thinking-capable models\",\n\t\t\t\t\t\tconfig.availableThinkingLevels.map((level) => ({\n\t\t\t\t\t\t\tvalue: level,\n\t\t\t\t\t\t\tlabel: level,\n\t\t\t\t\t\t\tdescription: THINKING_DESCRIPTIONS[level],\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThinkingLevelChange(value as ThinkingLevel);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"theme\",\n\t\t\t\tlabel: \"Theme\",\n\t\t\t\tdescription: \"Color theme for the interface\",\n\t\t\t\tcurrentValue: config.currentTheme,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Theme\",\n\t\t\t\t\t\t\"Select color theme\",\n\t\t\t\t\t\tconfig.availableThemes.map((t) => ({\n\t\t\t\t\t\t\tvalue: t,\n\t\t\t\t\t\t\tlabel: t,\n\t\t\t\t\t\t\tdescription: getThemeDescription(t),\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThemeChange(value);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t// Restore original theme on cancel\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(currentValue);\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\t// Preview theme on selection change\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\n\t\t// Only show image toggle if terminal supports it\n\t\tif (supportsImages) {\n\t\t\t// Insert after autocompact\n\t\t\titems.splice(1, 0, {\n\t\t\t\tid: \"show-images\",\n\t\t\t\tlabel: \"Show images\",\n\t\t\t\tdescription: \"Render images inline in terminal\",\n\t\t\t\tcurrentValue: config.showImages ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t});\n\t\t\titems.splice(2, 0, {\n\t\t\t\tid: \"image-width-cells\",\n\t\t\t\tlabel: \"Image width\",\n\t\t\t\tdescription: \"Preferred inline image width in terminal cells\",\n\t\t\t\tcurrentValue: String(config.imageWidthCells),\n\t\t\t\tvalues: [\"60\", \"80\", \"120\"],\n\t\t\t});\n\t\t}\n\n\t\t// Image auto-resize toggle (always available, affects both attached and read images)\n\t\titems.splice(supportsImages ? 3 : 1, 0, {\n\t\t\tid: \"auto-resize-images\",\n\t\t\tlabel: \"Auto-resize images\",\n\t\t\tdescription: \"Resize large images to 2000x2000 max for better model compatibility\",\n\t\t\tcurrentValue: config.autoResizeImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Block images toggle (always available, insert after auto-resize-images)\n\t\tconst autoResizeIndex = items.findIndex((item) => item.id === \"auto-resize-images\");\n\t\titems.splice(autoResizeIndex + 1, 0, {\n\t\t\tid: \"block-images\",\n\t\t\tlabel: \"Block images\",\n\t\t\tdescription: \"Prevent images from being sent to LLM providers\",\n\t\t\tcurrentValue: config.blockImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Skill commands toggle (insert after block-images)\n\t\tconst blockImagesIndex = items.findIndex((item) => item.id === \"block-images\");\n\t\titems.splice(blockImagesIndex + 1, 0, {\n\t\t\tid: \"skill-commands\",\n\t\t\tlabel: \"Skill commands\",\n\t\t\tdescription: \"Register skills as /skill:name commands\",\n\t\t\tcurrentValue: config.enableSkillCommands ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Plugin install scope (insert after skill-commands). Governs the\n\t\t// autonomous InstallPlugin only — /plugin install asks per install.\n\t\tconst skillCommandsIdx = items.findIndex((item) => item.id === \"skill-commands\");\n\t\titems.splice(skillCommandsIdx + 1, 0, {\n\t\t\tid: \"plugin-install-scope\",\n\t\t\tlabel: \"Plugin install scope\",\n\t\t\tdescription: \"Where autonomous plugin installs go: user (~/.agents) or project (this repo, shared)\",\n\t\t\tcurrentValue: config.pluginInstallScope,\n\t\t\tvalues: [\"user\", \"project\"],\n\t\t});\n\n\t\t// Hardware cursor toggle (insert after plugin-install-scope)\n\t\tconst skillCommandsIndex = items.findIndex((item) => item.id === \"plugin-install-scope\");\n\t\titems.splice(skillCommandsIndex + 1, 0, {\n\t\t\tid: \"show-hardware-cursor\",\n\t\t\tlabel: \"Show hardware cursor\",\n\t\t\tdescription: \"Show the terminal cursor while still positioning it for IME support\",\n\t\t\tcurrentValue: config.showHardwareCursor ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Editor border toggle (insert after show-hardware-cursor)\n\t\tconst hardwareCursorIndex = items.findIndex((item) => item.id === \"show-hardware-cursor\");\n\t\titems.splice(hardwareCursorIndex + 1, 0, {\n\t\t\tid: \"editor-border\",\n\t\t\tlabel: \"Editor border\",\n\t\t\tdescription: \"Box draws side borders, rule draws horizontal lines only\",\n\t\t\tcurrentValue: config.editorBorder,\n\t\t\tvalues: [\"box\", \"rule\"],\n\t\t});\n\n\t\t// Editor padding toggle (insert after editor-border)\n\t\tconst editorBorderIndex = items.findIndex((item) => item.id === \"editor-border\");\n\t\titems.splice(editorBorderIndex + 1, 0, {\n\t\t\tid: \"editor-padding\",\n\t\t\tlabel: \"Editor padding\",\n\t\t\tdescription: \"Horizontal padding for input editor (0-3)\",\n\t\t\tcurrentValue: String(config.editorPaddingX),\n\t\t\tvalues: [\"0\", \"1\", \"2\", \"3\"],\n\t\t});\n\n\t\t// Autocomplete max visible toggle (insert after editor-padding)\n\t\tconst editorPaddingIndex = items.findIndex((item) => item.id === \"editor-padding\");\n\t\titems.splice(editorPaddingIndex + 1, 0, {\n\t\t\tid: \"autocomplete-max-visible\",\n\t\t\tlabel: \"Autocomplete max items\",\n\t\t\tdescription: \"Max visible items in autocomplete dropdown (3-20)\",\n\t\t\tcurrentValue: String(config.autocompleteMaxVisible),\n\t\t\tvalues: [\"3\", \"5\", \"7\", \"10\", \"15\", \"20\"],\n\t\t});\n\n\t\t// Clear on shrink toggle (insert after autocomplete-max-visible)\n\t\tconst autocompleteIndex = items.findIndex((item) => item.id === \"autocomplete-max-visible\");\n\t\titems.splice(autocompleteIndex + 1, 0, {\n\t\t\tid: \"clear-on-shrink\",\n\t\t\tlabel: \"Clear on shrink\",\n\t\t\tdescription: \"Clear empty rows when content shrinks (may cause flicker)\",\n\t\t\tcurrentValue: config.clearOnShrink ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Terminal progress toggle (insert after clear-on-shrink)\n\t\tconst clearOnShrinkIndex = items.findIndex((item) => item.id === \"clear-on-shrink\");\n\t\titems.splice(clearOnShrinkIndex + 1, 0, {\n\t\t\tid: \"terminal-progress\",\n\t\t\tlabel: \"Terminal progress\",\n\t\t\tdescription: \"Show OSC 9;4 progress indicators in the terminal tab bar\",\n\t\t\tcurrentValue: config.showTerminalProgress ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Voice silence window (insert after terminal-progress)\n\t\tconst terminalProgressIndex = items.findIndex((item) => item.id === \"terminal-progress\");\n\t\titems.splice(terminalProgressIndex + 1, 0, {\n\t\t\tid: \"voice-silence-ms\",\n\t\t\tlabel: \"Voice silence window\",\n\t\t\tdescription: \"Trailing-silence (ms) before voice capture auto-stops (300-10000). Env: VOICETOOLS_SILENCE_MS.\",\n\t\t\tcurrentValue: String(config.voiceSilenceMs),\n\t\t\tvalues: [\"300\", \"500\", \"800\", \"1200\", \"2000\", \"3000\", \"5000\", \"8000\", \"10000\"],\n\t\t});\n\n\t\t// Webtools request timeout (insert after voice-silence-ms)\n\t\tconst voiceSilenceIndex = items.findIndex((item) => item.id === \"voice-silence-ms\");\n\t\titems.splice(voiceSilenceIndex + 1, 0, {\n\t\t\tid: \"webtools-timeout-secs\",\n\t\t\tlabel: \"Web tools timeout\",\n\t\t\tdescription: \"Per-request timeout (secs) for webfetch/websearch (1-120). Env: HOOCODE_WEBTOOLS_TIMEOUT.\",\n\t\t\tcurrentValue: String(config.webtoolsTimeoutSecs),\n\t\t\tvalues: [\"5\", \"10\", \"15\", \"30\", \"60\", \"120\"],\n\t\t});\n\n\t\t// The /learn thresholds, appended as leaf rows and gathered into their own\n\t\t// category below. They are written to the user settings.json, which /learn\n\t\t// re-reads on every invocation, so a change here applies to the next run.\n\t\tconst webtoolsIndex = items.findIndex((item) => item.id === \"webtools-timeout-secs\");\n\t\titems.splice(\n\t\t\twebtoolsIndex + 1,\n\t\t\t0,\n\t\t\t...LEARN_SETTINGS.map(({ key, label, description, presets }) => ({\n\t\t\t\tid: key,\n\t\t\t\tlabel,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: String(config.learn[key]),\n\t\t\t\tvalues: presetValues(presets, config.learn[key]),\n\t\t\t})),\n\t\t);\n\n\t\t// Keep the tool/flag controls together as one block near the top, inserted\n\t\t// after the image/terminal splices above so they aren't leapfrogged.\n\t\tconst toolFlagGroup: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"tools\",\n\t\t\t\tlabel: \"Tools\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enable/disable tools and tool groups (web, semantic search). Changes persist across sessions.\",\n\t\t\t\tcurrentValue: toolsOff > 0 ? `${toolsOn} on · ${toolsOff} off` : `${toolsOn} on`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolsSubmenu(\n\t\t\t\t\t\tconfig.tools,\n\t\t\t\t\t\tconfig.toolGroups,\n\t\t\t\t\t\t(name, enabled) => callbacks.onToolEnabledChange(name, enabled),\n\t\t\t\t\t\t(id, enabled) => callbacks.onToolGroupChange(id, enabled),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-output-display\",\n\t\t\t\tlabel: \"Tool output display\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"How tool results render. 'standard': shown (expandable). 'collapsed': hidden. 'peek': hidden with a ▸ reveal caret (press the expand key to reveal).\",\n\t\t\t\tcurrentValue: config.toolOutputDisplay,\n\t\t\t\tvalues: [\"standard\", \"collapsed\", \"peek\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-settings\",\n\t\t\t\tlabel: \"Tool settings\",\n\t\t\t\tdescription: \"Per-tool runtime settings: output truncation caps and context garbage collection.\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolSettingsSubmenu(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttoolOutputMaxBytes: config.toolOutputMaxBytes,\n\t\t\t\t\t\t\ttoolOutputMaxLines: config.toolOutputMaxLines,\n\t\t\t\t\t\t\tcontextGc: config.contextGc,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tonToolOutputMaxBytesChange: callbacks.onToolOutputMaxBytesChange,\n\t\t\t\t\t\t\tonToolOutputMaxLinesChange: callbacks.onToolOutputMaxLinesChange,\n\t\t\t\t\t\t\tonContextGcChange: callbacks.onContextGcChange,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\t\tif (config.flags.length > 0) {\n\t\t\ttoolFlagGroup.push({\n\t\t\t\tid: \"flags\",\n\t\t\t\tlabel: \"Flags\",\n\t\t\t\tdescription: \"Set flags registered by extensions. Changes persist across sessions.\",\n\t\t\t\tcurrentValue: `${config.flags.length} flag${config.flags.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew FlagsSubmenu(\n\t\t\t\t\t\tconfig.flags,\n\t\t\t\t\t\t(name, value) => callbacks.onFlagChange(name, value),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\t// Add borders\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Shared change handler for every leaf (cycle) setting; used by the\n\t\t// top-level list and each category submenu.\n\t\tconst applyChange = (id: string, newValue: string): void => {\n\t\t\tswitch (id) {\n\t\t\t\tcase \"autocompact\":\n\t\t\t\t\tcallbacks.onAutoCompactChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tool-output-display\":\n\t\t\t\t\tcallbacks.onToolOutputDisplayChange(newValue as \"collapsed\" | \"peek\" | \"standard\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-images\":\n\t\t\t\t\tcallbacks.onShowImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"image-width-cells\":\n\t\t\t\t\tcallbacks.onImageWidthCellsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"auto-resize-images\":\n\t\t\t\t\tcallbacks.onAutoResizeImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"block-images\":\n\t\t\t\t\tcallbacks.onBlockImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skill-commands\":\n\t\t\t\t\tcallbacks.onEnableSkillCommandsChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"plugin-install-scope\":\n\t\t\t\t\tcallbacks.onPluginInstallScopeChange(newValue as \"user\" | \"project\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"steering-mode\":\n\t\t\t\t\tcallbacks.onSteeringModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"follow-up-mode\":\n\t\t\t\t\tcallbacks.onFollowUpModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"transport\":\n\t\t\t\t\tcallbacks.onTransportChange(newValue as Transport);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"hide-thinking\":\n\t\t\t\t\tcallbacks.onHideThinkingBlockChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"collapse-changelog\":\n\t\t\t\t\tcallbacks.onCollapseChangelogChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"quiet-startup\":\n\t\t\t\t\tcallbacks.onQuietStartupChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"install-telemetry\":\n\t\t\t\t\tcallbacks.onEnableInstallTelemetryChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"double-escape-action\":\n\t\t\t\t\tcallbacks.onDoubleEscapeActionChange(newValue as \"fork\" | \"tree\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tree-filter-mode\":\n\t\t\t\t\tcallbacks.onTreeFilterModeChange(\n\t\t\t\t\t\tnewValue as \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\",\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-hardware-cursor\":\n\t\t\t\t\tcallbacks.onShowHardwareCursorChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-border\":\n\t\t\t\t\tcallbacks.onEditorBorderChange(newValue as \"rule\" | \"box\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-padding\":\n\t\t\t\t\tcallbacks.onEditorPaddingXChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"autocomplete-max-visible\":\n\t\t\t\t\tcallbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"clear-on-shrink\":\n\t\t\t\t\tcallbacks.onClearOnShrinkChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"terminal-progress\":\n\t\t\t\t\tcallbacks.onShowTerminalProgressChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"voice-silence-ms\":\n\t\t\t\t\tcallbacks.onVoiceSilenceMsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"webtools-timeout-secs\":\n\t\t\t\t\tcallbacks.onWebtoolsTimeoutSecsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t// The /learn rows are keyed by their settings.json name, so they need\n\t\t\t\t\t// no case of their own — the id is the key to write.\n\t\t\t\t\tif (LEARN_KEYS.has(id)) callbacks.onLearnSettingChange(id as LearnSettingKey, parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\n\t\t// Partition the flat leaf settings into named category submenus so the\n\t\t// top level stays short. `items` holds autocompact + every leaf setting.\n\t\tconst byId = new Map(items.map((item) => [item.id, item] as const));\n\t\tconst pick = (ids: string[]): SettingItem[] =>\n\t\t\tids.map((id) => byId.get(id)).filter((item): item is SettingItem => item !== undefined);\n\t\tconst categoryRow = (id: string, label: string, description: string, ids: string[]): SettingItem => {\n\t\t\tconst members = pick(ids);\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tlabel,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: `${members.length} setting${members.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) => new CategorySubmenu(members, applyChange, () => done()),\n\t\t\t};\n\t\t};\n\n\t\tconst topItems: SettingItem[] = [\n\t\t\t...(byId.has(\"autocompact\") ? [byId.get(\"autocompact\")!] : []),\n\t\t\t...toolFlagGroup,\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-behavior\",\n\t\t\t\t\"Behavior\",\n\t\t\t\t\"Agent and session behavior: steering, follow-up, thinking, escape, tree filter, transport.\",\n\t\t\t\t[\"steering-mode\", \"follow-up-mode\", \"thinking\", \"double-escape-action\", \"tree-filter-mode\", \"transport\"],\n\t\t\t),\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-interface\",\n\t\t\t\t\"Interface\",\n\t\t\t\t\"Appearance and editor: theme, thinking visibility, cursor, border, padding, autocomplete, terminal.\",\n\t\t\t\t[\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"hide-thinking\",\n\t\t\t\t\t\"show-hardware-cursor\",\n\t\t\t\t\t\"editor-border\",\n\t\t\t\t\t\"editor-padding\",\n\t\t\t\t\t\"autocomplete-max-visible\",\n\t\t\t\t\t\"clear-on-shrink\",\n\t\t\t\t\t\"terminal-progress\",\n\t\t\t\t],\n\t\t\t),\n\t\t\tcategoryRow(\"cat-images\", \"Images\", \"Inline image rendering and resizing.\", [\n\t\t\t\t\"show-images\",\n\t\t\t\t\"image-width-cells\",\n\t\t\t\t\"auto-resize-images\",\n\t\t\t\t\"block-images\",\n\t\t\t]),\n\t\t\t// Top level rather than folded into Advanced: these are the thresholds\n\t\t\t// that decide whether /learn finds anything, and burying them is what\n\t\t\t// made them undiscoverable in the first place.\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-learn\",\n\t\t\t\t\"Learning\",\n\t\t\t\t\"Thresholds /learn mines sessions with: how far back to look, and how often something must repeat.\",\n\t\t\t\tLEARN_SETTINGS.map((setting) => setting.key),\n\t\t\t),\n\t\t\tcategoryRow(\"cat-advanced\", \"Advanced\", \"Startup, telemetry, skills, warnings, voice, and web tools.\", [\n\t\t\t\t\"quiet-startup\",\n\t\t\t\t\"collapse-changelog\",\n\t\t\t\t\"install-telemetry\",\n\t\t\t\t\"skill-commands\",\n\t\t\t\t\"warnings\",\n\t\t\t\t\"voice-silence-ms\",\n\t\t\t\t\"webtools-timeout-secs\",\n\t\t\t]),\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\ttopItems,\n\t\t\tMath.min(topItems.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tapplyChange,\n\t\t\tcallbacks.onCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}
|
|
@@ -40,7 +40,7 @@ const LEARN_SETTINGS = [
|
|
|
40
40
|
presets: [2, 3, 4, 5],
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
|
-
key: "
|
|
43
|
+
key: "learnMinRequestRepeats",
|
|
44
44
|
label: "Workflow repeats",
|
|
45
45
|
description: "Non-overlapping repeats a tool sequence needs before it is proposed as a skill.",
|
|
46
46
|
presets: [2, 3, 4, 5, 6],
|