@selesai/code 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/dist/core/diagnostics.d.ts +1 -0
  2. package/dist/core/diagnostics.d.ts.map +1 -1
  3. package/dist/core/diagnostics.js.map +1 -1
  4. package/dist/core/package-manager.d.ts +10 -0
  5. package/dist/core/package-manager.d.ts.map +1 -1
  6. package/dist/core/package-manager.js +93 -8
  7. package/dist/core/package-manager.js.map +1 -1
  8. package/dist/core/package-manager.test.d.ts +2 -0
  9. package/dist/core/package-manager.test.d.ts.map +1 -0
  10. package/dist/core/package-manager.test.js +20 -0
  11. package/dist/core/package-manager.test.js.map +1 -0
  12. package/dist/core/resource-loader.d.ts +3 -0
  13. package/dist/core/resource-loader.d.ts.map +1 -1
  14. package/dist/core/resource-loader.js +10 -0
  15. package/dist/core/resource-loader.js.map +1 -1
  16. package/dist/core/settings-manager.d.ts +1 -0
  17. package/dist/core/settings-manager.d.ts.map +1 -1
  18. package/dist/core/settings-manager.js.map +1 -1
  19. package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
  20. package/dist/extensions/caveman/index.js +116 -0
  21. package/dist/extensions/caveman/package.json +8 -0
  22. package/dist/extensions/caveman/test/extension.test.js +185 -0
  23. package/dist/extensions/caveman/test/helpers.test.js +67 -0
  24. package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
  25. package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
  26. package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
  27. package/dist/extensions/pi-intercom/config.ts +2 -2
  28. package/dist/extensions/pi-intercom/index.ts +2 -2
  29. package/dist/extensions/pi-intercom/package.json +2 -2
  30. package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
  31. package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
  32. package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
  33. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
  34. package/dist/extensions/pi-powerline-footer/index.ts +9 -16
  35. package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
  36. package/dist/extensions/pi-powerline-footer/working-vibes.ts +2 -9
  37. package/dist/extensions/pi-rewind-hook/index.ts +1 -1
  38. package/dist/extensions/pi-rewind-hook/package.json +1 -1
  39. package/dist/extensions/pi-subagents/README.md +224 -52
  40. package/dist/extensions/pi-subagents/install.mjs +93 -0
  41. package/dist/extensions/pi-subagents/package.json +4 -5
  42. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
  43. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
  44. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
  45. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
  46. package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
  47. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
  48. package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
  49. package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
  50. package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
  51. package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
  52. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
  53. package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
  54. package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
  55. package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
  56. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
  57. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
  58. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
  59. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
  61. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
  62. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
  63. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
  64. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
  65. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
  66. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
  67. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
  68. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
  69. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
  70. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
  71. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
  72. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
  73. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
  74. package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
  75. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
  76. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
  77. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
  78. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
  79. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
  80. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
  81. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
  84. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
  86. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
  87. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
  88. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
  89. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
  90. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
  92. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
  93. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
  94. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
  95. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
  96. package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
  97. package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
  98. package/dist/extensions/pi-subagents/src/shared/utils.ts +100 -15
  99. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
  100. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
  101. package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
  102. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
  103. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
  104. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
  105. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
  106. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
  107. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
  108. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
  109. package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
  110. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
  111. package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
  112. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
  113. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
  114. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
  115. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
  116. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
  117. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
  118. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
  119. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
  120. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
  121. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
  122. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
  123. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
  124. package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
  125. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
  126. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
  127. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
  128. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
  129. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
  130. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
  131. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
  132. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
  133. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
  134. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
  135. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
  136. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
  137. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
  138. package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
  139. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
  140. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
  141. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
  142. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
  143. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
  144. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
  145. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
  146. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
  147. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
  148. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
  149. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
  150. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
  151. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
  152. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
  153. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
  154. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
  155. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
  156. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
  157. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
  158. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
  159. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
  160. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
  161. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
  162. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
  163. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
  164. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
  165. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
  166. package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
  167. package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
  168. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
  169. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
  170. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
  171. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
  172. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
  173. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
  174. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
  175. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
  176. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
  177. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
  178. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
  179. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
  180. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
  181. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
  182. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
  183. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
  184. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
  185. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
  186. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
  187. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
  188. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
  189. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
  190. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
  191. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
  192. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
  193. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
  194. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
  195. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
  196. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
  197. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
  198. package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
  199. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
  200. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
  201. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
  202. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
  203. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
  204. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
  205. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
  206. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
  207. package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
  208. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
  209. package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
  210. package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
  211. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
  212. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
  213. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
  214. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
  215. package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
  216. package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
  217. package/dist/main.d.ts.map +1 -1
  218. package/dist/main.js +4 -0
  219. package/dist/main.js.map +1 -1
  220. package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
  221. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  222. package/dist/modes/interactive/components/first-time-setup.js +19 -5
  223. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  224. package/dist/skills/caveman/SKILL.md +49 -0
  225. package/dist/skills/handoff-text/SKILL.md +13 -0
  226. package/docs/docs.json +4 -0
  227. package/docs/extensions.md +2 -0
  228. package/docs/settings.md +15 -0
  229. package/docs/shared-host-extensions.md +109 -0
  230. package/package.json +1 -1
