@astralyn/pi 0.84.4 → 0.84.6
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 +57 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +11 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +5 -4
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +11 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +4 -0
- package/dist/core/experimental.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/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +10 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +19 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +29 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +4 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +1 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts +2 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/project-trust.js +2 -2
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/sdk.d.ts +5 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +2 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +12 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +7 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +4 -2
- 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 +2 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -0
- package/dist/core/tools/write.js.map +1 -1
- package/dist/extensions/background/index.d.ts +149 -0
- package/dist/extensions/background/index.d.ts.map +1 -0
- package/dist/extensions/background/index.js +666 -0
- package/dist/extensions/background/index.js.map +1 -0
- package/dist/extensions/background/manager.d.ts +95 -0
- package/dist/extensions/background/manager.d.ts.map +1 -0
- package/dist/extensions/background/manager.js +421 -0
- package/dist/extensions/background/manager.js.map +1 -0
- package/dist/extensions/background/registry.d.ts +220 -0
- package/dist/extensions/background/registry.d.ts.map +1 -0
- package/dist/extensions/background/registry.js +589 -0
- package/dist/extensions/background/registry.js.map +1 -0
- package/dist/extensions/background/render.d.ts +50 -0
- package/dist/extensions/background/render.d.ts.map +1 -0
- package/dist/extensions/background/render.js +260 -0
- package/dist/extensions/background/render.js.map +1 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/subagent/activity.d.ts +3 -6
- package/dist/extensions/subagent/activity.d.ts.map +1 -1
- package/dist/extensions/subagent/activity.js +9 -93
- package/dist/extensions/subagent/activity.js.map +1 -1
- package/dist/extensions/subagent/agents-command.d.ts.map +1 -1
- package/dist/extensions/subagent/agents-command.js +270 -169
- package/dist/extensions/subagent/agents-command.js.map +1 -1
- package/dist/extensions/subagent/agents.d.ts +5 -6
- package/dist/extensions/subagent/agents.d.ts.map +1 -1
- package/dist/extensions/subagent/agents.js +28 -192
- package/dist/extensions/subagent/agents.js.map +1 -1
- package/dist/extensions/subagent/budget.d.ts +15 -0
- package/dist/extensions/subagent/budget.d.ts.map +1 -0
- package/dist/extensions/subagent/budget.js +96 -0
- package/dist/extensions/subagent/budget.js.map +1 -0
- package/dist/extensions/subagent/cancellation.d.ts +29 -0
- package/dist/extensions/subagent/cancellation.d.ts.map +1 -0
- package/dist/extensions/subagent/cancellation.js +73 -0
- package/dist/extensions/subagent/cancellation.js.map +1 -0
- package/dist/extensions/subagent/constants.d.ts +4 -5
- package/dist/extensions/subagent/constants.d.ts.map +1 -1
- package/dist/extensions/subagent/constants.js +11 -7
- package/dist/extensions/subagent/constants.js.map +1 -1
- package/dist/extensions/subagent/index.d.ts +0 -3
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +59 -208
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/model-selection.d.ts +16 -0
- package/dist/extensions/subagent/model-selection.d.ts.map +1 -0
- package/dist/extensions/subagent/model-selection.js +51 -0
- package/dist/extensions/subagent/model-selection.js.map +1 -0
- package/dist/extensions/subagent/render.d.ts +7 -2
- package/dist/extensions/subagent/render.d.ts.map +1 -1
- package/dist/extensions/subagent/render.js +219 -576
- package/dist/extensions/subagent/render.js.map +1 -1
- package/dist/extensions/subagent/resolve.d.ts +2 -2
- package/dist/extensions/subagent/resolve.d.ts.map +1 -1
- package/dist/extensions/subagent/resolve.js +24 -35
- package/dist/extensions/subagent/resolve.js.map +1 -1
- package/dist/extensions/subagent/runner.d.ts +4 -6
- package/dist/extensions/subagent/runner.d.ts.map +1 -1
- package/dist/extensions/subagent/runner.js +101 -316
- package/dist/extensions/subagent/runner.js.map +1 -1
- package/dist/extensions/subagent/schema.d.ts +4 -10
- package/dist/extensions/subagent/schema.d.ts.map +1 -1
- package/dist/extensions/subagent/schema.js +8 -13
- package/dist/extensions/subagent/schema.js.map +1 -1
- package/dist/extensions/subagent/sdk-runner.d.ts +13 -11
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
- package/dist/extensions/subagent/sdk-runner.js +169 -161
- package/dist/extensions/subagent/sdk-runner.js.map +1 -1
- package/dist/extensions/subagent/settings.d.ts +2 -2
- package/dist/extensions/subagent/settings.d.ts.map +1 -1
- package/dist/extensions/subagent/settings.js +27 -14
- package/dist/extensions/subagent/settings.js.map +1 -1
- package/dist/extensions/subagent/state.d.ts +89 -0
- package/dist/extensions/subagent/state.d.ts.map +1 -0
- package/dist/extensions/subagent/state.js +294 -0
- package/dist/extensions/subagent/state.js.map +1 -0
- package/dist/extensions/subagent/text.d.ts +7 -0
- package/dist/extensions/subagent/text.d.ts.map +1 -0
- package/dist/extensions/subagent/text.js +91 -0
- package/dist/extensions/subagent/text.js.map +1 -0
- package/dist/extensions/subagent/types.d.ts +16 -43
- package/dist/extensions/subagent/types.d.ts.map +1 -1
- package/dist/extensions/subagent/types.js.map +1 -1
- package/dist/extensions/subagent/ui/choices.d.ts +36 -0
- package/dist/extensions/subagent/ui/choices.d.ts.map +1 -0
- package/dist/extensions/subagent/ui/choices.js +54 -0
- package/dist/extensions/subagent/ui/choices.js.map +1 -0
- package/dist/extensions/subagent/ui/model-list.d.ts +4 -3
- package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -1
- package/dist/extensions/subagent/ui/model-list.js +20 -22
- package/dist/extensions/subagent/ui/model-list.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +2 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +77 -25
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +36 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +23 -10
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +5 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +12 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +4 -1
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +5 -2
- package/dist/modes/json-event.js.map +1 -1
- package/dist/package-manager-cli.js +1 -1
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +2 -0
- package/dist/server/create-harness.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +10 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +14 -19
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/bundled/README.md +1 -0
- package/docs/bundled/extensions/background.md +99 -0
- package/docs/bundled/extensions/subagent.md +83 -71
- package/docs/compaction.md +6 -6
- package/docs/custom-provider.md +1 -1
- package/docs/docs.json +4 -0
- package/docs/environment-variables.md +8 -2
- package/docs/extensions.md +19 -2
- package/docs/json.md +6 -4
- package/docs/keybindings.md +9 -2
- package/docs/rpc.md +15 -4
- package/docs/sdk.md +9 -0
- package/docs/settings.md +31 -0
- package/docs/terminal-setup.md +23 -2
- package/docs/themes.md +24 -3
- package/docs/tui.md +2 -2
- package/docs/usage.md +4 -3
- package/examples/README.md +1 -1
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +170 -221
- package/package.json +6 -6
- package/dist/extensions/subagent/display-name.d.ts +0 -6
- package/dist/extensions/subagent/display-name.d.ts.map +0 -1
- package/dist/extensions/subagent/display-name.js +0 -13
- package/dist/extensions/subagent/display-name.js.map +0 -1
- package/dist/extensions/subagent/retry.d.ts +0 -17
- package/dist/extensions/subagent/retry.d.ts.map +0 -1
- package/dist/extensions/subagent/retry.js +0 -41
- package/dist/extensions/subagent/retry.js.map +0 -1
- package/dist/extensions/subagent/ui/profile-editor.d.ts +0 -54
- package/dist/extensions/subagent/ui/profile-editor.d.ts.map +0 -1
- package/dist/extensions/subagent/ui/profile-editor.js +0 -154
- package/dist/extensions/subagent/ui/profile-editor.js.map +0 -1
- package/dist/extensions/subagent/ui/profile-picker.d.ts +0 -32
- package/dist/extensions/subagent/ui/profile-picker.d.ts.map +0 -1
- package/dist/extensions/subagent/ui/profile-picker.js +0 -149
- package/dist/extensions/subagent/ui/profile-picker.js.map +0 -1
- package/examples/extensions/subagent/README.md +0 -175
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -1009
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
|
@@ -1,223 +1,44 @@
|
|
|
1
1
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
|
-
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
3
2
|
import { getMarkdownTheme } from "../../modes/interactive/theme/theme.js";
|
|
4
|
-
import {
|
|
5
|
-
import { getSubagentRetryView } from "./
|
|
6
|
-
import {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const COLLAPSED_RUN_LIMIT = 4;
|
|
13
|
-
const PROMPT_PREVIEW_LINES = 2;
|
|
14
|
-
const PROMPT_PREVIEW_LINE_LIMIT = 120;
|
|
15
|
-
const ACTIVITY_SUMMARY_LIMIT = 96;
|
|
16
|
-
const ACTIVITY_RESULT_LIMIT = 64;
|
|
17
|
-
// Sub-10s tool calls are routine; a duration only earns ink when it
|
|
18
|
-
// explains where the time went.
|
|
3
|
+
import { AGENT_PROFILE_LABELS } from "./agents.js";
|
|
4
|
+
import { getSubagentRetryView, statusSummary } from "./state.js";
|
|
5
|
+
import { firstPlainLine, plainLine, truncate } from "./text.js";
|
|
6
|
+
const TASK_SUMMARY_LIMIT = 72;
|
|
7
|
+
const ACTIVITY_SUMMARY_LIMIT = 120;
|
|
8
|
+
const ACTIVITY_RESULT_LIMIT = 96;
|
|
9
|
+
const RETRY_ERROR_LIMIT = 160;
|
|
10
|
+
const FALLBACK_OUTPUT_LIMIT = 4_000;
|
|
19
11
|
const ACTIVITY_DURATION_MIN_MS = 10_000;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
render(width) {
|
|
27
|
-
const indent = Math.min(this.indent, Math.max(0, width - 1));
|
|
28
|
-
const prefix = " ".repeat(indent);
|
|
29
|
-
return this.text.render(Math.max(1, width - indent)).map((line) => `${prefix}${line}`);
|
|
30
|
-
}
|
|
31
|
-
invalidate() {
|
|
32
|
-
this.text.invalidate();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function singleAgentName(args) {
|
|
36
|
-
return displayAgentName(args.agent ?? "general");
|
|
37
|
-
}
|
|
38
|
-
function truncate(text, limit) {
|
|
39
|
-
// Count code points, not UTF-16 code units: slicing a surrogate pair in
|
|
40
|
-
// half would render as a lone replacement glyph in the terminal.
|
|
41
|
-
const chars = [...text];
|
|
42
|
-
return chars.length <= limit ? text : `${chars.slice(0, Math.max(0, limit - 1)).join("")}…`;
|
|
12
|
+
function taskPrompt(args, index, run) {
|
|
13
|
+
// Tool calls restored from before the tasks-array shape carry a legacy
|
|
14
|
+
// args object without `tasks`; those rows fall back to the description.
|
|
15
|
+
const tasks = args.tasks;
|
|
16
|
+
return tasks?.[index]?.prompt ?? run.description;
|
|
43
17
|
}
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const CODE_KEYWORD_SUFFIX_PATTERN = /\b(?:return|yield|raise|del|global|nonlocal|import|from|as)\s*$/u;
|
|
48
|
-
// Double underscores are ambiguous: Markdown uses them for strong emphasis,
|
|
49
|
-
// while code uses them for dunder identifiers. Preserve identifier-shaped
|
|
50
|
-
// spans in clear code contexts and strip the remaining emphasis markers.
|
|
51
|
-
function stripUnderscoreStrong(text) {
|
|
52
|
-
return text.replace(/__([^_\n]+)__/gu, (match, content, offset) => {
|
|
53
|
-
const before = text[offset - 1] ?? "";
|
|
54
|
-
const after = text[offset + match.length] ?? "";
|
|
55
|
-
const pairedCodeDelimiter = (before === "`" && after === "`") || (before === '"' && after === '"') || (before === "'" && after === "'");
|
|
56
|
-
const codeAdjacent = before === "." || after === "(" || after === "." || after === "[";
|
|
57
|
-
const trimmedBefore = text.slice(0, offset).trimEnd();
|
|
58
|
-
const trimmedAfter = text.slice(offset + match.length).trimStart();
|
|
59
|
-
const operatorAdjacent = CODE_OPERATOR_SUFFIX_PATTERN.test(trimmedBefore) || CODE_OPERATOR_PREFIX_PATTERN.test(trimmedAfter);
|
|
60
|
-
const keywordAdjacent = CODE_KEYWORD_SUFFIX_PATTERN.test(trimmedBefore);
|
|
61
|
-
return DUNDER_IDENTIFIER_PATTERN.test(content) &&
|
|
62
|
-
(pairedCodeDelimiter || codeAdjacent || operatorAdjacent || keywordAdjacent)
|
|
63
|
-
? match
|
|
64
|
-
: content;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
// Transcript excerpts render inside plain Text components, so markdown
|
|
68
|
-
// punctuation would show up literally; strip the common inline syntax.
|
|
69
|
-
function stripInlineMarkdown(text) {
|
|
70
|
-
return (stripUnderscoreStrong(text.replace(/```[a-zA-Z0-9-]*/gu, ""))
|
|
71
|
-
.replace(/`([^`]*)`/gu, "$1")
|
|
72
|
-
// Demarcated bold only: `x**2**` is exponentiation, not emphasis.
|
|
73
|
-
.replace(/(?<![A-Za-z0-9])\*\*([^*]+)\*\*(?![A-Za-z0-9])/gu, "$1")
|
|
74
|
-
.replace(/\[([^\]]+)\]\([^)]*\)/gu, "$1")
|
|
75
|
-
.replace(/^#{1,6}\s+/gmu, ""));
|
|
76
|
-
}
|
|
77
|
-
// Markdown tables and horizontal rules turn into symbol soup when
|
|
78
|
-
// flattened onto one line; drop those lines before inline stripping.
|
|
79
|
-
function stripBlockMarkdown(text) {
|
|
80
|
-
return text
|
|
81
|
-
.split("\n")
|
|
82
|
-
.filter((line) => {
|
|
83
|
-
const trimmed = line.trim();
|
|
84
|
-
if (trimmed.startsWith("|"))
|
|
85
|
-
return false;
|
|
86
|
-
return !(trimmed.length >= 3 && /^[-—─═=*_|:\s]+$/u.test(trimmed));
|
|
87
|
-
})
|
|
88
|
-
.join("\n");
|
|
89
|
-
}
|
|
90
|
-
// finalOutput/liveText carry model-facing truncation notices from
|
|
91
|
-
// boundText/tailText. Only recognize their exact boundary lines so literal
|
|
92
|
-
// discussion of the marker remains visible in the transcript.
|
|
93
|
-
const OUTPUT_TRUNCATION_NOTICE_PATTERN = /^\[Output truncated(?:: \d+ bytes omitted)?\.\]$/u;
|
|
94
|
-
const EARLIER_OUTPUT_NOTICE = "[Earlier output omitted.]";
|
|
95
|
-
function terminalOutputNotice(text) {
|
|
96
|
-
const lines = text.split("\n");
|
|
97
|
-
let index = lines.length - 1;
|
|
98
|
-
while (index >= 0 && !lines[index]?.trim())
|
|
99
|
-
index--;
|
|
100
|
-
if (index < 0 || !OUTPUT_TRUNCATION_NOTICE_PATTERN.test(lines[index]?.trim() ?? "")) {
|
|
101
|
-
return { text, truncated: false };
|
|
102
|
-
}
|
|
103
|
-
lines.splice(index, 1);
|
|
104
|
-
return { text: lines.join("\n").trimEnd(), truncated: true };
|
|
18
|
+
function taskSummary(args, index, run) {
|
|
19
|
+
const firstLine = firstPlainLine(taskPrompt(args, index, run));
|
|
20
|
+
return truncate(firstLine || plainLine(run.description) || `Task ${index + 1}`, TASK_SUMMARY_LIMIT);
|
|
105
21
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
const first = lines.findIndex((line) => Boolean(line.trim()));
|
|
109
|
-
if (first >= 0 && lines[first]?.trim() === EARLIER_OUTPUT_NOTICE)
|
|
110
|
-
lines[first] = "…";
|
|
111
|
-
let last = lines.length - 1;
|
|
112
|
-
while (last >= 0 && !lines[last]?.trim())
|
|
113
|
-
last--;
|
|
114
|
-
if (last >= 0 && OUTPUT_TRUNCATION_NOTICE_PATTERN.test(lines[last]?.trim() ?? ""))
|
|
115
|
-
lines[last] = "…";
|
|
116
|
-
return lines.join("\n");
|
|
22
|
+
function profileLabel(agent) {
|
|
23
|
+
return AGENT_PROFILE_LABELS[agent] ?? agent;
|
|
117
24
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
// Generated notice-only boundary lines must never become the live line the
|
|
125
|
-
// user watches; identical text in the body remains ordinary model output.
|
|
126
|
-
function liveTailLines(run) {
|
|
127
|
-
const { text } = terminalOutputNotice(run.liveText);
|
|
128
|
-
const lines = text
|
|
129
|
-
.split("\n")
|
|
130
|
-
.map((line) => line.trim())
|
|
131
|
-
.filter(Boolean);
|
|
132
|
-
if (lines[0] === EARLIER_OUTPUT_NOTICE)
|
|
133
|
-
lines.shift();
|
|
134
|
-
return lines;
|
|
135
|
-
}
|
|
136
|
-
function liveTail(run) {
|
|
137
|
-
const last = liveTailLines(run).at(-1);
|
|
138
|
-
return last ? excerpt(last, LIVE_TAIL_LIMIT) : undefined;
|
|
139
|
-
}
|
|
140
|
-
// Completed-run excerpts read better cut at a sentence boundary than
|
|
141
|
-
// mid-word; fall back to a hard truncate when no boundary lands in the
|
|
142
|
-
// second half of the window.
|
|
143
|
-
function leadingSentences(text, limit) {
|
|
144
|
-
const cleaned = excerpt(text, Number.MAX_SAFE_INTEGER);
|
|
145
|
-
if (cleaned.length <= limit)
|
|
146
|
-
return cleaned;
|
|
147
|
-
const window = cleaned.slice(0, limit);
|
|
148
|
-
let boundary = -1;
|
|
149
|
-
for (const match of window.matchAll(/[.!?。!?](?=\s|$)/gu)) {
|
|
150
|
-
boundary = (match.index ?? 0) + match[0].length;
|
|
151
|
-
}
|
|
152
|
-
if (boundary >= limit / 2)
|
|
153
|
-
return window.slice(0, boundary).trimEnd();
|
|
154
|
-
return truncate(cleaned, limit);
|
|
155
|
-
}
|
|
156
|
-
const VERIFY_COMMAND_PATTERN = /\b(test|lint|typecheck|tsc|vitest|jest|pytest|build|check|fmt|format|compile)\b/iu;
|
|
157
|
-
const INSPECT_COMMAND_PATTERN = /\b(rg|grep|find|git (?:diff|status|log|show)|ls|tree|cat|head|tail)\b/iu;
|
|
158
|
-
const RETRY_MARKER_PATTERN = /^Retrying\b/u;
|
|
159
|
-
const STREAM_MARKERS = new Set(["Thinking…", "Writing response…"]);
|
|
160
|
-
function retryText(run) {
|
|
161
|
-
const retry = getSubagentRetryView(run);
|
|
162
|
-
if (!retry)
|
|
163
|
-
return undefined;
|
|
164
|
-
const status = retry.remainingSeconds > 0
|
|
165
|
-
? `Retrying (${retry.attempt}/${retry.maxAttempts}) in ${retry.remainingSeconds}s`
|
|
166
|
-
: `Retrying now… (${retry.attempt}/${retry.maxAttempts})`;
|
|
167
|
-
const error = excerpt(retry.error, RETRY_ERROR_EXCERPT_LIMIT);
|
|
168
|
-
return error ? `${status} — ${error}` : status;
|
|
169
|
-
}
|
|
170
|
-
// Translates raw tool traffic into what the worker is doing right now, so
|
|
171
|
-
// the collapsed card reads as a phase instead of a command line. Modelled
|
|
172
|
-
// on tool-type classification; the raw tool summary stays as evidence on
|
|
173
|
-
// the next line.
|
|
174
|
-
function runIntent(run) {
|
|
175
|
-
const retry = retryText(run);
|
|
176
|
-
if (retry)
|
|
177
|
-
return retry;
|
|
178
|
-
const current = run.currentActivity;
|
|
179
|
-
if (current && RETRY_MARKER_PATTERN.test(current))
|
|
180
|
-
return current;
|
|
181
|
-
const running = [...run.activities].reverse().find((activity) => activity.status === "running");
|
|
182
|
-
if (running) {
|
|
183
|
-
if (running.toolName === "edit" || running.toolName === "write")
|
|
184
|
-
return "Applying changes";
|
|
185
|
-
if (running.toolName === "bash") {
|
|
186
|
-
if (VERIFY_COMMAND_PATTERN.test(running.summary))
|
|
187
|
-
return "Verifying changes";
|
|
188
|
-
return INSPECT_COMMAND_PATTERN.test(running.summary) ? "Exploring code" : "Running commands";
|
|
189
|
-
}
|
|
190
|
-
return "Exploring code";
|
|
191
|
-
}
|
|
192
|
-
const recent = run.activities.slice(-5);
|
|
193
|
-
if (current && STREAM_MARKERS.has(current)) {
|
|
194
|
-
if (recent.some((activity) => activity.status === "failed"))
|
|
195
|
-
return "Investigating a failure";
|
|
196
|
-
return current;
|
|
197
|
-
}
|
|
198
|
-
if (recent.some((activity) => activity.status === "failed"))
|
|
199
|
-
return "Investigating a failure";
|
|
200
|
-
if (recent.some((activity) => activity.toolName === "edit" || activity.toolName === "write"))
|
|
201
|
-
return "Reviewing changes";
|
|
202
|
-
if (recent.length > 0)
|
|
203
|
-
return "Exploring code";
|
|
204
|
-
return current ?? "Thinking…";
|
|
25
|
+
function formatTokens(value) {
|
|
26
|
+
if (value < 1_000)
|
|
27
|
+
return String(value);
|
|
28
|
+
if (value < 1_000_000)
|
|
29
|
+
return `${(value / 1_000).toFixed(value < 10_000 ? 1 : 0)}k`;
|
|
30
|
+
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
205
31
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const current = run.currentActivity;
|
|
212
|
-
if (!current || STREAM_MARKERS.has(current) || RETRY_MARKER_PATTERN.test(current))
|
|
213
|
-
return undefined;
|
|
214
|
-
return current;
|
|
32
|
+
function formatDuration(seconds) {
|
|
33
|
+
const roundedSeconds = Math.round(seconds);
|
|
34
|
+
if (roundedSeconds >= 60)
|
|
35
|
+
return `${Math.floor(roundedSeconds / 60)}m ${roundedSeconds % 60}s`;
|
|
36
|
+
return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
|
|
215
37
|
}
|
|
216
|
-
function
|
|
217
|
-
if (
|
|
38
|
+
function elapsed(startedAt, endedAt) {
|
|
39
|
+
if (startedAt === undefined)
|
|
218
40
|
return undefined;
|
|
219
|
-
|
|
220
|
-
return formatDuration(Math.max(0, (end - run.startedAt) / 1000));
|
|
41
|
+
return formatDuration(Math.max(0, ((endedAt ?? Date.now()) - startedAt) / 1_000));
|
|
221
42
|
}
|
|
222
43
|
function statusColor(status) {
|
|
223
44
|
switch (status) {
|
|
@@ -233,9 +54,6 @@ function statusColor(status) {
|
|
|
233
54
|
return "muted";
|
|
234
55
|
}
|
|
235
56
|
}
|
|
236
|
-
// The tool shell already paints a call-level status dot (● in
|
|
237
|
-
// warning/success/error), so `●` is off-limits here: `›` marks where the
|
|
238
|
-
// work currently is, and the remaining glyphs are terminal states.
|
|
239
57
|
function statusMarker(status, theme) {
|
|
240
58
|
const glyph = status === "completed"
|
|
241
59
|
? "✓"
|
|
@@ -249,413 +67,238 @@ function statusMarker(status, theme) {
|
|
|
249
67
|
return theme.fg(statusColor(status), glyph);
|
|
250
68
|
}
|
|
251
69
|
function statusWord(status) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
default:
|
|
262
|
-
return "Queued";
|
|
263
|
-
}
|
|
70
|
+
if (status === "completed")
|
|
71
|
+
return "Completed";
|
|
72
|
+
if (status === "failed")
|
|
73
|
+
return "Failed";
|
|
74
|
+
if (status === "aborted")
|
|
75
|
+
return "Aborted";
|
|
76
|
+
if (status === "running")
|
|
77
|
+
return "Running";
|
|
78
|
+
return "Queued";
|
|
264
79
|
}
|
|
265
|
-
function
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (run.usage.contextTokens)
|
|
275
|
-
items.push(`ctx: ${formatTokens(run.usage.contextTokens)}`);
|
|
276
|
-
return items.join(" · ");
|
|
80
|
+
function retryText(run) {
|
|
81
|
+
const retry = getSubagentRetryView(run);
|
|
82
|
+
if (!retry)
|
|
83
|
+
return undefined;
|
|
84
|
+
const state = retry.remainingSeconds > 0
|
|
85
|
+
? `Retrying (${retry.attempt}/${retry.maxAttempts}) in ${retry.remainingSeconds}s`
|
|
86
|
+
: `Retrying now… (${retry.attempt}/${retry.maxAttempts})`;
|
|
87
|
+
const error = truncate(plainLine(retry.error), RETRY_ERROR_LIMIT);
|
|
88
|
+
return error ? `${state} — ${error}` : state;
|
|
277
89
|
}
|
|
278
|
-
function
|
|
279
|
-
let line = runTitle(run, theme, index);
|
|
90
|
+
function runningDetail(run) {
|
|
280
91
|
const retry = retryText(run);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (
|
|
92
|
+
if (retry)
|
|
93
|
+
return retry;
|
|
94
|
+
if (run.currentActivity)
|
|
95
|
+
return truncate(plainLine(run.currentActivity), ACTIVITY_SUMMARY_LIMIT);
|
|
96
|
+
if (run.status === "queued")
|
|
97
|
+
return "Waiting for a worker slot";
|
|
98
|
+
if (run.status === "running" && run.usage.turns === 0 && run.usage.toolUses === 0)
|
|
99
|
+
return "Initializing…";
|
|
100
|
+
if (run.status === "running")
|
|
101
|
+
return "Thinking…";
|
|
102
|
+
if ((run.status === "failed" || run.status === "aborted") && run.error) {
|
|
103
|
+
return truncate(plainLine(run.error), ACTIVITY_RESULT_LIMIT);
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
function batchMarker(details, theme) {
|
|
108
|
+
if (details.runs.some((run) => run.status === "failed"))
|
|
109
|
+
return theme.fg("error", "×");
|
|
110
|
+
if (details.runs.some((run) => run.status === "aborted"))
|
|
111
|
+
return theme.fg("warning", "■");
|
|
112
|
+
return theme.fg("success", "✓");
|
|
113
|
+
}
|
|
114
|
+
function progressHeader(details, theme) {
|
|
115
|
+
const settled = details.runs.filter((run) => run.status !== "running" && run.status !== "queued").length;
|
|
116
|
+
return theme.fg("accent", `› ${settled}/${details.runs.length} done · ${elapsed(details.startedAt) ?? "0.0s"}`);
|
|
117
|
+
}
|
|
118
|
+
function taskRow(run, index, args, theme, live) {
|
|
119
|
+
let line = ` ${statusMarker(run.status, theme)} ${theme.fg("accent", profileLabel(run.agent))}${theme.fg("dim", ` · ${taskSummary(args, index, run)}`)}`;
|
|
120
|
+
const detail = live ? runningDetail(run) : undefined;
|
|
121
|
+
if (detail) {
|
|
288
122
|
line += theme.fg("dim", ` — ${detail}`);
|
|
289
|
-
if (run.status === "running") {
|
|
290
|
-
line += theme.fg("dim", ` · ${runProgressText(run)}`);
|
|
291
|
-
if (run.usage.cost)
|
|
292
|
-
line += theme.fg("dim", ` · $${run.usage.cost.toFixed(3)}`);
|
|
293
|
-
const elapsed = liveElapsed(run);
|
|
294
|
-
if (elapsed)
|
|
295
|
-
line += theme.fg("dim", ` · ${elapsed}`);
|
|
296
123
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
124
|
+
else if (run.status !== "running" && run.status !== "queued") {
|
|
125
|
+
const duration = elapsed(run.startedAt, run.endedAt);
|
|
126
|
+
if (duration)
|
|
127
|
+
line += theme.fg("dim", ` · ${duration}`);
|
|
300
128
|
}
|
|
301
129
|
return line;
|
|
302
130
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return [...retrying, ...otherActive, ...settled].slice(0, COLLAPSED_RUN_LIMIT);
|
|
330
|
-
}
|
|
331
|
-
function singleCollapsedLines(details, theme) {
|
|
332
|
-
const run = details.runs[0];
|
|
333
|
-
if (!run)
|
|
334
|
-
return [theme.fg("muted", "Initializing…")];
|
|
335
|
-
if (run.status === "running" || run.status === "queued") {
|
|
336
|
-
const retry = retryText(run);
|
|
337
|
-
const isInitializing = run.status === "running" &&
|
|
338
|
-
!retry &&
|
|
339
|
-
run.usage.turns === 0 &&
|
|
340
|
-
run.usage.toolUses === 0 &&
|
|
341
|
-
run.liveText.length === 0;
|
|
342
|
-
const rawIntent = retry ??
|
|
343
|
-
(run.status === "queued"
|
|
344
|
-
? (run.currentActivity ?? statusWord(run.status))
|
|
345
|
-
: isInitializing
|
|
346
|
-
? "Initializing…"
|
|
347
|
-
: runIntent(run));
|
|
348
|
-
const intent = excerpt(rawIntent, LIVE_TAIL_LIMIT);
|
|
349
|
-
// No status marker here: the shell's call-level dot already covers
|
|
350
|
-
// the whole card, and a single run has nothing to disambiguate.
|
|
351
|
-
let status = theme.fg("dim", intent);
|
|
352
|
-
if (run.status === "running" && !isInitializing) {
|
|
353
|
-
status += theme.fg("dim", ` · ${runProgressText(run)}`);
|
|
354
|
-
if (run.usage.cost)
|
|
355
|
-
status += theme.fg("dim", ` · $${run.usage.cost.toFixed(3)}`);
|
|
356
|
-
const elapsed = liveElapsed(run);
|
|
357
|
-
if (elapsed)
|
|
358
|
-
status += theme.fg("dim", ` · ${elapsed}`);
|
|
359
|
-
}
|
|
360
|
-
const lines = [status];
|
|
361
|
-
const evidence = runEvidence(run);
|
|
362
|
-
if (evidence && evidence !== rawIntent)
|
|
363
|
-
lines.push(`${theme.fg("accent", "›")} ${theme.fg("dim", excerpt(evidence, LIVE_TAIL_LIMIT))}`);
|
|
364
|
-
const tail = liveTail(run);
|
|
365
|
-
if (tail)
|
|
366
|
-
lines.push(theme.fg("dim", tail));
|
|
367
|
-
return lines;
|
|
368
|
-
}
|
|
369
|
-
if (run.error)
|
|
370
|
-
return [theme.fg("error", excerpt(run.error, SINGLE_EXCERPT_LIMIT))];
|
|
371
|
-
if (run.finalOutput)
|
|
372
|
-
return [theme.fg("toolOutput", leadingSentences(run.finalOutput, SINGLE_EXCERPT_LIMIT))];
|
|
373
|
-
return [theme.fg("muted", statusWord(run.status))];
|
|
374
|
-
}
|
|
375
|
-
// Deep-trimmed settled footer: turns and per-run breakdowns belong to the
|
|
376
|
-
// expanded view. Parallel batches keep only whole-call numbers — the run
|
|
377
|
-
// rows above already localize the work.
|
|
378
|
-
function collapsedUsageText(details, extended) {
|
|
379
|
-
const usage = details.usage;
|
|
380
|
-
const items = [];
|
|
381
|
-
if (usage.totalTokens)
|
|
382
|
-
items.push(`${formatTokens(usage.totalTokens)} tok`);
|
|
383
|
-
if (extended && usage.toolUses)
|
|
384
|
-
items.push(`${usage.toolUses} tool use${usage.toolUses === 1 ? "" : "s"}`);
|
|
385
|
-
if (extended && usage.contextTokens)
|
|
386
|
-
items.push(`ctx: ${formatTokens(usage.contextTokens)}`);
|
|
387
|
-
if (usage.cost)
|
|
388
|
-
items.push(`$${usage.cost.toFixed(3)}`);
|
|
389
|
-
if (details.endedAt !== undefined) {
|
|
390
|
-
items.push(formatDuration(Math.max(0, (details.endedAt - details.startedAt) / 1000)));
|
|
391
|
-
}
|
|
392
|
-
return items.join(" · ");
|
|
393
|
-
}
|
|
394
|
-
function batchUsageText(details, isPartial) {
|
|
395
|
-
const usage = details.usage;
|
|
396
|
-
const items = [];
|
|
397
|
-
// The aggregate mixes settled and running runs while partial, and the
|
|
398
|
-
// running share is cache-inflated — quote the total only once settled.
|
|
399
|
-
if (!isPartial && usage.totalTokens)
|
|
400
|
-
items.push(`${formatTokens(usage.totalTokens)} tok`);
|
|
401
|
-
if (usage.toolUses)
|
|
402
|
-
items.push(`${usage.toolUses} tool use${usage.toolUses === 1 ? "" : "s"}`);
|
|
403
|
-
if (usage.turns)
|
|
404
|
-
items.push(`${usage.turns} turn${usage.turns === 1 ? "" : "s"}`);
|
|
405
|
-
if (usage.contextTokens)
|
|
406
|
-
items.push(`ctx: ${formatTokens(usage.contextTokens)}`);
|
|
407
|
-
if (usage.cost)
|
|
408
|
-
items.push(`$${usage.cost.toFixed(3)}`);
|
|
409
|
-
if (details.endedAt !== undefined) {
|
|
410
|
-
items.push(formatDuration(Math.max(0, (details.endedAt - details.startedAt) / 1000)));
|
|
411
|
-
}
|
|
412
|
-
return items.join(" · ");
|
|
413
|
-
}
|
|
414
|
-
function formatTokens(value) {
|
|
415
|
-
if (value < 1_000)
|
|
416
|
-
return String(value);
|
|
417
|
-
if (value < 1_000_000)
|
|
418
|
-
return `${(value / 1_000).toFixed(value < 10_000 ? 1 : 0)}k`;
|
|
419
|
-
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
420
|
-
}
|
|
421
|
-
function formatDuration(seconds) {
|
|
422
|
-
const roundedSeconds = Math.round(seconds);
|
|
423
|
-
if (roundedSeconds >= 60)
|
|
424
|
-
return `${Math.floor(roundedSeconds / 60)}m ${roundedSeconds % 60}s`;
|
|
425
|
-
return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
|
|
426
|
-
}
|
|
427
|
-
function runDurationText(run) {
|
|
428
|
-
if (run.startedAt === undefined || run.endedAt === undefined)
|
|
429
|
-
return undefined;
|
|
430
|
-
return formatDuration(Math.max(0, (run.endedAt - run.startedAt) / 1000));
|
|
431
|
-
}
|
|
432
|
-
// One dim line per run: what it cost. Settled runs quote the real token
|
|
433
|
-
// total; running runs skip it (cache-inflated mid-run) and tick elapsed
|
|
434
|
-
// time instead. Batch sections fold model/thinking/cwd in here because
|
|
435
|
-
// their `──` header already carries verdict and duration.
|
|
436
|
-
function runMetricsText(run, includeIdentity) {
|
|
437
|
-
const usage = run.usage;
|
|
131
|
+
function outcomeSummary(details) {
|
|
132
|
+
const completed = details.runs.filter((run) => run.status === "completed").length;
|
|
133
|
+
const failed = details.runs.filter((run) => run.status === "failed").length;
|
|
134
|
+
const aborted = details.runs.filter((run) => run.status === "aborted").length;
|
|
135
|
+
const parts = [];
|
|
136
|
+
if (completed)
|
|
137
|
+
parts.push(`${completed} completed`);
|
|
138
|
+
if (failed)
|
|
139
|
+
parts.push(`${failed} failed`);
|
|
140
|
+
if (aborted)
|
|
141
|
+
parts.push(`${aborted} aborted`);
|
|
142
|
+
if (parts.length === 0)
|
|
143
|
+
parts.push(statusSummary(details));
|
|
144
|
+
const duration = elapsed(details.startedAt, details.endedAt);
|
|
145
|
+
if (duration)
|
|
146
|
+
parts.push(duration);
|
|
147
|
+
if (details.usage.cost)
|
|
148
|
+
parts.push(`$${details.usage.cost.toFixed(3)}`);
|
|
149
|
+
return parts.join(" · ");
|
|
150
|
+
}
|
|
151
|
+
function runMetrics(run) {
|
|
152
|
+
const items = [run.model];
|
|
153
|
+
if (run.thinking !== "off")
|
|
154
|
+
items.push(`${run.thinking} thinking`);
|
|
155
|
+
if (run.cwd)
|
|
156
|
+
items.push(`cwd: ${run.cwd}`);
|
|
438
157
|
const settled = run.status !== "running" && run.status !== "queued";
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (
|
|
448
|
-
items.push(
|
|
449
|
-
|
|
450
|
-
items.push(`${usage.toolUses} tool use${usage.toolUses === 1 ? "" : "s"}`);
|
|
451
|
-
if (usage.turns)
|
|
452
|
-
items.push(`${usage.turns} turn${usage.turns === 1 ? "" : "s"}`);
|
|
453
|
-
if (usage.contextTokens)
|
|
454
|
-
items.push(`ctx: ${formatTokens(usage.contextTokens)}`);
|
|
455
|
-
if (usage.cost)
|
|
456
|
-
items.push(`$${usage.cost.toFixed(3)}`);
|
|
457
|
-
const duration = settled ? (includeIdentity ? undefined : runDurationText(run)) : liveElapsed(run);
|
|
158
|
+
if (settled && run.usage.totalTokens)
|
|
159
|
+
items.push(`${formatTokens(run.usage.totalTokens)} tok`);
|
|
160
|
+
if (run.usage.toolUses)
|
|
161
|
+
items.push(`${run.usage.toolUses} tool use${run.usage.toolUses === 1 ? "" : "s"}`);
|
|
162
|
+
if (run.usage.turns)
|
|
163
|
+
items.push(`${run.usage.turns} turn${run.usage.turns === 1 ? "" : "s"}`);
|
|
164
|
+
if (run.usage.contextTokens)
|
|
165
|
+
items.push(`ctx: ${formatTokens(run.usage.contextTokens)}`);
|
|
166
|
+
if (run.usage.cost)
|
|
167
|
+
items.push(`$${run.usage.cost.toFixed(3)}`);
|
|
168
|
+
const duration = elapsed(run.startedAt, run.endedAt);
|
|
458
169
|
if (duration)
|
|
459
170
|
items.push(duration);
|
|
460
171
|
return items.join(" · ");
|
|
461
172
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const capped = boundedPrompt.truncated;
|
|
477
|
-
const section = [theme.fg("muted", "Prompt"), ...preview];
|
|
478
|
-
if (remaining > 0 || capped || clipped) {
|
|
479
|
-
let note = "… continues";
|
|
480
|
-
if (remaining > 0)
|
|
481
|
-
note += `, ${remaining} more line${remaining === 1 ? "" : "s"}`;
|
|
482
|
-
if (capped)
|
|
483
|
-
note += " · capped at 1KB";
|
|
484
|
-
section.push(theme.fg("muted", note));
|
|
173
|
+
function activityLine(activity, run, theme) {
|
|
174
|
+
const summary = activity.status === "running" && run.currentActivity
|
|
175
|
+
? truncate(plainLine(run.currentActivity), ACTIVITY_SUMMARY_LIMIT)
|
|
176
|
+
: truncate(plainLine(activity.summary), ACTIVITY_SUMMARY_LIMIT);
|
|
177
|
+
let line = activity.status === "failed"
|
|
178
|
+
? `${theme.fg("error", "×")} ${theme.fg("toolOutput", summary)}`
|
|
179
|
+
: activity.status === "running"
|
|
180
|
+
? `${theme.fg("accent", "›")} ${theme.fg("toolOutput", summary)}`
|
|
181
|
+
: ` ${theme.fg("toolOutput", summary)}`;
|
|
182
|
+
if (activity.status === "failed" && activity.resultSummary) {
|
|
183
|
+
line += theme.fg("error", ` · ${truncate(plainLine(activity.resultSummary), ACTIVITY_RESULT_LIMIT)}`);
|
|
184
|
+
}
|
|
185
|
+
if (activity.endedAt !== undefined && activity.endedAt - activity.startedAt >= ACTIVITY_DURATION_MIN_MS) {
|
|
186
|
+
line += theme.fg("dim", ` · ${formatDuration((activity.endedAt - activity.startedAt) / 1_000)}`);
|
|
485
187
|
}
|
|
486
|
-
return
|
|
188
|
+
return line;
|
|
189
|
+
}
|
|
190
|
+
function runHeader(run, index, args, theme) {
|
|
191
|
+
return `${theme.fg("dim", `── #${index + 1} `)}${statusMarker(run.status, theme)} ${theme.fg("accent", profileLabel(run.agent))}${theme.fg("dim", ` · ${taskSummary(args, index, run)}`)}`;
|
|
487
192
|
}
|
|
488
|
-
|
|
489
|
-
// bounded Prompt/Activity digest in the middle, then the full Report —
|
|
490
|
-
// the one unbounded element, placed last so the card ends where reading
|
|
491
|
-
// begins. Single runs skip the `──` header (the call header already names
|
|
492
|
-
// them); batch sections carry a numbered header matching the contents
|
|
493
|
-
// list above.
|
|
494
|
-
function renderRunDetails(run, theme, single, index) {
|
|
193
|
+
function renderRunningRun(run, index, args, theme) {
|
|
495
194
|
const container = new Container();
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const identity = [run.model];
|
|
499
|
-
if (run.thinking !== "off")
|
|
500
|
-
identity.push(`${run.thinking} thinking`);
|
|
501
|
-
if (run.cwd)
|
|
502
|
-
identity.push(`cwd: ${run.cwd}`);
|
|
503
|
-
container.addChild(new Text(`${statusMarker(run.status, theme)} ${theme.fg(statusColor(run.status), statusWord(run.status))}${theme.fg("dim", ` · ${identity.join(" · ")}`)}`, 0, 0));
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
const label = index === undefined ? "" : `${index} · `;
|
|
507
|
-
const duration = settled ? runDurationText(run) : undefined;
|
|
508
|
-
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));
|
|
509
|
-
}
|
|
510
|
-
if (run.status !== "queued") {
|
|
511
|
-
const metrics = runMetricsText(run, !single);
|
|
512
|
-
if (metrics)
|
|
513
|
-
container.addChild(new Text(theme.fg("dim", metrics), 0, 0));
|
|
514
|
-
}
|
|
195
|
+
container.addChild(new Text(runHeader(run, index, args, theme), 0, 0));
|
|
196
|
+
container.addChild(new Text(theme.fg("dim", runMetrics(run)), 0, 0));
|
|
515
197
|
const retry = retryText(run);
|
|
516
198
|
if (retry)
|
|
517
199
|
container.addChild(new Text(theme.fg("accent", retry), 0, 0));
|
|
518
200
|
container.addChild(new Spacer(1));
|
|
519
|
-
|
|
520
|
-
|
|
201
|
+
const total = Math.max(run.usage.toolUses, run.activities.length);
|
|
202
|
+
const label = total > run.activities.length ? `Activity · last ${run.activities.length} of ${total}` : "Activity";
|
|
203
|
+
container.addChild(new Text(theme.fg("muted", label), 0, 0));
|
|
521
204
|
if (run.activities.length > 0) {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const label = total > run.activities.length ? `Activity · last ${run.activities.length} of ${total}` : "Activity";
|
|
525
|
-
container.addChild(new Text(theme.fg("muted", label), 0, 0));
|
|
526
|
-
for (const activity of run.activities) {
|
|
527
|
-
// Deviation earns ink: × and › are marked, success rows are quiet
|
|
528
|
-
// one-liners without result echoes.
|
|
529
|
-
let text;
|
|
530
|
-
if (activity.status === "failed") {
|
|
531
|
-
text = `${theme.fg("error", "×")} ${theme.fg("toolOutput", excerpt(activity.summary, ACTIVITY_SUMMARY_LIMIT))}`;
|
|
532
|
-
if (activity.resultSummary) {
|
|
533
|
-
text += ` ${theme.fg("error", `· ${excerpt(activity.resultSummary, ACTIVITY_RESULT_LIMIT)}`)}`;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
else if (activity.status === "running") {
|
|
537
|
-
text = `${theme.fg("accent", "›")} ${theme.fg("toolOutput", excerpt(activity.summary, ACTIVITY_SUMMARY_LIMIT))}`;
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
text = ` ${theme.fg("toolOutput", excerpt(activity.summary, ACTIVITY_SUMMARY_LIMIT))}`;
|
|
541
|
-
}
|
|
542
|
-
if (activity.endedAt !== undefined && activity.endedAt - activity.startedAt >= ACTIVITY_DURATION_MIN_MS) {
|
|
543
|
-
text += ` ${theme.fg("dim", `· ${formatDuration((activity.endedAt - activity.startedAt) / 1000)}`)}`;
|
|
544
|
-
}
|
|
545
|
-
container.addChild(new Text(text, 0, 0));
|
|
546
|
-
}
|
|
205
|
+
for (const activity of run.activities)
|
|
206
|
+
container.addChild(new Text(activityLine(activity, run, theme), 0, 0));
|
|
547
207
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
for (const line of tail) {
|
|
556
|
-
container.addChild(new Text(theme.fg("dim", excerpt(line, LIVE_TAIL_LIMIT)), 0, 0));
|
|
557
|
-
}
|
|
558
|
-
}
|
|
208
|
+
else {
|
|
209
|
+
const fallback = retry
|
|
210
|
+
? "Waiting to retry"
|
|
211
|
+
: run.status === "failed" || run.status === "aborted"
|
|
212
|
+
? "No activity recorded"
|
|
213
|
+
: (runningDetail(run) ?? statusWord(run.status));
|
|
214
|
+
container.addChild(new Text(theme.fg("dim", ` ${fallback}`), 0, 0));
|
|
559
215
|
}
|
|
560
216
|
if (run.error) {
|
|
561
217
|
container.addChild(new Spacer(1));
|
|
562
218
|
container.addChild(new Text(theme.fg("muted", "Error"), 0, 0));
|
|
563
219
|
container.addChild(new Text(theme.fg("error", run.error), 0, 0));
|
|
564
220
|
}
|
|
565
|
-
|
|
221
|
+
return container;
|
|
222
|
+
}
|
|
223
|
+
function renderSettledRun(run, index, args, theme) {
|
|
224
|
+
const container = new Container();
|
|
225
|
+
container.addChild(new Text(runHeader(run, index, args, theme), 0, 0));
|
|
226
|
+
container.addChild(new Text(theme.fg("dim", runMetrics(run)), 0, 0));
|
|
227
|
+
container.addChild(new Spacer(1));
|
|
228
|
+
container.addChild(new Text(theme.fg("muted", "Prompt"), 0, 0));
|
|
229
|
+
container.addChild(new Text(theme.fg("toolOutput", taskPrompt(args, index, run)), 0, 0));
|
|
230
|
+
if (run.error) {
|
|
566
231
|
container.addChild(new Spacer(1));
|
|
567
|
-
|
|
568
|
-
container.addChild(new Text(theme.fg("
|
|
569
|
-
|
|
232
|
+
container.addChild(new Text(theme.fg("muted", "Error"), 0, 0));
|
|
233
|
+
container.addChild(new Text(theme.fg("error", run.error), 0, 0));
|
|
234
|
+
}
|
|
235
|
+
container.addChild(new Spacer(1));
|
|
236
|
+
const reportLabel = run.status !== "completed" && run.report ? "Report · partial" : "Report";
|
|
237
|
+
container.addChild(new Text(theme.fg("muted", reportLabel), 0, 0));
|
|
238
|
+
if (run.report) {
|
|
239
|
+
container.addChild(new Markdown(run.report, 0, 0, getMarkdownTheme(), {
|
|
570
240
|
color: (text) => theme.fg("toolOutput", text),
|
|
571
241
|
}));
|
|
572
242
|
}
|
|
243
|
+
else {
|
|
244
|
+
container.addChild(new Text(theme.fg("muted", "(No report.)"), 0, 0));
|
|
245
|
+
}
|
|
573
246
|
return container;
|
|
574
247
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
248
|
+
function fallbackResult(result, theme, isError) {
|
|
249
|
+
const text = result.content.find((part) => part.type === "text");
|
|
250
|
+
const value = text?.type === "text" ? truncate(text.text, FALLBACK_OUTPUT_LIMIT) : "(no output)";
|
|
251
|
+
return new Text(theme.fg(isError ? "error" : "muted", value), 0, 0);
|
|
252
|
+
}
|
|
253
|
+
// Re-render elapsed time and retry countdowns once per second while the
|
|
254
|
+
// result is still partial; the first settled render clears the timer.
|
|
255
|
+
export function scheduleLiveRefresh(context, isPartial) {
|
|
256
|
+
const state = context.state;
|
|
257
|
+
if (isPartial) {
|
|
258
|
+
if (state.refreshTimer === undefined) {
|
|
259
|
+
state.refreshTimer = setTimeout(() => {
|
|
260
|
+
state.refreshTimer = undefined;
|
|
261
|
+
context.invalidate();
|
|
262
|
+
}, 1000);
|
|
263
|
+
state.refreshTimer.unref?.();
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
583
266
|
}
|
|
584
|
-
if (
|
|
585
|
-
|
|
267
|
+
if (state.refreshTimer !== undefined) {
|
|
268
|
+
clearTimeout(state.refreshTimer);
|
|
269
|
+
state.refreshTimer = undefined;
|
|
586
270
|
}
|
|
587
|
-
let text = theme.fg("toolTitle", theme.bold(`${singleAgentName(args)} Agent`));
|
|
588
|
-
if (args.description)
|
|
589
|
-
text += theme.fg("dim", ` · ${truncate(args.description, 72)}`);
|
|
590
|
-
return new Text(text, 0, 0);
|
|
591
271
|
}
|
|
592
|
-
export function
|
|
272
|
+
export function renderSubagentCall(args, theme) {
|
|
273
|
+
const count = Array.isArray(args.tasks) ? args.tasks.length : 0;
|
|
274
|
+
const label = `${count} task${count === 1 ? "" : "s"}`;
|
|
275
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("Subagent"))}${theme.fg(count > 0 ? "dim" : "error", ` · ${label}`)}`, 0, 0);
|
|
276
|
+
}
|
|
277
|
+
export function renderSubagentResult(result, options, theme, args, isError) {
|
|
593
278
|
const details = result.details;
|
|
594
|
-
if (!details)
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
return new Text(theme.fg(isError ? "error" : "muted", label), 0, 0);
|
|
598
|
-
}
|
|
599
|
-
// The pre-resolution first paint carries no runs yet, in either mode.
|
|
600
|
-
if (details.runs.length === 0) {
|
|
279
|
+
if (!details || !Array.isArray(details.runs))
|
|
280
|
+
return fallbackResult(result, theme, isError);
|
|
281
|
+
if (details.runs.length === 0)
|
|
601
282
|
return new Text(theme.fg("muted", "Initializing…"), 0, 0);
|
|
602
|
-
}
|
|
603
283
|
if (!options.expanded) {
|
|
604
|
-
if (details.mode === "single") {
|
|
605
|
-
const lines = singleCollapsedLines(details, theme);
|
|
606
|
-
if (!options.isPartial) {
|
|
607
|
-
const usage = collapsedUsageText(details, true);
|
|
608
|
-
if (usage)
|
|
609
|
-
lines.push(theme.fg("dim", usage));
|
|
610
|
-
lines.push(theme.fg("muted", `(${keyHint("app.tools.expand", "to expand")})`));
|
|
611
|
-
}
|
|
612
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
613
|
-
}
|
|
614
284
|
const container = new Container();
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
// Stable ordinals (the run's task position, not its display slot)
|
|
622
|
-
// keep identity readable while active-first ordering reshuffles
|
|
623
|
-
// rows, and match the expanded section numbers.
|
|
624
|
-
for (const run of shown)
|
|
625
|
-
addRunSummary(container, run, theme, details.runs.indexOf(run) + 1);
|
|
626
|
-
const hidden = details.runs.length - shown.length;
|
|
627
|
-
if (hidden > 0)
|
|
628
|
-
container.addChild(new Text(theme.fg("muted", `+${hidden} more`), 0, 0));
|
|
629
|
-
if (!options.isPartial) {
|
|
630
|
-
const usage = collapsedUsageText(details, false);
|
|
631
|
-
if (usage)
|
|
632
|
-
container.addChild(new Text(theme.fg("dim", usage), 0, 0));
|
|
633
|
-
container.addChild(new Text(theme.fg("muted", `(${keyHint("app.tools.expand", "to expand")})`), 0, 0));
|
|
285
|
+
const header = options.isPartial
|
|
286
|
+
? progressHeader(details, theme)
|
|
287
|
+
: `${batchMarker(details, theme)} ${theme.fg(isError ? "error" : "muted", outcomeSummary(details))}`;
|
|
288
|
+
container.addChild(new Text(header, 0, 0));
|
|
289
|
+
for (const [index, run] of details.runs.entries()) {
|
|
290
|
+
container.addChild(new Text(taskRow(run, index, args, theme, options.isPartial), 0, 0));
|
|
634
291
|
}
|
|
635
292
|
return container;
|
|
636
293
|
}
|
|
637
294
|
const container = new Container();
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
addRunSummary(container, run, theme, index + 1);
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
details.runs.forEach((run, index) => {
|
|
649
|
-
if (!single || index > 0)
|
|
650
|
-
container.addChild(new Spacer(1));
|
|
651
|
-
container.addChild(renderRunDetails(run, theme, single, single ? undefined : index + 1));
|
|
652
|
-
});
|
|
653
|
-
if (!single) {
|
|
654
|
-
const usage = batchUsageText(details, Boolean(options.isPartial));
|
|
655
|
-
if (usage) {
|
|
656
|
-
container.addChild(new Spacer(1));
|
|
657
|
-
container.addChild(new Text(theme.fg("dim", usage), 0, 0));
|
|
658
|
-
}
|
|
295
|
+
const header = options.isPartial
|
|
296
|
+
? progressHeader(details, theme)
|
|
297
|
+
: `${batchMarker(details, theme)} ${theme.fg(isError ? "error" : "toolTitle", outcomeSummary(details))}`;
|
|
298
|
+
container.addChild(new Text(header, 0, 0));
|
|
299
|
+
for (const [index, run] of details.runs.entries()) {
|
|
300
|
+
container.addChild(new Spacer(1));
|
|
301
|
+
container.addChild(options.isPartial ? renderRunningRun(run, index, args, theme) : renderSettledRun(run, index, args, theme));
|
|
659
302
|
}
|
|
660
303
|
return container;
|
|
661
304
|
}
|