mixdog 0.9.1 → 0.9.3
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/package.json +9 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +355 -22
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +80 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +75 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import React, { useEffect, useState } from 'react';
|
|
12
12
|
import { Box, Text } from 'ink';
|
|
13
13
|
import stringWidth from 'string-width';
|
|
14
|
+
import stripAnsi from 'strip-ansi';
|
|
15
|
+
import { displayWidth } from '../display-width.mjs';
|
|
14
16
|
import { theme, TURN_MARKER, RESULT_GUTTER, RESULT_GUTTER_CONT } from '../theme.mjs';
|
|
15
17
|
import { formatElapsed } from '../time-format.mjs';
|
|
16
18
|
import { BULLET_OPERATOR } from '../figures.mjs';
|
|
@@ -28,6 +30,11 @@ import { backgroundTaskFailureStatusLabel, isBackgroundErrorOnlyBody } from '../
|
|
|
28
30
|
import { formatExpandedResult, wrapExpandedResultLines } from './tool-output-format.mjs';
|
|
29
31
|
|
|
30
32
|
const MIN_RESULT_LINE_CHARS = 24;
|
|
33
|
+
// Hard cap for the collapsed result detail row (the second line under the ⎿
|
|
34
|
+
// gutter). Independent of terminal width so a wide terminal never lets a long
|
|
35
|
+
// line (e.g. an agent response brief) stretch the whole row — anything past
|
|
36
|
+
// this is truncated with an ellipsis. ctrl+o expand still shows the full body.
|
|
37
|
+
const RESULT_LINE_HARD_MAX = 80;
|
|
31
38
|
// Hard cap for the parenthesized header arg summary so a long path/query does
|
|
32
39
|
// not eat the whole header line; anything longer is truncated with an ellipsis.
|
|
33
40
|
const SUMMARY_MAX_CHARS = 48;
|
|
@@ -42,6 +49,21 @@ const TOOL_BLINK_LIMIT_MS = 3000;
|
|
|
42
49
|
const TOOL_PENDING_SHOW_DELAY_MS = 1000;
|
|
43
50
|
// Read `theme.subtle` at use-time (not captured here) so a live `/theme`
|
|
44
51
|
// switch re-tones the tool hints. `theme` is mutated in-place on switch.
|
|
52
|
+
// Collapsed tool headers/details are laid out as single terminal rows. Never let
|
|
53
|
+
// raw C0/control bytes (CR, tabs, cursor escapes, etc.) reach those rows: a
|
|
54
|
+
// terminal can apply them after Ink has already clipped/measured the row, which
|
|
55
|
+
// makes a scrolled tool card appear to write through the prompt/statusline.
|
|
56
|
+
const INLINE_CONTROL_RE = /[\u0000-\u001F\u007F]/g;
|
|
57
|
+
|
|
58
|
+
function safeInlineText(value) {
|
|
59
|
+
return stripAnsi(String(value ?? ''))
|
|
60
|
+
.replace(/\r\n?/g, '\n')
|
|
61
|
+
.replace(/\t/g, ' ')
|
|
62
|
+
.replace(/\n+/g, ' ')
|
|
63
|
+
.replace(INLINE_CONTROL_RE, ' ')
|
|
64
|
+
.replace(/\s+/g, ' ')
|
|
65
|
+
.trim();
|
|
66
|
+
}
|
|
45
67
|
|
|
46
68
|
function normalizeCount(value) {
|
|
47
69
|
const n = Number(value || 0);
|
|
@@ -169,10 +191,27 @@ function resultTerminalStatus(value) {
|
|
|
169
191
|
if (tagged) return normalizeTerminalStatus(tagged);
|
|
170
192
|
const bracketed = text.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
|
|
171
193
|
if (bracketed) return normalizeTerminalStatus(bracketed);
|
|
194
|
+
// Loose inline `status: x` / `state: x` matches are a last-resort fallback —
|
|
195
|
+
// prefer the engine-controlled `<status>` tag or `[status: …]` marker above.
|
|
196
|
+
// A loose match can false-positive on prose that happens to start with
|
|
197
|
+
// "status:" (rare, but shellResultStatus below already owns real shell
|
|
198
|
+
// output parsing; this fallback stays narrow and unchanged in behavior).
|
|
172
199
|
const inline = text.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
|
|
173
200
|
return normalizeTerminalStatus(inline);
|
|
174
201
|
}
|
|
175
202
|
|
|
203
|
+
const LEADING_STATUS_MARKER_LINE_RE = /^\[status:\s*[^\]]*\]\s*$/i;
|
|
204
|
+
|
|
205
|
+
function stripLeadingStatusMarkerLines(lines) {
|
|
206
|
+
const out = Array.isArray(lines) ? lines.slice() : [];
|
|
207
|
+
if (out.length > 0 && LEADING_STATUS_MARKER_LINE_RE.test(String(out[0] ?? '').trim())) out.shift();
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function stripLeadingStatusMarkerFromText(text) {
|
|
212
|
+
return stripLeadingStatusMarkerLines(String(text || '').split('\n')).join('\n');
|
|
213
|
+
}
|
|
214
|
+
|
|
176
215
|
function shellDisplayStatus({ pending = false, failedCount = 0, isError = false, result = '' } = {}) {
|
|
177
216
|
const status = shellResultStatus(result);
|
|
178
217
|
if (pending || /^(running|pending|queued)$/.test(status)) return 'running';
|
|
@@ -188,11 +227,6 @@ function shellHeader(status, count = 1) {
|
|
|
188
227
|
return `Ran ${object}`;
|
|
189
228
|
}
|
|
190
229
|
|
|
191
|
-
function shellDetail(status, elapsed = '') {
|
|
192
|
-
const label = displayTerminalStatus(status) || status;
|
|
193
|
-
return elapsed ? `${elapsed} · ${label}` : label;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
230
|
function shellResultElapsed(value) {
|
|
197
231
|
const match = String(value || '').match(/^\[elapsed:\s*(\d+)\s*ms\]/mi);
|
|
198
232
|
if (!match) return '';
|
|
@@ -211,20 +245,20 @@ function statusCopy(name, label, count, doneCount, pending, isError, args = {})
|
|
|
211
245
|
}
|
|
212
246
|
|
|
213
247
|
function fitResultLine(line, columns) {
|
|
214
|
-
const max = Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7);
|
|
215
|
-
const text =
|
|
216
|
-
return
|
|
248
|
+
const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
|
|
249
|
+
const text = safeInlineText(line);
|
|
250
|
+
return displayWidth(text) > max ? truncateToWidth(text, max) : text;
|
|
217
251
|
}
|
|
218
252
|
|
|
219
253
|
/** Trim text from the end (by display width) so it fits maxWidth, appending '…'. */
|
|
220
254
|
function truncateToWidth(text, maxWidth) {
|
|
221
|
-
const str =
|
|
255
|
+
const str = safeInlineText(text);
|
|
222
256
|
if (maxWidth < 1) return '';
|
|
223
|
-
if (
|
|
257
|
+
if (displayWidth(str) <= maxWidth) return str;
|
|
224
258
|
const chars = Array.from(str);
|
|
225
259
|
let out = '';
|
|
226
260
|
for (const ch of chars) {
|
|
227
|
-
if (
|
|
261
|
+
if (displayWidth(out + ch + '…') > maxWidth) break;
|
|
228
262
|
out += ch;
|
|
229
263
|
}
|
|
230
264
|
return `${out}…`;
|
|
@@ -272,11 +306,6 @@ function agentModelLabel(args) {
|
|
|
272
306
|
return displayModelName(model, provider, displayHint);
|
|
273
307
|
}
|
|
274
308
|
|
|
275
|
-
function withModel(label, args) {
|
|
276
|
-
const model = agentModelLabel(args);
|
|
277
|
-
return model ? `${label} (${model})` : label;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
309
|
function agentTagLabel(args) {
|
|
281
310
|
// The real spawn tag (engine fills parsedArgs.tag from the envelope target).
|
|
282
311
|
// Never fall back to task_id — only the human-meaningful spawn tag belongs in
|
|
@@ -291,43 +320,46 @@ function withModelAndTag(label, args) {
|
|
|
291
320
|
return inner ? `${label} (${inner})` : label;
|
|
292
321
|
}
|
|
293
322
|
|
|
294
|
-
// Append
|
|
295
|
-
// when the
|
|
296
|
-
function
|
|
297
|
-
return
|
|
323
|
+
// Append an agent name to a base action word without leaving a trailing space
|
|
324
|
+
// when the agent is unknown (no generic "Agent" fallback).
|
|
325
|
+
function joinActionAgent(action, agent) {
|
|
326
|
+
return agent ? `${action} ${agent}` : action;
|
|
298
327
|
}
|
|
299
328
|
|
|
300
329
|
function agentResponseTitle(args) {
|
|
301
|
-
const name = titleizeAgentName(args?.agent || args?.
|
|
302
|
-
// The agent
|
|
330
|
+
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
331
|
+
// The agent + model identify the responder; the response summary itself
|
|
303
332
|
// is hidden in the collapsed card (ctrl+o expand still shows the full body).
|
|
304
|
-
// No generic "Agent" fallback — render just "Response" when the
|
|
305
|
-
return withModelAndTag(
|
|
333
|
+
// No generic "Agent" fallback — render just "Response" when the agent is empty.
|
|
334
|
+
return withModelAndTag(joinActionAgent('Response', name), args);
|
|
306
335
|
}
|
|
307
336
|
|
|
308
337
|
function agentActionTitle(args) {
|
|
309
|
-
const name = titleizeAgentName(args?.agent || args?.
|
|
310
|
-
|
|
338
|
+
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
339
|
+
// Runtime treats an omitted type/action as "spawn" (see agent-tool.mjs default),
|
|
340
|
+
// so mirror that contract here instead of falling through to the generic
|
|
341
|
+
// "Called agent" status copy.
|
|
342
|
+
const action = String(args?.type || args?.action || 'spawn').toLowerCase();
|
|
311
343
|
// Fixed action verbs regardless of running/completed status. No generic
|
|
312
|
-
// "Agent" fallback for the
|
|
344
|
+
// "Agent" fallback for the agent: when the agent is unknown render the action
|
|
313
345
|
// word alone ("Spawn") instead of "Spawn Agent".
|
|
314
|
-
if (action === 'spawn') return withModelAndTag(
|
|
315
|
-
if (action === 'send') return withModelAndTag(
|
|
346
|
+
if (action === 'spawn') return withModelAndTag(joinActionAgent('Spawn', name), args);
|
|
347
|
+
if (action === 'send') return withModelAndTag(joinActionAgent('Send', name), args);
|
|
316
348
|
if (action === 'list') return 'Agent status';
|
|
317
|
-
if (action === 'cancel') return withModelAndTag(
|
|
318
|
-
if (action === 'close') return withModelAndTag(
|
|
319
|
-
if (action === 'cleanup') return withModelAndTag(
|
|
320
|
-
if (action === 'read' || action === 'status') return withModelAndTag(
|
|
349
|
+
if (action === 'cancel') return withModelAndTag(joinActionAgent('Cancel', name), args);
|
|
350
|
+
if (action === 'close') return withModelAndTag(joinActionAgent('Close', name), args);
|
|
351
|
+
if (action === 'cleanup') return withModelAndTag(joinActionAgent('Cleanup', name), args);
|
|
352
|
+
if (action === 'read' || action === 'status') return withModelAndTag(joinActionAgent('Status', name), args);
|
|
321
353
|
return '';
|
|
322
354
|
}
|
|
323
355
|
|
|
324
356
|
function agentActionSummary(args, summary) {
|
|
325
357
|
const text = String(summary || '').trim();
|
|
326
358
|
if (!text) return '';
|
|
327
|
-
const name = titleizeAgentName(args?.agent || args?.
|
|
359
|
+
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
328
360
|
if (name && text === name) return '';
|
|
329
361
|
let rest = name && text.startsWith(`${name} · `) ? text.slice(name.length + 3).trim() : text;
|
|
330
|
-
// The
|
|
362
|
+
// The agent/model/tag surface summary ("Heavy Worker · Opus 4.8") is now folded
|
|
331
363
|
// into the header label itself ("Spawn Heavy Worker (Opus 4.8, tag)"), so drop
|
|
332
364
|
// the model and tag tokens from the parenthesized summary to avoid showing
|
|
333
365
|
// them twice.
|
|
@@ -358,7 +390,7 @@ function hasAgentResponseResult(value) {
|
|
|
358
390
|
if (/^agent result\b/i.test(trimmed)) continue;
|
|
359
391
|
if (/^(?:undefined|null)$/i.test(trimmed)) continue;
|
|
360
392
|
if (/^<\/?(?:final-answer|task-notification|task-id|tool-use-id|output-file|result|status|summary|usage|total_tokens|tool_uses|duration_ms|worktree|worktreePath|worktreeBranch)[^>]*>$/i.test(trimmed)) continue;
|
|
361
|
-
if (!sawBlank && /^(?:agent task|background task|agent message queued\b|agent close:|task_id|surface|operation|label|status|type|target|
|
|
393
|
+
if (!sawBlank && /^(?:agent task|background task|agent message queued\b|agent close:|task_id|surface|operation|label|status|type|target|agent|preset|model|effort|fast|limits|started|finished|error|notification|queueDepth):?\s*/i.test(trimmed)) continue;
|
|
362
394
|
if (!sawBlank && /^(?:agents|tasks):\s*/i.test(trimmed)) continue;
|
|
363
395
|
if (/^\(no agents or tasks\)$/i.test(trimmed)) continue;
|
|
364
396
|
if (!sawBlank && /^-\s+\S+/i.test(trimmed)) continue;
|
|
@@ -440,7 +472,10 @@ function prefixElapsed(detail, elapsed = '') {
|
|
|
440
472
|
const text = String(detail || '').trim();
|
|
441
473
|
const time = String(elapsed || '').trim();
|
|
442
474
|
if (!time) return text;
|
|
443
|
-
|
|
475
|
+
// Unified convention: the elapsed time ALWAYS goes at the END, ` · ` separated.
|
|
476
|
+
// Guard against a double-append when the text already ends with the same time.
|
|
477
|
+
if (text && text.endsWith(`· ${time}`)) return text;
|
|
478
|
+
return text ? `${text} · ${time}` : time;
|
|
444
479
|
}
|
|
445
480
|
|
|
446
481
|
function mergeTerminalDetail(status, detail = '') {
|
|
@@ -518,10 +553,6 @@ function isOutputDetailTool(normalizedName, label) {
|
|
|
518
553
|
]).has(n) || l === 'read' || l === 'search' || l === 'web search' || l === 'run';
|
|
519
554
|
}
|
|
520
555
|
|
|
521
|
-
function progressDetail({ normalizedName, label, elapsed }) {
|
|
522
|
-
return elapsed ? `${elapsed} elapsed` : '';
|
|
523
|
-
}
|
|
524
|
-
|
|
525
556
|
function genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) {
|
|
526
557
|
const n = String(normalizedName || '').toLowerCase();
|
|
527
558
|
const l = String(label || '').toLowerCase();
|
|
@@ -540,7 +571,10 @@ function toolSearchLoadedSummary(resultText) {
|
|
|
540
571
|
try {
|
|
541
572
|
parsed = JSON.parse(String(resultText || ''));
|
|
542
573
|
} catch {
|
|
543
|
-
|
|
574
|
+
const text = String(resultText || '');
|
|
575
|
+
const match = /^Loaded deferred tools:\s*(.+)$/m.exec(text);
|
|
576
|
+
if (!match) return '';
|
|
577
|
+
return [...new Set(match[1].split(',').map((name) => name.trim()).filter(Boolean))].join(', ');
|
|
544
578
|
}
|
|
545
579
|
const tools = parsed?.selected?.tools;
|
|
546
580
|
if (!tools || typeof tools !== 'object') return '';
|
|
@@ -566,7 +600,8 @@ function agentTerminalDetail(status, isError, elapsed, error = '') {
|
|
|
566
600
|
: /done|success|complete|closed/.test(s)
|
|
567
601
|
? 'Finished'
|
|
568
602
|
: '';
|
|
569
|
-
|
|
603
|
+
// Unified ` · <time>` convention (previously "Finished after 12s").
|
|
604
|
+
return word ? `${word}${elapsed ? ` · ${elapsed}` : ''}` : '';
|
|
570
605
|
}
|
|
571
606
|
|
|
572
607
|
function clampFailureCount(errorCount, groupCount, isError) {
|
|
@@ -575,17 +610,30 @@ function clampFailureCount(errorCount, groupCount, isError) {
|
|
|
575
610
|
return isError ? groupCount : 0;
|
|
576
611
|
}
|
|
577
612
|
|
|
613
|
+
// Single source of truth for the tool-card dot (●) color. Both the aggregate
|
|
614
|
+
// and normal (single-tool) render paths must call this with a resolved
|
|
615
|
+
// `terminalStatus` — do not recompute color inline elsewhere.
|
|
616
|
+
// running/pending -> theme.text (white; blink handled by caller)
|
|
617
|
+
// success -> theme.success
|
|
618
|
+
// partial failure -> mixdogOrange || warning (some, not all, of the group failed)
|
|
619
|
+
// all failed -> theme.error
|
|
620
|
+
// cancelled -> theme.warning
|
|
621
|
+
// Note: callers resolve terminalStatus to 'failed' whenever failedCount > 0
|
|
622
|
+
// (they cannot tell partial from total failure), so a 'failed' status must
|
|
623
|
+
// still fall through to the failedCount check below rather than short-circuit
|
|
624
|
+
// to error — otherwise a mixed-success batch (e.g. 1 of 3 failed) renders as
|
|
625
|
+
// full-failure red instead of partial orange.
|
|
578
626
|
function toolStatusColor({ pending, groupCount, failedCount, terminalStatus = '' }) {
|
|
579
|
-
if (pending) return theme.
|
|
627
|
+
if (pending) return theme.text;
|
|
580
628
|
const status = normalizeTerminalStatus(terminalStatus);
|
|
581
|
-
if (status === '
|
|
582
|
-
if (status === '
|
|
583
|
-
if (failedCount <= 0) return theme.success;
|
|
629
|
+
if (status === 'cancelled') return theme.warning;
|
|
630
|
+
if (failedCount <= 0) return status === 'failed' ? theme.error : theme.success;
|
|
584
631
|
if (groupCount > 1 && failedCount < groupCount) return theme.mixdogOrange || theme.warning;
|
|
585
632
|
return theme.error;
|
|
586
633
|
}
|
|
587
634
|
|
|
588
|
-
export function ToolExecution({ name, args, result, rawResult, isError, errorCount, expanded,
|
|
635
|
+
export function ToolExecution({ name, args, result, rawResult, isError, errorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, headerFinalized = true, deferredDisplayReady = false }) {
|
|
636
|
+
const rowWidth = Math.max(1, Number(columns || 80));
|
|
589
637
|
const [blinkOn, setBlinkOn] = useState(true);
|
|
590
638
|
const [blinkExpired, setBlinkExpired] = useState(false);
|
|
591
639
|
const [pendingDelayElapsed, setPendingDelayElapsed] = useState(false);
|
|
@@ -619,7 +667,6 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
619
667
|
const elapsedMs = startedAtMs ? Math.max(0, (pending ? Date.now() : (completedAtMs || Date.now())) - startedAtMs) : 0;
|
|
620
668
|
const elapsed = elapsedMs >= 1000 ? formatElapsed(elapsedMs) : '';
|
|
621
669
|
const failedCount = clampFailureCount(errorCount, groupCount, isError);
|
|
622
|
-
const statusColor = toolStatusColor({ pending, groupCount, failedCount });
|
|
623
670
|
const displayGroupCount = groupCount;
|
|
624
671
|
const displayCategories = normalizeCountMap(categories || {});
|
|
625
672
|
|
|
@@ -692,7 +739,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
692
739
|
// header + one brief detail row (see estimateTranscriptItemRows).
|
|
693
740
|
const placeholderSingleRow = !aggregate && SKILL_SURFACE_NAMES.has(placeholderNormalizedName);
|
|
694
741
|
return (
|
|
695
|
-
<Box flexDirection="column" marginTop={attached ? 0 : 1}>
|
|
742
|
+
<Box flexDirection="column" marginTop={attached ? 0 : 1} width={rowWidth} overflow="hidden">
|
|
696
743
|
<Text> </Text>
|
|
697
744
|
{placeholderSingleRow ? null : <Text> </Text>}
|
|
698
745
|
</Box>
|
|
@@ -708,7 +755,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
708
755
|
// No stableVerbWidth: see statusCopy — the padding only left a mid-header
|
|
709
756
|
// gap ("Searched 1 pattern, Read 1 file") since Ink trims trailing
|
|
710
757
|
// spaces and never stabilized the flip.
|
|
711
|
-
const headerText = formatAggregateHeader(displayCategories || {}, { pending: headerPending, order: headerOrder });
|
|
758
|
+
const headerText = safeInlineText(formatAggregateHeader(displayCategories || {}, { pending: headerPending, order: headerOrder }));
|
|
712
759
|
let detailText;
|
|
713
760
|
if (hasResult) {
|
|
714
761
|
// The aggregate card reserves EXACTLY ONE detail row when it is not
|
|
@@ -719,33 +766,40 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
719
766
|
// "settle" taller than reserved. Collapse to a single logical line
|
|
720
767
|
// (whitespace-normalized); fitResultLine below trims it to the column
|
|
721
768
|
// width so it can never exceed one terminal row.
|
|
722
|
-
detailText =
|
|
769
|
+
detailText = safeInlineText(rt);
|
|
723
770
|
} else {
|
|
724
771
|
detailText = '';
|
|
725
772
|
}
|
|
726
773
|
|
|
727
|
-
|
|
774
|
+
// Resolve the aggregate's terminalStatus from the collapsed detail `rt`
|
|
775
|
+
// (which carries a `[status: cancelled]`/`<status>` marker when the
|
|
776
|
+
// aggregate was cancelled) plus isError/failedCount for failures. Pending
|
|
777
|
+
// stays running; a clean completion stays success. toolStatusColor is the
|
|
778
|
+
// single source of dot color for both aggregate and normal cards.
|
|
779
|
+
const aggregateTerminalStatus = pending
|
|
780
|
+
? 'running'
|
|
781
|
+
: (resultTerminalStatus(rt) || (isError || failedCount > 0 ? 'failed' : 'completed'));
|
|
782
|
+
const dotColor = toolStatusColor({ pending, groupCount, failedCount, terminalStatus: aggregateTerminalStatus });
|
|
728
783
|
const dotText = pending && !blinkExpired && !blinkOn ? ' ' : TURN_MARKER;
|
|
729
784
|
const gutter = 2;
|
|
730
785
|
const showHeaderExpandHint = hasRawResult;
|
|
731
786
|
const hintLabel = `ctrl+o ${expanded ? 'collapse' : 'expand'}`;
|
|
732
787
|
const hintText = ` ${BULLET_OPERATOR} ${hintLabel}`;
|
|
733
|
-
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
const rightReserve = Math.max(stringWidth(hintText), stringWidth(headerMetaText));
|
|
788
|
+
// The header right-side trailing slot only ever shows the ctrl+o hint. The
|
|
789
|
+
// pending elapsed meta was removed from the header — it lives on the detail
|
|
790
|
+
// row now (`Running · 12s`) so a per-second digit change never reflows the
|
|
791
|
+
// header. Still reserve the hint slot for the whole lifecycle so the body
|
|
792
|
+
// clip point stays fixed when the hint appears on completion.
|
|
793
|
+
const rightReserve = stringWidth(hintText);
|
|
740
794
|
const avail = Math.max(1, (Number(columns) || 80) - 1 - gutter - rightReserve);
|
|
741
|
-
const trailingText =
|
|
795
|
+
const trailingText = showHeaderExpandHint ? hintText : '';
|
|
742
796
|
const trailingColor = theme.subtle;
|
|
743
797
|
const clippedHeader = stringWidth(headerText) > avail
|
|
744
798
|
? truncateToWidth(headerText, avail)
|
|
745
799
|
: headerText;
|
|
746
|
-
// Trailing content (
|
|
747
|
-
// sits immediately after the header body — no fixed right-edge pin — so
|
|
748
|
-
// never jumps to the right edge and snaps back on the pending→done flip.
|
|
800
|
+
// Trailing content (ctrl+o hint only; pending elapsed lives on the detail
|
|
801
|
+
// row) sits immediately after the header body — no fixed right-edge pin — so
|
|
802
|
+
// it never jumps to the right edge and snaps back on the pending→done flip.
|
|
749
803
|
// Keep the aggregate card at a fixed height (header + one detail row) for
|
|
750
804
|
// its whole lifecycle. Pending cards have no result yet, so reserve the
|
|
751
805
|
// detail row up front instead of growing from 1→2 rows when the summary
|
|
@@ -759,13 +813,22 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
759
813
|
// color; the status placeholder is rendered dim.
|
|
760
814
|
const isPlaceholderDetail = !(expanded && hasRawResult) && !detailText;
|
|
761
815
|
const showRawAggregate = expanded && hasRawResult;
|
|
816
|
+
// Aggregate cards intentionally omit elapsed time once grouped. A brief
|
|
817
|
+
// `Running · 1s` tick during the grouped→finished handoff reads as visual
|
|
818
|
+
// noise, and the grouped header already communicates that work is active.
|
|
819
|
+
// The placeholder tracks `pending` (real completion), NOT headerPending:
|
|
820
|
+
// the header verb stays active until the block seals, but the detail row
|
|
821
|
+
// must not keep saying "Running" after every call already resolved.
|
|
822
|
+
const pendingPlaceholder = pending
|
|
823
|
+
? 'Running'
|
|
824
|
+
: 'Finished';
|
|
762
825
|
const detailLines = showRawAggregate
|
|
763
826
|
? rawRt.split('\n')
|
|
764
|
-
: (detailText ? [detailText] : [
|
|
827
|
+
: (detailText ? [detailText] : [pendingPlaceholder]);
|
|
765
828
|
const aggregateDetailColor = isPlaceholderDetail ? theme.subtle : theme.text;
|
|
766
829
|
return (
|
|
767
|
-
<Box flexDirection="column" marginTop={attached ? 0 : 1}>
|
|
768
|
-
<Box flexDirection="row">
|
|
830
|
+
<Box flexDirection="column" marginTop={attached ? 0 : 1} width={rowWidth} overflow="hidden">
|
|
831
|
+
<Box flexDirection="row" width={rowWidth} overflow="hidden">
|
|
769
832
|
<Box flexShrink={0} minWidth={2}>
|
|
770
833
|
<Text color={dotColor}>{dotText}</Text>
|
|
771
834
|
</Box>
|
|
@@ -797,30 +860,35 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
797
860
|
const backgroundResultText = backgroundMeta?.hasResponse ? backgroundMeta.body : '';
|
|
798
861
|
const displayedResultText = backgroundResultText || (errorOnlyResult ? '' : (rt || ''));
|
|
799
862
|
const hasDisplayResult = Boolean(String(displayedResultText || '').trim());
|
|
800
|
-
const
|
|
863
|
+
const displayedResultBodyText = stripLeadingStatusMarkerFromText(displayedResultText);
|
|
864
|
+
const hasDisplayBody = Boolean(String(displayedResultBodyText || '').trim());
|
|
865
|
+
const lines = displayedResultBodyText ? displayedResultBodyText.split('\n') : [];
|
|
801
866
|
const totalLines = lines.length;
|
|
802
867
|
// Semantic one-line summary derived purely from name/args/result text.
|
|
803
868
|
// Shown in the collapsed, non-error view in place of the raw result block.
|
|
804
869
|
// Grouped cards ("Searched N files" / "Read N files") get the same treatment
|
|
805
870
|
// as single calls: a one-line semantic summary stands in for the raw block.
|
|
806
|
-
const resultSummary = !pending &&
|
|
807
|
-
? surfaceSummarizeToolResult(name, args,
|
|
871
|
+
const resultSummary = !pending && hasDisplayBody
|
|
872
|
+
? surfaceSummarizeToolResult(name, args, displayedResultBodyText, isError)
|
|
808
873
|
: null;
|
|
809
874
|
// Same fit budget fitResultLine() uses, to detect a line that will be clipped.
|
|
810
|
-
const maxResultChars = Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7);
|
|
875
|
+
const maxResultChars = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
|
|
811
876
|
const resultColor = theme.text;
|
|
812
877
|
const firstResultLine = hasDisplayResult ? String(lines[0] ?? '') : '';
|
|
813
|
-
const firstResultLineClipped =
|
|
814
|
-
const hasHiddenDetail = !pending &&
|
|
878
|
+
const firstResultLineClipped = hasDisplayBody && stringWidth(firstResultLine) > maxResultChars;
|
|
879
|
+
const hasHiddenDetail = !pending && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(resultSummary));
|
|
815
880
|
const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, isError, result: displayedResultText }) : '';
|
|
816
881
|
const shellElapsed = isShellSurface ? (shellResultElapsed(displayedResultText) || elapsed) : '';
|
|
817
|
-
const shellStatusDetail = isShellSurface ? shellDetail(shellStatus, shellElapsed) : '';
|
|
818
882
|
const backgroundElapsed = backgroundMeta
|
|
819
883
|
? backgroundTaskElapsed(backgroundMeta, elapsed)
|
|
820
884
|
: (isBackgroundTaskTool(normalizedName) ? backgroundTaskElapsed(parsedArgs, elapsed) : '');
|
|
821
885
|
|
|
822
886
|
const toolArgPath = parsedArgs?.path ?? parsedArgs?.file_path ?? parsedArgs?.file ?? '';
|
|
823
|
-
|
|
887
|
+
// Audit HIGH: on a FAILED view_image the path detail used to win over the
|
|
888
|
+
// error cause (nonShellDetail order puts imageDetail before genericDetail),
|
|
889
|
+
// so the card showed the filename instead of why it failed. Suppress the
|
|
890
|
+
// path detail on error; the error-cause summary/first line takes the row.
|
|
891
|
+
const imageDetail = normalizedName === 'view_image' && toolArgPath && !isError ? String(toolArgPath) : '';
|
|
824
892
|
const isBackgroundResult = !pending && isBackgroundTaskTool(normalizedName) && Boolean(backgroundMeta);
|
|
825
893
|
const isBackgroundResponse = isBackgroundResult && (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgs(normalizedName, parsedArgs));
|
|
826
894
|
const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
|
|
@@ -868,20 +936,22 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
868
936
|
// the final summary just fills in place. Skill surfaces collapse to a single
|
|
869
937
|
// row in BOTH the estimate and the render (visibleDetailLines drops the row
|
|
870
938
|
// for isSkillSurface below), so they get no placeholder.
|
|
871
|
-
const pendingDetailPlaceholder = pending && !isSkillSurface
|
|
939
|
+
const pendingDetailPlaceholder = pending && !isSkillSurface
|
|
940
|
+
? (elapsed ? `Running · ${elapsed}` : 'Running')
|
|
941
|
+
: '';
|
|
872
942
|
const shellCollapsedSummary = isShellSurface && !pending && hasDisplayResult
|
|
873
943
|
? (resultSummary || truncateToWidth(firstResultLine, Math.min(120, maxResultChars)))
|
|
874
944
|
: resultSummary;
|
|
875
945
|
const collapsedDetail = pending
|
|
876
946
|
? pendingDetailPlaceholder
|
|
877
947
|
: isShellSurface
|
|
878
|
-
? mergeTerminalDetail(shellStatus, shellCollapsedSummary)
|
|
948
|
+
? prefixElapsed(mergeTerminalDetail(shellStatus, shellCollapsedSummary), shellElapsed)
|
|
879
949
|
: mergeTerminalDetail(terminalStatus, nonShellDetail);
|
|
880
950
|
const backgroundMetadataExpandable = isBackgroundMetadataResult && hasRawResult && !pending;
|
|
881
|
-
const showRawResult = expanded && (
|
|
951
|
+
const showRawResult = expanded && (hasDisplayBody || hasRawResult)
|
|
882
952
|
&& (!isBackgroundMetadataResult || hasRawResult);
|
|
883
953
|
const detailLines = showRawResult
|
|
884
|
-
? (
|
|
954
|
+
? (hasDisplayBody ? lines : (rawRt ? stripLeadingStatusMarkerLines(rawRt.split('\n')) : []))
|
|
885
955
|
: (collapsedDetail ? [collapsedDetail] : []);
|
|
886
956
|
const isPendingPlaceholderDetail = !showRawResult && Boolean(pendingDetailPlaceholder);
|
|
887
957
|
const detailColor = isPendingPlaceholderDetail ? theme.subtle : theme.text;
|
|
@@ -903,7 +973,10 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
903
973
|
// collapsed; ctrl+o expand still surfaces the full body.
|
|
904
974
|
let visibleDetailLines = detailLines;
|
|
905
975
|
if (isSkillSurface && !showRawResult) {
|
|
906
|
-
|
|
976
|
+
// Audit HIGH: a FAILED skill load used to drop its detail row with the
|
|
977
|
+
// success path, hiding the one-line cause entirely. Keep the detail row
|
|
978
|
+
// when the call errored; only the redundant success repeat is dropped.
|
|
979
|
+
visibleDetailLines = isError && collapsedDetail ? [collapsedDetail] : [];
|
|
907
980
|
} else if (isBackgroundMetadataResult && backgroundMetadataHeaderFailure && !showRawResult) {
|
|
908
981
|
visibleDetailLines = [];
|
|
909
982
|
} else if (isAgentSurfaceCard && !showRawResult) {
|
|
@@ -922,14 +995,25 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
922
995
|
else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
|
|
923
996
|
else if (isShellSurface) labelText = shellHeader(shellStatus, displayGroupCount);
|
|
924
997
|
else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : '') || statusCopy(name, label, displayGroupCount, doneCount, headerPending, isError, parsedArgs);
|
|
998
|
+
labelText = safeInlineText(labelText);
|
|
925
999
|
// Show the parenthesized arg summary for grouped cards too, matching single
|
|
926
1000
|
// calls so the header carries the same context.
|
|
927
1001
|
const toolSearchSummary = !pending && normalizedName === 'tool_search' && hasResult
|
|
928
1002
|
? toolSearchLoadedSummary(displayedResultText)
|
|
929
1003
|
: '';
|
|
930
|
-
const
|
|
1004
|
+
const rawSummaryText = safeInlineText(isAgentResponse || isBackgroundResponse
|
|
931
1005
|
? ''
|
|
932
|
-
: toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary);
|
|
1006
|
+
: toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary));
|
|
1007
|
+
// Drop the parenthesized arg summary when it is a bare "<n> <unit>" count
|
|
1008
|
+
// that the header verb already spells out (e.g. header "Searching 6 patterns"
|
|
1009
|
+
// + summary "6 patterns"). Multi-arg array calls hit this; single calls keep
|
|
1010
|
+
// their descriptive summary ("pattern: \"foo\"") since it never matches the
|
|
1011
|
+
// header tail. Channel surfaces are unaffected — they build the summary from
|
|
1012
|
+
// summarizeToolArgs directly and never render this header verb.
|
|
1013
|
+
const summaryIsHeaderCount = rawSummaryText
|
|
1014
|
+
&& /^\d+\s+\S+$/.test(rawSummaryText)
|
|
1015
|
+
&& labelText.endsWith(rawSummaryText);
|
|
1016
|
+
const summaryText = summaryIsHeaderCount ? '' : rawSummaryText;
|
|
933
1017
|
// Agent cards hide their collapsed body but still expose ctrl+o expand only
|
|
934
1018
|
// when expanding would actually reveal something: an agent response body, or a
|
|
935
1019
|
// multiline / clipped raw result (e.g. the "agents: N …" worker list). A
|
|
@@ -941,6 +1025,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
941
1025
|
// hasHiddenDetail, which goes true for any single-line resultSummary and would
|
|
942
1026
|
// wrongly show ctrl+o on a status-only one-liner that has nothing to expand.
|
|
943
1027
|
const shellHasExpandableBody = isShellSurface && !pending && hasDisplayResult
|
|
1028
|
+
&& hasDisplayBody
|
|
944
1029
|
&& (totalLines > 1 || firstResultLineClipped || Boolean(shellCollapsedSummary && shellCollapsedSummary !== firstResultLine));
|
|
945
1030
|
const showHeaderExpandHint = (isShellSurface ? shellHasExpandableBody : (isAgentSurfaceCard ? agentHasExpandableBody : (hasHiddenDetail || backgroundMetadataExpandable)))
|
|
946
1031
|
&& normalizedName !== 'tool_search';
|
|
@@ -954,25 +1039,22 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
954
1039
|
const gutter = 2;
|
|
955
1040
|
const hintLabel = showHeaderExpandHint ? `ctrl+o ${expanded ? 'collapse' : 'expand'}` : '';
|
|
956
1041
|
const hintText = hintLabel ? ` ${BULLET_OPERATOR} ${hintLabel}` : '';
|
|
957
|
-
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
//
|
|
962
|
-
//
|
|
963
|
-
// the two for the whole lifecycle (matching the aggregate card's rightReserve)
|
|
964
|
-
// so `avail` stays fixed and nothing reflows. The hint slot is reserved even
|
|
965
|
-
// while pending so its later appearance does not push the body either.
|
|
1042
|
+
// The header right-side trailing slot only ever shows the ctrl+o hint. The
|
|
1043
|
+
// pending elapsed meta was removed from the header — it lives on the detail
|
|
1044
|
+
// row now (`Running · 12s`) so a per-second digit change (9s→10s) or the
|
|
1045
|
+
// pending→done swap never reflows the header. The hint slot is reserved for
|
|
1046
|
+
// the whole lifecycle (even while pending) so its later appearance on
|
|
1047
|
+
// completion does not push the body clip point.
|
|
966
1048
|
const hintReserveLabel = `ctrl+o ${expanded ? 'collapse' : 'expand'}`;
|
|
967
1049
|
const hintReserveText = ` ${BULLET_OPERATOR} ${hintReserveLabel}`;
|
|
968
1050
|
const headerFailureText = headerFailureStatus
|
|
969
1051
|
? truncateToWidth(headerFailureStatus, HEADER_FAILURE_STATUS_MAX)
|
|
970
1052
|
: '';
|
|
971
1053
|
const inlineFailureText = headerFailureText ? ` ${BULLET_OPERATOR} ${headerFailureText}` : '';
|
|
972
|
-
const rightReserve =
|
|
1054
|
+
const rightReserve = stringWidth(hintReserveText) + stringWidth(inlineFailureText);
|
|
973
1055
|
const avail = Math.max(1, (Number(columns) || 80) - 1 - gutter - rightReserve);
|
|
974
|
-
const trailingText =
|
|
975
|
-
const trailingColor =
|
|
1056
|
+
const trailingText = showHeaderExpandHint ? hintText : '';
|
|
1057
|
+
const trailingColor = expandHintColor;
|
|
976
1058
|
let labelOut;
|
|
977
1059
|
let summaryOut;
|
|
978
1060
|
if (stringWidth(labelText) >= avail) {
|
|
@@ -989,13 +1071,13 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
989
1071
|
: '';
|
|
990
1072
|
summaryOut = truncatedSummary ? ` (${truncatedSummary})` : '';
|
|
991
1073
|
}
|
|
992
|
-
// Keep trailing content (pending elapsed
|
|
993
|
-
// directly after the body for the whole lifecycle. The
|
|
994
|
-
// previously used for elapsed is what made the trailing text
|
|
995
|
-
// edge and snap back on the pending→done flip, so there is no
|
|
996
|
-
// stays reserved (rightReserve) so the body clip point never reflows.
|
|
1074
|
+
// Keep trailing content (ctrl+o hint only; pending elapsed lives on the detail
|
|
1075
|
+
// row) attached directly after the body for the whole lifecycle. The
|
|
1076
|
+
// fixed-column pin previously used for elapsed is what made the trailing text
|
|
1077
|
+
// jump to the right edge and snap back on the pending→done flip, so there is no
|
|
1078
|
+
// pad. `avail` stays reserved (rightReserve) so the body clip point never reflows.
|
|
997
1079
|
return (
|
|
998
|
-
<Box flexDirection="column" marginTop={attached ? 0 : 1}>
|
|
1080
|
+
<Box flexDirection="column" marginTop={attached ? 0 : 1} width={rowWidth} overflow="hidden">
|
|
999
1081
|
<Box flexDirection="row" width="100%">
|
|
1000
1082
|
<Box flexShrink={1} flexGrow={1} overflow="hidden" minWidth={0}>
|
|
1001
1083
|
<Box flexDirection="row">
|
|
@@ -1014,7 +1096,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
1014
1096
|
|
|
1015
1097
|
<ResultBody
|
|
1016
1098
|
lines={visibleDetailLines}
|
|
1017
|
-
rawText={
|
|
1099
|
+
rawText={hasDisplayBody ? displayedResultBodyText : stripLeadingStatusMarkerFromText(rawRt || '')}
|
|
1018
1100
|
pathArg={toolArgPath}
|
|
1019
1101
|
isShell={isShellSurface}
|
|
1020
1102
|
columns={columns}
|
|
@@ -26,7 +26,7 @@ function cleanRightMessage(value) {
|
|
|
26
26
|
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export function TurnDone({ elapsedMs = 0, status = 'done', verb = 'Thought', rightMessage = '', rightTone = 'info', rightMessageWidth = 24 }) {
|
|
29
|
+
export function TurnDone({ elapsedMs = 0, status = 'done', verb = 'Thought', rightMessage = '', rightTone = 'info', rightMessageWidth = 24, marginTop = 1 }) {
|
|
30
30
|
const elapsed = formatDuration(elapsedMs);
|
|
31
31
|
const cancelled = status === 'cancelled';
|
|
32
32
|
const doneVerb = String(verb || 'Thought').trim() || 'Thought';
|
|
@@ -37,7 +37,7 @@ export function TurnDone({ elapsedMs = 0, status = 'done', verb = 'Thought', rig
|
|
|
37
37
|
const rightWidth = Math.max(1, Number(rightMessageWidth) || 24);
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
|
-
<Box marginTop={
|
|
40
|
+
<Box marginTop={marginTop} flexDirection="row" width="100%">
|
|
41
41
|
<Box flexGrow={1} flexShrink={1} overflow="hidden">
|
|
42
42
|
<Text wrap="truncate">
|
|
43
43
|
<Text color={theme.spinnerGlyph}>{TURN_DONE_MARKER} </Text>
|
|
@@ -53,14 +53,14 @@ export function TurnDone({ elapsedMs = 0, status = 'done', verb = 'Thought', rig
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export function StatusDone({ label = 'Complete', detail = '', rightMessage = '', rightTone = 'info', rightMessageWidth = 24 }) {
|
|
56
|
+
export function StatusDone({ label = 'Complete', detail = '', rightMessage = '', rightTone = 'info', rightMessageWidth = 24, marginTop = 1 }) {
|
|
57
57
|
const copy = String(label || 'Complete').trim() || 'Complete';
|
|
58
58
|
const suffix = String(detail || '').trim();
|
|
59
59
|
const rightText = cleanRightMessage(rightMessage);
|
|
60
60
|
const rightWidth = Math.max(1, Number(rightMessageWidth) || 24);
|
|
61
61
|
|
|
62
62
|
return (
|
|
63
|
-
<Box marginTop={
|
|
63
|
+
<Box marginTop={marginTop} flexDirection="row" width="100%">
|
|
64
64
|
<Box flexGrow={1} flexShrink={1} overflow="hidden">
|
|
65
65
|
<Text wrap="truncate">
|
|
66
66
|
<Text color={theme.spinnerGlyph}>{TURN_DONE_MARKER} </Text>
|
|
@@ -12,7 +12,7 @@ const STATUS_SEPARATOR = ' │ ';
|
|
|
12
12
|
|
|
13
13
|
function money(value) {
|
|
14
14
|
const n = Number(value);
|
|
15
|
-
if (!Number.isFinite(n)) return '
|
|
15
|
+
if (!Number.isFinite(n)) return 'N/A';
|
|
16
16
|
if (n === 0) return '$0';
|
|
17
17
|
if (n >= 10) return `$${n.toFixed(0)}`;
|
|
18
18
|
if (n >= 1) return `$${n.toFixed(2)}`;
|