@@ -0,0 +1,330 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
5
+ import { parseFrontmatter } from "../agents/frontmatter.ts";
6
+ import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
7
+ import type { ChainStep } from "../shared/settings.ts";
8
+ import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
9
+
10
+ interface PromptWorkflow {
11
+ name: string;
12
+ description: string;
13
+ body: string;
14
+ filePath: string;
15
+ agent: string;
16
+ context?: "fresh" | "fork";
17
+ model?: string;
18
+ skill?: string | string[] | false;
19
+ cwd?: string;
20
+ worktree?: boolean;
21
+ chain?: string;
22
+ }
23
+
24
+ type PromptWorkflowRunner = (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<void>;
25
+
26
+ const RESERVED_COMMAND_NAMES = new Set([
27
+ "chain-prompts",
28
+ "prompt-workflow",
29
+ "run",
30
+ "chain",
31
+ "parallel",
32
+ "run-chain",
33
+ "subagents-doctor",
34
+ "subagents-models",
35
+ ]);
36
+
37
+ function packagePromptsDir(): string {
38
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "prompts");
39
+ }
40
+
41
+ function promptDirs(cwd: string): string[] {
42
+ return [
43
+ packagePromptsDir(),
44
+ path.join(getAgentDir(), "prompts"),
45
+ path.join(getProjectConfigDir(cwd), "prompts"),
46
+ ];
47
+ }
48
+
49
+ function readPromptFiles(cwd: string): string[] {
50
+ const files: string[] = [];
51
+ for (const dir of promptDirs(cwd)) {
52
+ let entries: fs.Dirent[];
53
+ try {
54
+ entries = fs.readdirSync(dir, { withFileTypes: true });
55
+ } catch {
56
+ continue;
57
+ }
58
+ for (const entry of entries) {
59
+ if (entry.isFile() && entry.name.endsWith(".md")) files.push(path.join(dir, entry.name));
60
+ }
61
+ }
62
+ return files;
63
+ }
64
+
65
+ function firstNonEmptyLine(value: string): string {
66
+ return value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "Prompt workflow";
67
+ }
68
+
69
+ function stringField(frontmatter: Record<string, string>, key: string): string | undefined {
70
+ const value = frontmatter[key]?.trim();
71
+ return value ? value : undefined;
72
+ }
73
+
74
+ function booleanField(frontmatter: Record<string, string>, key: string): boolean | undefined {
75
+ const value = frontmatter[key]?.trim().toLowerCase();
76
+ if (value === "true" || value === "yes" || value === "1") return true;
77
+ if (value === "false" || value === "no" || value === "0") return false;
78
+ return undefined;
79
+ }
80
+
81
+ function parseSkill(value: string | undefined): string | string[] | false | undefined {
82
+ if (!value) return undefined;
83
+ if (value === "false") return false;
84
+ const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
85
+ return parts.length > 1 ? parts : parts[0];
86
+ }
87
+
88
+ function parseAgent(frontmatter: Record<string, string>): string {
89
+ const subagent = stringField(frontmatter, "subagent");
90
+ if (!subagent || subagent === "true") return "delegate";
91
+ return subagent;
92
+ }
93
+
94
+ function loadPromptWorkflow(filePath: string): PromptWorkflow | undefined {
95
+ const content = fs.readFileSync(filePath, "utf-8");
96
+ const { frontmatter, body } = parseFrontmatter(content);
97
+ const name = path.basename(filePath, ".md");
98
+ if (!name || RESERVED_COMMAND_NAMES.has(name)) return undefined;
99
+ const model = stringField(frontmatter, "model");
100
+ const skill = parseSkill(stringField(frontmatter, "skill"));
101
+ const cwd = stringField(frontmatter, "cwd");
102
+ const chain = stringField(frontmatter, "chain");
103
+ return {
104
+ name,
105
+ description: stringField(frontmatter, "description") ?? firstNonEmptyLine(body),
106
+ body,
107
+ filePath,
108
+ agent: parseAgent(frontmatter),
109
+ ...(booleanField(frontmatter, "inheritContext") === true || booleanField(frontmatter, "fork") === true ? { context: "fork" as const } : {}),
110
+ ...(booleanField(frontmatter, "fresh") === true ? { context: "fresh" as const } : {}),
111
+ ...(model ? { model } : {}),
112
+ ...(skill !== undefined ? { skill } : {}),
113
+ ...(cwd ? { cwd } : {}),
114
+ ...(booleanField(frontmatter, "worktree") === true ? { worktree: true } : {}),
115
+ ...(chain ? { chain } : {}),
116
+ };
117
+ }
118
+
119
+ export function discoverPromptWorkflows(cwd: string): PromptWorkflow[] {
120
+ const workflows = new Map<string, PromptWorkflow>();
121
+ for (const file of readPromptFiles(cwd)) {
122
+ const workflow = loadPromptWorkflow(file);
123
+ if (workflow) workflows.set(workflow.name, workflow);
124
+ }
125
+ return [...workflows.values()].sort((a, b) => a.name.localeCompare(b.name));
126
+ }
127
+
128
+ function shellWords(input: string): string[] {
129
+ const words: string[] = [];
130
+ let current = "";
131
+ let quote: "'" | '"' | undefined;
132
+ let escaped = false;
133
+ for (const ch of input) {
134
+ if (escaped) {
135
+ current += ch;
136
+ escaped = false;
137
+ continue;
138
+ }
139
+ if (ch === "\\") {
140
+ escaped = true;
141
+ continue;
142
+ }
143
+ if (quote) {
144
+ if (ch === quote) quote = undefined;
145
+ else current += ch;
146
+ continue;
147
+ }
148
+ if (ch === "'" || ch === '"') {
149
+ quote = ch;
150
+ continue;
151
+ }
152
+ if (/\s/.test(ch)) {
153
+ if (current) {
154
+ words.push(current);
155
+ current = "";
156
+ }
157
+ continue;
158
+ }
159
+ current += ch;
160
+ }
161
+ if (current) words.push(current);
162
+ return words;
163
+ }
164
+
165
+ function substituteArgs(template: string, args: string[]): string {
166
+ const all = args.join(" ");
167
+ return template
168
+ .replace(/\$ARGUMENTS/g, all)
169
+ .replace(/\$@/g, all)
170
+ .replace(/\$\{(\d+):-([^}]*)\}/g, (_match, index: string, fallback: string) => args[Number(index) - 1] || fallback)
171
+ .replace(/\$(\d+)/g, (_match, index: string) => args[Number(index) - 1] ?? "");
172
+ }
173
+
174
+ function parseRuntimeOptions(words: string[]): { args: string[]; agentOverride?: string; fork?: boolean; fresh?: boolean; worktree?: boolean; bg?: boolean } {
175
+ const args: string[] = [];
176
+ let agentOverride: string | undefined;
177
+ let fork = false;
178
+ let fresh = false;
179
+ let worktree = false;
180
+ let bg = false;
181
+ for (let i = 0; i < words.length; i++) {
182
+ const word = words[i]!;
183
+ if (word === "--fork") {
184
+ fork = true;
185
+ continue;
186
+ }
187
+ if (word === "--fresh") {
188
+ fresh = true;
189
+ continue;
190
+ }
191
+ if (word === "--worktree") {
192
+ worktree = true;
193
+ continue;
194
+ }
195
+ if (word === "--bg" || word === "--async") {
196
+ bg = true;
197
+ continue;
198
+ }
199
+ if (word === "--subagent") {
200
+ agentOverride = words[++i];
201
+ continue;
202
+ }
203
+ const eq = word.match(/^--subagent(?:=|:)(.+)$/);
204
+ if (eq) {
205
+ agentOverride = eq[1];
206
+ continue;
207
+ }
208
+ args.push(word);
209
+ }
210
+ return { args, agentOverride, fork, fresh, worktree, bg };
211
+ }
212
+
213
+ function splitChainDeclaration(input: string): { declaration: string; argsText: string } {
214
+ const delimiter = input.indexOf(" -- ");
215
+ if (delimiter === -1) return { declaration: input.trim(), argsText: "" };
216
+ return { declaration: input.slice(0, delimiter).trim(), argsText: input.slice(delimiter + 4).trim() };
217
+ }
218
+
219
+ function splitPromptChain(input: string): string[] {
220
+ return input.split(" -> ").map((part) => part.trim()).filter(Boolean);
221
+ }
222
+
223
+ function workflowParams(workflow: PromptWorkflow, args: string[], runtime: ReturnType<typeof parseRuntimeOptions>): SubagentParamsLike {
224
+ const task = substituteArgs(workflow.body, args).trim();
225
+ const context = runtime.fork ? "fork" : runtime.fresh ? "fresh" : workflow.context;
226
+ return {
227
+ agent: runtime.agentOverride ?? workflow.agent,
228
+ task,
229
+ clarify: false,
230
+ agentScope: "both",
231
+ ...(context ? { context } : {}),
232
+ ...(workflow.model ? { model: workflow.model } : {}),
233
+ ...(workflow.skill !== undefined ? { skill: workflow.skill } : {}),
234
+ ...(workflow.cwd ? { cwd: workflow.cwd } : {}),
235
+ ...(runtime.worktree || workflow.worktree ? { worktree: true } : {}),
236
+ ...(runtime.bg ? { async: true } : {}),
237
+ };
238
+ }
239
+
240
+ function workflowChainStep(workflow: PromptWorkflow, args: string[], runtime: ReturnType<typeof parseRuntimeOptions>): ChainStep {
241
+ const params = workflowParams(workflow, args, runtime);
242
+ return {
243
+ agent: params.agent ?? "delegate",
244
+ task: params.task,
245
+ ...(params.model ? { model: params.model } : {}),
246
+ ...(params.skill !== undefined ? { skill: params.skill } : {}),
247
+ ...(params.cwd ? { cwd: params.cwd } : {}),
248
+ };
249
+ }
250
+
251
+ function findWorkflow(workflows: PromptWorkflow[], name: string): PromptWorkflow | undefined {
252
+ return workflows.find((workflow) => workflow.name === name);
253
+ }
254
+
255
+ function formatWorkflowList(workflows: PromptWorkflow[]): string {
256
+ if (workflows.length === 0) return "No prompt workflows found in package, user, or project prompts.";
257
+ return [
258
+ "Prompt workflows:",
259
+ ...workflows.map((workflow) => `- ${workflow.name}: ${workflow.description} (${workflow.filePath})`),
260
+ ].join("\n");
261
+ }
262
+
263
+ export function registerPromptWorkflowCommands(input: {
264
+ pi: ExtensionAPI;
265
+ run: PromptWorkflowRunner;
266
+ }): void {
267
+ const { pi, run } = input;
268
+
269
+ pi.registerCommand("prompt-workflow", {
270
+ description: "Run a prompt template through native pi-subagents: /prompt-workflow <name> [args]",
271
+ handler: async (rawArgs, ctx) => {
272
+ const words = shellWords(rawArgs);
273
+ const name = words.shift();
274
+ const workflows = discoverPromptWorkflows(ctx.cwd);
275
+ if (!name || name === "list") {
276
+ pi.sendMessage({ content: formatWorkflowList(workflows), display: true });
277
+ return;
278
+ }
279
+ const workflow = findWorkflow(workflows, name);
280
+ if (!workflow) {
281
+ ctx.ui.notify(`Unknown prompt workflow: ${name}`, "error");
282
+ return;
283
+ }
284
+ const runtime = parseRuntimeOptions(words);
285
+ try {
286
+ if (workflow.chain) {
287
+ const chainNames = splitPromptChain(workflow.chain);
288
+ const chain = chainNames.map((stepName) => {
289
+ const step = findWorkflow(workflows, stepName);
290
+ if (!step) throw new Error(`Unknown prompt workflow in chain '${workflow.name}': ${stepName}`);
291
+ return workflowChainStep(step, runtime.args, runtime);
292
+ });
293
+ await run({ chain, task: runtime.args.join(" "), clarify: false, agentScope: "both", ...(runtime.bg ? { async: true } : {}) }, ctx);
294
+ return;
295
+ }
296
+ await run(workflowParams(workflow, runtime.args, runtime), ctx);
297
+ } catch (error) {
298
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
299
+ }
300
+ },
301
+ });
302
+
303
+ pi.registerCommand("chain-prompts", {
304
+ description: "Run prompt templates as a native subagent chain: /chain-prompts analyze -> fix -- args",
305
+ handler: async (rawArgs, ctx) => {
306
+ const { declaration, argsText } = splitChainDeclaration(rawArgs);
307
+ const workflows = discoverPromptWorkflows(ctx.cwd);
308
+ if (!declaration || declaration === "list") {
309
+ pi.sendMessage({ content: formatWorkflowList(workflows), display: true });
310
+ return;
311
+ }
312
+ const runtime = parseRuntimeOptions(shellWords(argsText));
313
+ const names = splitPromptChain(declaration);
314
+ if (names.length === 0) {
315
+ ctx.ui.notify("Usage: /chain-prompts prompt-a -> prompt-b -- args", "error");
316
+ return;
317
+ }
318
+ try {
319
+ const chain = names.map((name) => {
320
+ const workflow = findWorkflow(workflows, name);
321
+ if (!workflow) throw new Error(`Unknown prompt workflow: ${name}`);
322
+ return workflowChainStep(workflow, runtime.args, runtime);
323
+ });
324
+ await run({ chain, task: runtime.args.join(" "), clarify: false, agentScope: "both", ...(runtime.bg ? { async: true } : {}) }, ctx);
325
+ } catch (error) {
326
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
327
+ }
328
+ },
329
+ });
330
+ }
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
4
+ import { keyText, type ExtensionAPI, type ExtensionContext } from "@selesai/code";
5
5
  import { Key, matchesKey } from "@earendil-works/pi-tui";
