@kolisachint/hoocode-agent 0.5.34 → 0.5.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +2 -2
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +2 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +3 -2
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +5 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/progress-bar.d.ts.map +1 -1
- package/dist/modes/interactive/components/progress-bar.js +1 -1
- package/dist/modes/interactive/components/progress-bar.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +3 -3
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +24 -0
- package/dist/modes/interactive/theme/theme.d.ts +19 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +66 -1
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/theme/vox-cutout-dark.json +116 -0
- package/dist/modes/interactive/theme/vox-cutout-light.json +115 -0
- package/docs/themes.md +101 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"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;AAIxF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AA4ExE;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAK/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IALnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,cAAc,CAA4B;IAElD,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,4EAA4E;IAC5E,iBAAiB,CAAC,IAAI,EAAE,cAAc,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,CAqK9B;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 type { ToolOutputView } from \"../../../core/tool-output-view.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/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\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\tprivate toolOutputView: ToolOutputView = \"glance\";\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/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\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\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\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;AAIxF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAgFxE;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAK/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IALnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,cAAc,CAA4B;IAElD,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,4EAA4E;IAC5E,iBAAiB,CAAC,IAAI,EAAE,cAAc,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,CAqK9B;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 type { ToolOutputView } from \"../../../core/tool-output-view.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\t// The unfilled track takes `halftone`, which falls back to `dim` — the tone it\n\t// used before the token existed. The point of separating them is that `dim` is\n\t// body-weight text: a track drawn in it reads as writing rather than as the\n\t// space the fill has yet to reach.\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"halftone\", track) };\n}\n\n/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\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\tprivate toolOutputView: ToolOutputView = \"glance\";\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/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\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\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\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"]}
|
|
@@ -29,7 +29,11 @@ function contextGauge(percent, errorLevel, warnLevel) {
|
|
|
29
29
|
const fill = "▰".repeat(filled);
|
|
30
30
|
const track = "▱".repeat(CELLS - filled);
|
|
31
31
|
const color = percent >= errorLevel ? "error" : percent >= warnLevel ? "warning" : "accent";
|
|
32
|
-
|
|
32
|
+
// The unfilled track takes `halftone`, which falls back to `dim` — the tone it
|
|
33
|
+
// used before the token existed. The point of separating them is that `dim` is
|
|
34
|
+
// body-weight text: a track drawn in it reads as writing rather than as the
|
|
35
|
+
// space the fill has yet to reach.
|
|
36
|
+
return { plain: fill + track, styled: theme.fg(color, fill) + theme.fg("halftone", track) };
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
39
|
* The view dial's glyph, filling up as the view widens — so the footer shows
|
|
@@ -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 type { ToolOutputView } from \"../../../core/tool-output-view.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/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\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\tprivate toolOutputView: ToolOutputView = \"glance\";\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/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\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\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\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 type { ToolOutputView } from \"../../../core/tool-output-view.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\t// The unfilled track takes `halftone`, which falls back to `dim` — the tone it\n\t// used before the token existed. The point of separating them is that `dim` is\n\t// body-weight text: a track drawn in it reads as writing rather than as the\n\t// space the fill has yet to reach.\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"halftone\", track) };\n}\n\n/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\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\tprivate toolOutputView: ToolOutputView = \"glance\";\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/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\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\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\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 +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(\"
|
|
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(\"halftone\", \"▱\".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"]}
|
|
@@ -44,7 +44,7 @@ export function formatMegabytes(bytes) {
|
|
|
44
44
|
function track(ratio, cells) {
|
|
45
45
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
46
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("
|
|
47
|
+
return theme.fg("accent", "▰".repeat(filled)) + theme.fg("halftone", "▱".repeat(cells - filled));
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* A determinate bar: track, percent, and a trailing detail such as
|
|
@@ -1 +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,
|
|
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,UAAU,EAAE,KAAG,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;AAAA,CACjG;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(\"halftone\", \"▱\".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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-invocation-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAQ,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAIvE;;;;GAIG;AACH,qBAAa,+BAAgC,SAAQ,GAAG;IACvD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,UAAU,EAAE,gBAAgB,EAAE,aAAa,GAAE,aAAkC,EAK1F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CAsBrB","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Text } from \"@kolisachint/hoocode-tui\";\nimport type { ParsedSkillBlock } from \"../../../core/agent-session.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a skill invocation message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n * Only renders the skill block itself - user message is rendered separately.\n */\nexport class SkillInvocationMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate skillBlock: ParsedSkillBlock;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.skillBlock = skillBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tif (this.expanded) {\n\t\t\t// Expanded: label + skill name header + full content\n\t\t\tconst label =
|
|
1
|
+
{"version":3,"file":"skill-invocation-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAQ,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAIvE;;;;GAIG;AACH,qBAAa,+BAAgC,SAAQ,GAAG;IACvD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,UAAU,EAAE,gBAAgB,EAAE,aAAa,GAAE,aAAkC,EAK1F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CAsBrB","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Text } from \"@kolisachint/hoocode-tui\";\nimport type { ParsedSkillBlock } from \"../../../core/agent-session.js\";\nimport { getMarkdownTheme, messageLabel, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a skill invocation message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n * Only renders the skill block itself - user message is rendered separately.\n */\nexport class SkillInvocationMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate skillBlock: ParsedSkillBlock;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.skillBlock = skillBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tif (this.expanded) {\n\t\t\t// Expanded: label + skill name header + full content\n\t\t\tconst label = messageLabel(\"skill\");\n\t\t\tthis.addChild(new Text(label, 0, 0));\n\t\t\tconst header = `**${this.skillBlock.name}**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\t// Collapsed: single line - [skill] name (hint to expand)\n\t\t\tconst line =\n\t\t\t\t`${messageLabel(\"skill\")} ` +\n\t\t\t\ttheme.fg(\"customMessageText\", this.skillBlock.name) +\n\t\t\t\ttheme.fg(\"dim\", ` (${keyText(\"app.tools.expand\")} to expand)`);\n\t\t\tthis.addChild(new Text(line, 0, 0));\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box, Markdown, Text } from "@kolisachint/hoocode-tui";
|
|
2
|
-
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
2
|
+
import { getMarkdownTheme, messageLabel, theme } from "../theme/theme.js";
|
|
3
3
|
import { keyText } from "./keybinding-hints.js";
|
|
4
4
|
/**
|
|
5
5
|
* Component that renders a skill invocation message with collapsed/expanded state.
|
|
@@ -28,7 +28,7 @@ export class SkillInvocationMessageComponent extends Box {
|
|
|
28
28
|
this.clear();
|
|
29
29
|
if (this.expanded) {
|
|
30
30
|
// Expanded: label + skill name header + full content
|
|
31
|
-
const label =
|
|
31
|
+
const label = messageLabel("skill");
|
|
32
32
|
this.addChild(new Text(label, 0, 0));
|
|
33
33
|
const header = `**${this.skillBlock.name}**\n\n`;
|
|
34
34
|
this.addChild(new Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {
|
|
@@ -37,7 +37,7 @@ export class SkillInvocationMessageComponent extends Box {
|
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
39
|
// Collapsed: single line - [skill] name (hint to expand)
|
|
40
|
-
const line =
|
|
40
|
+
const line = `${messageLabel("skill")} ` +
|
|
41
41
|
theme.fg("customMessageText", this.skillBlock.name) +
|
|
42
42
|
theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`);
|
|
43
43
|
this.addChild(new Text(line, 0, 0));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-invocation-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEnF,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"skill-invocation-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEnF,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD;;;;GAIG;AACH,MAAM,OAAO,+BAAgC,SAAQ,GAAG;IAC/C,QAAQ,GAAG,KAAK,CAAC;IACjB,UAAU,CAAmB;IAC7B,aAAa,CAAgB;IAErC,YAAY,UAA4B,EAAE,aAAa,GAAkB,gBAAgB,EAAE,EAAE;QAC5F,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAED,WAAW,CAAC,QAAiB,EAAQ;QACpC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEQ,UAAU,GAAS;QAC3B,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,aAAa,GAAS;QAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,qDAAqD;YACrD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACpC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,MAAM,GAAG,KAAK,IAAI,CAAC,UAAU,CAAC,IAAI,QAAQ,CAAC;YACjD,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACxE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,yDAAyD;YACzD,MAAM,IAAI,GACT,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG;gBAC3B,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBACnD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,OAAO,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;IAAA,CACD;CACD","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Text } from \"@kolisachint/hoocode-tui\";\nimport type { ParsedSkillBlock } from \"../../../core/agent-session.js\";\nimport { getMarkdownTheme, messageLabel, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a skill invocation message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n * Only renders the skill block itself - user message is rendered separately.\n */\nexport class SkillInvocationMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate skillBlock: ParsedSkillBlock;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.skillBlock = skillBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tif (this.expanded) {\n\t\t\t// Expanded: label + skill name header + full content\n\t\t\tconst label = messageLabel(\"skill\");\n\t\t\tthis.addChild(new Text(label, 0, 0));\n\t\t\tconst header = `**${this.skillBlock.name}**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\t// Collapsed: single line - [skill] name (hint to expand)\n\t\t\tconst line =\n\t\t\t\t`${messageLabel(\"skill\")} ` +\n\t\t\t\ttheme.fg(\"customMessageText\", this.skillBlock.name) +\n\t\t\t\ttheme.fg(\"dim\", ` (${keyText(\"app.tools.expand\")} to expand)`);\n\t\t\tthis.addChild(new Text(line, 0, 0));\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,SAAS,EAAY,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAOxF;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;IAClD,OAAO,CAAC,UAAU,CAAM;IAExB,YAAY,IAAI,EAAE,MAAM,EAAE,aAAa,GAAE,aAAkC,
|
|
1
|
+
{"version":3,"file":"user-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,SAAS,EAAY,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAOxF;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;IAClD,OAAO,CAAC,UAAU,CAAM;IAExB,YAAY,IAAI,EAAE,MAAM,EAAE,aAAa,GAAE,aAAkC,EAU1E;IAED;;8DAE0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAmC;IAE3C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAcvC;CACD","sourcesContent":["import { Box, Container, Markdown, type MarkdownTheme } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, getPaperShadowFn, theme } from \"../theme/theme.js\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\n/**\n * Component that renders a user message\n */\nexport class UserMessageComponent extends Container {\n\tprivate contentBox: Box;\n\n\tconstructor(text: string, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper();\n\t\tthis.contentBox = new Box(1, 1, (content: string) => theme.bg(\"userMessageBg\", content));\n\t\tthis.contentBox.setShadowFn(getPaperShadowFn());\n\t\tthis.contentBox.addChild(\n\t\t\tnew Markdown(text, 0, 0, markdownTheme, {\n\t\t\t\tcolor: (content: string) => theme.fg(\"userMessageText\", content),\n\t\t\t}),\n\t\t);\n\t\tthis.addChild(this.contentBox);\n\t}\n\n\t/** OSC-zone wrap memo: Container.render returns the same array across\n\t * frames when nothing changed, so it must not be mutated — the wrapped\n\t * copy is cached keyed on the source array's identity. */\n\tprivate zoneMemo?: { src: string[]; out: string[] };\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\tif (this.zoneMemo?.src === lines) {\n\t\t\treturn this.zoneMemo.out;\n\t\t}\n\t\tconst out = lines.slice();\n\t\tout[0] = OSC133_ZONE_START + out[0];\n\t\tout[out.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + out[out.length - 1];\n\t\tthis.zoneMemo = { src: lines, out };\n\t\treturn out;\n\t}\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box, Container, Markdown } from "@kolisachint/hoocode-tui";
|
|
2
|
-
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
2
|
+
import { getMarkdownTheme, getPaperShadowFn, theme } from "../theme/theme.js";
|
|
3
3
|
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
4
4
|
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
5
5
|
const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
@@ -11,6 +11,7 @@ export class UserMessageComponent extends Container {
|
|
|
11
11
|
constructor(text, markdownTheme = getMarkdownTheme()) {
|
|
12
12
|
super();
|
|
13
13
|
this.contentBox = new Box(1, 1, (content) => theme.bg("userMessageBg", content));
|
|
14
|
+
this.contentBox.setShadowFn(getPaperShadowFn());
|
|
14
15
|
this.contentBox.addChild(new Markdown(text, 0, 0, markdownTheme, {
|
|
15
16
|
color: (content) => theme.fg("userMessageText", content),
|
|
16
17
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAsB,MAAM,0BAA0B,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"user-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAsB,MAAM,0BAA0B,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE9E,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAC3C,MAAM,eAAe,GAAG,gBAAgB,CAAC;AACzC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAE3C;;GAEG;AACH,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IAC1C,UAAU,CAAM;IAExB,YAAY,IAAY,EAAE,aAAa,GAAkB,gBAAgB,EAAE,EAAE;QAC5E,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;QACzF,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,QAAQ,CACvB,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE;YACvC,KAAK,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,OAAO,CAAC;SAChE,CAAC,CACF,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAAA,CAC/B;IAED;;8DAE0D;IAClD,QAAQ,CAAoC;IAE3C,MAAM,CAAC,KAAa,EAAY;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC;QACd,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,EAAE,GAAG,KAAK,KAAK,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAC1B,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC1B,GAAG,CAAC,CAAC,CAAC,GAAG,iBAAiB,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACpC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,eAAe,GAAG,iBAAiB,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,QAAQ,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QACpC,OAAO,GAAG,CAAC;IAAA,CACX;CACD","sourcesContent":["import { Box, Container, Markdown, type MarkdownTheme } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, getPaperShadowFn, theme } from \"../theme/theme.js\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\n/**\n * Component that renders a user message\n */\nexport class UserMessageComponent extends Container {\n\tprivate contentBox: Box;\n\n\tconstructor(text: string, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper();\n\t\tthis.contentBox = new Box(1, 1, (content: string) => theme.bg(\"userMessageBg\", content));\n\t\tthis.contentBox.setShadowFn(getPaperShadowFn());\n\t\tthis.contentBox.addChild(\n\t\t\tnew Markdown(text, 0, 0, markdownTheme, {\n\t\t\t\tcolor: (content: string) => theme.fg(\"userMessageText\", content),\n\t\t\t}),\n\t\t);\n\t\tthis.addChild(this.contentBox);\n\t}\n\n\t/** OSC-zone wrap memo: Container.render returns the same array across\n\t * frames when nothing changed, so it must not be mutated — the wrapped\n\t * copy is cached keyed on the source array's identity. */\n\tprivate zoneMemo?: { src: string[]; out: string[] };\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\tif (this.zoneMemo?.src === lines) {\n\t\t\treturn this.zoneMemo.out;\n\t\t}\n\t\tconst out = lines.slice();\n\t\tout[0] = OSC133_ZONE_START + out[0];\n\t\tout[out.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + out[out.length - 1];\n\t\tthis.zoneMemo = { src: lines, out };\n\t\treturn out;\n\t}\n}\n"]}
|