@vitos-pizza/vitos-pizza 0.1.0 → 0.2.0

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 (259) hide show
  1. package/AGENTS.md +158 -158
  2. package/CHANGELOG.md +42 -24
  3. package/LICENSE +21 -21
  4. package/README.md +237 -234
  5. package/assets/logo.svg +28 -28
  6. package/node_modules/@vitos-pizza/agent-mode/extensions/index.ts +138 -117
  7. package/node_modules/@vitos-pizza/agent-mode/package.json +3 -3
  8. package/node_modules/@vitos-pizza/agent-mode/src/apply-mode.ts +60 -60
  9. package/node_modules/@vitos-pizza/agent-mode/src/mode-events.ts +102 -102
  10. package/node_modules/@vitos-pizza/agent-mode/src/modes.ts +43 -43
  11. package/node_modules/@vitos-pizza/agent-mode/src/plan-instructions.ts +85 -44
  12. package/node_modules/@vitos-pizza/agent-mode/tests/apply-mode.test.ts +36 -36
  13. package/node_modules/@vitos-pizza/agent-mode/tests/mode-events.test.ts +22 -22
  14. package/node_modules/@vitos-pizza/agent-mode/tests/modes.test.ts +38 -38
  15. package/node_modules/@vitos-pizza/agent-mode/tests/plan-instructions.test.ts +68 -45
  16. package/node_modules/@vitos-pizza/agent-mode/tsconfig.json +13 -13
  17. package/node_modules/@vitos-pizza/agent-mode/vitest.config.ts +8 -8
  18. package/node_modules/@vitos-pizza/keybindings/extensions/index.ts +104 -104
  19. package/node_modules/@vitos-pizza/keybindings/package.json +1 -1
  20. package/node_modules/@vitos-pizza/keybindings/presets/shortcuts.json +3 -3
  21. package/node_modules/@vitos-pizza/keybindings/src/bind.ts +57 -57
  22. package/node_modules/@vitos-pizza/keybindings/src/config.ts +84 -84
  23. package/node_modules/@vitos-pizza/keybindings/src/registry.ts +43 -43
  24. package/node_modules/@vitos-pizza/keybindings/src/types.ts +54 -54
  25. package/node_modules/@vitos-pizza/keybindings/tests/config.test.ts +83 -83
  26. package/node_modules/@vitos-pizza/keybindings/tests/registry.test.ts +142 -142
  27. package/node_modules/@vitos-pizza/keybindings/tsconfig.json +13 -13
  28. package/node_modules/@vitos-pizza/keybindings/vitest.config.ts +8 -8
  29. package/node_modules/@vitos-pizza/permission-system/extensions/index.ts +162 -162
  30. package/node_modules/@vitos-pizza/permission-system/package.json +2 -2
  31. package/node_modules/@vitos-pizza/permission-system/src/bash/matcher.ts +54 -51
  32. package/node_modules/@vitos-pizza/permission-system/src/bash/parser.ts +40 -40
  33. package/node_modules/@vitos-pizza/permission-system/src/config-paths.ts +23 -23
  34. package/node_modules/@vitos-pizza/permission-system/src/detect-preset-mode.ts +61 -61
  35. package/node_modules/@vitos-pizza/permission-system/src/expand-home.ts +12 -12
  36. package/node_modules/@vitos-pizza/permission-system/src/forwarding/forwarder.ts +224 -224
  37. package/node_modules/@vitos-pizza/permission-system/src/forwarding/subagent-registry.ts +61 -61
  38. package/node_modules/@vitos-pizza/permission-system/src/gates/path-utils.ts +66 -66
  39. package/node_modules/@vitos-pizza/permission-system/src/gates/pipeline.ts +151 -155
  40. package/node_modules/@vitos-pizza/permission-system/src/handlers/before-agent-start.ts +86 -65
  41. package/node_modules/@vitos-pizza/permission-system/src/handlers/skill-input.ts +1 -1
  42. package/node_modules/@vitos-pizza/permission-system/src/handlers/tool-call-gate.ts +204 -199
  43. package/node_modules/@vitos-pizza/permission-system/src/mode-api.ts +44 -44
  44. package/node_modules/@vitos-pizza/permission-system/src/permission-evaluator.ts +233 -216
  45. package/node_modules/@vitos-pizza/permission-system/src/permission-session.ts +62 -62
  46. package/node_modules/@vitos-pizza/permission-system/src/policy-loader.ts +211 -211
  47. package/node_modules/@vitos-pizza/permission-system/src/sanitizers/skill-prompt.ts +99 -99
  48. package/node_modules/@vitos-pizza/permission-system/src/sanitizers/system-prompt.ts +221 -221
  49. package/node_modules/@vitos-pizza/permission-system/src/service.ts +66 -66
  50. package/node_modules/@vitos-pizza/permission-system/src/session-approvals.ts +4 -1
  51. package/node_modules/@vitos-pizza/permission-system/src/types.ts +80 -80
  52. package/node_modules/@vitos-pizza/permission-system/src/ui/approval-summary.ts +43 -43
  53. package/node_modules/@vitos-pizza/permission-system/src/ui/config-modal.ts +36 -36
  54. package/node_modules/@vitos-pizza/permission-system/src/ui/permission-dialog.ts +52 -52
  55. package/node_modules/@vitos-pizza/permission-system/src/wildcard-matcher.ts +81 -81
  56. package/node_modules/@vitos-pizza/permission-system/src/yaml-frontmatter.ts +84 -84
  57. package/node_modules/@vitos-pizza/permission-system/src/yolo/runtime-api.ts +49 -49
  58. package/node_modules/@vitos-pizza/permission-system/tests/bash-parser.test.ts +32 -32
  59. package/node_modules/@vitos-pizza/permission-system/tests/before-agent-start.test.ts +61 -0
  60. package/node_modules/@vitos-pizza/permission-system/tests/detect-preset-mode.test.ts +68 -68
  61. package/node_modules/@vitos-pizza/permission-system/tests/forwarder.test.ts +55 -55
  62. package/node_modules/@vitos-pizza/permission-system/tests/gates.test.ts +170 -61
  63. package/node_modules/@vitos-pizza/permission-system/tests/path-utils.test.ts +68 -68
  64. package/node_modules/@vitos-pizza/permission-system/tests/permission-evaluator.test.ts +82 -55
  65. package/node_modules/@vitos-pizza/permission-system/tests/policy-loader.test.ts +25 -25
  66. package/node_modules/@vitos-pizza/permission-system/tests/system-prompt-sanitizer.test.ts +22 -22
  67. package/node_modules/@vitos-pizza/permission-system/tests/wildcard-matcher.test.ts +27 -27
  68. package/node_modules/@vitos-pizza/permission-system/tsconfig.json +13 -13
  69. package/node_modules/@vitos-pizza/permission-system/vitest.config.ts +8 -8
  70. package/node_modules/@vitos-pizza/question/package.json +1 -1
  71. package/node_modules/@vitos-pizza/question/src/question-ui.ts +5 -1
  72. package/node_modules/@vitos-pizza/question/src/register-tool.ts +1 -4
  73. package/node_modules/@vitos-pizza/question/tests/question-ui-width-cache.test.ts +45 -0
  74. package/node_modules/@vitos-pizza/question/tests/register-tool-render.test.ts +48 -45
  75. package/node_modules/@vitos-pizza/session-title/extensions/index.ts +137 -137
  76. package/node_modules/@vitos-pizza/session-title/package.json +3 -3
  77. package/node_modules/@vitos-pizza/session-title/src/build-title-prompt.ts +16 -16
  78. package/node_modules/@vitos-pizza/session-title/src/extract-title-context.ts +55 -55
  79. package/node_modules/@vitos-pizza/session-title/src/fast-rules.ts +48 -48
  80. package/node_modules/@vitos-pizza/session-title/src/load-settings.ts +70 -70
  81. package/node_modules/@vitos-pizza/session-title/src/normalize-title.ts +20 -20
  82. package/node_modules/@vitos-pizza/session-title/src/parse-title-response.ts +6 -6
  83. package/node_modules/@vitos-pizza/session-title/src/resolve-title-model.ts +33 -33
  84. package/node_modules/@vitos-pizza/session-title/src/schedule-title-job.ts +53 -53
  85. package/node_modules/@vitos-pizza/session-title/src/should-schedule.ts +30 -30
  86. package/node_modules/@vitos-pizza/session-title/src/title-agent.ts +60 -60
  87. package/node_modules/@vitos-pizza/session-title/src/truncate-input.ts +10 -10
  88. package/node_modules/@vitos-pizza/session-title/src/types.ts +29 -29
  89. package/node_modules/@vitos-pizza/session-title/tests/build-title-prompt.test.ts +34 -34
  90. package/node_modules/@vitos-pizza/session-title/tests/extension.test.ts +198 -198
  91. package/node_modules/@vitos-pizza/session-title/tests/extract-title-context.test.ts +48 -48
  92. package/node_modules/@vitos-pizza/session-title/tests/fast-rules.test.ts +43 -43
  93. package/node_modules/@vitos-pizza/session-title/tests/parse-title-response.test.ts +34 -34
  94. package/node_modules/@vitos-pizza/session-title/tests/should-schedule.test.ts +30 -30
  95. package/node_modules/@vitos-pizza/session-title/tests/title-agent.test.ts +154 -154
  96. package/node_modules/@vitos-pizza/session-title/tests/truncate-input.test.ts +15 -15
  97. package/node_modules/@vitos-pizza/session-title/tsconfig.json +13 -13
  98. package/node_modules/@vitos-pizza/session-title/vitest.config.ts +8 -8
  99. package/node_modules/@vitos-pizza/subagents/agents/planner.md +46 -43
  100. package/node_modules/@vitos-pizza/subagents/agents/scout.md +40 -40
  101. package/node_modules/@vitos-pizza/subagents/agents/title.md +13 -13
  102. package/node_modules/@vitos-pizza/subagents/agents/worker.md +36 -36
  103. package/node_modules/@vitos-pizza/subagents/extensions/index.ts +164 -164
  104. package/node_modules/@vitos-pizza/subagents/package.json +1 -1
  105. package/node_modules/@vitos-pizza/subagents/skills/subagents/SKILL.md +86 -80
  106. package/node_modules/@vitos-pizza/subagents/src/agents.ts +166 -166
  107. package/node_modules/@vitos-pizza/subagents/src/async/job-tracker.ts +71 -71
  108. package/node_modules/@vitos-pizza/subagents/src/async/spawn-async.ts +220 -220
  109. package/node_modules/@vitos-pizza/subagents/src/async/storage.ts +57 -57
  110. package/node_modules/@vitos-pizza/subagents/src/env.ts +4 -4
  111. package/node_modules/@vitos-pizza/subagents/src/execute-subagent.ts +285 -285
  112. package/node_modules/@vitos-pizza/subagents/src/progress.ts +187 -187
  113. package/node_modules/@vitos-pizza/subagents/src/render-animation.ts +40 -40
  114. package/node_modules/@vitos-pizza/subagents/src/render.ts +428 -428
  115. package/node_modules/@vitos-pizza/subagents/src/rpc/channels.ts +21 -21
  116. package/node_modules/@vitos-pizza/subagents/src/rpc/client.ts +144 -144
  117. package/node_modules/@vitos-pizza/subagents/src/rpc/server.ts +11 -11
  118. package/node_modules/@vitos-pizza/subagents/src/run-agent.ts +313 -313
  119. package/node_modules/@vitos-pizza/subagents/src/runtime.ts +102 -102
  120. package/node_modules/@vitos-pizza/subagents/src/schema.ts +62 -62
  121. package/node_modules/@vitos-pizza/subagents/src/spawn.ts +104 -104
  122. package/node_modules/@vitos-pizza/subagents/src/types.ts +92 -92
  123. package/node_modules/@vitos-pizza/subagents/src/utils.ts +100 -100
  124. package/node_modules/@vitos-pizza/subagents/src/wait.ts +159 -159
  125. package/node_modules/@vitos-pizza/subagents/tests/agents.test.ts +19 -19
  126. package/node_modules/@vitos-pizza/subagents/tests/execute-subagent.test.ts +96 -96
  127. package/node_modules/@vitos-pizza/subagents/tests/extension.test.ts +42 -42
  128. package/node_modules/@vitos-pizza/subagents/tests/progress.test.ts +206 -206
  129. package/node_modules/@vitos-pizza/subagents/tests/rpc.test.ts +74 -74
  130. package/node_modules/@vitos-pizza/subagents/tsconfig.json +13 -13
  131. package/node_modules/@vitos-pizza/subagents/vitest.config.ts +8 -8
  132. package/node_modules/@vitos-pizza/ui-enhancements/package.json +1 -1
  133. package/node_modules/@vitos-pizza/ui-enhancements/src/chrome/agent-mode-badge.ts +60 -60
  134. package/node_modules/@vitos-pizza/ui-enhancements/src/chrome/border-status.ts +14 -1
  135. package/node_modules/@vitos-pizza/ui-enhancements/src/chrome/resize-recovery.ts +63 -0
  136. package/node_modules/@vitos-pizza/ui-enhancements/tests/agent-mode-badge.test.ts +35 -35
  137. package/node_modules/@vitos-pizza/ui-enhancements/tests/resize-recovery.test.ts +135 -0
  138. package/node_modules/@vitos-pizza/websearch/package.json +1 -1
  139. package/node_modules/node-addon-api/tools/conversion.js +0 -0
  140. package/node_modules/node-gyp-build/bin.js +0 -0
  141. package/node_modules/node-gyp-build/build-test.js +0 -0
  142. package/node_modules/node-gyp-build/optional.js +0 -0
  143. package/package.json +11 -9
  144. package/packages/agent-mode/extensions/index.ts +138 -117
  145. package/packages/agent-mode/package.json +3 -3
  146. package/packages/agent-mode/src/apply-mode.ts +60 -60
  147. package/packages/agent-mode/src/mode-events.ts +102 -102
  148. package/packages/agent-mode/src/modes.ts +43 -43
  149. package/packages/agent-mode/src/plan-instructions.ts +85 -44
  150. package/packages/agent-mode/tsconfig.json +13 -13
  151. package/packages/agent-mode/vitest.config.ts +8 -8
  152. package/packages/keybindings/extensions/index.ts +104 -104
  153. package/packages/keybindings/package.json +1 -1
  154. package/packages/keybindings/presets/shortcuts.json +3 -3
  155. package/packages/keybindings/src/bind.ts +57 -57
  156. package/packages/keybindings/src/config.ts +84 -84
  157. package/packages/keybindings/src/registry.ts +43 -43
  158. package/packages/keybindings/src/types.ts +54 -54
  159. package/packages/keybindings/tsconfig.json +13 -13
  160. package/packages/keybindings/vitest.config.ts +8 -8
  161. package/packages/permission-system/extensions/index.ts +162 -162
  162. package/packages/permission-system/node_modules/web-tree-sitter/debug/tree-sitter.wasm +0 -0
  163. package/packages/permission-system/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
  164. package/packages/permission-system/package.json +2 -2
  165. package/packages/permission-system/src/bash/matcher.ts +54 -51
  166. package/packages/permission-system/src/bash/parser.ts +40 -40
  167. package/packages/permission-system/src/config-paths.ts +23 -23
  168. package/packages/permission-system/src/detect-preset-mode.ts +61 -61
  169. package/packages/permission-system/src/expand-home.ts +12 -12
  170. package/packages/permission-system/src/forwarding/forwarder.ts +224 -224
  171. package/packages/permission-system/src/forwarding/subagent-registry.ts +61 -61
  172. package/packages/permission-system/src/gates/path-utils.ts +66 -66
  173. package/packages/permission-system/src/gates/pipeline.ts +151 -155
  174. package/packages/permission-system/src/handlers/before-agent-start.ts +86 -65
  175. package/packages/permission-system/src/handlers/skill-input.ts +1 -1
  176. package/packages/permission-system/src/handlers/tool-call-gate.ts +204 -199
  177. package/packages/permission-system/src/mode-api.ts +44 -44
  178. package/packages/permission-system/src/permission-evaluator.ts +233 -216
  179. package/packages/permission-system/src/permission-session.ts +62 -62
  180. package/packages/permission-system/src/policy-loader.ts +211 -211
  181. package/packages/permission-system/src/sanitizers/skill-prompt.ts +99 -99
  182. package/packages/permission-system/src/sanitizers/system-prompt.ts +221 -221
  183. package/packages/permission-system/src/service.ts +66 -66
  184. package/packages/permission-system/src/session-approvals.ts +4 -1
  185. package/packages/permission-system/src/types.ts +80 -80
  186. package/packages/permission-system/src/ui/approval-summary.ts +43 -43
  187. package/packages/permission-system/src/ui/config-modal.ts +36 -36
  188. package/packages/permission-system/src/ui/permission-dialog.ts +52 -52
  189. package/packages/permission-system/src/wildcard-matcher.ts +81 -81
  190. package/packages/permission-system/src/yaml-frontmatter.ts +84 -84
  191. package/packages/permission-system/src/yolo/runtime-api.ts +49 -49
  192. package/packages/permission-system/tsconfig.json +13 -13
  193. package/packages/permission-system/vitest.config.ts +8 -8
  194. package/packages/question/package.json +1 -1
  195. package/packages/question/src/question-ui.ts +5 -1
  196. package/packages/question/src/register-tool.ts +1 -4
  197. package/packages/session-title/extensions/index.ts +137 -137
  198. package/packages/session-title/package.json +3 -3
  199. package/packages/session-title/src/build-title-prompt.ts +16 -16
  200. package/packages/session-title/src/extract-title-context.ts +55 -55
  201. package/packages/session-title/src/fast-rules.ts +48 -48
  202. package/packages/session-title/src/load-settings.ts +70 -70
  203. package/packages/session-title/src/normalize-title.ts +20 -20
  204. package/packages/session-title/src/parse-title-response.ts +6 -6
  205. package/packages/session-title/src/resolve-title-model.ts +33 -33
  206. package/packages/session-title/src/schedule-title-job.ts +53 -53
  207. package/packages/session-title/src/should-schedule.ts +30 -30
  208. package/packages/session-title/src/title-agent.ts +60 -60
  209. package/packages/session-title/src/truncate-input.ts +10 -10
  210. package/packages/session-title/src/types.ts +29 -29
  211. package/packages/session-title/tsconfig.json +13 -13
  212. package/packages/session-title/vitest.config.ts +8 -8
  213. package/packages/subagents/agents/planner.md +46 -43
  214. package/packages/subagents/agents/scout.md +40 -40
  215. package/packages/subagents/agents/title.md +13 -13
  216. package/packages/subagents/agents/worker.md +36 -36
  217. package/packages/subagents/extensions/index.ts +164 -164
  218. package/packages/subagents/package.json +1 -1
  219. package/packages/subagents/skills/subagents/SKILL.md +86 -80
  220. package/packages/subagents/src/agents.ts +166 -166
  221. package/packages/subagents/src/async/job-tracker.ts +71 -71
  222. package/packages/subagents/src/async/spawn-async.ts +220 -220
  223. package/packages/subagents/src/async/storage.ts +57 -57
  224. package/packages/subagents/src/env.ts +4 -4
  225. package/packages/subagents/src/execute-subagent.ts +285 -285
  226. package/packages/subagents/src/progress.ts +187 -187
  227. package/packages/subagents/src/render-animation.ts +40 -40
  228. package/packages/subagents/src/render.ts +428 -428
  229. package/packages/subagents/src/rpc/channels.ts +21 -21
  230. package/packages/subagents/src/rpc/client.ts +144 -144
  231. package/packages/subagents/src/rpc/server.ts +11 -11
  232. package/packages/subagents/src/run-agent.ts +313 -313
  233. package/packages/subagents/src/runtime.ts +102 -102
  234. package/packages/subagents/src/schema.ts +62 -62
  235. package/packages/subagents/src/spawn.ts +104 -104
  236. package/packages/subagents/src/types.ts +92 -92
  237. package/packages/subagents/src/utils.ts +100 -100
  238. package/packages/subagents/src/wait.ts +159 -159
  239. package/packages/subagents/tsconfig.json +13 -13
  240. package/packages/subagents/vitest.config.ts +8 -8
  241. package/packages/todoist/extensions/index.ts +163 -0
  242. package/packages/todoist/package.json +34 -0
  243. package/packages/todoist/src/events.ts +7 -0
  244. package/packages/todoist/src/silent-render.ts +8 -0
  245. package/packages/todoist/src/state.ts +147 -0
  246. package/packages/todoist/src/tools.ts +175 -0
  247. package/packages/todoist/src/types.ts +34 -0
  248. package/packages/todoist/src/widget.ts +109 -0
  249. package/packages/todoist/tsconfig.json +13 -0
  250. package/packages/todoist/vitest.config.ts +8 -0
  251. package/packages/ui-enhancements/package.json +1 -1
  252. package/packages/ui-enhancements/src/chrome/agent-mode-badge.ts +60 -60
  253. package/packages/ui-enhancements/src/chrome/border-status.ts +14 -1
  254. package/packages/ui-enhancements/src/chrome/resize-recovery.ts +63 -0
  255. package/packages/websearch/package.json +1 -1
  256. package/scripts/sync-pi-manifest.mjs +1 -1
  257. package/packages/permission-system/node_modules/web-tree-sitter/lib/tree-sitter.cjs +0 -2988
  258. package/packages/permission-system/node_modules/web-tree-sitter/lib/tree-sitter.wasm +0 -0
  259. package/packages/permission-system/node_modules/web-tree-sitter/lib/tree-sitter.wasm.map +0 -1
