mixdog 0.9.3 → 0.9.4
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 +7 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +23 -101
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* components/tool-execution/surface-detail.mjs — agent/background-task/shell
|
|
3
|
+
* surface detection, title/summary/detail derivation, and status-color logic
|
|
4
|
+
* for the tool card. Pure helpers (no React). Extracted verbatim from
|
|
5
|
+
* ToolExecution.jsx — behavior unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import { theme } from '../../theme.mjs';
|
|
8
|
+
import { formatElapsed } from '../../time-format.mjs';
|
|
9
|
+
import { displayModelName } from '../../../runtime/shared/tool-surface.mjs';
|
|
10
|
+
import { backgroundTaskFailureStatusLabel, isBackgroundErrorOnlyBody } from '../../../runtime/shared/err-text.mjs';
|
|
11
|
+
import {
|
|
12
|
+
plural,
|
|
13
|
+
displayTerminalStatus,
|
|
14
|
+
normalizeTerminalStatus,
|
|
15
|
+
shellResultStatus,
|
|
16
|
+
} from './text-format.mjs';
|
|
17
|
+
|
|
18
|
+
export function isShellTool(normalizedName, label = '') {
|
|
19
|
+
const n = String(normalizedName || '').toLowerCase();
|
|
20
|
+
const l = String(label || '').toLowerCase();
|
|
21
|
+
return n === 'shell' || n === 'bash' || n === 'bash_session' || n === 'shell_command' || n === 'job_wait' || l === 'run';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function shellDisplayStatus({ pending = false, failedCount = 0, isError = false, result = '' } = {}) {
|
|
25
|
+
const status = shellResultStatus(result);
|
|
26
|
+
if (pending || /^(running|pending|queued)$/.test(status)) return 'running';
|
|
27
|
+
if (/^cancel/.test(status)) return 'cancelled';
|
|
28
|
+
if (/^(failed|error|killed|timeout)$/.test(status) || isError || failedCount > 0) return 'failed';
|
|
29
|
+
return 'completed';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function shellHeader(status, count = 1) {
|
|
33
|
+
const n = Math.max(1, Number(count) || 1);
|
|
34
|
+
const object = `${n} ${plural(n, 'command')}`;
|
|
35
|
+
if (status === 'running') return `Running ${object}`;
|
|
36
|
+
return `Ran ${object}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isAgentTool(normalizedName) {
|
|
40
|
+
return normalizedName === 'agent';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const SKILL_SURFACE_NAMES = new Set([
|
|
44
|
+
'skill', 'skill_execute', 'skill_view', 'skills_list', 'use_skill',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export function isBackgroundTaskTool(normalizedName) {
|
|
48
|
+
return new Set(['explore', 'search', 'shell', 'bash', 'bash_session', 'shell_command', 'task']).has(String(normalizedName || '').toLowerCase());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const AGENT_DISPLAY_NAMES = new Map([
|
|
52
|
+
['explore', 'Explore'],
|
|
53
|
+
['maintainer', 'Maintainer'],
|
|
54
|
+
['worker', 'Worker'],
|
|
55
|
+
['heavy-worker', 'Heavy Worker'],
|
|
56
|
+
['reviewer', 'Reviewer'],
|
|
57
|
+
['debugger', 'Debugger'],
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
export function titleizeAgentName(value) {
|
|
61
|
+
const text = String(value || '').trim();
|
|
62
|
+
if (!text) return '';
|
|
63
|
+
const key = text.toLowerCase().replace(/[\s_]+/g, '-');
|
|
64
|
+
if (AGENT_DISPLAY_NAMES.has(key)) return AGENT_DISPLAY_NAMES.get(key);
|
|
65
|
+
return text
|
|
66
|
+
.replace(/[_-]+/g, ' ')
|
|
67
|
+
.split(/\s+/)
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1).toLowerCase()}`)
|
|
70
|
+
.join(' ');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function agentModelLabel(args) {
|
|
74
|
+
const a = args && typeof args === 'object' ? args : {};
|
|
75
|
+
const provider = String(a.provider || a.providerId || a.provider_id || '').trim();
|
|
76
|
+
const model = String(a.model || '').trim();
|
|
77
|
+
const displayHint = String(a.modelDisplay || a.model_display || a.displayModel || '').trim();
|
|
78
|
+
return displayModelName(model, provider, displayHint);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function agentTagLabel(args) {
|
|
82
|
+
// The real spawn tag (engine fills parsedArgs.tag from the envelope target).
|
|
83
|
+
// Never fall back to task_id — only the human-meaningful spawn tag belongs in
|
|
84
|
+
// the header parentheses.
|
|
85
|
+
return String(args?.tag || '').trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function withModelAndTag(label, args) {
|
|
89
|
+
const model = agentModelLabel(args);
|
|
90
|
+
const tag = agentTagLabel(args);
|
|
91
|
+
const inner = [model, tag].filter(Boolean).join(', ');
|
|
92
|
+
return inner ? `${label} (${inner})` : label;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Append an agent name to a base action word without leaving a trailing space
|
|
96
|
+
// when the agent is unknown (no generic "Agent" fallback).
|
|
97
|
+
export function joinActionAgent(action, agent) {
|
|
98
|
+
return agent ? `${action} ${agent}` : action;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function agentResponseTitle(args) {
|
|
102
|
+
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
103
|
+
// The agent + model identify the responder; the response summary itself
|
|
104
|
+
// is hidden in the collapsed card (ctrl+o expand still shows the full body).
|
|
105
|
+
// No generic "Agent" fallback — render just "Response" when the agent is empty.
|
|
106
|
+
return withModelAndTag(joinActionAgent('Response', name), args);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function agentActionTitle(args) {
|
|
110
|
+
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
111
|
+
// Runtime treats an omitted type/action as "spawn" (see agent-tool.mjs default),
|
|
112
|
+
// so mirror that contract here instead of falling through to the generic
|
|
113
|
+
// "Called agent" status copy.
|
|
114
|
+
const action = String(args?.type || args?.action || 'spawn').toLowerCase();
|
|
115
|
+
// Fixed action verbs regardless of running/completed status. No generic
|
|
116
|
+
// "Agent" fallback for the agent: when the agent is unknown render the action
|
|
117
|
+
// word alone ("Spawn") instead of "Spawn Agent".
|
|
118
|
+
if (action === 'spawn') return withModelAndTag(joinActionAgent('Spawn', name), args);
|
|
119
|
+
if (action === 'send') return withModelAndTag(joinActionAgent('Send', name), args);
|
|
120
|
+
if (action === 'list') return 'Agent status';
|
|
121
|
+
if (action === 'cancel') return withModelAndTag(joinActionAgent('Cancel', name), args);
|
|
122
|
+
if (action === 'close') return withModelAndTag(joinActionAgent('Close', name), args);
|
|
123
|
+
if (action === 'cleanup') return withModelAndTag(joinActionAgent('Cleanup', name), args);
|
|
124
|
+
if (action === 'read' || action === 'status') return withModelAndTag(joinActionAgent('Status', name), args);
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function agentActionSummary(args, summary) {
|
|
129
|
+
const text = String(summary || '').trim();
|
|
130
|
+
if (!text) return '';
|
|
131
|
+
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
132
|
+
if (name && text === name) return '';
|
|
133
|
+
let rest = name && text.startsWith(`${name} · `) ? text.slice(name.length + 3).trim() : text;
|
|
134
|
+
// The agent/model/tag surface summary ("Heavy Worker · Opus 4.8") is now folded
|
|
135
|
+
// into the header label itself ("Spawn Heavy Worker (Opus 4.8, tag)"), so drop
|
|
136
|
+
// the model and tag tokens from the parenthesized summary to avoid showing
|
|
137
|
+
// them twice.
|
|
138
|
+
const model = agentModelLabel(args);
|
|
139
|
+
if (model && rest === model) return '';
|
|
140
|
+
const tag = agentTagLabel(args);
|
|
141
|
+
if (tag && rest === tag) return '';
|
|
142
|
+
return rest;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function hasAgentResponseResult(value) {
|
|
146
|
+
const text = String(value || '').trim();
|
|
147
|
+
if (!text) return false;
|
|
148
|
+
if (/^(?:undefined|null)$/i.test(text)) return false;
|
|
149
|
+
if (/^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)(?:\s*·\s*task_id:\s*\S+)?$/i.test(text)) return false;
|
|
150
|
+
const isBridgeEnvelope = /^(?:agent task:|background task\b|agent message queued\b|agent close:)/i.test(text)
|
|
151
|
+
|| /^(?:agents|tasks):\s*\d/i.test(text)
|
|
152
|
+
|| /^\(no agents or tasks\)$/i.test(text)
|
|
153
|
+
|| (/^task_id:\s*\S+/mi.test(text) && /^(?:surface|operation|status):\s*/mi.test(text));
|
|
154
|
+
if (!isBridgeEnvelope) return true;
|
|
155
|
+
let sawBlank = false;
|
|
156
|
+
for (const line of text.split('\n')) {
|
|
157
|
+
const trimmed = line.trim();
|
|
158
|
+
if (!trimmed) {
|
|
159
|
+
sawBlank = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (/^agent result\b/i.test(trimmed)) continue;
|
|
163
|
+
if (/^(?:undefined|null)$/i.test(trimmed)) continue;
|
|
164
|
+
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;
|
|
165
|
+
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;
|
|
166
|
+
if (!sawBlank && /^(?:agents|tasks):\s*/i.test(trimmed)) continue;
|
|
167
|
+
if (/^\(no agents or tasks\)$/i.test(trimmed)) continue;
|
|
168
|
+
if (!sawBlank && /^-\s+\S+/i.test(trimmed)) continue;
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function parseBackgroundTaskResult(value) {
|
|
175
|
+
const text = String(value || '').trim();
|
|
176
|
+
if (!text) return null;
|
|
177
|
+
const allLines = text.split('\n');
|
|
178
|
+
const start = allLines.findIndex((line) => line.trim() === 'background task');
|
|
179
|
+
if (start < 0) return null;
|
|
180
|
+
const rest = allLines.slice(start + 1);
|
|
181
|
+
const blank = rest.findIndex((line) => !line.trim());
|
|
182
|
+
const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
|
|
183
|
+
const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
|
|
184
|
+
const fields = {};
|
|
185
|
+
for (const line of headLines) {
|
|
186
|
+
const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
|
|
187
|
+
if (match) fields[match[1].toLowerCase()] = match[2].trim();
|
|
188
|
+
}
|
|
189
|
+
const status = String(fields.status || '').toLowerCase();
|
|
190
|
+
const error = fields.error || '';
|
|
191
|
+
return {
|
|
192
|
+
taskId: fields.task_id || fields.taskid || '',
|
|
193
|
+
surface: fields.surface || '',
|
|
194
|
+
operation: fields.operation || '',
|
|
195
|
+
label: fields.label || '',
|
|
196
|
+
status,
|
|
197
|
+
startedAt: fields.started || fields.startedat || '',
|
|
198
|
+
finishedAt: fields.finished || fields.finishedat || '',
|
|
199
|
+
body,
|
|
200
|
+
error,
|
|
201
|
+
hasResponse: Boolean(body) && !isBackgroundErrorOnlyBody(body, error) && !/^(running|pending|queued)$/i.test(status),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function backgroundTaskMetaFromArgs(args = {}) {
|
|
206
|
+
const taskId = String(args.task_id || args.taskId || '').trim();
|
|
207
|
+
if (!taskId) return null;
|
|
208
|
+
return {
|
|
209
|
+
taskId,
|
|
210
|
+
surface: args.surface || '',
|
|
211
|
+
operation: args.operation || '',
|
|
212
|
+
label: args.label || '',
|
|
213
|
+
status: String(args.status || '').toLowerCase(),
|
|
214
|
+
startedAt: args.startedAt || args.started || '',
|
|
215
|
+
finishedAt: args.finishedAt || args.finished || '',
|
|
216
|
+
error: args.error || '',
|
|
217
|
+
body: '',
|
|
218
|
+
hasResponse: false,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function resolveBackgroundTaskMeta(parsedArgs = {}, resultText = '') {
|
|
223
|
+
const parsed = parseBackgroundTaskResult(resultText);
|
|
224
|
+
if (parsed) {
|
|
225
|
+
if (!parsed.error && parsedArgs?.error) parsed.error = parsedArgs.error;
|
|
226
|
+
if (!parsed.status && parsedArgs?.status) parsed.status = String(parsedArgs.status).toLowerCase();
|
|
227
|
+
if (!parsed.surface && parsedArgs?.surface) parsed.surface = parsedArgs.surface;
|
|
228
|
+
return parsed;
|
|
229
|
+
}
|
|
230
|
+
return backgroundTaskMetaFromArgs(parsedArgs);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function backgroundTaskElapsed(meta = {}, fallback = '') {
|
|
234
|
+
const startedMs = Date.parse(meta.startedAt || '');
|
|
235
|
+
const finishedMs = Date.parse(meta.finishedAt || '');
|
|
236
|
+
if (Number.isFinite(startedMs) && Number.isFinite(finishedMs) && finishedMs >= startedMs) {
|
|
237
|
+
const elapsedMs = finishedMs - startedMs;
|
|
238
|
+
return elapsedMs >= 1000 ? formatElapsed(elapsedMs) : '';
|
|
239
|
+
}
|
|
240
|
+
return fallback || '';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function prefixElapsed(detail, elapsed = '') {
|
|
244
|
+
const text = String(detail || '').trim();
|
|
245
|
+
const time = String(elapsed || '').trim();
|
|
246
|
+
if (!time) return text;
|
|
247
|
+
// Unified convention: the elapsed time ALWAYS goes at the END, ` · ` separated.
|
|
248
|
+
// Guard against a double-append when the text already ends with the same time.
|
|
249
|
+
if (text && text.endsWith(`· ${time}`)) return text;
|
|
250
|
+
return text ? `${text} · ${time}` : time;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function mergeTerminalDetail(status, detail = '') {
|
|
254
|
+
const label = displayTerminalStatus(status);
|
|
255
|
+
const text = String(detail || '').trim();
|
|
256
|
+
if (!label) return text;
|
|
257
|
+
if (label === 'Finished' && text) return text;
|
|
258
|
+
if (!text) return label;
|
|
259
|
+
if (text.toLowerCase().startsWith(label.toLowerCase())) return text;
|
|
260
|
+
return `${label} · ${text}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function shouldPrefixSyncElapsed(normalizedName, label) {
|
|
264
|
+
const n = String(normalizedName || '').toLowerCase();
|
|
265
|
+
const l = String(label || '').toLowerCase();
|
|
266
|
+
return n === 'explore' || l === 'explore' || n === 'search' || l === 'search' || l === 'web search';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function backgroundTaskDisplayName(normalizedName, meta = {}) {
|
|
270
|
+
const surface = String(meta.surface || normalizedName || '').toLowerCase();
|
|
271
|
+
if (surface === 'explore') return 'Explore';
|
|
272
|
+
if (surface === 'search') return 'Search';
|
|
273
|
+
if (surface === 'shell' || surface === 'bash' || surface === 'bash_session' || surface === 'shell_command' || surface === 'task') return 'Shell';
|
|
274
|
+
return titleizeAgentName(surface || normalizedName || 'Task');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function backgroundTaskResultTitle(normalizedName, meta = {}) {
|
|
278
|
+
const display = backgroundTaskDisplayName(normalizedName, meta);
|
|
279
|
+
if (display === 'Shell') return 'Shell output';
|
|
280
|
+
if (display === 'Search') return 'Search results';
|
|
281
|
+
return `${display} response`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function backgroundTaskActionTitle(normalizedName, meta = {}) {
|
|
285
|
+
const display = backgroundTaskDisplayName(normalizedName, meta);
|
|
286
|
+
if (/^(running|pending|queued)$/i.test(meta.status || '')) return `Started ${display}`;
|
|
287
|
+
if (meta.hasResponse) return backgroundTaskResultTitle(normalizedName, meta);
|
|
288
|
+
return `${display} status`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function backgroundTaskFailureDetail(meta = {}, parsedArgs = {}) {
|
|
292
|
+
const status = meta.status || parsedArgs?.status;
|
|
293
|
+
const error = meta.error || parsedArgs?.error;
|
|
294
|
+
if (!error) return '';
|
|
295
|
+
const surface = meta.surface || parsedArgs?.surface || '';
|
|
296
|
+
return backgroundTaskFailureStatusLabel(status, error, { surface });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function backgroundTaskDetail(meta = {}, elapsed = '', parsedArgs = {}) {
|
|
300
|
+
const parts = [];
|
|
301
|
+
const status = displayTerminalStatus(meta.status);
|
|
302
|
+
if (status) parts.push(status);
|
|
303
|
+
if (meta.taskId) parts.push(`task_id: ${meta.taskId}`);
|
|
304
|
+
const firstBodyLine = String(meta.body || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
305
|
+
if (firstBodyLine && /^(running|pending|queued)$/i.test(meta.status || '')) parts.push(firstBodyLine);
|
|
306
|
+
return prefixElapsed(parts.join(' · '), elapsed);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function isBackgroundTaskResponseArgs(normalizedName, args = {}) {
|
|
310
|
+
if (!isBackgroundTaskTool(normalizedName)) return false;
|
|
311
|
+
const type = String(args?.type || args?.action || '').toLowerCase();
|
|
312
|
+
const status = String(args?.status || '').toLowerCase();
|
|
313
|
+
return type === 'result' || type === 'completion' || (/^(completed|cancelled|canceled)$/i.test(status) && Boolean(args?.task_id));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function isOutputDetailTool(normalizedName, label) {
|
|
317
|
+
const n = String(normalizedName || '').toLowerCase();
|
|
318
|
+
const l = String(label || '').toLowerCase();
|
|
319
|
+
return new Set([
|
|
320
|
+
'shell', 'bash', 'bash_session', 'shell_command', 'job_wait',
|
|
321
|
+
'read', 'view_image', 'read_mcp_resource',
|
|
322
|
+
'grep', 'glob', 'search', 'search_query', 'image_query', 'web_search', 'web_search_call', 'explore', 'web_fetch', 'fetch',
|
|
323
|
+
'list', 'ls', 'code_graph',
|
|
324
|
+
'recall', 'recall_memory', 'search_memories', 'remember', 'save_memory', 'update_memory',
|
|
325
|
+
]).has(n) || l === 'read' || l === 'search' || l === 'web search' || l === 'run';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) {
|
|
329
|
+
const n = String(normalizedName || '').toLowerCase();
|
|
330
|
+
const l = String(label || '').toLowerCase();
|
|
331
|
+
if (isError) return hasResult ? firstResultLine : 'Failed';
|
|
332
|
+
if (n === 'shell' || n === 'bash' || n === 'bash_session' || n === 'shell_command' || n === 'job_wait') {
|
|
333
|
+
return '';
|
|
334
|
+
}
|
|
335
|
+
if (isOutputDetailTool(n, l)) {
|
|
336
|
+
return hasResult ? firstResultLine : '';
|
|
337
|
+
}
|
|
338
|
+
return '';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function toolSearchLoadedSummary(resultText) {
|
|
342
|
+
let parsed;
|
|
343
|
+
try {
|
|
344
|
+
parsed = JSON.parse(String(resultText || ''));
|
|
345
|
+
} catch {
|
|
346
|
+
const text = String(resultText || '');
|
|
347
|
+
const match = /^Loaded deferred tools:\s*(.+)$/m.exec(text);
|
|
348
|
+
if (!match) return '';
|
|
349
|
+
return [...new Set(match[1].split(',').map((name) => name.trim()).filter(Boolean))].join(', ');
|
|
350
|
+
}
|
|
351
|
+
const tools = parsed?.selected?.tools;
|
|
352
|
+
if (!tools || typeof tools !== 'object') return '';
|
|
353
|
+
const names = [
|
|
354
|
+
...(Array.isArray(tools.added) ? tools.added : []),
|
|
355
|
+
...(Array.isArray(tools.already) ? tools.already : []),
|
|
356
|
+
]
|
|
357
|
+
.map((name) => String(name || '').trim())
|
|
358
|
+
.filter(Boolean);
|
|
359
|
+
return [...new Set(names)].join(', ');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function agentTerminalDetail(status, isError, elapsed, error = '') {
|
|
363
|
+
const failureDetail = isError && error
|
|
364
|
+
? backgroundTaskFailureStatusLabel(status, error, { surface: 'agent' })
|
|
365
|
+
: '';
|
|
366
|
+
if (failureDetail) return failureDetail;
|
|
367
|
+
const s = String(status || '').toLowerCase();
|
|
368
|
+
const word = /cancel/.test(s)
|
|
369
|
+
? 'Cancelled'
|
|
370
|
+
: /error|fail|killed|timeout/.test(s) || isError
|
|
371
|
+
? 'Failed'
|
|
372
|
+
: /done|success|complete|closed/.test(s)
|
|
373
|
+
? 'Finished'
|
|
374
|
+
: '';
|
|
375
|
+
// Unified ` · <time>` convention (previously "Finished after 12s").
|
|
376
|
+
return word ? `${word}${elapsed ? ` · ${elapsed}` : ''}` : '';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function clampFailureCount(errorCount, groupCount, isError) {
|
|
380
|
+
const explicit = Number(errorCount);
|
|
381
|
+
if (Number.isFinite(explicit)) return Math.max(0, Math.min(groupCount, Math.floor(explicit)));
|
|
382
|
+
return isError ? groupCount : 0;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Single source of truth for the tool-card dot (●) color. Both the aggregate
|
|
386
|
+
// and normal (single-tool) render paths must call this with a resolved
|
|
387
|
+
// `terminalStatus` — do not recompute color inline elsewhere.
|
|
388
|
+
// running/pending -> theme.text (white; blink handled by caller)
|
|
389
|
+
// success -> theme.success
|
|
390
|
+
// partial failure -> mixdogOrange || warning (some, not all, of the group failed)
|
|
391
|
+
// all failed -> theme.error
|
|
392
|
+
// cancelled -> theme.warning
|
|
393
|
+
// Note: callers resolve terminalStatus to 'failed' whenever failedCount > 0
|
|
394
|
+
// (they cannot tell partial from total failure), so a 'failed' status must
|
|
395
|
+
// still fall through to the failedCount check below rather than short-circuit
|
|
396
|
+
// to error — otherwise a mixed-success batch (e.g. 1 of 3 failed) renders as
|
|
397
|
+
// full-failure red instead of partial orange.
|
|
398
|
+
export function toolStatusColor({ pending, groupCount, failedCount, terminalStatus = '' }) {
|
|
399
|
+
if (pending) return theme.text;
|
|
400
|
+
const status = normalizeTerminalStatus(terminalStatus);
|
|
401
|
+
if (status === 'cancelled') return theme.warning;
|
|
402
|
+
if (failedCount <= 0) return status === 'failed' ? theme.error : theme.success;
|
|
403
|
+
if (groupCount > 1 && failedCount < groupCount) return theme.mixdogOrange || theme.warning;
|
|
404
|
+
return theme.error;
|
|
405
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* components/tool-execution/text-format.mjs — pure text/formatting helpers for
|
|
3
|
+
* the tool card (no React). Inline-row sanitization, width fitting/truncation,
|
|
4
|
+
* count normalization, diff-token splitting, and terminal-status parsing.
|
|
5
|
+
* Extracted verbatim from ToolExecution.jsx — behavior unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import { displayWidth } from '../../display-width.mjs';
|
|
8
|
+
import { theme } from '../../theme.mjs';
|
|
9
|
+
import { formatElapsed } from '../../time-format.mjs';
|
|
10
|
+
import stripAnsi from 'strip-ansi';
|
|
11
|
+
|
|
12
|
+
export const MIN_RESULT_LINE_CHARS = 24;
|
|
13
|
+
// Hard cap for the collapsed result detail row (the second line under the ⎿
|
|
14
|
+
// gutter). Independent of terminal width so a wide terminal never lets a long
|
|
15
|
+
// line (e.g. an agent response brief) stretch the whole row — anything past
|
|
16
|
+
// this is truncated with an ellipsis. ctrl+o expand still shows the full body.
|
|
17
|
+
export const RESULT_LINE_HARD_MAX = 80;
|
|
18
|
+
// Hard cap for the parenthesized header arg summary so a long path/query does
|
|
19
|
+
// not eat the whole header line; anything longer is truncated with an ellipsis.
|
|
20
|
+
export const SUMMARY_MAX_CHARS = 48;
|
|
21
|
+
export const HEADER_FAILURE_STATUS_MAX = 32;
|
|
22
|
+
|
|
23
|
+
// Read `theme.subtle` at use-time (not captured here) so a live `/theme`
|
|
24
|
+
// switch re-tones the tool hints. `theme` is mutated in-place on switch.
|
|
25
|
+
// Collapsed tool headers/details are laid out as single terminal rows. Never let
|
|
26
|
+
// raw C0/control bytes (CR, tabs, cursor escapes, etc.) reach those rows: a
|
|
27
|
+
// terminal can apply them after Ink has already clipped/measured the row, which
|
|
28
|
+
// makes a scrolled tool card appear to write through the prompt/statusline.
|
|
29
|
+
const INLINE_CONTROL_RE = /[\u0000-\u001F\u007F]/g;
|
|
30
|
+
|
|
31
|
+
export function safeInlineText(value) {
|
|
32
|
+
return stripAnsi(String(value ?? ''))
|
|
33
|
+
.replace(/\r\n?/g, '\n')
|
|
34
|
+
.replace(/\t/g, ' ')
|
|
35
|
+
.replace(/\n+/g, ' ')
|
|
36
|
+
.replace(INLINE_CONTROL_RE, ' ')
|
|
37
|
+
.replace(/\s+/g, ' ')
|
|
38
|
+
.trim();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function normalizeCount(value) {
|
|
42
|
+
const n = Number(value || 0);
|
|
43
|
+
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeCountMap(value = {}) {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, raw] of Object.entries(value || {})) {
|
|
49
|
+
if (raw && typeof raw === 'object') {
|
|
50
|
+
out[key] = { ...raw, count: normalizeCount(raw.count) };
|
|
51
|
+
} else {
|
|
52
|
+
out[key] = normalizeCount(raw);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function deltaColor(token) {
|
|
59
|
+
return String(token || '').startsWith('+') ? theme.success : theme.error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function deltaTextParts(text) {
|
|
63
|
+
const value = String(text ?? '');
|
|
64
|
+
const parts = [];
|
|
65
|
+
const re = /(^|[\s([,{·])([+-]\s*\d+)(?=\s+Lines?\b)/gi;
|
|
66
|
+
let last = 0;
|
|
67
|
+
let match;
|
|
68
|
+
while ((match = re.exec(value))) {
|
|
69
|
+
const prefix = match[1] || '';
|
|
70
|
+
const token = (match[2] || '').replace(/\s+/g, '');
|
|
71
|
+
const tokenStart = match.index + prefix.length;
|
|
72
|
+
if (match.index > last) parts.push({ text: value.slice(last, match.index) });
|
|
73
|
+
if (prefix) parts.push({ text: prefix });
|
|
74
|
+
if (token) parts.push({ text: token, color: deltaColor(token) });
|
|
75
|
+
last = tokenStart + (match[2] || '').length;
|
|
76
|
+
}
|
|
77
|
+
if (last < value.length) parts.push({ text: value.slice(last) });
|
|
78
|
+
return parts;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function plural(count, singular, pluralText = `${singular}s`) {
|
|
82
|
+
return count === 1 ? singular : pluralText;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function fitResultLine(line, columns) {
|
|
86
|
+
const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
|
|
87
|
+
const text = safeInlineText(line);
|
|
88
|
+
return displayWidth(text) > max ? truncateToWidth(text, max) : text;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Trim text from the end (by display width) so it fits maxWidth, appending '…'. */
|
|
92
|
+
export function truncateToWidth(text, maxWidth) {
|
|
93
|
+
const str = safeInlineText(text);
|
|
94
|
+
if (maxWidth < 1) return '';
|
|
95
|
+
if (displayWidth(str) <= maxWidth) return str;
|
|
96
|
+
const chars = Array.from(str);
|
|
97
|
+
let out = '';
|
|
98
|
+
for (const ch of chars) {
|
|
99
|
+
if (displayWidth(out + ch + '…') > maxWidth) break;
|
|
100
|
+
out += ch;
|
|
101
|
+
}
|
|
102
|
+
return `${out}…`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function shellResultStatus(value) {
|
|
106
|
+
const match = String(value || '').match(/(?:^|\b)status:\s*(running|pending|queued|completed|failed|cancelled|canceled)\b/im);
|
|
107
|
+
return match ? String(match[1] || '').toLowerCase() : '';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function normalizeTerminalStatus(value) {
|
|
111
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
112
|
+
if (!raw) return '';
|
|
113
|
+
if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return 'running';
|
|
114
|
+
if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return 'completed';
|
|
115
|
+
if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return 'failed';
|
|
116
|
+
if (/^(cancelled|canceled|cancel)$/.test(raw)) return 'cancelled';
|
|
117
|
+
return '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function displayTerminalStatus(value) {
|
|
121
|
+
const status = normalizeTerminalStatus(value);
|
|
122
|
+
if (status === 'running') return 'Running';
|
|
123
|
+
if (status === 'completed') return 'Finished';
|
|
124
|
+
if (status === 'failed') return 'Failed';
|
|
125
|
+
if (status === 'cancelled') return 'Cancelled';
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function resultTerminalStatus(value) {
|
|
130
|
+
const text = String(value || '');
|
|
131
|
+
const tagged = text.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
|
|
132
|
+
if (tagged) return normalizeTerminalStatus(tagged);
|
|
133
|
+
const bracketed = text.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
|
|
134
|
+
if (bracketed) return normalizeTerminalStatus(bracketed);
|
|
135
|
+
// Loose inline `status: x` / `state: x` matches are a last-resort fallback —
|
|
136
|
+
// prefer the engine-controlled `<status>` tag or `[status: …]` marker above.
|
|
137
|
+
// A loose match can false-positive on prose that happens to start with
|
|
138
|
+
// "status:" (rare, but shellResultStatus below already owns real shell
|
|
139
|
+
// output parsing; this fallback stays narrow and unchanged in behavior).
|
|
140
|
+
const inline = text.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
|
|
141
|
+
return normalizeTerminalStatus(inline);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const LEADING_STATUS_MARKER_LINE_RE = /^\[status:\s*[^\]]*\]\s*$/i;
|
|
145
|
+
|
|
146
|
+
export function stripLeadingStatusMarkerLines(lines) {
|
|
147
|
+
const out = Array.isArray(lines) ? lines.slice() : [];
|
|
148
|
+
if (out.length > 0 && LEADING_STATUS_MARKER_LINE_RE.test(String(out[0] ?? '').trim())) out.shift();
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function stripLeadingStatusMarkerFromText(text) {
|
|
153
|
+
return stripLeadingStatusMarkerLines(String(text || '').split('\n')).join('\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function shellResultElapsed(value) {
|
|
157
|
+
const match = String(value || '').match(/^\[elapsed:\s*(\d+)\s*ms\]/mi);
|
|
158
|
+
if (!match) return '';
|
|
159
|
+
const elapsedMs = Number(match[1]);
|
|
160
|
+
return Number.isFinite(elapsedMs) && elapsedMs >= 1000 ? formatElapsed(elapsedMs) : '';
|
|
161
|
+
}
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import stringWidth from 'string-width';
|
|
25
25
|
|
|
26
|
+
// Grapheme segmenter for cluster-aware width math (e.g. `↔️` = U+2194 U+FE0F
|
|
27
|
+
// is one cluster, counted once). Kept identical to vendor/ink/build/display-width.js.
|
|
28
|
+
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
29
|
+
|
|
26
30
|
/**
|
|
27
31
|
* True for code points we treat as wide (2 cells) when the policy is ON:
|
|
28
32
|
* - U+2460–U+24FF Enclosed Alphanumerics (① ② ③ …)
|
|
@@ -33,6 +37,12 @@ export function isProblemCodePoint(cp) {
|
|
|
33
37
|
return (cp >= 0x2460 && cp <= 0x24ff) || (cp >= 0x2190 && cp <= 0x21ff);
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
// Fast precheck for the problem ranges above. Lets the hot path bail before
|
|
41
|
+
// the per-grapheme segmenter loop when a string (the overwhelmingly common
|
|
42
|
+
// ASCII/status-text case) contains no widenable glyph. Kept identical to
|
|
43
|
+
// vendor/ink/build/display-width.js.
|
|
44
|
+
const PROBLEM_RE = /[\u2190-\u21ff\u2460-\u24ff]/;
|
|
45
|
+
|
|
36
46
|
/** Resolve the wide policy from env. Override wins over the WT_SESSION default. */
|
|
37
47
|
export function resolveAmbiguousWidePolicy(env = process.env) {
|
|
38
48
|
const override = env?.MIXDOG_TUI_AMBIGUOUS_WIDE;
|
|
@@ -55,10 +65,17 @@ export function displayWidthWith(str, wide) {
|
|
|
55
65
|
const s = String(str ?? '');
|
|
56
66
|
const base = stringWidth(s);
|
|
57
67
|
if (!wide) return base;
|
|
68
|
+
// Fast bail: no problem glyph ⇒ segmenting can only return `base`.
|
|
69
|
+
if (!PROBLEM_RE.test(s)) return base;
|
|
58
70
|
let extra = 0;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
// Widen per GRAPHEME CLUSTER, not per code point: a clustered glyph like
|
|
72
|
+
// `↔️` (U+2194 U+FE0F) is one visible cell-group — count its problem base
|
|
73
|
+
// once. Only add +1 when the WHOLE cluster still measures 1 cell under
|
|
74
|
+
// string-width (so a cluster the terminal already draws as 2 isn't
|
|
75
|
+
// double-widened).
|
|
76
|
+
for (const { segment } of graphemeSegmenter.segment(s)) {
|
|
77
|
+
const cp = segment.codePointAt(0);
|
|
78
|
+
if (isProblemCodePoint(cp) && stringWidth(segment) === 1) extra += 1;
|
|
62
79
|
}
|
|
63
80
|
return base + extra;
|
|
64
81
|
}
|