@kolisachint/hoocode-agent 0.5.17 → 0.5.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/core/learn/cache.d.ts +58 -0
  3. package/dist/core/learn/cache.d.ts.map +1 -0
  4. package/dist/core/learn/cache.js +120 -0
  5. package/dist/core/learn/cache.js.map +1 -0
  6. package/dist/core/learn/coverage.d.ts +58 -0
  7. package/dist/core/learn/coverage.d.ts.map +1 -0
  8. package/dist/core/learn/coverage.js +142 -0
  9. package/dist/core/learn/coverage.js.map +1 -0
  10. package/dist/core/learn/digest.d.ts +1 -0
  11. package/dist/core/learn/digest.d.ts.map +1 -1
  12. package/dist/core/learn/digest.js +31 -4
  13. package/dist/core/learn/digest.js.map +1 -1
  14. package/dist/core/learn/extract.d.ts +71 -103
  15. package/dist/core/learn/extract.d.ts.map +1 -1
  16. package/dist/core/learn/extract.js +162 -437
  17. package/dist/core/learn/extract.js.map +1 -1
  18. package/dist/core/learn/mine.d.ts +123 -0
  19. package/dist/core/learn/mine.d.ts.map +1 -0
  20. package/dist/core/learn/mine.js +285 -0
  21. package/dist/core/learn/mine.js.map +1 -0
  22. package/dist/core/learn/reduce.d.ts +78 -0
  23. package/dist/core/learn/reduce.d.ts.map +1 -0
  24. package/dist/core/learn/reduce.js +123 -0
  25. package/dist/core/learn/reduce.js.map +1 -0
  26. package/dist/core/learn/state.d.ts +8 -0
  27. package/dist/core/learn/state.d.ts.map +1 -1
  28. package/dist/core/learn/state.js +18 -3
  29. package/dist/core/learn/state.js.map +1 -1
  30. package/dist/core/settings-manager.d.ts +2 -0
  31. package/dist/core/settings-manager.d.ts.map +1 -1
  32. package/dist/core/settings-manager.js +4 -0
  33. package/dist/core/settings-manager.js.map +1 -1
  34. package/dist/core/startup-progress.d.ts +12 -7
  35. package/dist/core/startup-progress.d.ts.map +1 -1
  36. package/dist/core/startup-progress.js +12 -7
  37. package/dist/core/startup-progress.js.map +1 -1
  38. package/dist/extensions/core/learn.d.ts +8 -4
  39. package/dist/extensions/core/learn.d.ts.map +1 -1
  40. package/dist/extensions/core/learn.js +208 -27
  41. package/dist/extensions/core/learn.js.map +1 -1
  42. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  43. package/dist/modes/interactive/components/footer.js +7 -25
  44. package/dist/modes/interactive/components/footer.js.map +1 -1
  45. package/dist/modes/interactive/components/progress-bar.d.ts +50 -0
  46. package/dist/modes/interactive/components/progress-bar.d.ts.map +1 -0
  47. package/dist/modes/interactive/components/progress-bar.js +77 -0
  48. package/dist/modes/interactive/components/progress-bar.js.map +1 -0
  49. package/dist/modes/interactive/voice/voice-panel.d.ts +6 -1
  50. package/dist/modes/interactive/voice/voice-panel.d.ts.map +1 -1
  51. package/dist/modes/interactive/voice/voice-panel.js +18 -14
  52. package/dist/modes/interactive/voice/voice-panel.js.map +1 -1
  53. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  54. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  55. package/examples/extensions/sandbox/package.json +1 -1
  56. package/examples/extensions/with-deps/package.json +1 -1
  57. package/package.json +4 -4
  58. package/dist/core/learn/normalize.d.ts +0 -65
  59. package/dist/core/learn/normalize.d.ts.map +0 -1
  60. package/dist/core/learn/normalize.js +0 -245
  61. package/dist/core/learn/normalize.js.map +0 -1