@@ -1,428 +1,428 @@
1
- import { keyHint } from "@earendil-works/pi-coding-agent";
2
- import type { Theme } from "@earendil-works/pi-coding-agent";
3
- import { Container, Spacer, Text, type Component } from "@earendil-works/pi-tui";
4
- import type { AgentProgress } from "./progress.ts";
5
- import {
6
- formatDuration,
7
- formatTokenCount,
8
- formatToolArgs,
9
- } from "./progress.ts";
10
- import type { RenderAnimationContext } from "./render-animation.ts";
11
- import { getAnimationFrame } from "./render-animation.ts";
12
- import type { SingleResult, SubagentDetails } from "./types.ts";
13
- import { getFinalOutput, getResultOutput, isFailedResult } from "./utils.ts";
14
-
15
- const RUNNING_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
16
-
17
- export function renderSubagentCall(
18
- args: Record<string, unknown>,
19
- theme: Theme,
20
- ): Text {
21
- const scope = (args.agentScope as string | undefined) ?? "both";
22
-
23
- if (Array.isArray(args.chain) && args.chain.length > 0) {
24
- let text =
25
- theme.fg("toolTitle", theme.bold("subagent ")) +
26
- theme.fg("accent", `chain (${args.chain.length} steps)`) +
27
- theme.fg("muted", ` [${scope}]`);
28
- for (let i = 0; i < Math.min(args.chain.length, 3); i++) {
29
- const step = args.chain[i] as { agent: string; task: string };
30
- const preview =
31
- step.task.length > 40 ? `${step.task.slice(0, 40)}...` : step.task;
32
- text += `\n ${theme.fg("muted", `${i + 1}.`)} ${theme.fg("accent", step.agent)}${theme.fg("dim", ` ${preview}`)}`;
33
- }
34
- return new Text(text, 0, 0);
35
- }
36
-
37
- if (Array.isArray(args.tasks) && args.tasks.length > 0) {
38
- let text =
39
- theme.fg("toolTitle", theme.bold("subagent ")) +
40
- theme.fg("accent", `parallel (${args.tasks.length} tasks)`) +
41
- theme.fg("muted", ` [${scope}]`);
42
- for (const task of (args.tasks as Array<{ agent: string; task: string }>).slice(0, 3)) {
43
- const preview = task.task.length > 40 ? `${task.task.slice(0, 40)}...` : task.task;
44
- text += `\n ${theme.fg("accent", task.agent)}${theme.fg("dim", ` ${preview}`)}`;
45
- }
46
- return new Text(text, 0, 0);
47
- }
48
-
49
- const agentName = (args.agent as string | undefined) || "...";
50
- const task = args.task as string | undefined;
51
- const preview = task ? (task.length > 60 ? `${task.slice(0, 60)}...` : task) : "...";
52
- const asyncLabel = args.async ? theme.fg("warning", " async") : "";
53
- let text =
54
- theme.fg("toolTitle", theme.bold("subagent ")) +
55
- theme.fg("accent", agentName) +
56
- asyncLabel +
57
- theme.fg("muted", ` [${scope}]`);
58
- text += `\n ${theme.fg("dim", preview)}`;
59
- return new Text(text, 0, 0);
60
- }
61
-
62
- export interface RenderSubagentResultOptions {
63
- expanded: boolean;
64
- isPartial?: boolean;
65
- }
66
-
67
- type SubagentToolResult = {
68
- content: Array<{ type: string; text?: string }>;
69
- details?: SubagentDetails;
70
- };
71
-
72
- export function subagentResultIsRunning(
73
- result: SubagentToolResult,
74
- options?: RenderSubagentResultOptions,
75
- ): boolean {
76
- if (options?.isPartial) return true;
77
- const details = result.details;
78
- if (!details || details.async) return false;
79
- return details.results.some(
80
- (entry) =>
81
- entry.progress?.status === "running" || entry.exitCode === -1,
82
- );
83
- }
84
-
85
- function runningGlyph(frame: number): string {
86
- return RUNNING_FRAMES[frame % RUNNING_FRAMES.length] ?? "●";
87
- }
88
-
89
- function getProgressStats(
90
- progress: Pick<AgentProgress, "toolCount" | "tokens" | "durationMs"> | undefined,
91
- ): string {
92
- if (!progress) return "";
93
- const parts: string[] = [];
94
- if (progress.toolCount > 0) parts.push(`${progress.toolCount} tools`);
95
- if (progress.tokens > 0) parts.push(`${formatTokenCount(progress.tokens)} tok`);
96
- if (progress.durationMs > 0) parts.push(formatDuration(progress.durationMs));
97
- return parts.join(" · ");
98
- }
99
-
100
- function formatCurrentToolLine(progress: AgentProgress, expanded: boolean): string | null {
101
- if (!progress.currentTool) return null;
102
- const args = progress.currentToolArgs;
103
- if (args) {
104
- return expanded
105
- ? `${progress.currentTool}: ${args}`
106
- : `${progress.currentTool}`;
107
- }
108
- return progress.currentTool;
109
- }
110
-
111
- function compactActivity(progress: AgentProgress): string {
112
- return formatCurrentToolLine(progress, false) ?? "thinking…";
113
- }
114
-
115
- function isResultRunning(entry: SingleResult): boolean {
116
- return entry.progress?.status === "running" || entry.exitCode === -1;
117
- }
118
-
119
- function getToolCallLines(entry: SingleResult): string[] {
120
- const lines: string[] = [];
121
- for (const msg of entry.messages) {
122
- if (msg.role !== "assistant") continue;
123
- for (const part of msg.content) {
124
- if (part.type === "toolCall") {
125
- const args = formatToolArgs(part.arguments);
126
- lines.push(args ? `${part.name}: ${args}` : part.name);
127
- }
128
- }
129
- }
130
- return lines;
131
- }
132
-
133
- function renderSingleResult(
134
- entry: SingleResult,
135
- theme: Theme,
136
- options: RenderSubagentResultOptions,
137
- context?: RenderAnimationContext,
138
- ): Component {
139
- const expanded = options.expanded;
140
- const running = isResultRunning(entry);
141
- const progress = entry.progress;
142
- const summary = progress ?? entry.progressSummary;
143
- const frame = getAnimationFrame(context);
144
-
145
- if (running && progress) {
146
- const c = new Container();
147
- const stats = getProgressStats(progress);
148
- const glyph = theme.fg("warning", runningGlyph(frame));
149
- let header = `${glyph} ${theme.fg("toolTitle", theme.bold(entry.agent))}`;
150
- if (stats) header += ` ${theme.fg("dim", `· ${stats}`)}`;
151
- c.addChild(new Text(header, 0, 0));
152
-
153
- if (expanded) {
154
- c.addChild(new Spacer(1));
155
- const taskPreview =
156
- entry.task.length > 200 && !expanded
157
- ? `${entry.task.slice(0, 200)}...`
158
- : entry.task;
159
- c.addChild(new Text(theme.fg("dim", `Task: ${taskPreview}`), 0, 0));
160
- c.addChild(new Spacer(1));
161
- }
162
-
163
- const toolLine = formatCurrentToolLine(progress, expanded);
164
- if (toolLine) {
165
- c.addChild(new Text(theme.fg("warning", `> ${toolLine}`), 0, 0));
166
- } else {
167
- c.addChild(new Text(theme.fg("dim", ` ⎿ ${compactActivity(progress)}`), 0, 0));
168
- }
169
-
170
- if (!expanded) {
171
- c.addChild(new Text(theme.fg("accent", ` ${keyHint("app.tools.expand", "for live detail")}`), 0, 0));
172
- return c;
173
- }
174
-
175
- c.addChild(new Text(theme.fg("accent", keyHint("app.tools.expand", "for live detail")), 0, 0));
176
-
177
- if (progress.recentTools.length > 0) {
178
- c.addChild(new Spacer(1));
179
- for (const tool of progress.recentTools.slice(-3)) {
180
- const line = tool.args ? `${tool.tool}: ${tool.args}` : tool.tool;
181
- c.addChild(new Text(theme.fg("dim", line), 0, 0));
182
- }
183
- }
184
-
185
- if (progress.recentOutput.length > 0) {
186
- c.addChild(new Spacer(1));
187
- for (const line of progress.recentOutput.slice(-5)) {
188
- c.addChild(new Text(theme.fg("dim", ` ${line}`), 0, 0));
189
- }
190
- }
191
-
192
- return c;
193
- }
194
-
195
- const failed = isFailedResult(entry);
196
- const icon = failed ? theme.fg("error", "✗") : theme.fg("success", "✓");
197
- const output = getResultOutput(entry);
198
- const stats = summary ? getProgressStats(summary as AgentProgress) : "";
199
-
200
- const c = new Container();
201
- let header = `${icon} ${theme.fg("toolTitle", theme.bold(entry.agent))}`;
202
- if (stats) header += ` ${theme.fg("dim", `· ${stats}`)}`;
203
- c.addChild(new Text(header, 0, 0));
204
-
205
- if (expanded) {
206
- c.addChild(new Spacer(1));
207
- c.addChild(new Text(theme.fg("dim", `Task: ${entry.task}`), 0, 0));
208
- const toolLines = getToolCallLines(entry);
209
- if (toolLines.length > 0) {
210
- c.addChild(new Spacer(1));
211
- for (const line of toolLines) {
212
- c.addChild(new Text(theme.fg("muted", line), 0, 0));
213
- }
214
- }
215
- c.addChild(new Spacer(1));
216
- c.addChild(new Text(theme.fg("toolOutput", output), 0, 0));
217
- return c;
218
- }
219
-
220
- const preview = output.length > 120 ? `${output.slice(0, 120)}...` : output;
221
- c.addChild(new Text(theme.fg("dim", ` ⎿ ${preview}`), 0, 0));
222
- return c;
223
- }
224
-
225
- function renderChainResult(
226
- details: SubagentDetails,
227
- theme: Theme,
228
- options: RenderSubagentResultOptions,
229
- context?: RenderAnimationContext,
230
- ): Component {
231
- const expanded = options.expanded;
232
- const frame = getAnimationFrame(context);
233
- const c = new Container();
234
-
235
- const flowParts: string[] = [];
236
- for (let i = 0; i < details.results.length; i++) {
237
- const entry = details.results[i];
238
- if (isResultRunning(entry)) {
239
- flowParts.push(
240
- `${theme.fg("warning", runningGlyph(frame))} ${theme.fg("accent", entry.agent)}`,
241
- );
242
- } else if (isFailedResult(entry)) {
243
- flowParts.push(`${theme.fg("error", "✗")} ${theme.fg("accent", entry.agent)}`);
244
- } else {
245
- flowParts.push(`${theme.fg("success", "✓")} ${theme.fg("accent", entry.agent)}`);
246
- }
247
- if (i < details.results.length - 1) flowParts.push(theme.fg("muted", " → "));
248
- }
249
- c.addChild(
250
- new Text(
251
- `${theme.fg("toolTitle", theme.bold("chain"))} ${flowParts.join("")}`,
252
- 0,
253
- 0,
254
- ),
255
- );
256
-
257
- if (expanded) {
258
- c.addChild(new Spacer(1));
259
- for (const entry of details.results) {
260
- c.addChild(renderSingleResult(entry, theme, { ...options, expanded: false }, context));
261
- c.addChild(new Spacer(1));
262
- }
263
- const last = details.results[details.results.length - 1];
264
- if (last && !isResultRunning(last)) {
265
- c.addChild(new Text(theme.fg("toolOutput", getResultOutput(last)), 0, 0));
266
- }
267
- return c;
268
- }
269
-
270
- const runningEntry = details.results.find((entry) => isResultRunning(entry));
271
- if (runningEntry?.progress) {
272
- c.addChild(
273
- new Text(
274
- theme.fg("dim", ` ⎿ ${compactActivity(runningEntry.progress)}`),
275
- 0,
276
- 0,
277
- ),
278
- );
279
- c.addChild(
280
- new Text(
281
- theme.fg("accent", ` ${keyHint("app.tools.expand", "for live detail")}`),
282
- 0,
283
- 0,
284
- ),
285
- );
286
- }
287
-
288
- return c;
289
- }
290
-
291
- function renderParallelResult(
292
- details: SubagentDetails,
293
- theme: Theme,
294
- options: RenderSubagentResultOptions,
295
- context?: RenderAnimationContext,
296
- ): Component {
297
- const expanded = options.expanded;
298
- const frame = getAnimationFrame(context);
299
- const total = details.results.length;
300
- const done = details.results.filter((entry) => !isResultRunning(entry)).length;
301
- const c = new Container();
302
-
303
- c.addChild(
304
- new Text(
305
- `${theme.fg("toolTitle", theme.bold("parallel"))} ${theme.fg("dim", `${done}/${total} done`)}`,
306
- 0,
307
- 0,
308
- ),
309
- );
310
-
311
- for (const entry of details.results) {
312
- const running = isResultRunning(entry);
313
- const glyph = running
314
- ? theme.fg("warning", runningGlyph(frame))
315
- : isFailedResult(entry)
316
- ? theme.fg("error", "✗")
317
- : theme.fg("success", "✓");
318
- let line = `${glyph} ${theme.fg("accent", entry.agent)}`;
319
- if (running && entry.progress) {
320
- line += theme.fg("dim", ` ⎿ ${compactActivity(entry.progress)}`);
321
- } else if (!running) {
322
- const output = getFinalOutput(entry.messages) || getResultOutput(entry);
323
- const preview = output.length > 80 ? `${output.slice(0, 80)}...` : output;
324
- if (preview) line += theme.fg("dim", ` ${preview}`);
325
- }
326
- c.addChild(new Text(line, 0, 0));
327
-
328
- if (expanded && running && entry.progress) {
329
- for (const tool of entry.progress.recentTools.slice(-2)) {
330
- const toolLine = tool.args ? `${tool.tool}: ${tool.args}` : tool.tool;
331
- c.addChild(new Text(theme.fg("dim", ` ${toolLine}`), 0, 0));
332
- }
333
- }
334
- }
335
-
336
- if (!expanded && details.results.some((entry) => isResultRunning(entry))) {
337
- c.addChild(
338
- new Text(
339
- theme.fg("accent", keyHint("app.tools.expand", "for live detail")),
340
- 0,
341
- 0,
342
- ),
343
- );
344
- }
345
-
346
- return c;
347
- }
348
-
349
- export function renderSubagentResult(
350
- result: SubagentToolResult,
351
- options: RenderSubagentResultOptions,
352
- theme: Theme,
353
- context?: RenderAnimationContext,
354
- ): Component {
355
- const details = result.details;
356
- if (!details || details.results.length === 0) {
357
- const text = result.content[0];
358
- return new Text(
359
- text?.type === "text" ? (text.text ?? "(no output)") : "(no output)",
360
- 0,
361
- 0,
362
- );
363
- }
364
-
365
- if (details.async && details.runId) {
366
- return new Text(
367
- `${theme.fg("warning", "⏳")} ${theme.fg("toolTitle", theme.bold("async "))}${theme.fg("accent", details.runId)}`,
368
- 0,
369
- 0,
370
- );
371
- }
372
-
373
- if (details.mode === "chain") {
374
- return renderChainResult(details, theme, options, context);
375
- }
376
-
377
- if (details.mode === "parallel") {
378
- return renderParallelResult(details, theme, options, context);
379
- }
380
-
381
- if (details.mode === "single" && details.results.length === 1) {
382
- return renderSingleResult(details.results[0], theme, options, context);
383
- }
384
-
385
- const lines: string[] = [];
386
- const icon =
387
- details.results.every((entry) => !isFailedResult(entry))
388
- ? theme.fg("success", "✓")
389
- : theme.fg("error", "✗");
390
- lines.push(
391
- `${icon} ${theme.fg("toolTitle", theme.bold(details.mode))} (${details.results.length})`,
392
- );
393
- for (const entry of details.results) {
394
- const rIcon = isFailedResult(entry)
395
- ? theme.fg("error", "✗")
396
- : theme.fg("success", "✓");
397
- const output = getFinalOutput(entry.messages) || getResultOutput(entry);
398
- const preview = output.length > 120 ? `${output.slice(0, 120)}...` : output;
399
- lines.push(`${rIcon} ${theme.fg("accent", entry.agent)}: ${theme.fg("dim", preview)}`);
400
- }
401
- return new Text(lines.join("\n"), 0, 0);
402
- }
403
-
404
- export function renderWaitCall(
405
- args: Record<string, unknown>,
406
- theme: Theme,
407
- ): Text {
408
- const id = args.id as string | undefined;
409
- const all = args.all === true;
410
- const label = id ? `id=${id}` : all ? "all" : "next";
411
- return new Text(
412
- theme.fg("toolTitle", theme.bold("wait ")) + theme.fg("accent", label),
413
- 0,
414
- 0,
415
- );
416
- }
417
-
418
- export function renderWaitResult(
419
- result: { content: Array<{ type: string; text?: string }> },
420
- theme: Theme,
421
- ): Text {
422
- const text = result.content[0];
423
- return new Text(
424
- text?.type === "text" ? (text.text ?? "(no output)") : "(no output)",
425
- 0,
426
- 0,
427
- );
428
- }
1
+ import { keyHint } from "@earendil-works/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
+ import { Container, Spacer, Text, type Component } from "@earendil-works/pi-tui";
4
+ import type { AgentProgress } from "./progress.ts";
5
+ import {
6
+ formatDuration,
7
+ formatTokenCount,
8
+ formatToolArgs,
9
+ } from "./progress.ts";
10
+ import type { RenderAnimationContext } from "./render-animation.ts";
11
+ import { getAnimationFrame } from "./render-animation.ts";
12
+ import type { SingleResult, SubagentDetails } from "./types.ts";
13
+ import { getFinalOutput, getResultOutput, isFailedResult } from "./utils.ts";
14
+
15
+ const RUNNING_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
16
+
17
+ export function renderSubagentCall(
18
+ args: Record<string, unknown>,
19
+ theme: Theme,
20
+ ): Text {
21
+ const scope = (args.agentScope as string | undefined) ?? "both";
22
+
23
+ if (Array.isArray(args.chain) && args.chain.length > 0) {
24
+ let text =
25
+ theme.fg("toolTitle", theme.bold("subagent ")) +
26
+ theme.fg("accent", `chain (${args.chain.length} steps)`) +
27
+ theme.fg("muted", ` [${scope}]`);
28
+ for (let i = 0; i < Math.min(args.chain.length, 3); i++) {
29
+ const step = args.chain[i] as { agent: string; task: string };
30
+ const preview =
31
+ step.task.length > 40 ? `${step.task.slice(0, 40)}...` : step.task;
32
+ text += `\n ${theme.fg("muted", `${i + 1}.`)} ${theme.fg("accent", step.agent)}${theme.fg("dim", ` ${preview}`)}`;
33
+ }
34
+ return new Text(text, 0, 0);
35
+ }
36
+
37
+ if (Array.isArray(args.tasks) && args.tasks.length > 0) {
38
+ let text =
39
+ theme.fg("toolTitle", theme.bold("subagent ")) +
40
+ theme.fg("accent", `parallel (${args.tasks.length} tasks)`) +
41
+ theme.fg("muted", ` [${scope}]`);
42
+ for (const task of (args.tasks as Array<{ agent: string; task: string }>).slice(0, 3)) {
43
+ const preview = task.task.length > 40 ? `${task.task.slice(0, 40)}...` : task.task;
44
+ text += `\n ${theme.fg("accent", task.agent)}${theme.fg("dim", ` ${preview}`)}`;
45
+ }
46
+ return new Text(text, 0, 0);
47
+ }
48
+
49
+ const agentName = (args.agent as string | undefined) || "...";
50
+ const task = args.task as string | undefined;
51
+ const preview = task ? (task.length > 60 ? `${task.slice(0, 60)}...` : task) : "...";
52
+ const asyncLabel = args.async ? theme.fg("warning", " async") : "";
53
+ let text =
54
+ theme.fg("toolTitle", theme.bold("subagent ")) +
55
+ theme.fg("accent", agentName) +
56
+ asyncLabel +
57
+ theme.fg("muted", ` [${scope}]`);
58
+ text += `\n ${theme.fg("dim", preview)}`;
59
+ return new Text(text, 0, 0);
60
+ }
61
+
62
+ export interface RenderSubagentResultOptions {
63
+ expanded: boolean;
64
+ isPartial?: boolean;
65
+ }
66
+
67
+ type SubagentToolResult = {
68
+ content: Array<{ type: string; text?: string }>;
69
+ details?: SubagentDetails;
70
+ };
71
+
72
+ export function subagentResultIsRunning(
73
+ result: SubagentToolResult,
74
+ options?: RenderSubagentResultOptions,
75
+ ): boolean {
76
+ if (options?.isPartial) return true;
77
+ const details = result.details;
78
+ if (!details || details.async) return false;
79
+ return details.results.some(
80
+ (entry) =>
81
+ entry.progress?.status === "running" || entry.exitCode === -1,
82
+ );
83
+ }
84
+
85
+ function runningGlyph(frame: number): string {
86
+ return RUNNING_FRAMES[frame % RUNNING_FRAMES.length] ?? "●";
87
+ }
88
+
89
+ function getProgressStats(
90
+ progress: Pick<AgentProgress, "toolCount" | "tokens" | "durationMs"> | undefined,
91
+ ): string {
92
+ if (!progress) return "";
93
+ const parts: string[] = [];
94
+ if (progress.toolCount > 0) parts.push(`${progress.toolCount} tools`);
95
+ if (progress.tokens > 0) parts.push(`${formatTokenCount(progress.tokens)} tok`);
96
+ if (progress.durationMs > 0) parts.push(formatDuration(progress.durationMs));
97
+ return parts.join(" · ");
98
+ }
99
+
100
+ function formatCurrentToolLine(progress: AgentProgress, expanded: boolean): string | null {
101
+ if (!progress.currentTool) return null;
102
+ const args = progress.currentToolArgs;
103
+ if (args) {
104
+ return expanded
105
+ ? `${progress.currentTool}: ${args}`
106
+ : `${progress.currentTool}`;
107
+ }
108
+ return progress.currentTool;
109
+ }
110
+
111
+ function compactActivity(progress: AgentProgress): string {
112
+ return formatCurrentToolLine(progress, false) ?? "thinking…";
113
+ }
114
+
115
+ function isResultRunning(entry: SingleResult): boolean {
116
+ return entry.progress?.status === "running" || entry.exitCode === -1;
117
+ }
118
+
119
+ function getToolCallLines(entry: SingleResult): string[] {
120
+ const lines: string[] = [];
121
+ for (const msg of entry.messages) {
122
+ if (msg.role !== "assistant") continue;
123
+ for (const part of msg.content) {
124
+ if (part.type === "toolCall") {
125
+ const args = formatToolArgs(part.arguments);
126
+ lines.push(args ? `${part.name}: ${args}` : part.name);
127
+ }
128
+ }
129
+ }
130
+ return lines;
131
+ }
132
+
133
+ function renderSingleResult(
134
+ entry: SingleResult,
135
+ theme: Theme,
136
+ options: RenderSubagentResultOptions,
137
+ context?: RenderAnimationContext,
138
+ ): Component {
139
+ const expanded = options.expanded;
140
+ const running = isResultRunning(entry);
141
+ const progress = entry.progress;
142
+ const summary = progress ?? entry.progressSummary;
143
+ const frame = getAnimationFrame(context);
144
+
145
+ if (running && progress) {
146
+ const c = new Container();
147
+ const stats = getProgressStats(progress);
148
+ const glyph = theme.fg("warning", runningGlyph(frame));
149
+ let header = `${glyph} ${theme.fg("toolTitle", theme.bold(entry.agent))}`;
150
+ if (stats) header += ` ${theme.fg("dim", `· ${stats}`)}`;
151
+ c.addChild(new Text(header, 0, 0));
152
+
153
+ if (expanded) {
154
+ c.addChild(new Spacer(1));
155
+ const taskPreview =
156
+ entry.task.length > 200 && !expanded
157
+ ? `${entry.task.slice(0, 200)}...`
158
+ : entry.task;
159
+ c.addChild(new Text(theme.fg("dim", `Task: ${taskPreview}`), 0, 0));
160
+ c.addChild(new Spacer(1));
161
+ }
162
+
163
+ const toolLine = formatCurrentToolLine(progress, expanded);
164
+ if (toolLine) {
165
+ c.addChild(new Text(theme.fg("warning", `> ${toolLine}`), 0, 0));
166
+ } else {
167
+ c.addChild(new Text(theme.fg("dim", ` ⎿ ${compactActivity(progress)}`), 0, 0));
168
+ }
169
+
170
+ if (!expanded) {
171
+ c.addChild(new Text(theme.fg("accent", ` ${keyHint("app.tools.expand", "for live detail")}`), 0, 0));
172
+ return c;
173
+ }
174
+
175
+ c.addChild(new Text(theme.fg("accent", keyHint("app.tools.expand", "for live detail")), 0, 0));
176
+
177
+ if (progress.recentTools.length > 0) {
178
+ c.addChild(new Spacer(1));
179
+ for (const tool of progress.recentTools.slice(-3)) {
180
+ const line = tool.args ? `${tool.tool}: ${tool.args}` : tool.tool;
181
+ c.addChild(new Text(theme.fg("dim", line), 0, 0));
182
+ }
183
+ }
184
+
185
+ if (progress.recentOutput.length > 0) {
186
+ c.addChild(new Spacer(1));
187
+ for (const line of progress.recentOutput.slice(-5)) {
188
+ c.addChild(new Text(theme.fg("dim", ` ${line}`), 0, 0));
189
+ }
190
+ }
191
+
192
+ return c;
193
+ }
194
+
195
+ const failed = isFailedResult(entry);
196
+ const icon = failed ? theme.fg("error", "✗") : theme.fg("success", "✓");
197
+ const output = getResultOutput(entry);
198
+ const stats = summary ? getProgressStats(summary as AgentProgress) : "";
199
+
200
+ const c = new Container();
201
+ let header = `${icon} ${theme.fg("toolTitle", theme.bold(entry.agent))}`;
202
+ if (stats) header += ` ${theme.fg("dim", `· ${stats}`)}`;
203
+ c.addChild(new Text(header, 0, 0));
204
+
205
+ if (expanded) {
206
+ c.addChild(new Spacer(1));
207
+ c.addChild(new Text(theme.fg("dim", `Task: ${entry.task}`), 0, 0));
208
+ const toolLines = getToolCallLines(entry);
209
+ if (toolLines.length > 0) {
210
+ c.addChild(new Spacer(1));
211
+ for (const line of toolLines) {
212
+ c.addChild(new Text(theme.fg("muted", line), 0, 0));
213
+ }
214
+ }
215
+ c.addChild(new Spacer(1));
216
+ c.addChild(new Text(theme.fg("toolOutput", output), 0, 0));
217
+ return c;
218
+ }
219
+
220
+ const preview = output.length > 120 ? `${output.slice(0, 120)}...` : output;
221
+ c.addChild(new Text(theme.fg("dim", ` ⎿ ${preview}`), 0, 0));
222
+ return c;
223
+ }
224
+
225
+ function renderChainResult(
226
+ details: SubagentDetails,
227
+ theme: Theme,
228
+ options: RenderSubagentResultOptions,
229
+ context?: RenderAnimationContext,
230
+ ): Component {
231
+ const expanded = options.expanded;
232
+ const frame = getAnimationFrame(context);
233
+ const c = new Container();
234
+
235
+ const flowParts: string[] = [];
236
+ for (let i = 0; i < details.results.length; i++) {
237
+ const entry = details.results[i];
238
+ if (isResultRunning(entry)) {
239
+ flowParts.push(
240
+ `${theme.fg("warning", runningGlyph(frame))} ${theme.fg("accent", entry.agent)}`,
241
+ );
242
+ } else if (isFailedResult(entry)) {
243
+ flowParts.push(`${theme.fg("error", "✗")} ${theme.fg("accent", entry.agent)}`);
244
+ } else {
245
+ flowParts.push(`${theme.fg("success", "✓")} ${theme.fg("accent", entry.agent)}`);
246
+ }
247
+ if (i < details.results.length - 1) flowParts.push(theme.fg("muted", " → "));
248
+ }
249
+ c.addChild(
250
+ new Text(
251
+ `${theme.fg("toolTitle", theme.bold("chain"))} ${flowParts.join("")}`,
252
+ 0,
253
+ 0,
254
+ ),
255
+ );
256
+
257
+ if (expanded) {
258
+ c.addChild(new Spacer(1));
259
+ for (const entry of details.results) {
260
+ c.addChild(renderSingleResult(entry, theme, { ...options, expanded: false }, context));
261
+ c.addChild(new Spacer(1));
262
+ }
263
+ const last = details.results[details.results.length - 1];
264
+ if (last && !isResultRunning(last)) {
265
+ c.addChild(new Text(theme.fg("toolOutput", getResultOutput(last)), 0, 0));
266
+ }
267
+ return c;
268
+ }
269
+
270
+ const runningEntry = details.results.find((entry) => isResultRunning(entry));
271
+ if (runningEntry?.progress) {
272
+ c.addChild(
273
+ new Text(
274
+ theme.fg("dim", ` ⎿ ${compactActivity(runningEntry.progress)}`),
275
+ 0,
276
+ 0,
277
+ ),
278
+ );
279
+ c.addChild(
280
+ new Text(
281
+ theme.fg("accent", ` ${keyHint("app.tools.expand", "for live detail")}`),
282
+ 0,
283
+ 0,
284
+ ),
285
+ );
286
+ }
287
+
288
+ return c;
289
+ }
290
+
291
+ function renderParallelResult(
292
+ details: SubagentDetails,
293
+ theme: Theme,
294
+ options: RenderSubagentResultOptions,
295
+ context?: RenderAnimationContext,
296
+ ): Component {
297
+ const expanded = options.expanded;
298
+ const frame = getAnimationFrame(context);
299
+ const total = details.results.length;
300
+ const done = details.results.filter((entry) => !isResultRunning(entry)).length;
301
+ const c = new Container();
302
+
303
+ c.addChild(
304
+ new Text(
305
+ `${theme.fg("toolTitle", theme.bold("parallel"))} ${theme.fg("dim", `${done}/${total} done`)}`,
306
+ 0,
307
+ 0,
308
+ ),
309
+ );
310
+
311
+ for (const entry of details.results) {
312
+ const running = isResultRunning(entry);
313
+ const glyph = running
314
+ ? theme.fg("warning", runningGlyph(frame))
315
+ : isFailedResult(entry)
316
+ ? theme.fg("error", "✗")
317
+ : theme.fg("success", "✓");
318
+ let line = `${glyph} ${theme.fg("accent", entry.agent)}`;
319
+ if (running && entry.progress) {
320
+ line += theme.fg("dim", ` ⎿ ${compactActivity(entry.progress)}`);
321
+ } else if (!running) {
322
+ const output = getFinalOutput(entry.messages) || getResultOutput(entry);
323
+ const preview = output.length > 80 ? `${output.slice(0, 80)}...` : output;
324
+ if (preview) line += theme.fg("dim", ` ${preview}`);
325
+ }
326
+ c.addChild(new Text(line, 0, 0));
327
+
328
+ if (expanded && running && entry.progress) {
329
+ for (const tool of entry.progress.recentTools.slice(-2)) {
330
+ const toolLine = tool.args ? `${tool.tool}: ${tool.args}` : tool.tool;
331
+ c.addChild(new Text(theme.fg("dim", ` ${toolLine}`), 0, 0));
332
+ }
333
+ }
334
+ }
335
+
336
+ if (!expanded && details.results.some((entry) => isResultRunning(entry))) {
337
+ c.addChild(
338
+ new Text(
339
+ theme.fg("accent", keyHint("app.tools.expand", "for live detail")),
340
+ 0,
341
+ 0,
342
+ ),
343
+ );
344
+ }
345
+
346
+ return c;
347
+ }
348
+
349
+ export function renderSubagentResult(
350
+ result: SubagentToolResult,
351
+ options: RenderSubagentResultOptions,
352
+ theme: Theme,
353
+ context?: RenderAnimationContext,
354
+ ): Component {
355
+ const details = result.details;
356
+ if (!details || details.results.length === 0) {
357
+ const text = result.content[0];
358
+ return new Text(
359
+ text?.type === "text" ? (text.text ?? "(no output)") : "(no output)",
360
+ 0,
361
+ 0,
362
+ );
363
+ }
364
+
365
+ if (details.async && details.runId) {
366
+ return new Text(
367
+ `${theme.fg("warning", "⏳")} ${theme.fg("toolTitle", theme.bold("async "))}${theme.fg("accent", details.runId)}`,
368
+ 0,
369
+ 0,
370
+ );
371
+ }
372
+
373
+ if (details.mode === "chain") {
374
+ return renderChainResult(details, theme, options, context);
375
+ }
376
+
377
+ if (details.mode === "parallel") {
378
+ return renderParallelResult(details, theme, options, context);
379
+ }
380
+
381
+ if (details.mode === "single" && details.results.length === 1) {
382
+ return renderSingleResult(details.results[0], theme, options, context);
383
+ }
384
+
385
+ const lines: string[] = [];
386
+ const icon =
387
+ details.results.every((entry) => !isFailedResult(entry))
388
+ ? theme.fg("success", "✓")
389
+ : theme.fg("error", "✗");
390
+ lines.push(
391
+ `${icon} ${theme.fg("toolTitle", theme.bold(details.mode))} (${details.results.length})`,
392
+ );
393
+ for (const entry of details.results) {
394
+ const rIcon = isFailedResult(entry)
395
+ ? theme.fg("error", "✗")
396
+ : theme.fg("success", "✓");
397
+ const output = getFinalOutput(entry.messages) || getResultOutput(entry);
398
+ const preview = output.length > 120 ? `${output.slice(0, 120)}...` : output;
399
+ lines.push(`${rIcon} ${theme.fg("accent", entry.agent)}: ${theme.fg("dim", preview)}`);
400
+ }
401
+ return new Text(lines.join("\n"), 0, 0);
402
+ }
403
+
404
+ export function renderWaitCall(
405
+ args: Record<string, unknown>,
406
+ theme: Theme,
407
+ ): Text {
408
+ const id = args.id as string | undefined;
409
+ const all = args.all === true;
410
+ const label = id ? `id=${id}` : all ? "all" : "next";
411
+ return new Text(
412
+ theme.fg("toolTitle", theme.bold("wait ")) + theme.fg("accent", label),
413
+ 0,
414
+ 0,
415
+ );
416
+ }
417
+
418
+ export function renderWaitResult(
419
+ result: { content: Array<{ type: string; text?: string }> },
420
+ theme: Theme,
421
+ ): Text {
422
+ const text = result.content[0];
423
+ return new Text(
424
+ text?.type === "text" ? (text.text ?? "(no output)") : "(no output)",
425
+ 0,
426
+ 0,
427
+ );
428
+ }