@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.1
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 +136 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +25 -2
- package/dist/types/config/settings-schema.d.ts +41 -6
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +16 -6
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +19 -6
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +14 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +13 -9
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +5 -1
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +5 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +2 -2
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +226 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +41 -18
- package/src/config/settings-schema.ts +28 -5
- package/src/config/settings.ts +31 -2
- package/src/dap/client.ts +14 -16
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +111 -119
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/agent-bridge.ts +34 -7
- package/src/eval/llm-bridge.ts +8 -3
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +37 -27
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +53 -56
- package/src/memories/index.ts +12 -5
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +33 -1
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +115 -90
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +70 -57
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +135 -122
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +25 -27
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +171 -82
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +19 -8
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +32 -60
- package/src/session/agent-session.ts +89 -13
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +37 -10
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +25 -4
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -148
- package/src/telemetry-export.ts +25 -7
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +21 -18
- package/src/tools/eval.ts +5 -4
- package/src/tools/fetch.ts +391 -91
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +38 -40
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +189 -95
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +138 -59
- package/src/tools/write.ts +100 -60
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
- package/src/web/search/render.ts +39 -54
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure heading/section parser for the plan-review overlay. It splits a plan's
|
|
3
|
+
* markdown into a flat list of sections — a leading preamble (text before the
|
|
4
|
+
* first heading) followed by one entry per ATX heading — preserving the exact
|
|
5
|
+
* source bytes of each section so the overlay can render, reorder-free delete,
|
|
6
|
+
* and round-trip the document without a full markdown re-render.
|
|
7
|
+
*
|
|
8
|
+
* No TUI dependencies: this module is unit-tested in isolation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** ATX heading: 1-6 `#`, required whitespace, a title, optional closing `#`s. */
|
|
12
|
+
const HEADING_RE = /^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/;
|
|
13
|
+
/** Opening/closing code fence run (``` or ~~~), allowing up to 3 lead spaces. */
|
|
14
|
+
const FENCE_RE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
15
|
+
|
|
16
|
+
export interface PlanSection {
|
|
17
|
+
/** `0` = preamble (no heading, no ToC entry); `1..6` = heading depth. */
|
|
18
|
+
level: number;
|
|
19
|
+
/** Plain-text heading label with inline markdown lightly stripped. */
|
|
20
|
+
title: string;
|
|
21
|
+
/** Exact source slice for this section, including its trailing newline(s). */
|
|
22
|
+
raw: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Collapse inline markdown emphasis/link/code syntax to readable text. This is
|
|
27
|
+
* a deliberately light strip (not a full markdown render) just so ToC entries
|
|
28
|
+
* read cleanly — `**Goal** & [docs](x)` becomes `Goal & docs`.
|
|
29
|
+
*/
|
|
30
|
+
export function stripInlineMarkdown(text: string): string {
|
|
31
|
+
let out = text;
|
|
32
|
+
// Images first (so the link pass below does not eat the `(url)`), then links.
|
|
33
|
+
out = out.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
|
|
34
|
+
out = out.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1");
|
|
35
|
+
out = out.replace(/\[([^\]]*)\]\[[^\]]*\]/g, "$1");
|
|
36
|
+
// Autolinks `<https://…>` keep their URL as the readable text.
|
|
37
|
+
out = out.replace(/<([^>\s]+)>/g, "$1");
|
|
38
|
+
// Inline code, then bold/italic/strikethrough emphasis runs.
|
|
39
|
+
out = out.replace(/`([^`]+)`/g, "$1");
|
|
40
|
+
out = out.replace(/(\*\*|__)(.+?)\1/g, "$2");
|
|
41
|
+
out = out.replace(/(\*|_)(.+?)\1/g, "$2");
|
|
42
|
+
out = out.replace(/~~(.+?)~~/g, "$1");
|
|
43
|
+
return out.replace(/\s+/g, " ").trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Split `text` into preamble + heading sections. `#` characters inside fenced
|
|
48
|
+
* code blocks are never treated as headings. Concatenating every section's
|
|
49
|
+
* `raw` reproduces the original text exactly.
|
|
50
|
+
*/
|
|
51
|
+
export function parsePlanSections(text: string): PlanSection[] {
|
|
52
|
+
const lines = text.split("\n");
|
|
53
|
+
// Character offset of each line start so section `raw` can slice the source.
|
|
54
|
+
const offsets: number[] = new Array(lines.length);
|
|
55
|
+
let cursor = 0;
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
offsets[i] = cursor;
|
|
58
|
+
cursor += lines[i]!.length + 1; // +1 for the "\n" join separator
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Heading line indices (start of each heading section), with metadata.
|
|
62
|
+
const heads: { line: number; level: number; title: string }[] = [];
|
|
63
|
+
let fenceChar: string | null = null;
|
|
64
|
+
let fenceLen = 0;
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
const line = lines[i]!;
|
|
67
|
+
const fence = FENCE_RE.exec(line);
|
|
68
|
+
if (fenceChar === null) {
|
|
69
|
+
if (fence) {
|
|
70
|
+
fenceChar = fence[1]![0]!;
|
|
71
|
+
fenceLen = fence[1]!.length;
|
|
72
|
+
}
|
|
73
|
+
// Opening-fence lines are body, not headings.
|
|
74
|
+
if (fence) continue;
|
|
75
|
+
} else {
|
|
76
|
+
// Inside a fence: only a matching-or-longer run of the same char closes.
|
|
77
|
+
if (fence && fence[1]![0] === fenceChar && fence[1]!.length >= fenceLen && fence[2]!.trim() === "") {
|
|
78
|
+
fenceChar = null;
|
|
79
|
+
fenceLen = 0;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const heading = HEADING_RE.exec(line);
|
|
84
|
+
if (heading) {
|
|
85
|
+
heads.push({ line: i, level: heading[1]!.length, title: stripInlineMarkdown(heading[2]!) });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const sections: PlanSection[] = [];
|
|
90
|
+
const sliceRaw = (startLine: number, endLine: number): string => {
|
|
91
|
+
const startOffset = offsets[startLine]!;
|
|
92
|
+
const endOffset = endLine < lines.length ? offsets[endLine]! : text.length;
|
|
93
|
+
return text.slice(startOffset, endOffset);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Preamble: everything before the first heading (only when non-empty).
|
|
97
|
+
const firstHeadLine = heads.length > 0 ? heads[0]!.line : lines.length;
|
|
98
|
+
if (firstHeadLine > 0) {
|
|
99
|
+
const raw = sliceRaw(0, firstHeadLine);
|
|
100
|
+
if (raw.length > 0) sections.push({ level: 0, title: "", raw });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (let h = 0; h < heads.length; h++) {
|
|
104
|
+
const head = heads[h]!;
|
|
105
|
+
const endLine = h + 1 < heads.length ? heads[h + 1]!.line : lines.length;
|
|
106
|
+
sections.push({ level: head.level, title: head.title, raw: sliceRaw(head.line, endLine) });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return sections;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Concatenate every section's `raw` back into a single document and guarantee a
|
|
114
|
+
* single trailing newline. Inverse of {@link parsePlanSections} for any input
|
|
115
|
+
* that already ends with a newline.
|
|
116
|
+
*/
|
|
117
|
+
export function joinPlanSections(sections: readonly PlanSection[]): string {
|
|
118
|
+
let joined = "";
|
|
119
|
+
for (const section of sections) joined += section.raw;
|
|
120
|
+
if (joined.length === 0) return "";
|
|
121
|
+
return joined.endsWith("\n") ? joined : `${joined}\n`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Indices to remove when deleting `sections[index]`: the heading itself plus
|
|
126
|
+
* every following section nested deeper than it (its sub-headings). The
|
|
127
|
+
* preamble (level 0) is never a deletion target and yields an empty span.
|
|
128
|
+
*/
|
|
129
|
+
export function sectionDeletionSpan(sections: readonly PlanSection[], index: number): number[] {
|
|
130
|
+
const target = sections[index];
|
|
131
|
+
if (!target || target.level === 0) return [];
|
|
132
|
+
const span = [index];
|
|
133
|
+
for (let j = index + 1; j < sections.length; j++) {
|
|
134
|
+
if (sections[j]!.level > target.level) span.push(j);
|
|
135
|
+
else break;
|
|
136
|
+
}
|
|
137
|
+
return span;
|
|
138
|
+
}
|
|
@@ -81,6 +81,14 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
81
81
|
#text: Text;
|
|
82
82
|
#expanded = false;
|
|
83
83
|
#showContentPreview: boolean;
|
|
84
|
+
// A read group accretes entries across multiple assistant completions for as
|
|
85
|
+
// long as the run of reads is uninterrupted. While it is the active group it
|
|
86
|
+
// must stay in the transcript's repaintable live region — its header line
|
|
87
|
+
// re-layouts from `Read <path>` to `Read (N)` + tree as entries arrive, so a
|
|
88
|
+
// frozen snapshot taken on a risk terminal would strand the single-entry form
|
|
89
|
+
// (see TranscriptContainer / NativeScrollbackLiveRegion). The controller calls
|
|
90
|
+
// `finalize()` once the run breaks so the block can commit to native scrollback.
|
|
91
|
+
#finalized = false;
|
|
84
92
|
|
|
85
93
|
constructor(options: ReadToolGroupOptions = {}) {
|
|
86
94
|
super();
|
|
@@ -90,6 +98,14 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
90
98
|
this.#updateDisplay();
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
isTranscriptBlockFinalized(): boolean {
|
|
102
|
+
return this.#finalized;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
finalize(): void {
|
|
106
|
+
this.#finalized = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
updateArgs(args: ReadRenderArgs, toolCallId?: string): void {
|
|
94
110
|
if (!toolCallId) return;
|
|
95
111
|
const basePath = args.file_path || args.path || "";
|
|
@@ -181,9 +197,9 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
181
197
|
const total = entriesWithoutPreview.length;
|
|
182
198
|
for (const [index, entry] of entriesWithoutPreview.entries()) {
|
|
183
199
|
const connector = index === total - 1 ? theme.tree.last : theme.tree.branch;
|
|
184
|
-
const
|
|
200
|
+
const statusPrefix = entry.status === "success" ? "" : `${this.#formatStatus(entry.status)} `;
|
|
185
201
|
const pathDisplay = this.#formatPath(entry);
|
|
186
|
-
lines.push(` ${theme.fg("dim", connector)} ${
|
|
202
|
+
lines.push(` ${theme.fg("dim", connector)} ${statusPrefix}${pathDisplay}`.trimEnd());
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
this.#text.setText(lines.join("\n"));
|
|
@@ -198,7 +214,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
198
214
|
|
|
199
215
|
/**
|
|
200
216
|
* Add a code-cell content preview below the entry summary.
|
|
201
|
-
* When collapsed: shows first COLLAPSED_PREVIEW_LINES lines with "… N more lines
|
|
217
|
+
* When collapsed: shows first COLLAPSED_PREVIEW_LINES lines with a "… N more lines ⟨<key>: Expand⟩" hint.
|
|
202
218
|
* When expanded: shows full content.
|
|
203
219
|
*/
|
|
204
220
|
#addContentPreview(entry: ReadEntry): void {
|
|
@@ -254,7 +270,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
254
270
|
|
|
255
271
|
#formatStatus(status: ReadEntry["status"]): string {
|
|
256
272
|
if (status === "success") {
|
|
257
|
-
return theme.fg("
|
|
273
|
+
return theme.fg("text", theme.status.enabled);
|
|
258
274
|
}
|
|
259
275
|
if (status === "warning") {
|
|
260
276
|
return theme.fg("warning", theme.status.warning);
|
|
@@ -11,7 +11,6 @@ export class SkillMessageComponent extends Container {
|
|
|
11
11
|
|
|
12
12
|
constructor(private readonly message: CustomMessage<SkillPromptDetails>) {
|
|
13
13
|
super();
|
|
14
|
-
this.addChild(new Spacer(1));
|
|
15
14
|
|
|
16
15
|
this.#box = new Box(1, 1, t => theme.bg("customMessageBg", t));
|
|
17
16
|
this.#rebuild();
|
|
@@ -546,7 +546,7 @@ export class StatusLineComponent implements Component {
|
|
|
546
546
|
return `${modelId}|${sp.length}:${sp[0]?.length ?? 0}|${tools.length}|${skills.length}`;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
-
#buildSegmentContext(width: number): SegmentContext {
|
|
549
|
+
#buildSegmentContext(width: number, segmentOptions: StatusLineSettings["segmentOptions"]): SegmentContext {
|
|
550
550
|
const state = this.session.state;
|
|
551
551
|
|
|
552
552
|
// Trigger background fetch (5-min TTL); render uses cached value
|
|
@@ -575,7 +575,7 @@ export class StatusLineComponent implements Component {
|
|
|
575
575
|
return {
|
|
576
576
|
session: this.session,
|
|
577
577
|
width,
|
|
578
|
-
options:
|
|
578
|
+
options: segmentOptions ?? {},
|
|
579
579
|
planMode: this.#planModeStatus,
|
|
580
580
|
loopMode: this.#loopModeStatus,
|
|
581
581
|
goalMode: this.#goalModeStatus,
|
|
@@ -632,8 +632,8 @@ export class StatusLineComponent implements Component {
|
|
|
632
632
|
}
|
|
633
633
|
|
|
634
634
|
#buildStatusLine(width: number): string {
|
|
635
|
-
const ctx = this.#buildSegmentContext(width);
|
|
636
635
|
const effectiveSettings = this.#resolveSettings();
|
|
636
|
+
const ctx = this.#buildSegmentContext(width, effectiveSettings.segmentOptions);
|
|
637
637
|
const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
638
638
|
|
|
639
639
|
const bgAnsi = theme.getBgAnsi("statusLineBg");
|
|
@@ -759,8 +759,6 @@ export class StatusLineComponent implements Component {
|
|
|
759
759
|
return leftGroup + (leftGroup && rightGroup ? " " : "") + rightGroup;
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
-
leftWidth = groupWidth(left, leftCapWidth, leftSepWidth);
|
|
763
|
-
rightWidth = groupWidth(right, rightCapWidth, rightSepWidth);
|
|
764
762
|
const gapWidth = Math.max(1, topFillWidth - leftWidth - rightWidth);
|
|
765
763
|
const sessionName =
|
|
766
764
|
effectiveSettings.sessionAccent !== false ? this.session.sessionManager?.getSessionName() : undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Tired of typing "keep going"? Just send a '.'
|
|
2
2
|
You can /btw to ask a side question
|
|
3
|
+
Use /tan to fork the current conversation into a background agent
|
|
3
4
|
Ctrl+D can be used to exit, but with your draft saved!
|
|
4
5
|
Find out which model you emotionally abuse the most with `omp stats`
|
|
5
6
|
Try task isolation to create CoW worktrees
|
|
@@ -31,26 +31,12 @@ import {
|
|
|
31
31
|
renderJsonTreeLines,
|
|
32
32
|
} from "../../tools/json-tree";
|
|
33
33
|
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
|
34
|
-
import { toolRenderers } from "../../tools/renderers";
|
|
34
|
+
import { type ToolRenderer, toolRenderers } from "../../tools/renderers";
|
|
35
35
|
import { TODO_STRIKE_TOTAL_FRAMES } from "../../tools/todo";
|
|
36
36
|
import { isFramedBlockComponent, renderStatusLine } from "../../tui";
|
|
37
37
|
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
38
38
|
import { renderDiff } from "./diff";
|
|
39
39
|
|
|
40
|
-
function ensureInvalidate(component: unknown): Component {
|
|
41
|
-
const c = component as { render: Component["render"]; invalidate?: () => void };
|
|
42
|
-
if (!c.invalidate) {
|
|
43
|
-
c.invalidate = () => {};
|
|
44
|
-
}
|
|
45
|
-
return c as Component;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function addBoxChild(box: Box, component: unknown): boolean {
|
|
49
|
-
const child = ensureInvalidate(component);
|
|
50
|
-
box.addChild(child);
|
|
51
|
-
return isFramedBlockComponent(child);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
40
|
/**
|
|
55
41
|
* Drop trailing removal/hunk-header lines that appear in a streaming diff
|
|
56
42
|
* before the matching `+added` lines have arrived. Without this, a partial
|
|
@@ -147,12 +133,12 @@ export interface ToolExecutionHandle {
|
|
|
147
133
|
setExpanded(expanded: boolean): void;
|
|
148
134
|
}
|
|
149
135
|
|
|
150
|
-
/** Drive pending-tool redraws at
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
const SPINNER_RENDER_INTERVAL_MS =
|
|
136
|
+
/** Drive pending-tool redraws at 30fps so the animated border sweep stays
|
|
137
|
+
* smooth without spending twice the frame budget. The TUI throttles at the same
|
|
138
|
+
* cadence, and static frames diff to a no-op redraw at ~zero cost. */
|
|
139
|
+
const SPINNER_RENDER_INTERVAL_MS = 1000 / 30;
|
|
154
140
|
/** Advance the spinner glyph at its classic ~12.5fps step, decoupled from the
|
|
155
|
-
*
|
|
141
|
+
* render cadence (mirrors `Loader`). */
|
|
156
142
|
const SPINNER_GLYPH_ADVANCE_MS = 80;
|
|
157
143
|
|
|
158
144
|
// Stable per-instance counter so each tool execution's inline images get a
|
|
@@ -239,11 +225,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
239
225
|
this.#cwd = cwd;
|
|
240
226
|
this.#args = args;
|
|
241
227
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
228
|
+
// Always create both - contentBox for custom tools/bash/tools with renderers, contentText for other built-ins.
|
|
229
|
+
// paddingY is 1 so background-tinted blocks (custom/extension tools and the
|
|
230
|
+
// generic fallback) get top/bottom breathing room. TranscriptContainer
|
|
231
|
+
// strips PLAIN-blank edges, so framed/minimal blocks (no bg set) drop these
|
|
232
|
+
// lines and keep their tight spacing — only tinted lines survive.
|
|
233
|
+
this.#contentBox = new Box(0, 1);
|
|
234
|
+
this.#contentText = new Text("", 1, 1);
|
|
247
235
|
|
|
248
236
|
// Use Box for custom tools or built-in tools that have renderers
|
|
249
237
|
const hasRenderer = toolName in toolRenderers;
|
|
@@ -433,26 +421,43 @@ export class ToolExecutionComponent extends Container {
|
|
|
433
421
|
#updateSpinnerAnimation(): void {
|
|
434
422
|
// Spinner for: task tool with partial result, or edit/write while args streaming
|
|
435
423
|
const isStreamingArgs = !this.#argsComplete && (isEditLikeToolName(this.#toolName) || this.#toolName === "write");
|
|
436
|
-
const
|
|
437
|
-
this.#toolName === "task" &&
|
|
424
|
+
const isBackgroundAsyncRunning =
|
|
438
425
|
(this.#result?.details as { async?: { state?: string } } | undefined)?.async?.state === "running";
|
|
426
|
+
const isBackgroundAsyncTask = this.#toolName === "task" && isBackgroundAsyncRunning;
|
|
439
427
|
const isPartialTask = this.#isPartial && this.#toolName === "task" && !isBackgroundAsyncTask;
|
|
440
|
-
// Sweep the border of bash/eval execution blocks while they're pending
|
|
428
|
+
// Sweep the border of bash/eval execution blocks while they're pending — but
|
|
429
|
+
// not once they've been backgrounded: a backgrounded job's block gets
|
|
430
|
+
// committed to scrollback and finalizes later via the async update path, so a
|
|
431
|
+
// mid-sweep frame would freeze a stray dark "bar" segment into the border.
|
|
441
432
|
const isPendingExecBlock =
|
|
442
|
-
this.#isPartial &&
|
|
433
|
+
this.#isPartial &&
|
|
434
|
+
shimmerEnabled() &&
|
|
435
|
+
(this.#toolName === "bash" || this.#toolName === "eval") &&
|
|
436
|
+
!isBackgroundAsyncRunning;
|
|
443
437
|
const needsSpinner = isStreamingArgs || isPartialTask || isPendingExecBlock;
|
|
444
438
|
if (needsSpinner && !this.#spinnerInterval) {
|
|
445
|
-
|
|
439
|
+
const now = performance.now();
|
|
440
|
+
const frameCount = theme.spinnerFrames.length;
|
|
441
|
+
this.#lastSpinnerAdvanceAt = now;
|
|
442
|
+
if (frameCount > 0 && this.#spinnerFrame === undefined) {
|
|
443
|
+
this.#spinnerFrame = 0;
|
|
444
|
+
this.#renderState.spinnerFrame = 0;
|
|
445
|
+
}
|
|
446
446
|
this.#spinnerInterval = setInterval(() => {
|
|
447
447
|
const now = performance.now();
|
|
448
448
|
const frameCount = theme.spinnerFrames.length;
|
|
449
|
-
// Redraw at
|
|
450
|
-
// glyph
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
449
|
+
// Redraw at 30fps for a smooth border sweep, but keep the spinner
|
|
450
|
+
// glyph phase-locked to its classic ~12.5fps cadence. Advancing the
|
|
451
|
+
// anchor by elapsed frames instead of resetting to `now` avoids the
|
|
452
|
+
// 30fps timer quantizing the glyph down to one step every three ticks.
|
|
453
|
+
if (frameCount > 0) {
|
|
454
|
+
const elapsed = now - this.#lastSpinnerAdvanceAt;
|
|
455
|
+
if (elapsed >= SPINNER_GLYPH_ADVANCE_MS) {
|
|
456
|
+
const steps = Math.floor(elapsed / SPINNER_GLYPH_ADVANCE_MS);
|
|
457
|
+
this.#spinnerFrame = ((this.#spinnerFrame ?? 0) + steps) % frameCount;
|
|
458
|
+
this.#renderState.spinnerFrame = this.#spinnerFrame;
|
|
459
|
+
this.#lastSpinnerAdvanceAt += steps * SPINNER_GLYPH_ADVANCE_MS;
|
|
460
|
+
}
|
|
456
461
|
}
|
|
457
462
|
this.#ui.requestRender();
|
|
458
463
|
}, SPINNER_RENDER_INTERVAL_MS);
|
|
@@ -524,6 +529,39 @@ export class ToolExecutionComponent extends Container {
|
|
|
524
529
|
return (this.#result.details as { async?: { state?: string } } | undefined)?.async?.state === "running";
|
|
525
530
|
}
|
|
526
531
|
|
|
532
|
+
/**
|
|
533
|
+
* While a tool's preview is still streaming, a block whose preview is
|
|
534
|
+
* append-only (rows only grow at the bottom, never re-layout) lets the
|
|
535
|
+
* renderer commit the scrolled-off head of an over-tall preview to native
|
|
536
|
+
* scrollback instead of dropping it — the same anti-yank path a streaming
|
|
537
|
+
* assistant reply uses (see {@link TranscriptContainer} +
|
|
538
|
+
* `NativeScrollbackLiveRegion`). Covers both phases: a pre-result call preview
|
|
539
|
+
* (a `write` whose content streams in) and a partial-result preview that
|
|
540
|
+
* streams output below fixed input (an `eval`/`bash` whose stdout grows under
|
|
541
|
+
* its code cell). Gated on {@link isTranscriptBlockFinalized} so the boundary
|
|
542
|
+
* closes the instant the block reaches a terminal state — a final result that
|
|
543
|
+
* may collapse to a compact view, a backgrounded async tool, or a seal — and
|
|
544
|
+
* the renderer decides whether its current preview shape qualifies via
|
|
545
|
+
* `isStreamingPreviewAppendOnly` (typically: only the expanded full view,
|
|
546
|
+
* which is top-anchored; the collapsed tail window re-layouts but is bounded
|
|
547
|
+
* so it never overflows anyway).
|
|
548
|
+
*/
|
|
549
|
+
isTranscriptBlockAppendOnly(): boolean {
|
|
550
|
+
// A finalized block's preview can collapse/re-layout; only a live,
|
|
551
|
+
// still-streaming block is a candidate.
|
|
552
|
+
if (this.isTranscriptBlockFinalized()) return false;
|
|
553
|
+
const predicate =
|
|
554
|
+
(this.#tool as { isStreamingPreviewAppendOnly?: ToolRenderer["isStreamingPreviewAppendOnly"] } | undefined)
|
|
555
|
+
?.isStreamingPreviewAppendOnly ?? toolRenderers[this.#toolName]?.isStreamingPreviewAppendOnly;
|
|
556
|
+
if (!predicate) return false;
|
|
557
|
+
try {
|
|
558
|
+
return predicate(this.#getCallArgsForRender(), this.#renderState, this.#result);
|
|
559
|
+
} catch (err) {
|
|
560
|
+
logger.warn("Tool append-only predicate failed", { tool: this.#toolName, error: String(err) });
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
527
565
|
/**
|
|
528
566
|
* Mark the tool terminal even though no result arrived (the turn aborted or
|
|
529
567
|
* abandoned it) and stop animating, so it can freeze and stops pinning the
|
|
@@ -568,49 +606,49 @@ export class ToolExecutionComponent extends Container {
|
|
|
568
606
|
}
|
|
569
607
|
|
|
570
608
|
#updateDisplay(): void {
|
|
571
|
-
// Set background based on state
|
|
572
|
-
const bgFn = this.#isPartial
|
|
573
|
-
? (text: string) => theme.bg("toolPendingBg", text)
|
|
574
|
-
: this.#result?.isError
|
|
575
|
-
? (text: string) => theme.bg("toolErrorBg", text)
|
|
576
|
-
: (text: string) => theme.bg("toolSuccessBg", text);
|
|
577
|
-
|
|
578
609
|
// Sync shared mutable render state for component closures
|
|
579
610
|
this.#renderState.expanded = this.#expanded;
|
|
580
611
|
this.#renderState.isPartial = this.#isPartial;
|
|
581
612
|
this.#renderState.spinnerFrame = this.#spinnerFrame;
|
|
582
613
|
|
|
614
|
+
// Non-self-framing tools (custom/extension renderers and the generic
|
|
615
|
+
// fallback) get a padded, state-tinted block — built-ins that draw their
|
|
616
|
+
// own frame opt out below via the framed-component mark.
|
|
617
|
+
const stateBgKey = this.#isPartial ? "toolPendingBg" : this.#result?.isError ? "toolErrorBg" : "toolSuccessBg";
|
|
618
|
+
const stateBgFn = (t: string) => theme.bg(stateBgKey, t);
|
|
619
|
+
|
|
583
620
|
// Check for custom tool rendering
|
|
584
621
|
if (this.#tool && (this.#tool.renderCall || this.#tool.renderResult)) {
|
|
585
622
|
const tool = this.#tool;
|
|
586
623
|
const mergeCallAndResult = Boolean((tool as { mergeCallAndResult?: boolean }).mergeCallAndResult);
|
|
587
624
|
// Custom tools use Box for flexible component rendering
|
|
588
|
-
|
|
589
|
-
this.#contentBox.setBgFn(inline ? undefined : bgFn);
|
|
625
|
+
this.#contentBox.setBgFn(undefined);
|
|
590
626
|
this.#contentBox.clear();
|
|
591
|
-
let contentBoxHasFramedBlock = false;
|
|
592
627
|
// Mirror the built-in renderer branch so custom renderers (notably the
|
|
593
628
|
// task tool, whose live instance routes through here) receive the same
|
|
594
629
|
// render context — e.g. the `hasResult` flag that suppresses the task
|
|
595
630
|
// call preview once result lines exist.
|
|
596
631
|
this.#renderState.renderContext = this.#buildRenderContext();
|
|
597
632
|
|
|
598
|
-
// Render call component
|
|
633
|
+
// Render call component. The fallback label only stands in for a
|
|
634
|
+
// missing `renderCall`; when the call is intentionally suppressed
|
|
635
|
+
// (mergeCallAndResult once a result exists) we render nothing here so
|
|
636
|
+
// the result component isn't preceded by a redundant tool-name line.
|
|
599
637
|
const shouldRenderCall = !this.#result || !mergeCallAndResult;
|
|
600
|
-
if (shouldRenderCall
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
638
|
+
if (shouldRenderCall) {
|
|
639
|
+
if (tool.renderCall) {
|
|
640
|
+
try {
|
|
641
|
+
const callComponent = tool.renderCall(this.#getCallArgsForRender(), this.#renderState, theme);
|
|
642
|
+
if (callComponent) this.#contentBox.addChild(callComponent as Component);
|
|
643
|
+
} catch (err) {
|
|
644
|
+
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
645
|
+
// Fall back to default on error
|
|
646
|
+
this.#contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
605
647
|
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
addBoxChild(this.#contentBox, new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
648
|
+
} else {
|
|
649
|
+
// No custom renderCall, show tool name
|
|
650
|
+
this.#contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
610
651
|
}
|
|
611
|
-
} else {
|
|
612
|
-
// No custom renderCall, show tool name
|
|
613
|
-
addBoxChild(this.#contentBox, new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
614
652
|
}
|
|
615
653
|
|
|
616
654
|
// Render result component if we have a result
|
|
@@ -632,25 +670,27 @@ export class ToolExecutionComponent extends Container {
|
|
|
632
670
|
theme,
|
|
633
671
|
this.#args,
|
|
634
672
|
);
|
|
635
|
-
if (resultComponent)
|
|
636
|
-
contentBoxHasFramedBlock = addBoxChild(this.#contentBox, resultComponent) || contentBoxHasFramedBlock;
|
|
637
|
-
}
|
|
673
|
+
if (resultComponent) this.#contentBox.addChild(resultComponent);
|
|
638
674
|
} catch (err) {
|
|
639
675
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
640
676
|
// Fall back to showing raw output on error
|
|
641
677
|
const output = this.#getTextOutput();
|
|
642
678
|
if (output) {
|
|
643
|
-
|
|
679
|
+
this.#contentBox.addChild(new Text(theme.fg("toolOutput", replaceTabs(output)), 0, 0));
|
|
644
680
|
}
|
|
645
681
|
}
|
|
646
682
|
} else if (this.#result) {
|
|
647
683
|
// Has result but no custom renderResult
|
|
648
684
|
const output = this.#getTextOutput();
|
|
649
685
|
if (output) {
|
|
650
|
-
|
|
686
|
+
this.#contentBox.addChild(new Text(theme.fg("toolOutput", replaceTabs(output)), 0, 0));
|
|
651
687
|
}
|
|
652
688
|
}
|
|
653
|
-
|
|
689
|
+
// Custom tools that draw their own frame (task) render flush; plain
|
|
690
|
+
// extension renderers get the padded, state-tinted block back.
|
|
691
|
+
const customFramed = this.#contentBox.children.some(isFramedBlockComponent);
|
|
692
|
+
this.#contentBox.setPaddingX(customFramed ? 0 : 1);
|
|
693
|
+
this.#contentBox.setBgFn(customFramed ? undefined : stateBgFn);
|
|
654
694
|
} else if (this.#toolName in toolRenderers) {
|
|
655
695
|
// Built-in tools with renderers
|
|
656
696
|
const renderer = toolRenderers[this.#toolName];
|
|
@@ -669,7 +709,6 @@ export class ToolExecutionComponent extends Container {
|
|
|
669
709
|
// Multi-file: render each file as its own Box (identical to separate tool calls)
|
|
670
710
|
this.#contentBox.setBgFn(undefined);
|
|
671
711
|
this.#contentBox.clear();
|
|
672
|
-
this.#contentBox.setPaddingX(1);
|
|
673
712
|
|
|
674
713
|
const renderContext = this.#buildRenderContext();
|
|
675
714
|
this.#renderState.renderContext = renderContext;
|
|
@@ -681,20 +720,14 @@ export class ToolExecutionComponent extends Container {
|
|
|
681
720
|
this.#multiFileBoxes.push(spacer);
|
|
682
721
|
this.addChild(spacer);
|
|
683
722
|
}
|
|
684
|
-
const
|
|
685
|
-
? (text: string) => theme.bg("toolErrorBg", text)
|
|
686
|
-
: (text: string) => theme.bg("toolSuccessBg", text);
|
|
687
|
-
const fileBox = new Box(1, 1, fileBgFn);
|
|
723
|
+
const fileBox = new Box(0, 0);
|
|
688
724
|
try {
|
|
689
725
|
const resultComponent = renderer.renderResult(
|
|
690
726
|
{ content: [], details: fileResult, isError: fileResult.isError },
|
|
691
727
|
this.#renderState,
|
|
692
728
|
theme,
|
|
693
729
|
);
|
|
694
|
-
if (resultComponent)
|
|
695
|
-
const fileBoxHasFramedBlock = addBoxChild(fileBox, resultComponent);
|
|
696
|
-
fileBox.setPaddingX(fileBoxHasFramedBlock ? 0 : 1);
|
|
697
|
-
}
|
|
730
|
+
if (resultComponent) fileBox.addChild(resultComponent);
|
|
698
731
|
} catch (err) {
|
|
699
732
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
700
733
|
}
|
|
@@ -711,7 +744,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
711
744
|
const pendingSpacer = new Spacer(1);
|
|
712
745
|
this.#multiFileBoxes.push(pendingSpacer);
|
|
713
746
|
this.addChild(pendingSpacer);
|
|
714
|
-
const pendingBox = new Box(
|
|
747
|
+
const pendingBox = new Box(0, 0);
|
|
715
748
|
const pendingText = renderStatusLine(
|
|
716
749
|
{
|
|
717
750
|
icon: "pending",
|
|
@@ -727,9 +760,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
727
760
|
} else {
|
|
728
761
|
// Single-file or no result: standard rendering
|
|
729
762
|
// Inline renderers skip background styling
|
|
730
|
-
this.#contentBox.setBgFn(
|
|
763
|
+
this.#contentBox.setBgFn(undefined);
|
|
731
764
|
this.#contentBox.clear();
|
|
732
|
-
let contentBoxHasFramedBlock = false;
|
|
733
765
|
|
|
734
766
|
const renderContext = this.#buildRenderContext();
|
|
735
767
|
this.#renderState.renderContext = renderContext;
|
|
@@ -739,14 +771,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
739
771
|
// Render call component
|
|
740
772
|
try {
|
|
741
773
|
const callComponent = renderer.renderCall(this.#getCallArgsForRender(), this.#renderState, theme);
|
|
742
|
-
if (callComponent)
|
|
743
|
-
contentBoxHasFramedBlock =
|
|
744
|
-
addBoxChild(this.#contentBox, callComponent) || contentBoxHasFramedBlock;
|
|
745
|
-
}
|
|
774
|
+
if (callComponent) this.#contentBox.addChild(callComponent);
|
|
746
775
|
} catch (err) {
|
|
747
776
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
748
777
|
// Fall back to default on error
|
|
749
|
-
|
|
778
|
+
this.#contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
750
779
|
}
|
|
751
780
|
}
|
|
752
781
|
|
|
@@ -763,24 +792,20 @@ export class ToolExecutionComponent extends Container {
|
|
|
763
792
|
theme,
|
|
764
793
|
this.#getCallArgsForRender(),
|
|
765
794
|
);
|
|
766
|
-
if (resultComponent)
|
|
767
|
-
contentBoxHasFramedBlock =
|
|
768
|
-
addBoxChild(this.#contentBox, resultComponent) || contentBoxHasFramedBlock;
|
|
769
|
-
}
|
|
795
|
+
if (resultComponent) this.#contentBox.addChild(resultComponent);
|
|
770
796
|
} catch (err) {
|
|
771
797
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
772
798
|
// Fall back to showing raw output on error
|
|
773
799
|
const output = this.#getTextOutput();
|
|
774
800
|
if (output) {
|
|
775
|
-
|
|
801
|
+
this.#contentBox.addChild(new Text(theme.fg("toolOutput", replaceTabs(output)), 0, 0));
|
|
776
802
|
}
|
|
777
803
|
}
|
|
778
804
|
}
|
|
779
|
-
this.#contentBox.setPaddingX(contentBoxHasFramedBlock ? 0 : 1);
|
|
780
805
|
}
|
|
781
806
|
} else {
|
|
782
807
|
// Other built-in tools: use Text directly with caching
|
|
783
|
-
this.#contentText.setCustomBgFn(
|
|
808
|
+
this.#contentText.setCustomBgFn(stateBgFn);
|
|
784
809
|
this.#contentText.setText(this.#formatToolExecution());
|
|
785
810
|
}
|
|
786
811
|
|