@@ -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 deterministic extractor\n * over session transcripts on disk, renders the ranked result, and injects it\n * as a follow-up message; every judgement after that belongs to the model,\n * which can 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 * 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 { CONFIG_DIR_NAME, getHooCodeDir } from \"../../config.js\";\nimport { loadProjectContextFiles } from \"../../core/context-files.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { isEmptyDigest, renderLearnDigest } from \"../../core/learn/digest.js\";\nimport {\n\tbuildCoverageIndex,\n\textractLearnDigest,\n\ttype LearnDigest,\n\tmatchCoverage,\n\ttype SessionScanReport,\n\tscanSessions,\n} from \"../../core/learn/extract.js\";\nimport {\n\tgetLearnStatePath,\n\treadLearnState,\n\trecordSurfaced,\n\tsummarizeLearnState,\n\twriteLearnState,\n} from \"../../core/learn/state.js\";\nimport { getSessionDirPath } from \"../../core/session-manager.js\";\nimport { SettingsManager } from \"../../core/settings-manager.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/** 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 five 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 ranking 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 * `/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 is instant and answers a different question than a normal run: not\n * \"what should I write down\" but \"is this command earning its place\".\n */\nfunction reportStats(ctx: ExtensionCommandContext): void {\n\tconst agentDir = getHooCodeDir();\n\tconst window = SettingsManager.create(ctx.cwd, agentDir).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\tconst stats = summarizeLearnState(state, (normalized) => {\n\t\tconst match = matchCoverage(normalized, coverage);\n\t\treturn !!(match.rule || match.skill);\n\t});\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 extractor 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 window = SettingsManager.create(ctx.cwd, agentDir).getLearnSettings();\n\tconst lines = settingsLines(ctx, agentDir, window);\n\tlines.push(\"\");\n\tlines.push(...scanLines(sessionScanPreview(ctx, agentDir, window), window));\n\tlines.push(`State file ${displayPath(getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir)))}`);\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\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 window = SettingsManager.create(ctx.cwd, agentDir).getLearnSettings();\n\t\t\tconst statePath = getLearnStatePath(agentDir, stateKeyDir(ctx, agentDir));\n\n\t\t\tlet digest: LearnDigest;\n\t\t\ttry {\n\t\t\t\tdigest = extractLearnDigest({\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});\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}\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? `Scanned ${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: `Scanned ${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(`Mined ${digest.scannedSessions} session(s): ${counts.join(\", \")}${held}.`, \"info\");\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(renderLearnDigest(digest, { userScopePath: displayPath(USER_SCOPE_PATH) }), {\n\t\t\t\tdeliverAs: \"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 { 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 +1 @@
1
- {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AACzF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA0FxF;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAI/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAJnB,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsJ9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"dim\", track) };\n}\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/** Cells in a startup-progress bar; compact so several tools fit the footer. */\nconst STARTUP_BAR_CELLS = 12;\n\nfunction formatMb(bytes: number): string {\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * One footer line for a transient startup-progress entry (tool download or\n * index build), styled like the voice download bar: a `·` fill over a dim\n * track with percent and a `received / total` (or `done/total`) detail. An\n * indeterminate download (no Content-Length) drops the bar for a running byte\n * count; an error entry renders as a dim message. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\tif (entry.totalBytes === null || entry.totalBytes <= 0) {\n\t\t\treturn `${label} ${theme.fg(\"dim\", `${formatMb(entry.receivedBytes)}…`)}`;\n\t\t}\n\t\tconst detail = `${formatMb(entry.receivedBytes)} / ${formatMb(entry.totalBytes)}`;\n\t\treturn `${label} ${determinateBar(entry.receivedBytes / entry.totalBytes, detail)}`;\n\t}\n\tconst detail = `${entry.done}/${entry.total} ${entry.unit}`;\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${determinateBar(ratio, detail)}`;\n}\n\n/** `·`-fill bar + percent + trailing detail, matching the voice download bar. */\nfunction determinateBar(ratio: number, detail: string): string {\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst filled = Math.round(clamped * STARTUP_BAR_CELLS);\n\tconst bar = theme.fg(\"accent\", \"·\".repeat(filled)) + theme.fg(\"dim\", \"·\".repeat(STARTUP_BAR_CELLS - filled));\n\tconst pct = `${Math.round(clamped * 100)}%`;\n\treturn `${bar} ${theme.fg(\"muted\", pct)} ${theme.fg(\"dim\", `· ${detail}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\tconst l1RightPlain = nSub > 0 ? `◇${nSub} running` : \"\";\n\t\tconst l1RightStyled = nSub > 0 ? theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\") : \"\";\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AACzF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAqExF;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAI/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAJnB,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsJ9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { renderDownloadProgress, renderProgressBar } from \"./progress-bar.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"dim\", track) };\n}\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * One footer line for a transient progress entry (tool download, index build,\n * `/learn` reading transcripts). The bar comes from the shared progress-bar\n * component, so this line and the voice panel's cannot drift apart again. An\n * error entry renders as a dim message instead. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\treturn `${label} ${renderDownloadProgress(entry.receivedBytes, entry.totalBytes)}`;\n\t}\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${renderProgressBar(ratio, `${entry.done}/${entry.total} ${entry.unit}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\tconst l1RightPlain = nSub > 0 ? `◇${nSub} running` : \"\";\n\t\tconst l1RightStyled = nSub > 0 ? theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\") : \"\";\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
@@ -5,6 +5,7 @@ import { formatTokens } from "../../../core/format-tokens.js";
5
5
  import { startupProgress } from "../../../core/startup-progress.js";
6
6
  import { taskStore } from "../../../core/task-store.js";
7
7
  import { theme } from "../theme/theme.js";
8
+ import { renderDownloadProgress, renderProgressBar } from "./progress-bar.js";
8
9
  /**
9
10
  * Assemble one footer line: `left` flush left, `right` flush right when it fits
10
11
  * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`
@@ -45,17 +46,11 @@ function sanitizeStatusText(text) {
45
46
  .replace(/ +/g, " ")
46
47
  .trim();
47
48
  }
48
- /** Cells in a startup-progress bar; compact so several tools fit the footer. */
49
- const STARTUP_BAR_CELLS = 12;
50
- function formatMb(bytes) {
51
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
52
- }
53
49
  /**
54
- * One footer line for a transient startup-progress entry (tool download or
55
- * index build), styled like the voice download bar: a `·` fill over a dim
56
- * track with percent and a `received / total` (or `done/total`) detail. An
57
- * indeterminate download (no Content-Length) drops the bar for a running byte
58
- * count; an error entry renders as a dim message. Returns a styled string; the
50
+ * One footer line for a transient progress entry (tool download, index build,
51
+ * `/learn` reading transcripts). The bar comes from the shared progress-bar
52
+ * component, so this line and the voice panel's cannot drift apart again. An
53
+ * error entry renders as a dim message instead. Returns a styled string; the
59
54
  * caller width-clamps it.
60
55
  */
61
56
  function renderStartupLine(entry) {
@@ -64,23 +59,10 @@ function renderStartupLine(entry) {
64
59
  }
65
60
  const label = theme.fg("text", entry.label);
66
61
  if (entry.kind === "download") {
67
- if (entry.totalBytes === null || entry.totalBytes <= 0) {
68
- return `${label} ${theme.fg("dim", `${formatMb(entry.receivedBytes)}…`)}`;
69
- }
70
- const detail = `${formatMb(entry.receivedBytes)} / ${formatMb(entry.totalBytes)}`;
71
- return `${label} ${determinateBar(entry.receivedBytes / entry.totalBytes, detail)}`;
62
+ return `${label} ${renderDownloadProgress(entry.receivedBytes, entry.totalBytes)}`;
72
63
  }
73
- const detail = `${entry.done}/${entry.total} ${entry.unit}`;
74
64
  const ratio = entry.total > 0 ? entry.done / entry.total : 0;
75
- return `${label} ${determinateBar(ratio, detail)}`;
76
- }
77
- /** `·`-fill bar + percent + trailing detail, matching the voice download bar. */
78
- function determinateBar(ratio, detail) {
79
- const clamped = Math.max(0, Math.min(1, ratio));
80
- const filled = Math.round(clamped * STARTUP_BAR_CELLS);
81
- const bar = theme.fg("accent", "·".repeat(filled)) + theme.fg("dim", "·".repeat(STARTUP_BAR_CELLS - filled));
82
- const pct = `${Math.round(clamped * 100)}%`;
83
- return `${bar} ${theme.fg("muted", pct)} ${theme.fg("dim", `· ${detail}`)}`;
65
+ return `${label} ${renderProgressBar(ratio, `${entry.done}/${entry.total} ${entry.unit}`)}`;
84
66
  }
85
67
  /**
86
68
  * Footer component that shows pwd, token stats, and context usage.
@@ -1 +1 @@
1
- {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"dim\", track) };\n}\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/** Cells in a startup-progress bar; compact so several tools fit the footer. */\nconst STARTUP_BAR_CELLS = 12;\n\nfunction formatMb(bytes: number): string {\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * One footer line for a transient startup-progress entry (tool download or\n * index build), styled like the voice download bar: a `·` fill over a dim\n * track with percent and a `received / total` (or `done/total`) detail. An\n * indeterminate download (no Content-Length) drops the bar for a running byte\n * count; an error entry renders as a dim message. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\tif (entry.totalBytes === null || entry.totalBytes <= 0) {\n\t\t\treturn `${label} ${theme.fg(\"dim\", `${formatMb(entry.receivedBytes)}…`)}`;\n\t\t}\n\t\tconst detail = `${formatMb(entry.receivedBytes)} / ${formatMb(entry.totalBytes)}`;\n\t\treturn `${label} ${determinateBar(entry.receivedBytes / entry.totalBytes, detail)}`;\n\t}\n\tconst detail = `${entry.done}/${entry.total} ${entry.unit}`;\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${determinateBar(ratio, detail)}`;\n}\n\n/** `·`-fill bar + percent + trailing detail, matching the voice download bar. */\nfunction determinateBar(ratio: number, detail: string): string {\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst filled = Math.round(clamped * STARTUP_BAR_CELLS);\n\tconst bar = theme.fg(\"accent\", \"·\".repeat(filled)) + theme.fg(\"dim\", \"·\".repeat(STARTUP_BAR_CELLS - filled));\n\tconst pct = `${Math.round(clamped * 100)}%`;\n\treturn `${bar} ${theme.fg(\"muted\", pct)} ${theme.fg(\"dim\", `· ${detail}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\tconst l1RightPlain = nSub > 0 ? `◇${nSub} running` : \"\";\n\t\tconst l1RightStyled = nSub > 0 ? theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\") : \"\";\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { renderDownloadProgress, renderProgressBar } from \"./progress-bar.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"dim\", track) };\n}\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * One footer line for a transient progress entry (tool download, index build,\n * `/learn` reading transcripts). The bar comes from the shared progress-bar\n * component, so this line and the voice panel's cannot drift apart again. An\n * error entry renders as a dim message instead. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\treturn `${label} ${renderDownloadProgress(entry.receivedBytes, entry.totalBytes)}`;\n\t}\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${renderProgressBar(ratio, `${entry.done}/${entry.total} ${entry.unit}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\tconst l1RightPlain = nSub > 0 ? `◇${nSub} running` : \"\";\n\t\tconst l1RightStyled = nSub > 0 ? theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\") : \"\";\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The one progress bar. Every surface that makes the user wait on measurable
3
+ * work renders it through here.
4
+ *
5
+ * There used to be two, written months apart, and they had drifted in three
6
+ * separate ways: the footer drew `▰▱` while the voice panel drew `·` over `·`,
7
+ * the footer said `2.0 MB` while the voice panel said `2 MB`, and each carried
8
+ * its own copy of the percent-and-detail layout. None of that is a decision
9
+ * anyone made — it is just what happens when the same widget is written twice.
10
+ *
11
+ * Bar *width* is still the caller's, because that is a genuine property of the
12
+ * surface: a footer line shares its row with other status, a panel has the room
13
+ * to be finer-grained. Everything else — glyphs, colours, percent, separator,
14
+ * byte formatting — is fixed here, so two bars on screen at once always agree.
15
+ */
16
+ /**
17
+ * Default cells in a bar. Compact enough that several stacked footer lines
18
+ * still fit a narrow terminal.
19
+ */
20
+ export declare const PROGRESS_BAR_CELLS = 12;
21
+ /**
22
+ * Megabytes, to one decimal.
23
+ *
24
+ * The decimal is the point: whole megabytes make a slow download look frozen
25
+ * for tens of seconds at a time, which is precisely when the user is deciding
26
+ * whether it has hung.
27
+ */
28
+ export declare function formatMegabytes(bytes: number): string;
29
+ export interface ProgressBarOptions {
30
+ /** Track width. Defaults to {@link PROGRESS_BAR_CELLS}. */
31
+ cells?: number;
32
+ }
33
+ /**
34
+ * A determinate bar: track, percent, and a trailing detail such as
35
+ * `3/12 sessions` or `2.0 MB / 8.0 MB`.
36
+ *
37
+ * Returns a styled string with no leading or trailing space, so callers can
38
+ * place it after their own label. Callers width-clamp.
39
+ */
40
+ export declare function renderProgressBar(ratio: number, detail: string, options?: ProgressBarOptions): string;
41
+ /**
42
+ * A download's progress, determinate when the server sent a length and a
43
+ * running byte count when it did not.
44
+ *
45
+ * The indeterminate case deliberately drops the bar rather than animating a
46
+ * fake one: a moving byte count already says "still going", and a bar that
47
+ * cannot reach the end is worse than no bar.
48
+ */
49
+ export declare function renderDownloadProgress(receivedBytes: number, totalBytes: number | null, options?: ProgressBarOptions): string;
50
+ //# sourceMappingURL=progress-bar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-bar.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/progress-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAoBD,MAAM,WAAW,kBAAkB;IAClC,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAKzG;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACrC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,OAAO,GAAE,kBAAuB,GAC9B,MAAM,CAMR","sourcesContent":["/**\n * The one progress bar. Every surface that makes the user wait on measurable\n * work renders it through here.\n *\n * There used to be two, written months apart, and they had drifted in three\n * separate ways: the footer drew `▰▱` while the voice panel drew `·` over `·`,\n * the footer said `2.0 MB` while the voice panel said `2 MB`, and each carried\n * its own copy of the percent-and-detail layout. None of that is a decision\n * anyone made — it is just what happens when the same widget is written twice.\n *\n * Bar *width* is still the caller's, because that is a genuine property of the\n * surface: a footer line shares its row with other status, a panel has the room\n * to be finer-grained. Everything else — glyphs, colours, percent, separator,\n * byte formatting — is fixed here, so two bars on screen at once always agree.\n */\n\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Default cells in a bar. Compact enough that several stacked footer lines\n * still fit a narrow terminal.\n */\nexport const PROGRESS_BAR_CELLS = 12;\n\n/**\n * Megabytes, to one decimal.\n *\n * The decimal is the point: whole megabytes make a slow download look frozen\n * for tens of seconds at a time, which is precisely when the user is deciding\n * whether it has hung.\n */\nexport function formatMegabytes(bytes: number): string {\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * The filled/empty track.\n *\n * The glyphs differ in shape, not only in colour, matching the context gauge in\n * the footer. An earlier version drew both halves as `·` and left the whole\n * reading to the colour, so at a glance — or on a low-contrast theme, or piped\n * somewhere that drops styling — a bar at 10% and one at 90% were identical.\n *\n * A started-but-tiny ratio keeps one filled cell rather than rounding to an\n * empty bar: \"nothing has happened yet\" and \"the first of forty is done\" are\n * different things to be told.\n */\nfunction track(ratio: number, cells: number): string {\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst filled = Math.max(clamped > 0 ? 1 : 0, Math.min(cells, Math.round(clamped * cells)));\n\treturn theme.fg(\"accent\", \"▰\".repeat(filled)) + theme.fg(\"dim\", \"▱\".repeat(cells - filled));\n}\n\nexport interface ProgressBarOptions {\n\t/** Track width. Defaults to {@link PROGRESS_BAR_CELLS}. */\n\tcells?: number;\n}\n\n/**\n * A determinate bar: track, percent, and a trailing detail such as\n * `3/12 sessions` or `2.0 MB / 8.0 MB`.\n *\n * Returns a styled string with no leading or trailing space, so callers can\n * place it after their own label. Callers width-clamp.\n */\nexport function renderProgressBar(ratio: number, detail: string, options: ProgressBarOptions = {}): string {\n\tconst cells = options.cells ?? PROGRESS_BAR_CELLS;\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst percent = `${Math.round(clamped * 100)}%`;\n\treturn `${track(clamped, cells)} ${theme.fg(\"muted\", percent)} ${theme.fg(\"dim\", `· ${detail}`)}`;\n}\n\n/**\n * A download's progress, determinate when the server sent a length and a\n * running byte count when it did not.\n *\n * The indeterminate case deliberately drops the bar rather than animating a\n * fake one: a moving byte count already says \"still going\", and a bar that\n * cannot reach the end is worse than no bar.\n */\nexport function renderDownloadProgress(\n\treceivedBytes: number,\n\ttotalBytes: number | null,\n\toptions: ProgressBarOptions = {},\n): string {\n\tif (totalBytes === null || totalBytes <= 0) {\n\t\treturn theme.fg(\"dim\", `${formatMegabytes(receivedBytes)}…`);\n\t}\n\tconst detail = `${formatMegabytes(receivedBytes)} / ${formatMegabytes(totalBytes)}`;\n\treturn renderProgressBar(receivedBytes / totalBytes, detail, options);\n}\n"]}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The one progress bar. Every surface that makes the user wait on measurable
3
+ * work renders it through here.
4
+ *
5
+ * There used to be two, written months apart, and they had drifted in three
6
+ * separate ways: the footer drew `▰▱` while the voice panel drew `·` over `·`,
7
+ * the footer said `2.0 MB` while the voice panel said `2 MB`, and each carried
8
+ * its own copy of the percent-and-detail layout. None of that is a decision
9
+ * anyone made — it is just what happens when the same widget is written twice.
10
+ *
11
+ * Bar *width* is still the caller's, because that is a genuine property of the
12
+ * surface: a footer line shares its row with other status, a panel has the room
13
+ * to be finer-grained. Everything else — glyphs, colours, percent, separator,
14
+ * byte formatting — is fixed here, so two bars on screen at once always agree.
15
+ */
16
+ import { theme } from "../theme/theme.js";
17
+ /**
18
+ * Default cells in a bar. Compact enough that several stacked footer lines
19
+ * still fit a narrow terminal.
20
+ */
21
+ export const PROGRESS_BAR_CELLS = 12;
22
+ /**
23
+ * Megabytes, to one decimal.
24
+ *
25
+ * The decimal is the point: whole megabytes make a slow download look frozen
26
+ * for tens of seconds at a time, which is precisely when the user is deciding
27
+ * whether it has hung.
28
+ */
29
+ export function formatMegabytes(bytes) {
30
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
31
+ }
32
+ /**
33
+ * The filled/empty track.
34
+ *
35
+ * The glyphs differ in shape, not only in colour, matching the context gauge in
36
+ * the footer. An earlier version drew both halves as `·` and left the whole
37
+ * reading to the colour, so at a glance — or on a low-contrast theme, or piped
38
+ * somewhere that drops styling — a bar at 10% and one at 90% were identical.
39
+ *
40
+ * A started-but-tiny ratio keeps one filled cell rather than rounding to an
41
+ * empty bar: "nothing has happened yet" and "the first of forty is done" are
42
+ * different things to be told.
43
+ */
44
+ function track(ratio, cells) {
45
+ const clamped = Math.max(0, Math.min(1, ratio));
46
+ const filled = Math.max(clamped > 0 ? 1 : 0, Math.min(cells, Math.round(clamped * cells)));
47
+ return theme.fg("accent", "▰".repeat(filled)) + theme.fg("dim", "▱".repeat(cells - filled));
48
+ }
49
+ /**
50
+ * A determinate bar: track, percent, and a trailing detail such as
51
+ * `3/12 sessions` or `2.0 MB / 8.0 MB`.
52
+ *
53
+ * Returns a styled string with no leading or trailing space, so callers can
54
+ * place it after their own label. Callers width-clamp.
55
+ */
56
+ export function renderProgressBar(ratio, detail, options = {}) {
57
+ const cells = options.cells ?? PROGRESS_BAR_CELLS;
58
+ const clamped = Math.max(0, Math.min(1, ratio));
59
+ const percent = `${Math.round(clamped * 100)}%`;
60
+ return `${track(clamped, cells)} ${theme.fg("muted", percent)} ${theme.fg("dim", `· ${detail}`)}`;
61
+ }
62
+ /**
63
+ * A download's progress, determinate when the server sent a length and a
64
+ * running byte count when it did not.
65
+ *
66
+ * The indeterminate case deliberately drops the bar rather than animating a
67
+ * fake one: a moving byte count already says "still going", and a bar that
68
+ * cannot reach the end is worse than no bar.
69
+ */
70
+ export function renderDownloadProgress(receivedBytes, totalBytes, options = {}) {
71
+ if (totalBytes === null || totalBytes <= 0) {
72
+ return theme.fg("dim", `${formatMegabytes(receivedBytes)}…`);
73
+ }
74
+ const detail = `${formatMegabytes(receivedBytes)} / ${formatMegabytes(totalBytes)}`;
75
+ return renderProgressBar(receivedBytes / totalBytes, detail, options);
76
+ }
77
+ //# sourceMappingURL=progress-bar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-bar.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/progress-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAU;IACtD,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AAAA,CAClD;AAED;;;;;;;;;;;GAWG;AACH,SAAS,KAAK,CAAC,KAAa,EAAE,KAAa,EAAU;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3F,OAAO,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAG,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;AAAA,CAC5F;AAOD;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,MAAc,EAAE,OAAO,GAAuB,EAAE,EAAU;IAC1G,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC;IAChD,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAK,MAAM,EAAE,CAAC,EAAE,CAAC;AAAA,CAClG;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACrC,aAAqB,EACrB,UAAyB,EACzB,OAAO,GAAuB,EAAE,EACvB;IACT,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,eAAe,CAAC,aAAa,CAAC,KAAG,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,eAAe,CAAC,aAAa,CAAC,MAAM,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;IACpF,OAAO,iBAAiB,CAAC,aAAa,GAAG,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAAA,CACtE","sourcesContent":["/**\n * The one progress bar. Every surface that makes the user wait on measurable\n * work renders it through here.\n *\n * There used to be two, written months apart, and they had drifted in three\n * separate ways: the footer drew `▰▱` while the voice panel drew `·` over `·`,\n * the footer said `2.0 MB` while the voice panel said `2 MB`, and each carried\n * its own copy of the percent-and-detail layout. None of that is a decision\n * anyone made — it is just what happens when the same widget is written twice.\n *\n * Bar *width* is still the caller's, because that is a genuine property of the\n * surface: a footer line shares its row with other status, a panel has the room\n * to be finer-grained. Everything else — glyphs, colours, percent, separator,\n * byte formatting — is fixed here, so two bars on screen at once always agree.\n */\n\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Default cells in a bar. Compact enough that several stacked footer lines\n * still fit a narrow terminal.\n */\nexport const PROGRESS_BAR_CELLS = 12;\n\n/**\n * Megabytes, to one decimal.\n *\n * The decimal is the point: whole megabytes make a slow download look frozen\n * for tens of seconds at a time, which is precisely when the user is deciding\n * whether it has hung.\n */\nexport function formatMegabytes(bytes: number): string {\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * The filled/empty track.\n *\n * The glyphs differ in shape, not only in colour, matching the context gauge in\n * the footer. An earlier version drew both halves as `·` and left the whole\n * reading to the colour, so at a glance — or on a low-contrast theme, or piped\n * somewhere that drops styling — a bar at 10% and one at 90% were identical.\n *\n * A started-but-tiny ratio keeps one filled cell rather than rounding to an\n * empty bar: \"nothing has happened yet\" and \"the first of forty is done\" are\n * different things to be told.\n */\nfunction track(ratio: number, cells: number): string {\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst filled = Math.max(clamped > 0 ? 1 : 0, Math.min(cells, Math.round(clamped * cells)));\n\treturn theme.fg(\"accent\", \"▰\".repeat(filled)) + theme.fg(\"dim\", \"▱\".repeat(cells - filled));\n}\n\nexport interface ProgressBarOptions {\n\t/** Track width. Defaults to {@link PROGRESS_BAR_CELLS}. */\n\tcells?: number;\n}\n\n/**\n * A determinate bar: track, percent, and a trailing detail such as\n * `3/12 sessions` or `2.0 MB / 8.0 MB`.\n *\n * Returns a styled string with no leading or trailing space, so callers can\n * place it after their own label. Callers width-clamp.\n */\nexport function renderProgressBar(ratio: number, detail: string, options: ProgressBarOptions = {}): string {\n\tconst cells = options.cells ?? PROGRESS_BAR_CELLS;\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst percent = `${Math.round(clamped * 100)}%`;\n\treturn `${track(clamped, cells)} ${theme.fg(\"muted\", percent)} ${theme.fg(\"dim\", `· ${detail}`)}`;\n}\n\n/**\n * A download's progress, determinate when the server sent a length and a\n * running byte count when it did not.\n *\n * The indeterminate case deliberately drops the bar rather than animating a\n * fake one: a moving byte count already says \"still going\", and a bar that\n * cannot reach the end is worse than no bar.\n */\nexport function renderDownloadProgress(\n\treceivedBytes: number,\n\ttotalBytes: number | null,\n\toptions: ProgressBarOptions = {},\n): string {\n\tif (totalBytes === null || totalBytes <= 0) {\n\t\treturn theme.fg(\"dim\", `${formatMegabytes(receivedBytes)}…`);\n\t}\n\tconst detail = `${formatMegabytes(receivedBytes)} / ${formatMegabytes(totalBytes)}`;\n\treturn renderProgressBar(receivedBytes / totalBytes, detail, options);\n}\n"]}
@@ -68,7 +68,12 @@ export declare class VoicePanel implements Component {
68
68
  private renderWaveform;
69
69
  private renderPartial;
70
70
  private elapsedLabel;
71
- /** Determinate download bar: `·` fill over a dim track with % and MB counts. */
71
+ /**
72
+ * The model download's progress, rendered by the shared progress-bar
73
+ * component so this panel and the footer always agree. The panel is wider
74
+ * than a footer line, so it asks for a finer track — width is the only thing
75
+ * a surface gets to choose.
76
+ */
72
77
  private renderDownloadBar;
73
78
  render(width: number): string[];
74
79
  }