@kolisachint/hoocode-agent 0.5.30 → 0.5.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/dist/core/agent-selection-eval.d.ts +91 -0
  3. package/dist/core/agent-selection-eval.d.ts.map +1 -0
  4. package/dist/core/agent-selection-eval.js +186 -0
  5. package/dist/core/agent-selection-eval.js.map +1 -0
  6. package/dist/core/agent-session-runtime.d.ts +28 -0
  7. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  8. package/dist/core/agent-session-runtime.js +61 -2
  9. package/dist/core/agent-session-runtime.js.map +1 -1
  10. package/dist/core/builtin-skills.d.ts +61 -0
  11. package/dist/core/builtin-skills.d.ts.map +1 -0
  12. package/dist/core/builtin-skills.js +106 -0
  13. package/dist/core/builtin-skills.js.map +1 -0
  14. package/dist/core/extensions/plugins/default-marketplace/.agents-plugin/marketplace.json +5 -1
  15. package/dist/core/extensions/plugins/trigger-judge.d.ts +42 -0
  16. package/dist/core/extensions/plugins/trigger-judge.d.ts.map +1 -0
  17. package/dist/core/extensions/plugins/trigger-judge.js +121 -0
  18. package/dist/core/extensions/plugins/trigger-judge.js.map +1 -0
  19. package/dist/core/external-tools.d.ts +67 -0
  20. package/dist/core/external-tools.d.ts.map +1 -0
  21. package/dist/core/external-tools.js +120 -0
  22. package/dist/core/external-tools.js.map +1 -0
  23. package/dist/core/keybindings.d.ts +102 -30
  24. package/dist/core/keybindings.d.ts.map +1 -1
  25. package/dist/core/keybindings.js +123 -29
  26. package/dist/core/keybindings.js.map +1 -1
  27. package/dist/core/mode-prompts.d.ts +15 -3
  28. package/dist/core/mode-prompts.d.ts.map +1 -1
  29. package/dist/core/mode-prompts.js +17 -29
  30. package/dist/core/mode-prompts.js.map +1 -1
  31. package/dist/core/settings-defaults.d.ts +1 -1
  32. package/dist/core/settings-defaults.d.ts.map +1 -1
  33. package/dist/core/settings-defaults.js +1 -1
  34. package/dist/core/settings-defaults.js.map +1 -1
  35. package/dist/core/settings-manager.d.ts +10 -3
  36. package/dist/core/settings-manager.d.ts.map +1 -1
  37. package/dist/core/settings-manager.js +19 -8
  38. package/dist/core/settings-manager.js.map +1 -1
  39. package/dist/core/settings-types.d.ts +1 -1
  40. package/dist/core/settings-types.d.ts.map +1 -1
  41. package/dist/core/settings-types.js.map +1 -1
  42. package/dist/core/slash-commands.d.ts.map +1 -1
  43. package/dist/core/slash-commands.js +5 -1
  44. package/dist/core/slash-commands.js.map +1 -1
  45. package/dist/core/tool-output-view.d.ts +33 -0
  46. package/dist/core/tool-output-view.d.ts.map +1 -0
  47. package/dist/core/tool-output-view.js +31 -0
  48. package/dist/core/tool-output-view.js.map +1 -0
  49. package/dist/core/tools/edit.d.ts.map +1 -1
  50. package/dist/core/tools/edit.js +4 -0
  51. package/dist/core/tools/edit.js.map +1 -1
  52. package/dist/core/tools/propose-plugin.d.ts.map +1 -1
  53. package/dist/core/tools/propose-plugin.js +7 -7
  54. package/dist/core/tools/propose-plugin.js.map +1 -1
  55. package/dist/core/tools/subagent.d.ts +7 -1
  56. package/dist/core/tools/subagent.d.ts.map +1 -1
  57. package/dist/core/tools/subagent.js +12 -24
  58. package/dist/core/tools/subagent.js.map +1 -1
  59. package/dist/extensions/core/modes.d.ts.map +1 -1
  60. package/dist/extensions/core/modes.js +21 -23
  61. package/dist/extensions/core/modes.js.map +1 -1
  62. package/dist/extensions/core/scaffold.d.ts.map +1 -1
  63. package/dist/extensions/core/scaffold.js +28 -24
  64. package/dist/extensions/core/scaffold.js.map +1 -1
  65. package/dist/init-templates.generated.d.ts +3 -0
  66. package/dist/init-templates.generated.d.ts.map +1 -1
  67. package/dist/init-templates.generated.js +12 -0
  68. package/dist/init-templates.generated.js.map +1 -1
  69. package/dist/main.d.ts.map +1 -1
  70. package/dist/main.js +14 -1
  71. package/dist/main.js.map +1 -1
  72. package/dist/modes/interactive/command-executor.d.ts +20 -0
  73. package/dist/modes/interactive/command-executor.d.ts.map +1 -1
  74. package/dist/modes/interactive/command-executor.js +138 -9
  75. package/dist/modes/interactive/command-executor.js.map +1 -1
  76. package/dist/modes/interactive/components/footer.d.ts +4 -0
  77. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/footer.js +31 -2
  79. package/dist/modes/interactive/components/footer.js.map +1 -1
  80. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
  81. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/scoped-models-selector.js +2 -2
  83. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  84. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/session-selector.js +2 -2
  86. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  87. package/dist/modes/interactive/components/settings-selector.d.ts +10 -2
  88. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  89. package/dist/modes/interactive/components/settings-selector.js +165 -23
  90. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  91. package/dist/modes/interactive/components/tool-chain-summary.d.ts +65 -0
  92. package/dist/modes/interactive/components/tool-chain-summary.d.ts.map +1 -0
  93. package/dist/modes/interactive/components/tool-chain-summary.js +216 -0
  94. package/dist/modes/interactive/components/tool-chain-summary.js.map +1 -0
  95. package/dist/modes/interactive/components/tool-chain.d.ts +61 -0
  96. package/dist/modes/interactive/components/tool-chain.d.ts.map +1 -0
  97. package/dist/modes/interactive/components/tool-chain.js +156 -0
  98. package/dist/modes/interactive/components/tool-chain.js.map +1 -0
  99. package/dist/modes/interactive/components/tool-execution.d.ts +55 -9
  100. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  101. package/dist/modes/interactive/components/tool-execution.js +173 -33
  102. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  103. package/dist/modes/interactive/components/tool-signal.d.ts +50 -0
  104. package/dist/modes/interactive/components/tool-signal.d.ts.map +1 -0
  105. package/dist/modes/interactive/components/tool-signal.js +151 -0
  106. package/dist/modes/interactive/components/tool-signal.js.map +1 -0
  107. package/dist/modes/interactive/interactive-mode.d.ts +50 -1
  108. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/dist/modes/interactive/interactive-mode.js +244 -29
  110. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/dist/modes/interactive/resource-display.d.ts +9 -0
  112. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  113. package/dist/modes/interactive/resource-display.js +13 -0
  114. package/dist/modes/interactive/resource-display.js.map +1 -1
  115. package/dist/utils/tools-manager.d.ts +30 -0
  116. package/dist/utils/tools-manager.d.ts.map +1 -1
  117. package/dist/utils/tools-manager.js +35 -0
  118. package/dist/utils/tools-manager.js.map +1 -1
  119. package/docs/modes.md +4 -0
  120. package/docs/plugins.md +10 -0
  121. package/docs/settings.md +42 -0
  122. package/docs/skills.md +30 -0
  123. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  124. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  125. package/examples/extensions/sandbox/package.json +1 -1
  126. package/examples/extensions/with-deps/package.json +1 -1
  127. package/package.json +5 -4
  128. package/templates/prompts/grill-bridge.md +1 -0
  129. package/templates/prompts/grill-me.md +7 -0
  130. package/templates/prompts/grill-plan.md +9 -0
  131. package/templates/prompts/task-background-agents.md +2 -0
  132. package/templates/prompts/task-background-none.md +1 -0
  133. package/templates/prompts/task-main.md +19 -0
  134. package/templates/skills/plugin-authoring/SKILL.md +81 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The radar view's chain line: one line for a run of consecutive tool calls.
