@astralyn/pi 0.82.5 → 0.82.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +1 -1
- package/dist/cli/project-trust.d.ts.map +1 -1
- package/dist/cli/project-trust.js +4 -1
- package/dist/cli/project-trust.js.map +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +11 -13
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +4 -22
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +32 -5
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts +1 -5
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +16 -41
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +42 -11
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +6 -7
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +17 -8
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +3 -3
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +3 -13
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +6 -36
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -4
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +7 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +11 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -3
- package/dist/core/tools/write.js.map +1 -1
- package/dist/extensions/deepwiki/render.d.ts.map +1 -1
- package/dist/extensions/deepwiki/render.js +34 -22
- package/dist/extensions/deepwiki/render.js.map +1 -1
- package/dist/extensions/plan/constants.d.ts +43 -6
- package/dist/extensions/plan/constants.d.ts.map +1 -1
- package/dist/extensions/plan/constants.js +51 -8
- package/dist/extensions/plan/constants.js.map +1 -1
- package/dist/extensions/plan/index.d.ts +12 -3
- package/dist/extensions/plan/index.d.ts.map +1 -1
- package/dist/extensions/plan/index.js +173 -35
- package/dist/extensions/plan/index.js.map +1 -1
- package/dist/extensions/plan/schema.d.ts +5 -0
- package/dist/extensions/plan/schema.d.ts.map +1 -1
- package/dist/extensions/plan/schema.js.map +1 -1
- package/dist/extensions/plan/state.d.ts +11 -2
- package/dist/extensions/plan/state.d.ts.map +1 -1
- package/dist/extensions/plan/state.js +31 -9
- package/dist/extensions/plan/state.js.map +1 -1
- package/dist/extensions/plan/storage.d.ts +11 -3
- package/dist/extensions/plan/storage.d.ts.map +1 -1
- package/dist/extensions/plan/storage.js +45 -20
- package/dist/extensions/plan/storage.js.map +1 -1
- package/dist/extensions/plan/view.d.ts +44 -0
- package/dist/extensions/plan/view.d.ts.map +1 -0
- package/dist/extensions/plan/view.js +149 -0
- package/dist/extensions/plan/view.js.map +1 -0
- package/dist/extensions/question/dialog.d.ts.map +1 -1
- package/dist/extensions/question/dialog.js +29 -10
- package/dist/extensions/question/dialog.js.map +1 -1
- package/dist/extensions/question/index.d.ts.map +1 -1
- package/dist/extensions/question/index.js +7 -2
- package/dist/extensions/question/index.js.map +1 -1
- package/dist/extensions/question/render.d.ts +4 -8
- package/dist/extensions/question/render.d.ts.map +1 -1
- package/dist/extensions/question/render.js +202 -29
- package/dist/extensions/question/render.js.map +1 -1
- package/dist/extensions/question/results.d.ts.map +1 -1
- package/dist/extensions/question/results.js +1 -0
- package/dist/extensions/question/results.js.map +1 -1
- package/dist/extensions/question/schema.d.ts.map +1 -1
- package/dist/extensions/question/schema.js +2 -2
- package/dist/extensions/question/schema.js.map +1 -1
- package/dist/extensions/question/types.d.ts +1 -0
- package/dist/extensions/question/types.d.ts.map +1 -1
- package/dist/extensions/question/types.js +1 -1
- package/dist/extensions/question/types.js.map +1 -1
- package/dist/extensions/router/stream.d.ts +1 -0
- package/dist/extensions/router/stream.d.ts.map +1 -1
- package/dist/extensions/router/stream.js +75 -0
- package/dist/extensions/router/stream.js.map +1 -1
- package/dist/extensions/subagent/activity.d.ts.map +1 -1
- package/dist/extensions/subagent/activity.js +25 -4
- package/dist/extensions/subagent/activity.js.map +1 -1
- package/dist/extensions/subagent/constants.d.ts +1 -0
- package/dist/extensions/subagent/constants.d.ts.map +1 -1
- package/dist/extensions/subagent/constants.js +1 -0
- package/dist/extensions/subagent/constants.js.map +1 -1
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +16 -0
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/render.d.ts.map +1 -1
- package/dist/extensions/subagent/render.js +352 -152
- package/dist/extensions/subagent/render.js.map +1 -1
- package/dist/extensions/subagent/resolve.d.ts.map +1 -1
- package/dist/extensions/subagent/resolve.js +13 -9
- package/dist/extensions/subagent/resolve.js.map +1 -1
- package/dist/extensions/subagent/retry.d.ts +17 -0
- package/dist/extensions/subagent/retry.d.ts.map +1 -0
- package/dist/extensions/subagent/retry.js +41 -0
- package/dist/extensions/subagent/retry.js.map +1 -0
- package/dist/extensions/subagent/runner.d.ts.map +1 -1
- package/dist/extensions/subagent/runner.js +37 -7
- package/dist/extensions/subagent/runner.js.map +1 -1
- package/dist/extensions/subagent/schema.d.ts.map +1 -1
- package/dist/extensions/subagent/schema.js +2 -2
- package/dist/extensions/subagent/schema.js.map +1 -1
- package/dist/extensions/subagent/sdk-runner.d.ts +6 -0
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
- package/dist/extensions/subagent/sdk-runner.js +46 -24
- package/dist/extensions/subagent/sdk-runner.js.map +1 -1
- package/dist/extensions/subagent/types.d.ts +7 -0
- package/dist/extensions/subagent/types.d.ts.map +1 -1
- package/dist/extensions/subagent/types.js.map +1 -1
- package/dist/extensions/todo/constants.d.ts +17 -6
- package/dist/extensions/todo/constants.d.ts.map +1 -1
- package/dist/extensions/todo/constants.js +22 -9
- package/dist/extensions/todo/constants.js.map +1 -1
- package/dist/extensions/todo/index.d.ts +0 -14
- package/dist/extensions/todo/index.d.ts.map +1 -1
- package/dist/extensions/todo/index.js +13 -1
- package/dist/extensions/todo/index.js.map +1 -1
- package/dist/extensions/todo/schema.d.ts +53 -2
- package/dist/extensions/todo/schema.d.ts.map +1 -1
- package/dist/extensions/todo/schema.js +57 -8
- package/dist/extensions/todo/schema.js.map +1 -1
- package/dist/extensions/todo/state.d.ts +16 -5
- package/dist/extensions/todo/state.d.ts.map +1 -1
- package/dist/extensions/todo/state.js +874 -130
- package/dist/extensions/todo/state.js.map +1 -1
- package/dist/extensions/todo/view.d.ts +16 -1
- package/dist/extensions/todo/view.d.ts.map +1 -1
- package/dist/extensions/todo/view.js +428 -1
- package/dist/extensions/todo/view.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -1
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js +5 -0
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +4 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts +13 -6
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.js +54 -17
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +6 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +23 -11
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +11 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +56 -0
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +3 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +52 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +10 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +139 -18
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-group.d.ts +1 -0
- package/dist/modes/interactive/components/tool-group.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-group.js +5 -0
- package/dist/modes/interactive/components/tool-group.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +10 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +55 -2
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +5 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +79 -19
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +3 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +8 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +8 -0
- package/dist/utils/paths.js.map +1 -1
- package/docs/bundled/README.md +1 -0
- package/docs/bundled/extensions/deepwiki.md +1 -1
- package/docs/bundled/extensions/plan.md +13 -10
- package/docs/bundled/extensions/question.md +7 -5
- package/docs/bundled/extensions/router.md +3 -4
- package/docs/bundled/extensions/statusline.md +0 -1
- package/docs/bundled/extensions/subagent.md +5 -4
- package/docs/bundled/extensions/todo.md +9 -7
- package/docs/bundled/tool-presentation.md +17 -16
- package/docs/extensions.md +34 -5
- package/docs/rpc.md +1 -1
- package/docs/usage.md +1 -1
- package/npm-shrinkwrap.json +6 -6
- package/package.json +1 -1
- package/dist/utils/output-decoder.d.ts +0 -52
- package/dist/utils/output-decoder.d.ts.map +0 -1
- package/dist/utils/output-decoder.js +0 -134
- package/dist/utils/output-decoder.js.map +0 -1
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
3
3
|
import { getMarkdownTheme } from "../../modes/interactive/theme/theme.js";
|
|
4
|
+
import { getSubagentRetryView } from "./retry.js";
|
|
4
5
|
import { statusSummary } from "./runner.js";
|
|
5
6
|
const SINGLE_EXCERPT_LIMIT = 200;
|
|
6
|
-
const RUN_LINE_EXCERPT_LIMIT =
|
|
7
|
+
const RUN_LINE_EXCERPT_LIMIT = 96;
|
|
8
|
+
const RUN_ERROR_EXCERPT_LIMIT = 200;
|
|
9
|
+
const RETRY_ERROR_EXCERPT_LIMIT = 160;
|
|
7
10
|
const LIVE_TAIL_LIMIT = 100;
|
|
8
11
|
const COLLAPSED_RUN_LIMIT = 4;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
const PROMPT_PREVIEW_LINES = 2;
|
|
13
|
+
const PROMPT_PREVIEW_LINE_LIMIT = 120;
|
|
14
|
+
const ACTIVITY_SUMMARY_LIMIT = 96;
|
|
15
|
+
const ACTIVITY_RESULT_LIMIT = 64;
|
|
16
|
+
// Sub-10s tool calls are routine; a duration only earns ink when it
|
|
17
|
+
// explains where the time went.
|
|
18
|
+
const ACTIVITY_DURATION_MIN_MS = 10_000;
|
|
19
|
+
const EXPANDED_LIVE_TAIL_LINES = 3;
|
|
20
|
+
class IndentedText {
|
|
21
|
+
text;
|
|
22
|
+
indent;
|
|
23
|
+
constructor(text, indent) {
|
|
24
|
+
this.text = new Text(text, 0, 0);
|
|
25
|
+
this.indent = indent;
|
|
26
|
+
}
|
|
27
|
+
render(width) {
|
|
28
|
+
const indent = Math.min(this.indent, Math.max(0, width - 1));
|
|
29
|
+
const prefix = " ".repeat(indent);
|
|
30
|
+
return this.text.render(Math.max(1, width - indent)).map((line) => `${prefix}${line}`);
|
|
31
|
+
}
|
|
32
|
+
invalidate() {
|
|
33
|
+
this.text.invalidate();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
18
36
|
// Display-only: profile names stay lowercase everywhere the model sees
|
|
19
37
|
// them; the transcript shows "general" as "General", "code-reviewer" as
|
|
20
38
|
// "Code Reviewer".
|
|
@@ -55,18 +73,54 @@ function stripBlockMarkdown(text) {
|
|
|
55
73
|
})
|
|
56
74
|
.join("\n");
|
|
57
75
|
}
|
|
76
|
+
// finalOutput/liveText carry model-facing truncation notices from
|
|
77
|
+
// boundText/tailText. Only recognize their exact boundary lines so literal
|
|
78
|
+
// discussion of the marker remains visible in the transcript.
|
|
79
|
+
const OUTPUT_TRUNCATION_NOTICE_PATTERN = /^\[Output truncated(?:: \d+ bytes omitted)?\.\]$/u;
|
|
80
|
+
const EARLIER_OUTPUT_NOTICE = "[Earlier output omitted.]";
|
|
81
|
+
function terminalOutputNotice(text) {
|
|
82
|
+
const lines = text.split("\n");
|
|
83
|
+
let index = lines.length - 1;
|
|
84
|
+
while (index >= 0 && !lines[index]?.trim())
|
|
85
|
+
index--;
|
|
86
|
+
if (index < 0 || !OUTPUT_TRUNCATION_NOTICE_PATTERN.test(lines[index]?.trim() ?? "")) {
|
|
87
|
+
return { text, truncated: false };
|
|
88
|
+
}
|
|
89
|
+
lines.splice(index, 1);
|
|
90
|
+
return { text: lines.join("\n").trimEnd(), truncated: true };
|
|
91
|
+
}
|
|
92
|
+
function replaceBoundaryNotices(text) {
|
|
93
|
+
const lines = text.split("\n");
|
|
94
|
+
const first = lines.findIndex((line) => Boolean(line.trim()));
|
|
95
|
+
if (first >= 0 && lines[first]?.trim() === EARLIER_OUTPUT_NOTICE)
|
|
96
|
+
lines[first] = "…";
|
|
97
|
+
let last = lines.length - 1;
|
|
98
|
+
while (last >= 0 && !lines[last]?.trim())
|
|
99
|
+
last--;
|
|
100
|
+
if (last >= 0 && OUTPUT_TRUNCATION_NOTICE_PATTERN.test(lines[last]?.trim() ?? ""))
|
|
101
|
+
lines[last] = "…";
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
function plainExcerpt(text, limit) {
|
|
105
|
+
return truncate(stripInlineMarkdown(stripBlockMarkdown(text)).replace(/\s+/gu, " ").trim(), limit);
|
|
106
|
+
}
|
|
58
107
|
function excerpt(text, limit) {
|
|
59
|
-
|
|
60
|
-
// boundText/tailText; in the transcript an ellipsis is enough.
|
|
61
|
-
const cleaned = text.replace(/\[(?:Output truncated(?:: \d+ bytes omitted\.)?|Earlier output omitted\.)\]/gu, "…");
|
|
62
|
-
return truncate(stripInlineMarkdown(stripBlockMarkdown(cleaned)).replace(/\s+/gu, " ").trim(), limit);
|
|
108
|
+
return plainExcerpt(replaceBoundaryNotices(text), limit);
|
|
63
109
|
}
|
|
64
|
-
|
|
65
|
-
|
|
110
|
+
// Generated notice-only boundary lines must never become the live line the
|
|
111
|
+
// user watches; identical text in the body remains ordinary model output.
|
|
112
|
+
function liveTailLines(run) {
|
|
113
|
+
const { text } = terminalOutputNotice(run.liveText);
|
|
114
|
+
const lines = text
|
|
66
115
|
.split("\n")
|
|
67
116
|
.map((line) => line.trim())
|
|
68
117
|
.filter(Boolean);
|
|
69
|
-
|
|
118
|
+
if (lines[0] === EARLIER_OUTPUT_NOTICE)
|
|
119
|
+
lines.shift();
|
|
120
|
+
return lines;
|
|
121
|
+
}
|
|
122
|
+
function liveTail(run) {
|
|
123
|
+
const last = liveTailLines(run).at(-1);
|
|
70
124
|
return last ? excerpt(last, LIVE_TAIL_LIMIT) : undefined;
|
|
71
125
|
}
|
|
72
126
|
// Completed-run excerpts read better cut at a sentence boundary than
|
|
@@ -83,17 +137,30 @@ function leadingSentences(text, limit) {
|
|
|
83
137
|
}
|
|
84
138
|
if (boundary >= limit / 2)
|
|
85
139
|
return window.slice(0, boundary).trimEnd();
|
|
86
|
-
return truncate(
|
|
140
|
+
return truncate(cleaned, limit);
|
|
87
141
|
}
|
|
88
142
|
const VERIFY_COMMAND_PATTERN = /\b(test|lint|typecheck|tsc|vitest|jest|pytest|build|check|fmt|format|compile)\b/iu;
|
|
89
143
|
const INSPECT_COMMAND_PATTERN = /\b(rg|grep|find|git (?:diff|status|log|show)|ls|tree|cat|head|tail)\b/iu;
|
|
90
144
|
const RETRY_MARKER_PATTERN = /^Retrying\b/u;
|
|
91
145
|
const STREAM_MARKERS = new Set(["Thinking…", "Writing response…"]);
|
|
146
|
+
function retryText(run) {
|
|
147
|
+
const retry = getSubagentRetryView(run);
|
|
148
|
+
if (!retry)
|
|
149
|
+
return undefined;
|
|
150
|
+
const status = retry.remainingSeconds > 0
|
|
151
|
+
? `Retrying (${retry.attempt}/${retry.maxAttempts}) in ${retry.remainingSeconds}s`
|
|
152
|
+
: `Retrying now… (${retry.attempt}/${retry.maxAttempts})`;
|
|
153
|
+
const error = excerpt(retry.error, RETRY_ERROR_EXCERPT_LIMIT);
|
|
154
|
+
return error ? `${status} — ${error}` : status;
|
|
155
|
+
}
|
|
92
156
|
// Translates raw tool traffic into what the worker is doing right now, so
|
|
93
157
|
// the collapsed card reads as a phase instead of a command line. Modelled
|
|
94
158
|
// on tool-type classification; the raw tool summary stays as evidence on
|
|
95
159
|
// the next line.
|
|
96
160
|
function runIntent(run) {
|
|
161
|
+
const retry = retryText(run);
|
|
162
|
+
if (retry)
|
|
163
|
+
return retry;
|
|
97
164
|
const current = run.currentActivity;
|
|
98
165
|
if (current && RETRY_MARKER_PATTERN.test(current))
|
|
99
166
|
return current;
|
|
@@ -125,6 +192,8 @@ function runIntent(run) {
|
|
|
125
192
|
// The raw tool summary backs up the intent line; suppress it when it would
|
|
126
193
|
// just repeat a stream/retry marker.
|
|
127
194
|
function runEvidence(run) {
|
|
195
|
+
if (getSubagentRetryView(run))
|
|
196
|
+
return undefined;
|
|
128
197
|
const current = run.currentActivity;
|
|
129
198
|
if (!current || STREAM_MARKERS.has(current) || RETRY_MARKER_PATTERN.test(current))
|
|
130
199
|
return undefined;
|
|
@@ -136,44 +205,77 @@ function liveElapsed(run) {
|
|
|
136
205
|
const end = run.endedAt ?? Date.now();
|
|
137
206
|
return formatDuration(Math.max(0, (end - run.startedAt) / 1000));
|
|
138
207
|
}
|
|
208
|
+
function statusColor(status) {
|
|
209
|
+
switch (status) {
|
|
210
|
+
case "completed":
|
|
211
|
+
return "success";
|
|
212
|
+
case "failed":
|
|
213
|
+
return "error";
|
|
214
|
+
case "aborted":
|
|
215
|
+
return "warning";
|
|
216
|
+
case "running":
|
|
217
|
+
return "accent";
|
|
218
|
+
default:
|
|
219
|
+
return "muted";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
139
222
|
// The tool shell already paints a call-level status dot (● in
|
|
140
223
|
// warning/success/error), so `●` is off-limits here: `›` marks where the
|
|
141
224
|
// work currently is, and the remaining glyphs are terminal states.
|
|
142
225
|
function statusMarker(status, theme) {
|
|
226
|
+
const glyph = status === "completed"
|
|
227
|
+
? "✓"
|
|
228
|
+
: status === "failed"
|
|
229
|
+
? "×"
|
|
230
|
+
: status === "aborted"
|
|
231
|
+
? "■"
|
|
232
|
+
: status === "running"
|
|
233
|
+
? "›"
|
|
234
|
+
: "○";
|
|
235
|
+
return theme.fg(statusColor(status), glyph);
|
|
236
|
+
}
|
|
237
|
+
function statusWord(status) {
|
|
143
238
|
switch (status) {
|
|
144
239
|
case "completed":
|
|
145
|
-
return
|
|
240
|
+
return "Completed";
|
|
146
241
|
case "failed":
|
|
147
|
-
return
|
|
242
|
+
return "Failed";
|
|
148
243
|
case "aborted":
|
|
149
|
-
return
|
|
244
|
+
return "Aborted";
|
|
150
245
|
case "running":
|
|
151
|
-
return
|
|
246
|
+
return "Running";
|
|
152
247
|
default:
|
|
153
|
-
return
|
|
248
|
+
return "Queued";
|
|
154
249
|
}
|
|
155
250
|
}
|
|
156
251
|
function runTitle(run, theme, index) {
|
|
157
252
|
const number = index === undefined ? "" : theme.fg("dim", `${index} · `);
|
|
158
253
|
return `${statusMarker(run.status, theme)} ${number}${theme.fg("accent", displayAgentName(run.agent))}${theme.fg("dim", ` · ${truncate(run.description, 48)}`)}`;
|
|
159
254
|
}
|
|
255
|
+
// Mid-run token totals are cache-inflated (totalTokens sums cache reads
|
|
256
|
+
// across turns), so live lines quote tool uses and the context watermark
|
|
257
|
+
// instead; settled lines get the real total.
|
|
160
258
|
function runProgressText(run) {
|
|
161
259
|
const items = [`${run.usage.toolUses} tool use${run.usage.toolUses === 1 ? "" : "s"}`];
|
|
162
|
-
if (run.usage.
|
|
163
|
-
items.push(
|
|
260
|
+
if (run.usage.contextTokens)
|
|
261
|
+
items.push(`ctx: ${formatTokens(run.usage.contextTokens)}`);
|
|
164
262
|
return items.join(" · ");
|
|
165
263
|
}
|
|
166
264
|
function runLine(run, theme, index) {
|
|
167
265
|
let line = runTitle(run, theme, index);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
?
|
|
172
|
-
: run.
|
|
266
|
+
const retry = retryText(run);
|
|
267
|
+
const detail = retry ??
|
|
268
|
+
(run.status === "running"
|
|
269
|
+
? excerpt(runIntent(run), LIVE_TAIL_LIMIT)
|
|
270
|
+
: run.status === "failed" || run.status === "aborted"
|
|
271
|
+
? undefined
|
|
272
|
+
: run.finalOutput && leadingSentences(run.finalOutput, RUN_LINE_EXCERPT_LIMIT));
|
|
173
273
|
if (detail)
|
|
174
|
-
line += theme.fg(
|
|
274
|
+
line += theme.fg("dim", ` — ${detail}`);
|
|
175
275
|
if (run.status === "running") {
|
|
176
276
|
line += theme.fg("dim", ` · ${runProgressText(run)}`);
|
|
277
|
+
if (run.usage.cost)
|
|
278
|
+
line += theme.fg("dim", ` · $${run.usage.cost.toFixed(3)}`);
|
|
177
279
|
const elapsed = liveElapsed(run);
|
|
178
280
|
if (elapsed)
|
|
179
281
|
line += theme.fg("dim", ` · ${elapsed}`);
|
|
@@ -184,6 +286,20 @@ function runLine(run, theme, index) {
|
|
|
184
286
|
}
|
|
185
287
|
return line;
|
|
186
288
|
}
|
|
289
|
+
function runErrorExcerpt(run) {
|
|
290
|
+
if ((run.status !== "failed" && run.status !== "aborted") || !run.error)
|
|
291
|
+
return undefined;
|
|
292
|
+
return leadingSentences(run.error, RUN_ERROR_EXCERPT_LIMIT);
|
|
293
|
+
}
|
|
294
|
+
function runErrorIndent(index) {
|
|
295
|
+
return index === undefined ? 2 : 5 + String(index).length;
|
|
296
|
+
}
|
|
297
|
+
function addRunSummary(container, run, theme, index) {
|
|
298
|
+
container.addChild(new Text(runLine(run, theme, index), 0, 0));
|
|
299
|
+
const error = runErrorExcerpt(run);
|
|
300
|
+
if (error)
|
|
301
|
+
container.addChild(new IndentedText(theme.fg("error", error), runErrorIndent(index)));
|
|
302
|
+
}
|
|
187
303
|
// Keep active work visible while a batch is in flight: completed runs
|
|
188
304
|
// may otherwise crowd a still-running task out of the collapsed window.
|
|
189
305
|
function selectCollapsedRuns(runs, isPartial) {
|
|
@@ -192,32 +308,44 @@ function selectCollapsedRuns(runs, isPartial) {
|
|
|
192
308
|
if (!isPartial)
|
|
193
309
|
return runs.slice(0, COLLAPSED_RUN_LIMIT);
|
|
194
310
|
const active = runs.filter((run) => run.status === "running" || run.status === "queued");
|
|
311
|
+
const retrying = active.filter((run) => getSubagentRetryView(run));
|
|
312
|
+
const retryingSet = new Set(retrying);
|
|
313
|
+
const otherActive = active.filter((run) => !retryingSet.has(run));
|
|
195
314
|
const settled = runs.filter((run) => run.status !== "running" && run.status !== "queued");
|
|
196
|
-
return [...
|
|
315
|
+
return [...retrying, ...otherActive, ...settled].slice(0, COLLAPSED_RUN_LIMIT);
|
|
197
316
|
}
|
|
198
317
|
function singleCollapsedLines(details, theme) {
|
|
199
318
|
const run = details.runs[0];
|
|
200
319
|
if (!run)
|
|
201
320
|
return [theme.fg("muted", "Initializing…")];
|
|
202
321
|
if (run.status === "running" || run.status === "queued") {
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
322
|
+
const retry = retryText(run);
|
|
323
|
+
const isInitializing = run.status === "running" &&
|
|
324
|
+
!retry &&
|
|
325
|
+
run.usage.turns === 0 &&
|
|
326
|
+
run.usage.toolUses === 0 &&
|
|
327
|
+
run.liveText.length === 0;
|
|
328
|
+
const rawIntent = retry ??
|
|
329
|
+
(run.status === "queued"
|
|
330
|
+
? (run.currentActivity ?? statusWord(run.status))
|
|
331
|
+
: isInitializing
|
|
332
|
+
? "Initializing…"
|
|
333
|
+
: runIntent(run));
|
|
334
|
+
const intent = excerpt(rawIntent, LIVE_TAIL_LIMIT);
|
|
209
335
|
// No status marker here: the shell's call-level dot already covers
|
|
210
336
|
// the whole card, and a single run has nothing to disambiguate.
|
|
211
337
|
let status = theme.fg("dim", intent);
|
|
212
338
|
if (run.status === "running" && !isInitializing) {
|
|
213
339
|
status += theme.fg("dim", ` · ${runProgressText(run)}`);
|
|
340
|
+
if (run.usage.cost)
|
|
341
|
+
status += theme.fg("dim", ` · $${run.usage.cost.toFixed(3)}`);
|
|
214
342
|
const elapsed = liveElapsed(run);
|
|
215
343
|
if (elapsed)
|
|
216
344
|
status += theme.fg("dim", ` · ${elapsed}`);
|
|
217
345
|
}
|
|
218
346
|
const lines = [status];
|
|
219
347
|
const evidence = runEvidence(run);
|
|
220
|
-
if (evidence && evidence !==
|
|
348
|
+
if (evidence && evidence !== rawIntent)
|
|
221
349
|
lines.push(`${theme.fg("accent", "›")} ${theme.fg("dim", excerpt(evidence, LIVE_TAIL_LIMIT))}`);
|
|
222
350
|
const tail = liveTail(run);
|
|
223
351
|
if (tail)
|
|
@@ -228,55 +356,45 @@ function singleCollapsedLines(details, theme) {
|
|
|
228
356
|
return [theme.fg("error", excerpt(run.error, SINGLE_EXCERPT_LIMIT))];
|
|
229
357
|
if (run.finalOutput)
|
|
230
358
|
return [theme.fg("toolOutput", leadingSentences(run.finalOutput, SINGLE_EXCERPT_LIMIT))];
|
|
231
|
-
return [theme.fg("muted", run.status)];
|
|
359
|
+
return [theme.fg("muted", statusWord(run.status))];
|
|
232
360
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
activityCount++;
|
|
239
|
-
toolCounts.set(activity.toolName, (toolCounts.get(activity.toolName) ?? 0) + 1);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
361
|
+
// Deep-trimmed settled footer: turns and per-run breakdowns belong to the
|
|
362
|
+
// expanded view. Parallel batches keep only whole-call numbers — the run
|
|
363
|
+
// rows above already localize the work.
|
|
364
|
+
function collapsedUsageText(details, extended) {
|
|
365
|
+
const usage = details.usage;
|
|
242
366
|
const items = [];
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
367
|
+
if (usage.totalTokens)
|
|
368
|
+
items.push(`${formatTokens(usage.totalTokens)} tok`);
|
|
369
|
+
if (extended && usage.toolUses)
|
|
370
|
+
items.push(`${usage.toolUses} tool use${usage.toolUses === 1 ? "" : "s"}`);
|
|
371
|
+
if (extended && usage.contextTokens)
|
|
372
|
+
items.push(`ctx: ${formatTokens(usage.contextTokens)}`);
|
|
373
|
+
if (usage.cost)
|
|
374
|
+
items.push(`$${usage.cost.toFixed(3)}`);
|
|
375
|
+
if (details.endedAt !== undefined) {
|
|
376
|
+
items.push(formatDuration(Math.max(0, (details.endedAt - details.startedAt) / 1000)));
|
|
250
377
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (items.length === 0)
|
|
255
|
-
return "";
|
|
256
|
-
const earlierCount = Math.max(0, details.usage.toolUses - activityCount);
|
|
257
|
-
if (earlierCount > 0)
|
|
258
|
-
items.push(`${earlierCount} earlier tool use${earlierCount === 1 ? "" : "s"}`);
|
|
259
|
-
return `${items[0][0].toUpperCase()}${items[0].slice(1)}${items
|
|
260
|
-
.slice(1)
|
|
261
|
-
.map((item) => ` · ${item}`)
|
|
262
|
-
.join("")}`;
|
|
263
|
-
}
|
|
264
|
-
function usageText(details, includeToolUses = true) {
|
|
378
|
+
return items.join(" · ");
|
|
379
|
+
}
|
|
380
|
+
function batchUsageText(details, isPartial) {
|
|
265
381
|
const usage = details.usage;
|
|
266
382
|
const items = [];
|
|
267
|
-
|
|
383
|
+
// The aggregate mixes settled and running runs while partial, and the
|
|
384
|
+
// running share is cache-inflated — quote the total only once settled.
|
|
385
|
+
if (!isPartial && usage.totalTokens)
|
|
386
|
+
items.push(`${formatTokens(usage.totalTokens)} tok`);
|
|
387
|
+
if (usage.toolUses)
|
|
268
388
|
items.push(`${usage.toolUses} tool use${usage.toolUses === 1 ? "" : "s"}`);
|
|
269
389
|
if (usage.turns)
|
|
270
390
|
items.push(`${usage.turns} turn${usage.turns === 1 ? "" : "s"}`);
|
|
271
|
-
if (usage.output)
|
|
272
|
-
items.push(`↓${formatTokens(usage.output)}`);
|
|
273
391
|
if (usage.contextTokens)
|
|
274
|
-
items.push(`ctx
|
|
392
|
+
items.push(`ctx: ${formatTokens(usage.contextTokens)}`);
|
|
275
393
|
if (usage.cost)
|
|
276
394
|
items.push(`$${usage.cost.toFixed(3)}`);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
395
|
+
if (details.endedAt !== undefined) {
|
|
396
|
+
items.push(formatDuration(Math.max(0, (details.endedAt - details.startedAt) / 1000)));
|
|
397
|
+
}
|
|
280
398
|
return items.join(" · ");
|
|
281
399
|
}
|
|
282
400
|
function formatTokens(value) {
|
|
@@ -287,55 +405,139 @@ function formatTokens(value) {
|
|
|
287
405
|
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
288
406
|
}
|
|
289
407
|
function formatDuration(seconds) {
|
|
290
|
-
|
|
291
|
-
|
|
408
|
+
const roundedSeconds = Math.round(seconds);
|
|
409
|
+
if (roundedSeconds >= 60)
|
|
410
|
+
return `${Math.floor(roundedSeconds / 60)}m ${roundedSeconds % 60}s`;
|
|
292
411
|
return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
|
|
293
412
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
413
|
+
function runDurationText(run) {
|
|
414
|
+
if (run.startedAt === undefined || run.endedAt === undefined)
|
|
415
|
+
return undefined;
|
|
416
|
+
return formatDuration(Math.max(0, (run.endedAt - run.startedAt) / 1000));
|
|
417
|
+
}
|
|
418
|
+
// One dim line per run: what it cost. Settled runs quote the real token
|
|
419
|
+
// total; running runs skip it (cache-inflated mid-run) and tick elapsed
|
|
420
|
+
// time instead. Batch sections fold model/thinking/cwd in here because
|
|
421
|
+
// their `──` header already carries verdict and duration.
|
|
422
|
+
function runMetricsText(run, includeIdentity) {
|
|
423
|
+
const usage = run.usage;
|
|
424
|
+
const settled = run.status !== "running" && run.status !== "queued";
|
|
425
|
+
const items = [];
|
|
426
|
+
if (includeIdentity) {
|
|
427
|
+
items.push(run.model);
|
|
428
|
+
if (run.thinking !== "off")
|
|
429
|
+
items.push(run.thinking);
|
|
430
|
+
if (run.cwd)
|
|
431
|
+
items.push(`cwd: ${run.cwd}`);
|
|
432
|
+
}
|
|
433
|
+
if (settled && usage.totalTokens)
|
|
434
|
+
items.push(`${formatTokens(usage.totalTokens)} tok`);
|
|
435
|
+
if (usage.toolUses)
|
|
436
|
+
items.push(`${usage.toolUses} tool use${usage.toolUses === 1 ? "" : "s"}`);
|
|
437
|
+
if (usage.turns)
|
|
438
|
+
items.push(`${usage.turns} turn${usage.turns === 1 ? "" : "s"}`);
|
|
439
|
+
if (usage.contextTokens)
|
|
440
|
+
items.push(`ctx: ${formatTokens(usage.contextTokens)}`);
|
|
441
|
+
if (usage.cost)
|
|
442
|
+
items.push(`$${usage.cost.toFixed(3)}`);
|
|
443
|
+
const duration = settled ? (includeIdentity ? undefined : runDurationText(run)) : liveElapsed(run);
|
|
444
|
+
if (duration)
|
|
445
|
+
items.push(duration);
|
|
446
|
+
return items.join(" · ");
|
|
447
|
+
}
|
|
448
|
+
// The details prompt is a 1KB bounded copy of the briefing; the preview
|
|
449
|
+
// shows the first content lines (blank lines carry nothing at two lines
|
|
450
|
+
// of budget) and never pretends to more fidelity than that.
|
|
451
|
+
function promptSection(run, theme) {
|
|
452
|
+
const boundedPrompt = terminalOutputNotice(run.prompt);
|
|
453
|
+
const lines = boundedPrompt.text
|
|
454
|
+
.split("\n")
|
|
455
|
+
.map((line) => plainExcerpt(line.trim(), Number.MAX_SAFE_INTEGER))
|
|
456
|
+
.filter(Boolean);
|
|
457
|
+
const shown = lines.slice(0, PROMPT_PREVIEW_LINES);
|
|
458
|
+
const preview = shown.map((line) => theme.fg("dim", truncate(line, PROMPT_PREVIEW_LINE_LIMIT)));
|
|
459
|
+
// A one-line briefing clipped mid-sentence must still say it continues.
|
|
460
|
+
const clipped = shown.some((line) => line.length > PROMPT_PREVIEW_LINE_LIMIT);
|
|
461
|
+
const remaining = lines.length - shown.length;
|
|
462
|
+
const capped = boundedPrompt.truncated;
|
|
463
|
+
const section = [theme.fg("muted", "Prompt"), ...preview];
|
|
464
|
+
if (remaining > 0 || capped || clipped) {
|
|
465
|
+
let note = "… continues";
|
|
466
|
+
if (remaining > 0)
|
|
467
|
+
note += `, ${remaining} more line${remaining === 1 ? "" : "s"}`;
|
|
468
|
+
if (capped)
|
|
469
|
+
note += " · capped at 1KB";
|
|
470
|
+
section.push(theme.fg("muted", note));
|
|
471
|
+
}
|
|
472
|
+
return section;
|
|
473
|
+
}
|
|
474
|
+
// Each run expands into a report cover sheet: verdict and cost up top, a
|
|
475
|
+
// bounded Prompt/Activity digest in the middle, then the full Report —
|
|
476
|
+
// the one unbounded element, placed last so the card ends where reading
|
|
477
|
+
// begins. Single runs skip the `──` header (the call header already names
|
|
478
|
+
// them); batch sections carry a numbered header matching the contents
|
|
479
|
+
// list above.
|
|
298
480
|
function renderRunDetails(run, theme, single, index) {
|
|
299
481
|
const container = new Container();
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
482
|
+
const settled = run.status !== "running" && run.status !== "queued";
|
|
483
|
+
if (single) {
|
|
484
|
+
const identity = [run.model];
|
|
485
|
+
if (run.thinking !== "off")
|
|
486
|
+
identity.push(`${run.thinking} thinking`);
|
|
487
|
+
if (run.cwd)
|
|
488
|
+
identity.push(`cwd: ${run.cwd}`);
|
|
489
|
+
container.addChild(new Text(`${statusMarker(run.status, theme)} ${theme.fg(statusColor(run.status), statusWord(run.status))}${theme.fg("dim", ` · ${identity.join(" · ")}`)}`, 0, 0));
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
304
492
|
const label = index === undefined ? "" : `${index} · `;
|
|
305
|
-
|
|
493
|
+
const duration = settled ? runDurationText(run) : undefined;
|
|
494
|
+
container.addChild(new Text(`${theme.fg("dim", `── ${label}`)}${statusMarker(run.status, theme)} ${theme.fg("accent", displayAgentName(run.agent))}${theme.fg("dim", ` · ${truncate(run.description, 48)}`)}${duration ? theme.fg("dim", ` · ${duration}`) : ""}`, 0, 0));
|
|
495
|
+
}
|
|
496
|
+
if (run.status !== "queued") {
|
|
497
|
+
const metrics = runMetricsText(run, !single);
|
|
498
|
+
if (metrics)
|
|
499
|
+
container.addChild(new Text(theme.fg("dim", metrics), 0, 0));
|
|
306
500
|
}
|
|
307
|
-
|
|
308
|
-
|
|
501
|
+
const retry = retryText(run);
|
|
502
|
+
if (retry)
|
|
503
|
+
container.addChild(new Text(theme.fg("accent", retry), 0, 0));
|
|
504
|
+
container.addChild(new Spacer(1));
|
|
505
|
+
for (const line of promptSection(run, theme))
|
|
506
|
+
container.addChild(new Text(line, 0, 0));
|
|
309
507
|
if (run.activities.length > 0) {
|
|
310
508
|
container.addChild(new Spacer(1));
|
|
311
|
-
|
|
509
|
+
const total = run.usage.toolUses;
|
|
510
|
+
const label = total > run.activities.length ? `Activity · last ${run.activities.length} of ${total}` : "Activity";
|
|
511
|
+
container.addChild(new Text(theme.fg("muted", label), 0, 0));
|
|
312
512
|
for (const activity of run.activities) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
text += ` ${theme.fg(color, `· ${excerpt(activity.resultSummary, 64)}`)}`;
|
|
513
|
+
// Deviation earns ink: × and › are marked, success rows are quiet
|
|
514
|
+
// one-liners without result echoes.
|
|
515
|
+
let text;
|
|
516
|
+
if (activity.status === "failed") {
|
|
517
|
+
text = `${theme.fg("error", "×")} ${theme.fg("toolOutput", excerpt(activity.summary, ACTIVITY_SUMMARY_LIMIT))}`;
|
|
518
|
+
if (activity.resultSummary) {
|
|
519
|
+
text += ` ${theme.fg("error", `· ${excerpt(activity.resultSummary, ACTIVITY_RESULT_LIMIT)}`)}`;
|
|
520
|
+
}
|
|
322
521
|
}
|
|
323
|
-
if (activity.
|
|
522
|
+
else if (activity.status === "running") {
|
|
523
|
+
text = `${theme.fg("accent", "›")} ${theme.fg("toolOutput", excerpt(activity.summary, ACTIVITY_SUMMARY_LIMIT))}`;
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
text = ` ${theme.fg("toolOutput", excerpt(activity.summary, ACTIVITY_SUMMARY_LIMIT))}`;
|
|
527
|
+
}
|
|
528
|
+
if (activity.endedAt !== undefined && activity.endedAt - activity.startedAt >= ACTIVITY_DURATION_MIN_MS) {
|
|
324
529
|
text += ` ${theme.fg("dim", `· ${formatDuration((activity.endedAt - activity.startedAt) / 1000)}`)}`;
|
|
325
530
|
}
|
|
326
531
|
container.addChild(new Text(text, 0, 0));
|
|
327
532
|
}
|
|
328
533
|
}
|
|
329
|
-
// The live tail is the "now" end of the timeline
|
|
330
|
-
//
|
|
534
|
+
// The live tail is the "now" end of the timeline; once the run settles,
|
|
535
|
+
// Report takes this slot without moving anything above it.
|
|
331
536
|
if (run.status === "running" && run.liveText) {
|
|
332
|
-
const tail = run.
|
|
333
|
-
.split("\n")
|
|
334
|
-
.map((line) => line.trim())
|
|
335
|
-
.filter(Boolean)
|
|
336
|
-
.slice(-4);
|
|
537
|
+
const tail = liveTailLines(run).slice(-EXPANDED_LIVE_TAIL_LINES);
|
|
337
538
|
if (tail.length > 0) {
|
|
338
539
|
container.addChild(new Spacer(1));
|
|
540
|
+
container.addChild(new Text(theme.fg("muted", "Working"), 0, 0));
|
|
339
541
|
for (const line of tail) {
|
|
340
542
|
container.addChild(new Text(theme.fg("dim", excerpt(line, LIVE_TAIL_LIMIT)), 0, 0));
|
|
341
543
|
}
|
|
@@ -343,28 +545,16 @@ function renderRunDetails(run, theme, single, index) {
|
|
|
343
545
|
}
|
|
344
546
|
if (run.error) {
|
|
345
547
|
container.addChild(new Spacer(1));
|
|
346
|
-
container.addChild(new Text(theme.fg("
|
|
548
|
+
container.addChild(new Text(theme.fg("muted", "Error"), 0, 0));
|
|
549
|
+
container.addChild(new Text(theme.fg("error", run.error), 0, 0));
|
|
347
550
|
}
|
|
348
551
|
if (run.finalOutput) {
|
|
349
552
|
container.addChild(new Spacer(1));
|
|
350
|
-
|
|
351
|
-
container.addChild(new
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
run.cwd ? `cwd: ${run.cwd}` : undefined,
|
|
356
|
-
run.usage.toolUses ? `${run.usage.toolUses} tool use${run.usage.toolUses === 1 ? "" : "s"}` : undefined,
|
|
357
|
-
run.usage.turns ? `${run.usage.turns} turn${run.usage.turns === 1 ? "" : "s"}` : undefined,
|
|
358
|
-
run.usage.output ? `↓${formatTokens(run.usage.output)}` : undefined,
|
|
359
|
-
run.usage.contextTokens ? `ctx:${formatTokens(run.usage.contextTokens)}` : undefined,
|
|
360
|
-
run.usage.cost ? `$${run.usage.cost.toFixed(3)}` : undefined,
|
|
361
|
-
single ? duration : undefined,
|
|
362
|
-
]
|
|
363
|
-
.filter((item) => Boolean(item))
|
|
364
|
-
.join(" · ");
|
|
365
|
-
if (metadata) {
|
|
366
|
-
container.addChild(new Spacer(1));
|
|
367
|
-
container.addChild(new Text(theme.fg("dim", metadata), 0, 0));
|
|
553
|
+
const label = settled && run.status !== "completed" ? "Report · partial" : "Report";
|
|
554
|
+
container.addChild(new Text(theme.fg("muted", label), 0, 0));
|
|
555
|
+
container.addChild(new Markdown(run.finalOutput, 0, 0, getMarkdownTheme(), {
|
|
556
|
+
color: (text) => theme.fg("toolOutput", text),
|
|
557
|
+
}));
|
|
368
558
|
}
|
|
369
559
|
return container;
|
|
370
560
|
}
|
|
@@ -391,33 +581,43 @@ export function renderSubagentResult(result, options, theme, isError) {
|
|
|
391
581
|
const text = result.content.find((part) => part.type === "text");
|
|
392
582
|
return new Text(text?.type === "text" ? text.text : "(no output)", 0, 0);
|
|
393
583
|
}
|
|
584
|
+
// The pre-resolution first paint carries no runs yet, in either mode.
|
|
585
|
+
if (details.runs.length === 0) {
|
|
586
|
+
return new Text(theme.fg("muted", "Initializing…"), 0, 0);
|
|
587
|
+
}
|
|
394
588
|
if (!options.expanded) {
|
|
395
|
-
const lines = [];
|
|
396
589
|
if (details.mode === "single") {
|
|
397
|
-
lines
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
590
|
+
const lines = singleCollapsedLines(details, theme);
|
|
591
|
+
if (!options.isPartial) {
|
|
592
|
+
const usage = collapsedUsageText(details, true);
|
|
593
|
+
if (usage)
|
|
594
|
+
lines.push(theme.fg("dim", usage));
|
|
595
|
+
lines.push(theme.fg("muted", `(${keyHint("app.tools.expand", "to expand")})`));
|
|
403
596
|
}
|
|
404
|
-
lines.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
597
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
598
|
+
}
|
|
599
|
+
const container = new Container();
|
|
600
|
+
let header = statusSummary(details);
|
|
601
|
+
if (options.isPartial) {
|
|
602
|
+
header += ` · ${formatDuration(Math.max(0, (Date.now() - details.startedAt) / 1000))}`;
|
|
603
|
+
}
|
|
604
|
+
container.addChild(new Text(theme.fg(isError ? "error" : options.isPartial ? "accent" : "muted", header), 0, 0));
|
|
605
|
+
const shown = selectCollapsedRuns(details.runs, Boolean(options.isPartial));
|
|
606
|
+
// Stable ordinals (the run's task position, not its display slot)
|
|
607
|
+
// keep identity readable while active-first ordering reshuffles
|
|
608
|
+
// rows, and match the expanded section numbers.
|
|
609
|
+
for (const run of shown)
|
|
610
|
+
addRunSummary(container, run, theme, details.runs.indexOf(run) + 1);
|
|
611
|
+
const hidden = details.runs.length - shown.length;
|
|
612
|
+
if (hidden > 0)
|
|
613
|
+
container.addChild(new Text(theme.fg("muted", `+${hidden} more`), 0, 0));
|
|
614
|
+
if (!options.isPartial) {
|
|
615
|
+
const usage = collapsedUsageText(details, false);
|
|
616
|
+
if (usage)
|
|
617
|
+
container.addChild(new Text(theme.fg("dim", usage), 0, 0));
|
|
618
|
+
container.addChild(new Text(theme.fg("muted", `(${keyHint("app.tools.expand", "to expand")})`), 0, 0));
|
|
411
619
|
}
|
|
412
|
-
|
|
413
|
-
if (activitySummary)
|
|
414
|
-
lines.push(theme.fg("dim", activitySummary));
|
|
415
|
-
const usage = usageText(details, !activitySummary);
|
|
416
|
-
if (usage)
|
|
417
|
-
lines.push(theme.fg("dim", usage));
|
|
418
|
-
if (!options.isPartial)
|
|
419
|
-
lines.push(theme.fg("muted", `(${keyHint("app.tools.expand", "to expand")})`));
|
|
420
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
620
|
+
return container;
|
|
421
621
|
}
|
|
422
622
|
const container = new Container();
|
|
423
623
|
const single = details.mode === "single" && details.runs.length === 1;
|
|
@@ -427,7 +627,7 @@ export function renderSubagentResult(result, options, theme, isError) {
|
|
|
427
627
|
// numbers against the `──` section headers below makes an 8-run
|
|
428
628
|
// batch navigable by eye.
|
|
429
629
|
details.runs.forEach((run, index) => {
|
|
430
|
-
container
|
|
630
|
+
addRunSummary(container, run, theme, index + 1);
|
|
431
631
|
});
|
|
432
632
|
}
|
|
433
633
|
details.runs.forEach((run, index) => {
|
|
@@ -436,7 +636,7 @@ export function renderSubagentResult(result, options, theme, isError) {
|
|
|
436
636
|
container.addChild(renderRunDetails(run, theme, single, single ? undefined : index + 1));
|
|
437
637
|
});
|
|
438
638
|
if (!single) {
|
|
439
|
-
const usage =
|
|
639
|
+
const usage = batchUsageText(details, Boolean(options.isPartial));
|
|
440
640
|
if (usage) {
|
|
441
641
|
container.addChild(new Spacer(1));
|
|
442
642
|
container.addChild(new Text(theme.fg("dim", usage), 0, 0));
|