6
6
  import { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll, type ChainConfig } from "../agents/agents.ts";
7
7
  import {
@@ -16,14 +16,17 @@ import {
16
16
  import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
17
17
  import { isDynamicParallelStep, isParallelStep, type ChainStep } from "../shared/settings.ts";
18
18
  import { findModelInfo, toModelInfo } from "../shared/model-info.ts";
19
+ import { formatTokens } from "../shared/formatters.ts";
19
20
  import { assertJsonSchemaObject } from "../runs/shared/structured-output.ts";
20
21
  import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
21
22
  import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
23
+ import { registerPromptWorkflowCommands } from "./prompt-workflows.ts";
22
24
  import {
23
25
  applySlashUpdate,
24
26
  buildSlashInitialResult,
25
27
  failSlashResult,
26
28
  finalizeSlashResult,
29
+ resolveSlashMessageDetails,
27
30
  } from "./slash-live-state.ts";
28
31
  import {
29
32
  SLASH_RESULT_TYPE,
@@ -33,9 +36,11 @@ import {
33
36
  SLASH_SUBAGENT_RESPONSE_EVENT,
34
37
  SLASH_SUBAGENT_STARTED_EVENT,
35
38
  SLASH_SUBAGENT_UPDATE_EVENT,
39
+ type Details,
36
40
  type JsonSchemaObject,
37
41
  type SingleResult,
38
42
  type SubagentState,
43
+ type Usage,
39
44
  } from "../shared/types.ts";
40
45
 
41
46
  interface InlineConfig {
@@ -216,6 +221,113 @@ async function withSlashStatus<T>(
216
221
  }
217
222
  }
218
223
 
224
+ function emptyUsage(): Usage {
225
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
226
+ }
227
+
228
+ function addUsage(target: Usage, source: Usage): void {
229
+ target.input += source.input;
230
+ target.output += source.output;
231
+ target.cacheRead += source.cacheRead;
232
+ target.cacheWrite += source.cacheWrite;
233
+ target.cost += source.cost;
234
+ target.turns += source.turns;
235
+ }
236
+
237
+ function usageHasValue(usage: Usage): boolean {
238
+ return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0;
239
+ }
240
+
241
+ function assistantUsageFromMessage(message: unknown): Usage | undefined {
242
+ if (!message || typeof message !== "object") return undefined;
243
+ const msg = message as { role?: unknown; usage?: unknown };
244
+ if (msg.role !== "assistant" || !msg.usage || typeof msg.usage !== "object") return undefined;
245
+ const usage = msg.usage as {
246
+ input?: unknown;
247
+ output?: unknown;
248
+ cacheRead?: unknown;
249
+ cacheWrite?: unknown;
250
+ cost?: { total?: unknown };
251
+ };
252
+ return {
253
+ input: typeof usage.input === "number" ? usage.input : 0,
254
+ output: typeof usage.output === "number" ? usage.output : 0,
255
+ cacheRead: typeof usage.cacheRead === "number" ? usage.cacheRead : 0,
256
+ cacheWrite: typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0,
257
+ cost: typeof usage.cost?.total === "number" ? usage.cost.total : 0,
258
+ turns: 1,
259
+ };
260
+ }
261
+
262
+ function isSubagentDetails(value: unknown): value is Details {
263
+ if (!value || typeof value !== "object") return false;
264
+ const details = value as { mode?: unknown; results?: unknown };
265
+ return typeof details.mode === "string" && Array.isArray(details.results);
266
+ }
267
+
268
+ function detailsFromSessionEntry(entry: unknown): Details | undefined {
269
+ if (!entry || typeof entry !== "object") return undefined;
270
+ const record = entry as { type?: unknown; customType?: unknown; details?: unknown; message?: unknown };
271
+ if (record.type === "custom_message" && record.customType === SLASH_RESULT_TYPE) {
272
+ const details = resolveSlashMessageDetails(record.details)?.result.details;
273
+ return isSubagentDetails(details) ? details : undefined;
274
+ }
275
+ if (record.type !== "message" || !record.message || typeof record.message !== "object") return undefined;
276
+ const message = record.message as { role?: unknown; toolName?: unknown; details?: unknown };
277
+ if (message.role !== "toolResult" || message.toolName !== "subagent") return undefined;
278
+ return isSubagentDetails(message.details) ? message.details : undefined;
279
+ }
280
+
281
+ function formatCostUsage(label: string, usage: Usage): string {
282
+ const extras = [
283
+ usage.cacheRead ? `cache read ${formatTokens(usage.cacheRead)}` : "",
284
+ usage.cacheWrite ? `cache write ${formatTokens(usage.cacheWrite)}` : "",
285
+ usage.turns ? `${usage.turns} turn${usage.turns === 1 ? "" : "s"}` : "",
286
+ ].filter(Boolean);
287
+ return `${label}: ↑${formatTokens(usage.input)} ↓${formatTokens(usage.output)} $${usage.cost.toFixed(4)}${extras.length ? ` (${extras.join(", ")})` : ""}`;
288
+ }
289
+
290
+ function buildSubagentCostReport(ctx: ExtensionContext): string {
291
+ const parent = emptyUsage();
292
+ const childTotal = emptyUsage();
293
+ const total = emptyUsage();
294
+ const children: Array<{ label: string; usage: Usage; sessionFile?: string }> = [];
295
+ for (const entry of ctx.sessionManager.getBranch()) {
296
+ const message = entry.type === "message" ? (entry as { message?: unknown }).message : undefined;
297
+ const parentUsage = assistantUsageFromMessage(message);
298
+ if (parentUsage) addUsage(parent, parentUsage);
299
+ const details = detailsFromSessionEntry(entry);
300
+ if (!details) continue;
301
+ for (const result of details.results) {
302
+ if (!usageHasValue(result.usage)) continue;
303
+ const usage = { ...result.usage };
304
+ children.push({
305
+ label: `Child ${children.length + 1} (${result.agent})`,
306
+ usage,
307
+ ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
308
+ });
309
+ addUsage(childTotal, usage);
310
+ }
311
+ }
312
+ addUsage(total, parent);
313
+ addUsage(total, childTotal);
314
+ const lines = [
315
+ "Subagent cost",
316
+ "",
317
+ formatCostUsage("Parent", parent),
318
+ ];
319
+ if (children.length === 0) {
320
+ lines.push("No subagent child usage found in this session.");
321
+ } else {
322
+ for (const child of children) {
323
+ lines.push(formatCostUsage(child.label, child.usage));
324
+ if (child.sessionFile) lines.push(` Session: ${child.sessionFile}`);
325
+ }
326
+ }
327
+ lines.push("────────────────────────────", formatCostUsage("Children", childTotal), formatCostUsage("Total", total));
328
+ return lines.join("\n");
329
+ }
330
+
219
331
  function parseSingleRequiredArg(args: string, usage: string): { ok: true; value: string } | { ok: false; message: string } {
220
332
  const parts = args.trim().split(/\s+/).filter(Boolean);
221
333
  if (parts.length !== 1) return { ok: false, message: usage };
@@ -321,7 +433,8 @@ async function requestSlashRun(
321
433
  if (!ctx.hasUI) return;
322
434
  const tool = update.currentTool ? ` ${update.currentTool}` : "";
323
435
  const count = update.toolCount ?? 0;
324
- ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | Ctrl+O live detail`);
436
+ const liveDetailKey = keyText("app.tools.expand");
437
+ ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | ${liveDetailKey} live detail`);
325
438
  };
326
439
 
327
440
  const onTerminalInput = ctx.hasUI
@@ -962,6 +1075,12 @@ export function registerSlashCommands(
962
1075
  },
963
1076
  });
964
1077
 
1078
+ pi.registerCommand("subagent-cost", {
1079
+ description: "Show parent and subagent child usage cost for this session",
1080
+ handler: async (_args, ctx) => {
1081
+ sendSlashText(pi, buildSubagentCostReport(ctx));
1082
+ },
1083
+ });
965
1084
 
966
1085
  pi.registerCommand("subagents-doctor", {
967
1086
  description: "Show subagent diagnostics",
@@ -970,6 +1089,18 @@ export function registerSlashCommands(
970
1089
  },
971
1090
  });
972
1091
 
1092
+ pi.registerCommand("subagents-fleet", {
1093
+ description: "Show active subagent fleet status and transcript commands",
1094
+ handler: async (_args, ctx) => {
1095
+ await runSlashSubagent(pi, ctx, { action: "status", view: "fleet" });
1096
+ },
1097
+ });
1098
+
1099
+ registerPromptWorkflowCommands({
1100
+ pi,
1101
+ run: (params, ctx) => runSlashSubagent(pi, ctx, params),
1102
+ });
1103
+
973
1104
  pi.registerCommand("subagents-models", {
974
1105
  description: "Show runtime-loaded builtin subagent models",
975
1106
  getArgumentCompletions: makeBuiltinAgentNameCompletions(),
@@ -998,7 +1129,7 @@ export function registerSlashCommands(
998
1129
  handler: async (_args, _ctx) => {
999
1130
  const profiles = listSubagentProfiles();
1000
1131
  if (profiles.length === 0) {
1001
- sendSlashText(pi, "Subagent profiles\n\nNo subagent profiles found in ~/.pi/agent/profiles/pi-subagents/");
1132
+ sendSlashText(pi, "Subagent profiles\n\nNo subagent profiles found in ~/.selesai/agent/profiles/pi-subagents/");
1002
1133
  return;
1003
1134
  }
1004
1135
  sendSlashText(pi, `Subagent profiles\n\n${profiles.join("\n")}`);
@@ -1006,7 +1137,7 @@ export function registerSlashCommands(
1006
1137
  });
1007
1138
 
1008
1139
  pi.registerCommand("subagents-load-profile", {
1009
- description: "Load a subagent profile into ~/.pi/agent/settings.json",
1140
+ description: "Load a subagent profile into ~/.selesai/agent/settings.json",
1010
1141
  getArgumentCompletions: (prefix) => {
1011
1142
  if (prefix.includes(" ")) return null;
1012
1143
  return listSubagentProfiles()
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as path from "node:path";
6
6
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
7
- import { getMarkdownTheme, type ExtensionContext } from "@selesai/code";
7
+ import { getMarkdownTheme, keyText, type ExtensionContext } from "@selesai/code";
8
8
  import { Container, Markdown, Spacer, Text, visibleWidth, type Component } from "@earendil-works/pi-tui";
9
9
  import {
10
10
  type AgentProgress,
@@ -26,6 +26,14 @@ import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, form
26
26
 
27
27
  type Theme = ExtensionContext["ui"]["theme"];
28
28
 
29
+ function liveDetailKeyText(): string {
30
+ return keyText("app.tools.expand");
31
+ }
32
+
33
+ function liveDetailHintText(): string {
34
+ return `Press ${liveDetailKeyText()} for live detail`;
35
+ }
36
+
29
37
  function getTermWidth(): number {
30
38
  return process.stdout.columns || 120;
31
39
  }
@@ -236,6 +244,15 @@ function formatToolUseStat(count: number): string {
236
244
  return `${count} tool use${count === 1 ? "" : "s"}`;
237
245
  }
238
246
 
247
+ function formatTotalCostStat(totalCost: Details["totalCost"] | undefined): string {
248
+ if (!totalCost || (totalCost.inputTokens === 0 && totalCost.outputTokens === 0 && totalCost.costUsd === 0)) return "";
249
+ const parts: string[] = [];
250
+ if (totalCost.inputTokens) parts.push(`in:${formatTokens(totalCost.inputTokens)}`);
251
+ if (totalCost.outputTokens) parts.push(`out:${formatTokens(totalCost.outputTokens)}`);
252
+ if (totalCost.costUsd) parts.push(`$${totalCost.costUsd.toFixed(4)}`);
253
+ return parts.join(" ");
254
+ }
255
+
239
256
  function formatProgressStats(theme: Theme, progress: Pick<AgentProgress, "toolCount" | "tokens" | "durationMs"> | undefined, includeDuration = true): string {
240
257
  if (!progress) return "";
241
258
  const parts: string[] = [];
@@ -858,7 +875,7 @@ function foregroundStyleWidgetStepLines(
858
875
  lines.push(` ${nestedLine}`);
859
876
  }
860
877
  if (step.status === "running") {
861
- if (!expanded) lines.push(` ${theme.fg("accent", "Press Ctrl+O for live detail")}`);
878
+ if (!expanded) lines.push(` ${theme.fg("accent", liveDetailHintText())}`);
862
879
  const output = widgetOutputPath(job, step);
863
880
  if (output) lines.push(` ${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
864
881
  if (expanded) {
@@ -925,7 +942,7 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
925
942
  lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
926
943
  for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt)) lines.push(` ${nestedLine}`);
927
944
  }
928
- if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", " Press Ctrl+O for live detail"));
945
+ if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` ${liveDetailHintText()}`));
929
946
  return lines.map((line) => truncLine(line, width));
930
947
  }
931
948
 
@@ -1117,7 +1134,7 @@ function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expan
1117
1134
  const hiddenCount = lines.length - visibleLines;
1118
1135
  const hint = expanded
1119
1136
  ? `… ${hiddenCount} live-detail lines hidden`
1120
- : `… ${hiddenCount} lines hidden · Ctrl+O expands`;
1137
+ : `… ${hiddenCount} lines hidden · ${liveDetailKeyText()} expands`;
1121
1138
  return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
1122
1139
  }
1123
1140
 
@@ -1275,7 +1292,7 @@ function renderSingleCompact(d: Details, r: Details["results"][number], theme: T
1275
1292
  c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
1276
1293
  const liveStatus = buildLiveStatusLine(r.progress, progressSnapshotNow);
1277
1294
  if (liveStatus && liveStatus !== activity) c.addChild(new Text(truncLine(theme.fg("dim", ` ${liveStatus}`), width), 0, 0));
1278
- c.addChild(new Text(truncLine(theme.fg("accent", " Press Ctrl+O for live detail"), width), 0, 0));
1295
+ c.addChild(new Text(truncLine(theme.fg("accent", ` ${liveDetailHintText()}`), width), 0, 0));
1279
1296
  if (r.artifactPaths) c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
1280
1297
  return c;
1281
1298
  }
@@ -1315,7 +1332,7 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
1315
1332
  }
1316
1333
  const multiLabel = buildMultiProgressLabel(d, hasRunning);
1317
1334
  const itemTitle = multiLabel.itemTitle;
1318
- const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary)]);
1335
+ const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary), formatTotalCostStat(d.totalCost)]);
1319
1336
  const glyph = hasRunning
1320
1337
  ? theme.fg("accent", runningGlyph(frame !== undefined ? (runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex) ?? 0) + frame : runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex)))
1321
1338
  : failed
@@ -1371,7 +1388,7 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
1371
1388
  if (rRunning && rProg && "status" in rProg) {
1372
1389
  const activity = compactCurrentActivity(rProg);
1373
1390
  c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
1374
- c.addChild(new Text(truncLine(theme.fg("accent", " Press Ctrl+O for live detail"), width), 0, 0));
1391
+ c.addChild(new Text(truncLine(theme.fg("accent", ` ${liveDetailHintText()}`), width), 0, 0));
1375
1392
  } else if (!rPending && (r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output))) {
1376
1393
  c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, output)}`), width), 0, 0));
1377
1394
  }
@@ -1453,7 +1470,7 @@ export function renderSubagentResult(
1453
1470
  if (liveStatusLine) {
1454
1471
  c.addChild(new Text(fit(theme.fg("accent", liveStatusLine)), 0, 0));
1455
1472
  }
1456
- c.addChild(new Text(fit(theme.fg("accent", "Press Ctrl+O for live detail")), 0, 0));
1473
+ c.addChild(new Text(fit(theme.fg("accent", liveDetailHintText())), 0, 0));
1457
1474
  if (r.artifactPaths) {
1458
1475
  c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1459
1476
  }
@@ -1547,10 +1564,13 @@ export function renderSubagentResult(
1547
1564
  { toolCount: 0, tokens: 0, durationMs: 0 },
1548
1565
  );
1549
1566
 
1550
- const summaryStr =
1567
+ const summaryParts = [
1551
1568
  totalSummary.toolCount || totalSummary.tokens
1552
- ? ` | ${totalSummary.toolCount} tools, ${formatTokens(totalSummary.tokens)} tok, ${formatDuration(totalSummary.durationMs)}`
1553
- : "";
1569
+ ? `${totalSummary.toolCount} tools, ${formatTokens(totalSummary.tokens)} tok, ${formatDuration(totalSummary.durationMs)}`
1570
+ : "",
1571
+ formatTotalCostStat(d.totalCost),
1572
+ ].filter(Boolean);
1573
+ const summaryStr = summaryParts.length ? ` | ${summaryParts.join(", ")}` : "";
1554
1574
 
1555
1575
  const modeLabel = d.mode;
1556
1576
  const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
@@ -1687,7 +1707,7 @@ export function renderSubagentResult(
1687
1707
  if (liveStatusLine) {
1688
1708
  c.addChild(new Text(fit(theme.fg("accent", ` ${liveStatusLine}`)), 0, 0));
1689
1709
  }
1690
- c.addChild(new Text(fit(theme.fg("accent", " Press Ctrl+O for live detail")), 0, 0));
1710
+ c.addChild(new Text(fit(theme.fg("accent", ` ${liveDetailHintText()}`)), 0, 0));
1691
1711
  if (r.artifactPaths) {
1692
1712
  c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1693
1713
  }