3
+ *
4
+ * Two renderings of the same chain, because a run that is still going and a run
5
+ * that is over answer different questions.
6
+ *
7
+ * While it runs you want the shape — what it did, in order, and where it broke:
8
+ *
9
+ * ◐ grep › read › bash✗ › edit › bash… 4 done · 1 failed · running
10
+ *
11
+ * Once it is over the order has stopped mattering and what is left is what it
12
+ * amounted to:
13
+ *
14
+ * ● Edited packages/tui/src/keys.ts 5 calls · 1 failed
15
+ *
16
+ * A chain that did *not* finish keeps the running form plus a marker. The
17
+ * settled form is a claim about what was accomplished, and a chain cut off
18
+ * partway through has no such claim to make — the same reason
19
+ * `settleDanglingMainTasks` refuses to mark an aborted turn's plan items done.
20
+ *
21
+ * Everything here is pure and deterministic. A model-written phase label would
22
+ * read better than the inferred one, but it would cost tokens on every turn and
23
+ * this agent's whole premise is that nothing happens you did not pay for on
24
+ * purpose.
25
+ */
26
+ /** Segments beyond this many are elided in the middle rather than wrapped. */
27
+ const MAX_SEGMENTS = 8;
28
+ /** Kept at the head when eliding. */
29
+ const HEAD_SEGMENTS = 3;
30
+ /** Kept at the tail when eliding. */
31
+ const TAIL_SEGMENTS = 2;
32
+ function tone(entry) {
33
+ if (entry.isError)
34
+ return "error";
35
+ if (entry.isPartial)
36
+ return "running";
37
+ return "ok";
38
+ }
39
+ /**
40
+ * Collapse consecutive calls to the same tool into one segment.
41
+ *
42
+ * A run of five reads is one act, not five, and spelling it out crowds out the
43
+ * calls either side of it. A failure never merges: `read ×4` hiding one broken
44
+ * read would defeat the point of the line.
45
+ */
46
+ function collapseRepeats(entries) {
47
+ const segments = [];
48
+ let index = 0;
49
+ while (index < entries.length) {
50
+ const entry = entries[index];
51
+ const entryTone = tone(entry);
52
+ let run = 1;
53
+ while (index + run < entries.length &&
54
+ entries[index + run].tool === entry.tool &&
55
+ tone(entries[index + run]) === entryTone &&
56
+ entryTone === "ok") {
57
+ run++;
58
+ }
59
+ const marker = entryTone === "error" ? "✗" : "";
60
+ segments.push({ label: run > 1 ? `${entry.tool} ×${run}` : `${entry.tool}${marker}`, tone: entryTone });
61
+ index += run;
62
+ }
63
+ return segments;
64
+ }
65
+ /**
66
+ * Elide the middle of a long chain, keeping both ends legible.
67
+ *
68
+ * Failures are never elided. Where a run broke is the one thing the line exists
69
+ * to show, and a long chain is exactly when you most need it — hiding the ✗
70
+ * inside "… 22 more …" would leave the stats claiming a failure the line cannot
71
+ * point at.
72
+ */
73
+ function capSegments(segments) {
74
+ if (segments.length <= MAX_SEGMENTS)
75
+ return segments;
76
+ const keep = new Set();
77
+ for (let i = 0; i < HEAD_SEGMENTS; i++)
78
+ keep.add(i);
79
+ for (let i = segments.length - TAIL_SEGMENTS; i < segments.length; i++)
80
+ keep.add(i);
81
+ segments.forEach((segment, i) => {
82
+ if (segment.tone === "error")
83
+ keep.add(i);
84
+ });
85
+ const out = [];
86
+ let gap = 0;
87
+ const flushGap = () => {
88
+ if (gap > 0)
89
+ out.push({ label: `… ${gap} more …`, tone: "ok" });
90
+ gap = 0;
91
+ };
92
+ segments.forEach((segment, i) => {
93
+ if (keep.has(i)) {
94
+ flushGap();
95
+ out.push(segment);
96
+ }
97
+ else {
98
+ gap++;
99
+ }
100
+ });
101
+ flushGap();
102
+ return out;
103
+ }
104
+ /** The `grep › read × 3 › bash✗` part of a running chain line. */
105
+ export function chainSegments(entries) {
106
+ return capSegments(collapseRepeats(entries));
107
+ }
108
+ function plural(n, one, many = `${one}s`) {
109
+ return `${n} ${n === 1 ? one : many}`;
110
+ }
111
+ /** The flush-right stats for either rendering. */
112
+ export function chainStats(entries, state) {
113
+ const failed = entries.filter((e) => e.isError).length;
114
+ const parts = [];
115
+ if (state === "running") {
116
+ parts.push(`${entries.filter((e) => !e.isPartial).length} done`);
117
+ }
118
+ else {
119
+ parts.push(plural(entries.length, "call"));
120
+ }
121
+ if (failed > 0)
122
+ parts.push(`${failed} failed`);
123
+ if (state === "running") {
124
+ parts.push("running");
125
+ }
126
+ else if (state === "interrupted") {
127
+ parts.push("interrupted");
128
+ }
129
+ else {
130
+ const lines = entries.reduce((sum, e) => sum + e.outputLines, 0);
131
+ if (lines > 0)
132
+ parts.push(plural(lines, "line"));
133
+ }
134
+ return parts.join(" · ");
135
+ }
136
+ /**
137
+ * Tool families, in the order a settled chain reports them.
138
+ *
139
+ * The ordering is the whole trick: a chain that read six files and then changed
140
+ * one is remembered as the edit. Mutation outranks execution outranks reading
141
+ * outranks searching, so the phrase names the most consequential thing the
142
+ * chain did rather than the most frequent.
143
+ */
144
+ const FAMILIES = [
145
+ { verb: "Edited", noun: "file", tools: ["edit", "write", "MultiEdit", "NotebookEdit"] },
146
+ { verb: "Ran", noun: "command", tools: ["bash"] },
147
+ { verb: "Delegated", noun: "task", tools: ["Task", "TaskOutput"] },
148
+ { verb: "Fetched", noun: "page", tools: ["webfetch", "websearch"] },
149
+ { verb: "Read", noun: "file", tools: ["read"] },
150
+ { verb: "Searched", noun: "search", tools: ["grep", "find", "search", "ls"] },
151
+ ];
152
+ /** Longest common directory prefix of the given paths, or "" when they share none. */
153
+ function commonPathPrefix(paths) {
154
+ if (paths.length === 0)
155
+ return "";
156
+ const split = paths.map((p) => p.split("/").filter(Boolean));
157
+ const [first, ...rest] = split;
158
+ const prefix = [];
159
+ for (let i = 0; i < first.length; i++) {
160
+ if (rest.every((parts) => parts[i] === first[i]))
161
+ prefix.push(first[i]);
162
+ else
163
+ break;
164
+ }
165
+ return prefix.join("/");
166
+ }
167
+ /**
168
+ * Whether a subject can stand in for a location.
169
+ *
170
+ * Globs are excluded even though they look pathlike: "Searched *.test.ts" reads
171
+ * as a place the chain worked in, which is exactly what it is not.
172
+ */
173
+ function usableAsTarget(subject) {
174
+ if (/[*?]/.test(subject))
175
+ return false;
176
+ return subject.includes("/") || /\.[a-zA-Z0-9]+$/.test(subject);
177
+ }
178
+ /**
179
+ * The settled chain's phrase: what this run amounted to.
180
+ *
181
+ * Deliberately one clause, not an inventory. "Edited packages/tui, read 6
182
+ * files, searched packages" is a worse line than "Edited packages/tui" — the
183
+ * count is already in the stats, and the per-call detail is one `alt+u` away.
184
+ *
185
+ * A location is only named when the calls actually share one. Naming a single
186
+ * arbitrary file out of five would read as a claim the chain never made, so
187
+ * several unrelated targets collapse to a count instead.
188
+ */
189
+ export function chainPhrase(entries) {
190
+ if (entries.length === 0)
191
+ return "No calls";
192
+ for (const family of FAMILIES) {
193
+ const matched = entries.filter((e) => family.tools.includes(e.tool));
194
+ if (matched.length === 0)
195
+ continue;
196
+ const subjects = matched.map((e) => e.subject).filter(Boolean);
197
+ // A lone call is fully described by its own subject, whatever shape it is:
198
+ // a command, a pattern, a URL, a path.
199
+ if (matched.length === 1 && subjects.length === 1) {
200
+ return `${family.verb} ${subjects[0]}`;
201
+ }
202
+ // Several calls: name the place they share, or fall back to a count.
203
+ const target = commonPathPrefix(subjects.filter(usableAsTarget));
204
+ if (target)
205
+ return `${family.verb} ${target}`;
206
+ // Searching has no natural plural noun ("3 searches" says nothing the
207
+ // stats do not), so the bare verb carries it.
208
+ if (family.noun === "search")
209
+ return "Explored";
210
+ return `${family.verb} ${plural(matched.length, family.noun)}`;
211
+ }
212
+ // An unrecognised tool set still gets a line rather than a blank.
213
+ const names = [...new Set(entries.map((e) => e.tool))];
214
+ return names.length === 1 ? `Called ${names[0]}` : `Called ${plural(names.length, "tool")}`;
215
+ }
216
+ //# sourceMappingURL=tool-chain-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-chain-summary.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain-summary.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The radar view's chain line: one line for a run of consecutive tool calls.\n *\n * Two renderings of the same chain, because a run that is still going and a run\n * that is over answer different questions.\n *\n * While it runs you want the shape — what it did, in order, and where it broke:\n *\n * ◐ grep › read › bash✗ › edit › bash… 4 done · 1 failed · running\n *\n * Once it is over the order has stopped mattering and what is left is what it\n * amounted to:\n *\n * ● Edited packages/tui/src/keys.ts 5 calls · 1 failed\n *\n * A chain that did *not* finish keeps the running form plus a marker. The\n * settled form is a claim about what was accomplished, and a chain cut off\n * partway through has no such claim to make — the same reason\n * `settleDanglingMainTasks` refuses to mark an aborted turn's plan items done.\n *\n * Everything here is pure and deterministic. A model-written phase label would\n * read better than the inferred one, but it would cost tokens on every turn and\n * this agent's whole premise is that nothing happens you did not pay for on\n * purpose.\n */\n\n/** One tool call's contribution to its chain. */\nexport interface ChainEntry {\n\ttool: string;\n\t/** The call's subject — a path, a command, a pattern. */\n\tsubject: string;\n\tisError: boolean;\n\t/** Still executing. */\n\tisPartial: boolean;\n\t/** Lines of text output the call returned. */\n\toutputLines: number;\n}\n\nexport type ChainState =\n\t/** Calls are still arriving or executing. */\n\t| \"running\"\n\t/** Every call finished and the turn moved on cleanly. */\n\t| \"done\"\n\t/** The turn was aborted, errored, or hit a length cap partway through. */\n\t| \"interrupted\";\n\nexport type SegmentTone = \"ok\" | \"error\" | \"running\";\n\nexport interface ChainSegment {\n\tlabel: string;\n\ttone: SegmentTone;\n}\n\n/** Segments beyond this many are elided in the middle rather than wrapped. */\nconst MAX_SEGMENTS = 8;\n/** Kept at the head when eliding. */\nconst HEAD_SEGMENTS = 3;\n/** Kept at the tail when eliding. */\nconst TAIL_SEGMENTS = 2;\n\nfunction tone(entry: ChainEntry): SegmentTone {\n\tif (entry.isError) return \"error\";\n\tif (entry.isPartial) return \"running\";\n\treturn \"ok\";\n}\n\n/**\n * Collapse consecutive calls to the same tool into one segment.\n *\n * A run of five reads is one act, not five, and spelling it out crowds out the\n * calls either side of it. A failure never merges: `read ×4` hiding one broken\n * read would defeat the point of the line.\n */\nfunction collapseRepeats(entries: ChainEntry[]): ChainSegment[] {\n\tconst segments: ChainSegment[] = [];\n\tlet index = 0;\n\twhile (index < entries.length) {\n\t\tconst entry = entries[index];\n\t\tconst entryTone = tone(entry);\n\t\tlet run = 1;\n\t\twhile (\n\t\t\tindex + run < entries.length &&\n\t\t\tentries[index + run].tool === entry.tool &&\n\t\t\ttone(entries[index + run]) === entryTone &&\n\t\t\tentryTone === \"ok\"\n\t\t) {\n\t\t\trun++;\n\t\t}\n\t\tconst marker = entryTone === \"error\" ? \"✗\" : \"\";\n\t\tsegments.push({ label: run > 1 ? `${entry.tool} ×${run}` : `${entry.tool}${marker}`, tone: entryTone });\n\t\tindex += run;\n\t}\n\treturn segments;\n}\n\n/**\n * Elide the middle of a long chain, keeping both ends legible.\n *\n * Failures are never elided. Where a run broke is the one thing the line exists\n * to show, and a long chain is exactly when you most need it — hiding the ✗\n * inside \"… 22 more …\" would leave the stats claiming a failure the line cannot\n * point at.\n */\nfunction capSegments(segments: ChainSegment[]): ChainSegment[] {\n\tif (segments.length <= MAX_SEGMENTS) return segments;\n\n\tconst keep = new Set<number>();\n\tfor (let i = 0; i < HEAD_SEGMENTS; i++) keep.add(i);\n\tfor (let i = segments.length - TAIL_SEGMENTS; i < segments.length; i++) keep.add(i);\n\tsegments.forEach((segment, i) => {\n\t\tif (segment.tone === \"error\") keep.add(i);\n\t});\n\n\tconst out: ChainSegment[] = [];\n\tlet gap = 0;\n\tconst flushGap = () => {\n\t\tif (gap > 0) out.push({ label: `… ${gap} more …`, tone: \"ok\" });\n\t\tgap = 0;\n\t};\n\tsegments.forEach((segment, i) => {\n\t\tif (keep.has(i)) {\n\t\t\tflushGap();\n\t\t\tout.push(segment);\n\t\t} else {\n\t\t\tgap++;\n\t\t}\n\t});\n\tflushGap();\n\treturn out;\n}\n\n/** The `grep › read × 3 › bash✗` part of a running chain line. */\nexport function chainSegments(entries: ChainEntry[]): ChainSegment[] {\n\treturn capSegments(collapseRepeats(entries));\n}\n\nfunction plural(n: number, one: string, many = `${one}s`): string {\n\treturn `${n} ${n === 1 ? one : many}`;\n}\n\n/** The flush-right stats for either rendering. */\nexport function chainStats(entries: ChainEntry[], state: ChainState): string {\n\tconst failed = entries.filter((e) => e.isError).length;\n\tconst parts: string[] = [];\n\n\tif (state === \"running\") {\n\t\tparts.push(`${entries.filter((e) => !e.isPartial).length} done`);\n\t} else {\n\t\tparts.push(plural(entries.length, \"call\"));\n\t}\n\tif (failed > 0) parts.push(`${failed} failed`);\n\n\tif (state === \"running\") {\n\t\tparts.push(\"running\");\n\t} else if (state === \"interrupted\") {\n\t\tparts.push(\"interrupted\");\n\t} else {\n\t\tconst lines = entries.reduce((sum, e) => sum + e.outputLines, 0);\n\t\tif (lines > 0) parts.push(plural(lines, \"line\"));\n\t}\n\treturn parts.join(\" · \");\n}\n\n/**\n * Tool families, in the order a settled chain reports them.\n *\n * The ordering is the whole trick: a chain that read six files and then changed\n * one is remembered as the edit. Mutation outranks execution outranks reading\n * outranks searching, so the phrase names the most consequential thing the\n * chain did rather than the most frequent.\n */\nconst FAMILIES: Array<{ verb: string; noun: string; tools: string[] }> = [\n\t{ verb: \"Edited\", noun: \"file\", tools: [\"edit\", \"write\", \"MultiEdit\", \"NotebookEdit\"] },\n\t{ verb: \"Ran\", noun: \"command\", tools: [\"bash\"] },\n\t{ verb: \"Delegated\", noun: \"task\", tools: [\"Task\", \"TaskOutput\"] },\n\t{ verb: \"Fetched\", noun: \"page\", tools: [\"webfetch\", \"websearch\"] },\n\t{ verb: \"Read\", noun: \"file\", tools: [\"read\"] },\n\t{ verb: \"Searched\", noun: \"search\", tools: [\"grep\", \"find\", \"search\", \"ls\"] },\n];\n\n/** Longest common directory prefix of the given paths, or \"\" when they share none. */\nfunction commonPathPrefix(paths: string[]): string {\n\tif (paths.length === 0) return \"\";\n\tconst split = paths.map((p) => p.split(\"/\").filter(Boolean));\n\tconst [first, ...rest] = split;\n\tconst prefix: string[] = [];\n\tfor (let i = 0; i < first.length; i++) {\n\t\tif (rest.every((parts) => parts[i] === first[i])) prefix.push(first[i]);\n\t\telse break;\n\t}\n\treturn prefix.join(\"/\");\n}\n\n/**\n * Whether a subject can stand in for a location.\n *\n * Globs are excluded even though they look pathlike: \"Searched *.test.ts\" reads\n * as a place the chain worked in, which is exactly what it is not.\n */\nfunction usableAsTarget(subject: string): boolean {\n\tif (/[*?]/.test(subject)) return false;\n\treturn subject.includes(\"/\") || /\\.[a-zA-Z0-9]+$/.test(subject);\n}\n\n/**\n * The settled chain's phrase: what this run amounted to.\n *\n * Deliberately one clause, not an inventory. \"Edited packages/tui, read 6\n * files, searched packages\" is a worse line than \"Edited packages/tui\" — the\n * count is already in the stats, and the per-call detail is one `alt+u` away.\n *\n * A location is only named when the calls actually share one. Naming a single\n * arbitrary file out of five would read as a claim the chain never made, so\n * several unrelated targets collapse to a count instead.\n */\nexport function chainPhrase(entries: ChainEntry[]): string {\n\tif (entries.length === 0) return \"No calls\";\n\n\tfor (const family of FAMILIES) {\n\t\tconst matched = entries.filter((e) => family.tools.includes(e.tool));\n\t\tif (matched.length === 0) continue;\n\n\t\tconst subjects = matched.map((e) => e.subject).filter(Boolean);\n\n\t\t// A lone call is fully described by its own subject, whatever shape it is:\n\t\t// a command, a pattern, a URL, a path.\n\t\tif (matched.length === 1 && subjects.length === 1) {\n\t\t\treturn `${family.verb} ${subjects[0]}`;\n\t\t}\n\n\t\t// Several calls: name the place they share, or fall back to a count.\n\t\tconst target = commonPathPrefix(subjects.filter(usableAsTarget));\n\t\tif (target) return `${family.verb} ${target}`;\n\t\t// Searching has no natural plural noun (\"3 searches\" says nothing the\n\t\t// stats do not), so the bare verb carries it.\n\t\tif (family.noun === \"search\") return \"Explored\";\n\t\treturn `${family.verb} ${plural(matched.length, family.noun)}`;\n\t}\n\n\t// An unrecognised tool set still gets a line rather than a blank.\n\tconst names = [...new Set(entries.map((e) => e.tool))];\n\treturn names.length === 1 ? `Called ${names[0]}` : `Called ${plural(names.length, \"tool\")}`;\n}\n"]}
@@ -0,0 +1,61 @@
1
+ import { type Component, Container } from "@kolisachint/hoocode-tui";
2
+ import type { ToolOutputView } from "../../../core/tool-output-view.js";
3
+ import type { ToolExecutionComponent } from "./tool-execution.js";
4
+ /**
5
+ * A run of consecutive tool calls, rendered as one line in the radar view.
6
+ *
7
+ * The chain owns its blocks rather than sitting beside them, so there is one
8
+ * place that decides whether you are looking at a summary or at the calls. In
9
+ * every view but a collapsed radar it is a plain pass-through container and the
10
+ * blocks render exactly as they always did.
11
+ *
12
+ * A chain closes when the agent next speaks, or when the turn settles. Closing
13
+ * early matters more than it looks: rewriting a line that has scrolled above
14
+ * the viewport forces the TUI into a full redraw, which clears the terminal's
15
+ * scrollback. Chains that close while they are still the bottom of the screen
16
+ * flip for the price of one line.
17
+ */
18
+ export declare class ToolChainComponent extends Container {
19
+ private blocks;
20
+ private view;
21
+ private state;
22
+ /** Opened into its per-call rows by `app.tools.unfoldOne`. */
23
+ private opened;
24
+ private memo?;
25
+ constructor(view: ToolOutputView);
26
+ add(block: ToolExecutionComponent): void;
27
+ /** True until the agent speaks or the turn settles. */
28
+ get isOpen(): boolean;
29
+ get isOpened(): boolean;
30
+ get isEmpty(): boolean;
31
+ get toolBlocks(): readonly ToolExecutionComponent[];
32
+ /**
33
+ * Settle the chain.
34
+ *
35
+ * `interrupted` keeps the running rendering, because the settled phrase is a
36
+ * claim about what the run amounted to and a run cut off partway through has
37
+ * no such claim to make — the same reason an aborted turn's plan items settle
38
+ * to cancelled rather than done.
39
+ */
40
+ close(outcome: "done" | "interrupted"): void;
41
+ setView(view: ToolOutputView): void;
42
+ /** The global expand key opens every block, which also opens the chain. */
43
+ setExpanded(expanded: boolean): void;
44
+ /** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */
45
+ setOpened(opened: boolean): void;
46
+ invalidate(): void;
47
+ /** Whether this chain is currently drawn as a single summary line. */
48
+ private isCollapsed;
49
+ render(width: number): string[];
50
+ /**
51
+ * Each failed call's reason, indented under the chain line.
52
+ *
53
+ * A collapsed chain hides its blocks, so without this a failure would be a
54
+ * count in the stats and nothing else. Whatever else these views fold away,
55
+ * they never fold away why something broke.
56
+ */
57
+ private failureLines;
58
+ }
59
+ /** Narrow a transcript child to a chain. */
60
+ export declare function isToolChain(child: Component): child is ToolChainComponent;
61
+ //# sourceMappingURL=tool-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-chain.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AACpG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAGxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,qBAAa,kBAAmB,SAAQ,SAAS;IAChD,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,KAAK,CAAyB;IACtC,8DAA8D;IAC9D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAC,CAAmC;IAEhD,YAAY,IAAI,EAAE,cAAc,EAG/B;IAED,GAAG,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAIvC;IAED,uDAAuD;IACvD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,UAAU,IAAI,SAAS,sBAAsB,EAAE,CAElD;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAG3C;IAED,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAIlC;IAED,2EAA2E;IAC3E,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAInC;IAED,iFAAiF;IACjF,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAG/B;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,sEAAsE;IACtE,OAAO,CAAC,WAAW;IAIV,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA4CvC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;CAcpB;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,IAAI,kBAAkB,CAEzE","sourcesContent":["import { type Component, Container, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { type ChainState, chainPhrase, chainSegments, chainStats } from \"./tool-chain-summary.js\";\nimport type { ToolExecutionComponent } from \"./tool-execution.js\";\n\n/**\n * A run of consecutive tool calls, rendered as one line in the radar view.\n *\n * The chain owns its blocks rather than sitting beside them, so there is one\n * place that decides whether you are looking at a summary or at the calls. In\n * every view but a collapsed radar it is a plain pass-through container and the\n * blocks render exactly as they always did.\n *\n * A chain closes when the agent next speaks, or when the turn settles. Closing\n * early matters more than it looks: rewriting a line that has scrolled above\n * the viewport forces the TUI into a full redraw, which clears the terminal's\n * scrollback. Chains that close while they are still the bottom of the screen\n * flip for the price of one line.\n */\nexport class ToolChainComponent extends Container {\n\tprivate blocks: ToolExecutionComponent[] = [];\n\tprivate view: ToolOutputView;\n\tprivate state: ChainState = \"running\";\n\t/** Opened into its per-call rows by `app.tools.unfoldOne`. */\n\tprivate opened = false;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(view: ToolOutputView) {\n\t\tsuper();\n\t\tthis.view = view;\n\t}\n\n\tadd(block: ToolExecutionComponent): void {\n\t\tthis.blocks.push(block);\n\t\tthis.addChild(block);\n\t\tthis.memo = undefined;\n\t}\n\n\t/** True until the agent speaks or the turn settles. */\n\tget isOpen(): boolean {\n\t\treturn this.state === \"running\";\n\t}\n\n\tget isOpened(): boolean {\n\t\treturn this.opened;\n\t}\n\n\tget isEmpty(): boolean {\n\t\treturn this.blocks.length === 0;\n\t}\n\n\tget toolBlocks(): readonly ToolExecutionComponent[] {\n\t\treturn this.blocks;\n\t}\n\n\t/**\n\t * Settle the chain.\n\t *\n\t * `interrupted` keeps the running rendering, because the settled phrase is a\n\t * claim about what the run amounted to and a run cut off partway through has\n\t * no such claim to make — the same reason an aborted turn's plan items settle\n\t * to cancelled rather than done.\n\t */\n\tclose(outcome: \"done\" | \"interrupted\"): void {\n\t\tthis.state = outcome;\n\t\tthis.memo = undefined;\n\t}\n\n\tsetView(view: ToolOutputView): void {\n\t\tthis.view = view;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setView(view);\n\t}\n\n\t/** The global expand key opens every block, which also opens the chain. */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.opened = expanded;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setExpanded(expanded);\n\t}\n\n\t/** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */\n\tsetOpened(opened: boolean): void {\n\t\tthis.opened = opened;\n\t\tthis.memo = undefined;\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.memo = undefined;\n\t}\n\n\t/** Whether this chain is currently drawn as a single summary line. */\n\tprivate isCollapsed(): boolean {\n\t\treturn this.view === \"radar\" && !this.opened && this.blocks.length > 0;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (!this.isCollapsed()) return super.render(width);\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\n\t\tconst entries = this.blocks.map((block) => block.chainEntry());\n\t\tconst running = this.state === \"running\";\n\t\tconst glyph = running ? \"◐\" : \"●\";\n\t\tconst glyphTone = entries.some((e) => e.isError) ? \"error\" : running ? \"warning\" : \"success\";\n\n\t\t// While it runs the chain shows its shape in order; once it is over, what\n\t\t// it amounted to. See tool-chain-summary.ts for why they differ.\n\t\tlet leftPlain: string;\n\t\tlet leftStyled: string;\n\t\tif (this.state === \"done\") {\n\t\t\tconst phrase = chainPhrase(entries);\n\t\t\tleftPlain = phrase;\n\t\t\tleftStyled = theme.fg(\"toolTitle\", phrase);\n\t\t} else {\n\t\t\tconst segments = chainSegments(entries);\n\t\t\tconst sep = \" › \";\n\t\t\tleftPlain = segments.map((s) => s.label).join(sep) + (running ? \"…\" : \"\");\n\t\t\tleftStyled =\n\t\t\t\tsegments\n\t\t\t\t\t.map((s) =>\n\t\t\t\t\t\ttheme.fg(s.tone === \"error\" ? \"error\" : s.tone === \"running\" ? \"warning\" : \"toolTitle\", s.label),\n\t\t\t\t\t)\n\t\t\t\t\t.join(theme.fg(\"dim\", sep)) + (running ? theme.fg(\"dim\", \"…\") : \"\");\n\t\t}\n\n\t\tconst stats = chainStats(entries, this.state);\n\t\t// \" ● \" + left, then stats flush right when there is room for both.\n\t\tconst prefix = `${glyph} `;\n\t\tconst budget = Math.max(0, width - 1 - visibleWidth(prefix));\n\t\tlet line: string;\n\t\tif (visibleWidth(leftPlain) + 2 + visibleWidth(stats) <= budget) {\n\t\t\tconst pad = \" \".repeat(budget - visibleWidth(leftPlain) - visibleWidth(stats));\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${leftStyled}${pad}${theme.fg(\"muted\", stats)}`;\n\t\t} else {\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${truncateToWidth(leftStyled, budget)}`;\n\t\t}\n\n\t\tconst out = [line, ...this.failureLines(width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n\n\t/**\n\t * Each failed call's reason, indented under the chain line.\n\t *\n\t * A collapsed chain hides its blocks, so without this a failure would be a\n\t * count in the stats and nothing else. Whatever else these views fold away,\n\t * they never fold away why something broke.\n\t */\n\tprivate failureLines(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const block of this.blocks) {\n\t\t\tconst entry = block.chainEntry();\n\t\t\tif (!entry.isError) continue;\n\t\t\tconst header = ` ${theme.fg(\"error\", \"✗\")} ${theme.fg(\"toolTitle\", entry.tool)} ${theme.fg(\"toolOutput\", entry.subject)}`;\n\t\t\tlines.push(truncateToWidth(header, width));\n\t\t\tfor (const raw of block.errorText().split(\"\\n\")) {\n\t\t\t\tif (!raw.trim()) continue;\n\t\t\t\tlines.push(truncateToWidth(` ${theme.fg(\"toolOutput\", raw)}`, width));\n\t\t\t}\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/** Narrow a transcript child to a chain. */\nexport function isToolChain(child: Component): child is ToolChainComponent {\n\treturn child instanceof ToolChainComponent;\n}\n"]}
@@ -0,0 +1,156 @@
1
+ import { Container, truncateToWidth, visibleWidth } from "@kolisachint/hoocode-tui";
2
+ import { theme } from "../theme/theme.js";
3
+ import { chainPhrase, chainSegments, chainStats } from "./tool-chain-summary.js";
4
+ /**
5
+ * A run of consecutive tool calls, rendered as one line in the radar view.
6
+ *
7
+ * The chain owns its blocks rather than sitting beside them, so there is one
8
+ * place that decides whether you are looking at a summary or at the calls. In
9
+ * every view but a collapsed radar it is a plain pass-through container and the
10
+ * blocks render exactly as they always did.
11
+ *
12
+ * A chain closes when the agent next speaks, or when the turn settles. Closing
13
+ * early matters more than it looks: rewriting a line that has scrolled above
14
+ * the viewport forces the TUI into a full redraw, which clears the terminal's
15
+ * scrollback. Chains that close while they are still the bottom of the screen
16
+ * flip for the price of one line.
17
+ */
18
+ export class ToolChainComponent extends Container {
19
+ blocks = [];
20
+ view;
21
+ state = "running";
22
+ /** Opened into its per-call rows by `app.tools.unfoldOne`. */
23
+ opened = false;
24
+ memo;
25
+ constructor(view) {
26
+ super();
27
+ this.view = view;
28
+ }
29
+ add(block) {
30
+ this.blocks.push(block);
31
+ this.addChild(block);
32
+ this.memo = undefined;
33
+ }
34
+ /** True until the agent speaks or the turn settles. */
35
+ get isOpen() {
36
+ return this.state === "running";
37
+ }
38
+ get isOpened() {
39
+ return this.opened;
40
+ }
41
+ get isEmpty() {
42
+ return this.blocks.length === 0;
43
+ }
44
+ get toolBlocks() {
45
+ return this.blocks;
46
+ }
47
+ /**
48
+ * Settle the chain.
49
+ *
50
+ * `interrupted` keeps the running rendering, because the settled phrase is a
51
+ * claim about what the run amounted to and a run cut off partway through has
52
+ * no such claim to make — the same reason an aborted turn's plan items settle
53
+ * to cancelled rather than done.
54
+ */
55
+ close(outcome) {
56
+ this.state = outcome;
57
+ this.memo = undefined;
58
+ }
59
+ setView(view) {
60
+ this.view = view;
61
+ this.memo = undefined;
62
+ for (const block of this.blocks)
63
+ block.setView(view);
64
+ }
65
+ /** The global expand key opens every block, which also opens the chain. */
66
+ setExpanded(expanded) {
67
+ this.opened = expanded;
68
+ this.memo = undefined;
69
+ for (const block of this.blocks)
70
+ block.setExpanded(expanded);
71
+ }
72
+ /** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */
73
+ setOpened(opened) {
74
+ this.opened = opened;
75
+ this.memo = undefined;
76
+ }
77
+ invalidate() {
78
+ super.invalidate();
79
+ this.memo = undefined;
80
+ }
81
+ /** Whether this chain is currently drawn as a single summary line. */
82
+ isCollapsed() {
83
+ return this.view === "radar" && !this.opened && this.blocks.length > 0;
84
+ }
85
+ render(width) {
86
+ if (!this.isCollapsed())
87
+ return super.render(width);
88
+ if (this.memo && this.memo.width === width)
89
+ return this.memo.out;
90
+ const entries = this.blocks.map((block) => block.chainEntry());
91
+ const running = this.state === "running";
92
+ const glyph = running ? "◐" : "●";
93
+ const glyphTone = entries.some((e) => e.isError) ? "error" : running ? "warning" : "success";
94
+ // While it runs the chain shows its shape in order; once it is over, what
95
+ // it amounted to. See tool-chain-summary.ts for why they differ.
96
+ let leftPlain;
97
+ let leftStyled;
98
+ if (this.state === "done") {
99
+ const phrase = chainPhrase(entries);
100
+ leftPlain = phrase;
101
+ leftStyled = theme.fg("toolTitle", phrase);
102
+ }
103
+ else {
104
+ const segments = chainSegments(entries);
105
+ const sep = " › ";
106
+ leftPlain = segments.map((s) => s.label).join(sep) + (running ? "…" : "");
107
+ leftStyled =
108
+ segments
109
+ .map((s) => theme.fg(s.tone === "error" ? "error" : s.tone === "running" ? "warning" : "toolTitle", s.label))
110
+ .join(theme.fg("dim", sep)) + (running ? theme.fg("dim", "…") : "");
111
+ }
112
+ const stats = chainStats(entries, this.state);
113
+ // " ● " + left, then stats flush right when there is room for both.
114
+ const prefix = `${glyph} `;
115
+ const budget = Math.max(0, width - 1 - visibleWidth(prefix));
116
+ let line;
117
+ if (visibleWidth(leftPlain) + 2 + visibleWidth(stats) <= budget) {
118
+ const pad = " ".repeat(budget - visibleWidth(leftPlain) - visibleWidth(stats));
119
+ line = ` ${theme.fg(glyphTone, prefix)}${leftStyled}${pad}${theme.fg("muted", stats)}`;
120
+ }
121
+ else {
122
+ line = ` ${theme.fg(glyphTone, prefix)}${truncateToWidth(leftStyled, budget)}`;
123
+ }
124
+ const out = [line, ...this.failureLines(width)];
125
+ this.memo = { width, out };
126
+ return out;
127
+ }
128
+ /**
129
+ * Each failed call's reason, indented under the chain line.
130
+ *
131
+ * A collapsed chain hides its blocks, so without this a failure would be a
132
+ * count in the stats and nothing else. Whatever else these views fold away,
133
+ * they never fold away why something broke.
134
+ */
135
+ failureLines(width) {
136
+ const lines = [];
137
+ for (const block of this.blocks) {
138
+ const entry = block.chainEntry();
139
+ if (!entry.isError)
140
+ continue;
141
+ const header = ` ${theme.fg("error", "✗")} ${theme.fg("toolTitle", entry.tool)} ${theme.fg("toolOutput", entry.subject)}`;
142
+ lines.push(truncateToWidth(header, width));
143
+ for (const raw of block.errorText().split("\n")) {
144
+ if (!raw.trim())
145
+ continue;
146
+ lines.push(truncateToWidth(` ${theme.fg("toolOutput", raw)}`, width));
147
+ }
148
+ }
149
+ return lines;
150
+ }
151
+ }
152
+ /** Narrow a transcript child to a chain. */
153
+ export function isToolChain(child) {
154
+ return child instanceof ToolChainComponent;
155
+ }
156
+ //# sourceMappingURL=tool-chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-chain.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAmB,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGlG;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,kBAAmB,SAAQ,SAAS;IACxC,MAAM,GAA6B,EAAE,CAAC;IACtC,IAAI,CAAiB;IACrB,KAAK,GAAe,SAAS,CAAC;IACtC,8DAA8D;IACtD,MAAM,GAAG,KAAK,CAAC;IACf,IAAI,CAAoC;IAEhD,YAAY,IAAoB,EAAE;QACjC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAAA,CACjB;IAED,GAAG,CAAC,KAA6B,EAAQ;QACxC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IAAA,CACtB;IAED,uDAAuD;IACvD,IAAI,MAAM,GAAY;QACrB,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;IAAA,CAChC;IAED,IAAI,QAAQ,GAAY;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC;IAAA,CACnB;IAED,IAAI,OAAO,GAAY;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IAAA,CAChC;IAED,IAAI,UAAU,GAAsC;QACnD,OAAO,IAAI,CAAC,MAAM,CAAC;IAAA,CACnB;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,OAA+B,EAAQ;QAC5C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IAAA,CACtB;IAED,OAAO,CAAC,IAAoB,EAAQ;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACtB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;YAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAAA,CACrD;IAED,2EAA2E;IAC3E,WAAW,CAAC,QAAiB,EAAQ;QACpC,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACtB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;YAAE,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAAA,CAC7D;IAED,iFAAiF;IACjF,SAAS,CAAC,MAAe,EAAQ;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IAAA,CACtB;IAEQ,UAAU,GAAS;QAC3B,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IAAA,CACtB;IAED,sEAAsE;IAC9D,WAAW,GAAY;QAC9B,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAAA,CACvE;IAEQ,MAAM,CAAC,KAAa,EAAY;QACxC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAEjE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;QACzC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,KAAG,CAAC,CAAC,CAAC,KAAG,CAAC;QAClC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAE7F,0EAA0E;QAC1E,iEAAiE;QACjE,IAAI,SAAiB,CAAC;QACtB,IAAI,UAAkB,CAAC;QACvB,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;YACpC,SAAS,GAAG,MAAM,CAAC;YACnB,UAAU,GAAG,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;aAAM,CAAC;YACP,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,OAAK,CAAC;YAClB,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC1E,UAAU;gBACT,QAAQ;qBACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACV,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAChG;qBACA,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9C,sEAAoE;QACpE,MAAM,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,IAAI,IAAY,CAAC;QACjB,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;YACjE,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/E,IAAI,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,UAAU,GAAG,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;QACxF,CAAC;aAAM,CAAC;YACP,IAAI,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC;QAChF,CAAC;QAED,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAC3B,OAAO,GAAG,CAAC;IAAA,CACX;IAED;;;;;;OAMG;IACK,YAAY,CAAC,KAAa,EAAY;QAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,OAAO;gBAAE,SAAS;YAC7B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7H,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;YAC3C,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;oBAAE,SAAS;gBAC1B,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;YAC3E,CAAC;QACF,CAAC;QACD,OAAO,KAAK,CAAC;IAAA,CACb;CACD;AAED,4CAA4C;AAC5C,MAAM,UAAU,WAAW,CAAC,KAAgB,EAA+B;IAC1E,OAAO,KAAK,YAAY,kBAAkB,CAAC;AAAA,CAC3C","sourcesContent":["import { type Component, Container, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { type ChainState, chainPhrase, chainSegments, chainStats } from \"./tool-chain-summary.js\";\nimport type { ToolExecutionComponent } from \"./tool-execution.js\";\n\n/**\n * A run of consecutive tool calls, rendered as one line in the radar view.\n *\n * The chain owns its blocks rather than sitting beside them, so there is one\n * place that decides whether you are looking at a summary or at the calls. In\n * every view but a collapsed radar it is a plain pass-through container and the\n * blocks render exactly as they always did.\n *\n * A chain closes when the agent next speaks, or when the turn settles. Closing\n * early matters more than it looks: rewriting a line that has scrolled above\n * the viewport forces the TUI into a full redraw, which clears the terminal's\n * scrollback. Chains that close while they are still the bottom of the screen\n * flip for the price of one line.\n */\nexport class ToolChainComponent extends Container {\n\tprivate blocks: ToolExecutionComponent[] = [];\n\tprivate view: ToolOutputView;\n\tprivate state: ChainState = \"running\";\n\t/** Opened into its per-call rows by `app.tools.unfoldOne`. */\n\tprivate opened = false;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(view: ToolOutputView) {\n\t\tsuper();\n\t\tthis.view = view;\n\t}\n\n\tadd(block: ToolExecutionComponent): void {\n\t\tthis.blocks.push(block);\n\t\tthis.addChild(block);\n\t\tthis.memo = undefined;\n\t}\n\n\t/** True until the agent speaks or the turn settles. */\n\tget isOpen(): boolean {\n\t\treturn this.state === \"running\";\n\t}\n\n\tget isOpened(): boolean {\n\t\treturn this.opened;\n\t}\n\n\tget isEmpty(): boolean {\n\t\treturn this.blocks.length === 0;\n\t}\n\n\tget toolBlocks(): readonly ToolExecutionComponent[] {\n\t\treturn this.blocks;\n\t}\n\n\t/**\n\t * Settle the chain.\n\t *\n\t * `interrupted` keeps the running rendering, because the settled phrase is a\n\t * claim about what the run amounted to and a run cut off partway through has\n\t * no such claim to make — the same reason an aborted turn's plan items settle\n\t * to cancelled rather than done.\n\t */\n\tclose(outcome: \"done\" | \"interrupted\"): void {\n\t\tthis.state = outcome;\n\t\tthis.memo = undefined;\n\t}\n\n\tsetView(view: ToolOutputView): void {\n\t\tthis.view = view;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setView(view);\n\t}\n\n\t/** The global expand key opens every block, which also opens the chain. */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.opened = expanded;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setExpanded(expanded);\n\t}\n\n\t/** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */\n\tsetOpened(opened: boolean): void {\n\t\tthis.opened = opened;\n\t\tthis.memo = undefined;\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.memo = undefined;\n\t}\n\n\t/** Whether this chain is currently drawn as a single summary line. */\n\tprivate isCollapsed(): boolean {\n\t\treturn this.view === \"radar\" && !this.opened && this.blocks.length > 0;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (!this.isCollapsed()) return super.render(width);\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\n\t\tconst entries = this.blocks.map((block) => block.chainEntry());\n\t\tconst running = this.state === \"running\";\n\t\tconst glyph = running ? \"◐\" : \"●\";\n\t\tconst glyphTone = entries.some((e) => e.isError) ? \"error\" : running ? \"warning\" : \"success\";\n\n\t\t// While it runs the chain shows its shape in order; once it is over, what\n\t\t// it amounted to. See tool-chain-summary.ts for why they differ.\n\t\tlet leftPlain: string;\n\t\tlet leftStyled: string;\n\t\tif (this.state === \"done\") {\n\t\t\tconst phrase = chainPhrase(entries);\n\t\t\tleftPlain = phrase;\n\t\t\tleftStyled = theme.fg(\"toolTitle\", phrase);\n\t\t} else {\n\t\t\tconst segments = chainSegments(entries);\n\t\t\tconst sep = \" › \";\n\t\t\tleftPlain = segments.map((s) => s.label).join(sep) + (running ? \"…\" : \"\");\n\t\t\tleftStyled =\n\t\t\t\tsegments\n\t\t\t\t\t.map((s) =>\n\t\t\t\t\t\ttheme.fg(s.tone === \"error\" ? \"error\" : s.tone === \"running\" ? \"warning\" : \"toolTitle\", s.label),\n\t\t\t\t\t)\n\t\t\t\t\t.join(theme.fg(\"dim\", sep)) + (running ? theme.fg(\"dim\", \"…\") : \"\");\n\t\t}\n\n\t\tconst stats = chainStats(entries, this.state);\n\t\t// \" ● \" + left, then stats flush right when there is room for both.\n\t\tconst prefix = `${glyph} `;\n\t\tconst budget = Math.max(0, width - 1 - visibleWidth(prefix));\n\t\tlet line: string;\n\t\tif (visibleWidth(leftPlain) + 2 + visibleWidth(stats) <= budget) {\n\t\t\tconst pad = \" \".repeat(budget - visibleWidth(leftPlain) - visibleWidth(stats));\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${leftStyled}${pad}${theme.fg(\"muted\", stats)}`;\n\t\t} else {\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${truncateToWidth(leftStyled, budget)}`;\n\t\t}\n\n\t\tconst out = [line, ...this.failureLines(width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n\n\t/**\n\t * Each failed call's reason, indented under the chain line.\n\t *\n\t * A collapsed chain hides its blocks, so without this a failure would be a\n\t * count in the stats and nothing else. Whatever else these views fold away,\n\t * they never fold away why something broke.\n\t */\n\tprivate failureLines(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const block of this.blocks) {\n\t\t\tconst entry = block.chainEntry();\n\t\t\tif (!entry.isError) continue;\n\t\t\tconst header = ` ${theme.fg(\"error\", \"✗\")} ${theme.fg(\"toolTitle\", entry.tool)} ${theme.fg(\"toolOutput\", entry.subject)}`;\n\t\t\tlines.push(truncateToWidth(header, width));\n\t\t\tfor (const raw of block.errorText().split(\"\\n\")) {\n\t\t\t\tif (!raw.trim()) continue;\n\t\t\t\tlines.push(truncateToWidth(` ${theme.fg(\"toolOutput\", raw)}`, width));\n\t\t\t}\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/** Narrow a transcript child to a chain. */\nexport function isToolChain(child: Component): child is ToolChainComponent {\n\treturn child instanceof ToolChainComponent;\n}\n"]}
@@ -1,16 +1,16 @@
1
1
  import { Container, type TUI } from "@kolisachint/hoocode-tui";
2
2
  import type { ToolDefinition } from "../../../core/extensions/types.js";
3
+ import type { ToolOutputView } from "../../../core/tool-output-view.js";
4
+ import type { ChainEntry } from "./tool-chain-summary.js";
3
5
  /**
4
- * How a tool block's result body is displayed:
5
- * - "standard": result shown (truncated preview, expandable) the default.
6
- * - "collapsed": result hidden; only the call line + status dot render.
7
- * - "peek": result hidden by default with a ▸ affordance; the expand key reveals it.
6
+ * Re-exported so tool-block callers keep a single import site for the view
7
+ * dial; the type itself lives in core (the settings manager persists it).
8
8
  */
9
- export type ToolOutputDisplayLevel = "collapsed" | "peek" | "standard";
9
+ export type { ToolOutputView } from "../../../core/tool-output-view.js";
10
10
  export interface ToolExecutionOptions {
11
11
  showImages?: boolean;
12
12
  imageWidthCells?: number;
13
- displayLevel?: ToolOutputDisplayLevel;
13
+ view?: ToolOutputView;
14
14
  }
15
15
  export declare class ToolExecutionComponent extends Container {
16
16
  private contentBox;
@@ -25,8 +25,10 @@ export declare class ToolExecutionComponent extends Container {
25
25
  private toolCallId;
26
26
  private args;
27
27
  private expanded;
28
- private displayLevel;
28
+ private view;
29
29
  private revealed;
30
+ private signalComponent?;
31
+ private leadingSpacer;
30
32
  private showImages;
31
33
  private imageWidthCells;
32
34
  private isPartial;
@@ -69,9 +71,39 @@ export declare class ToolExecutionComponent extends Container {
69
71
  }, isPartial?: boolean): void;
70
72
  private maybeConvertImagesForKitty;
71
73
  setExpanded(expanded: boolean): void;
72
- setDisplayLevel(level: ToolOutputDisplayLevel): void;
73
- /** Whether the result body should render given the level and reveal state. */
74
+ setView(view: ToolOutputView): void;
75
+ /**
76
+ * Whether this block is currently opened up.
77
+ *
78
+ * Read by the one-at-a-time unfold, which walks back from the newest block
79
+ * to find the first one still folded.
80
+ */
81
+ isRevealed(): boolean;
82
+ /** This call's contribution to its chain's summary line. */
83
+ chainEntry(): ChainEntry;
84
+ /** The failure text, for a collapsed chain to print under its line. */
85
+ errorText(): string;
86
+ /**
87
+ * Whether the result body should render.
88
+ *
89
+ * A failure always shows its reason, in every view. Everything else about
90
+ * these views is a judgement about how much detail you want; why something
91
+ * broke is not detail, and a red dot with no explanation was the one thing
92
+ * the folded views got wrong.
93
+ */
74
94
  private shouldShowBody;
95
+ /**
96
+ * Whether this block draws its radar signal line instead of the tool's own
97
+ * call renderer. A revealed block leaves radar for as long as it is open, so
98
+ * "expand" means the same thing in every view.
99
+ */
100
+ private shouldShowSignalLine;
101
+ /**
102
+ * A failure's body under a radar row hangs off the row; anywhere else it sits
103
+ * directly under its own call line and needs no extra indent.
104
+ */
105
+ private indentUnderSignalRow;
106
+ private getSignalComponent;
75
107
  setShowImages(show: boolean): void;
76
108
  setImageWidthCells(width: number): void;
77
109
  invalidate(): void;
@@ -86,6 +118,20 @@ export declare class ToolExecutionComponent extends Container {
86
118
  freeze(): void;
87
119
  private releaseHeavyState;
88
120
  render(width: number): string[];
121
+ /**
122
+ * The container this block renders into.
123
+ *
124
+ * A self-rendering tool (`edit` draws its own framed diff) normally gets the
125
+ * bare container so its frame can use the full width; everything else gets
126
+ * the padded box. That only matters while the body is on screen. With the
127
+ * body folded away the block is a single line, and the unpadded container
128
+ * put it one column left of every other row — invisible while `peek` was an
129
+ * opt-in, glaring now that `glance` is the default. So: padded box whenever
130
+ * nothing is expanded, which also gives radar the uniform rows it exists for.
131
+ */
132
+ private activeShell;
133
+ /** Swap the attached shell when the view changes which one applies. */
134
+ private syncShellChild;
89
135
  private updateDisplay;
90
136
  private getTextOutput;
91
137
  private formatToolExecution;