mixdog 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -23
- package/package.json +34 -28
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +205 -33
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1222 -90
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -29
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +242 -98
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2211 -446
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +108 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
- package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +149 -51
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -6
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +519 -118
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
- package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
- package/src/runtime/memory/lib/session-ingest.mjs +196 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +104 -473
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +44 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +139 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -95
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +1 -78
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4800 -2140
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +14 -4
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +428 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +597 -362
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +499 -0
- package/src/tui/components/tool-output-format.test.mjs +312 -0
- package/src/tui/dist/index.mjs +9192 -2399
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +671 -69
- package/src/tui/markdown/format-token.test.mjs +312 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +756 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +2 -13
- package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/ink.js +54 -8
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -1,10 +1,29 @@
|
|
|
1
|
+
import { displayModelName as sharedDisplayModelName } from '../../ui/model-display.mjs';
|
|
2
|
+
|
|
1
3
|
const DEFAULT_SUMMARY_MAX = 160;
|
|
4
|
+
// Semantic cap for collapsed agent/task card one-liners (spawn, send, response).
|
|
5
|
+
export const AGENT_SURFACE_BRIEF_MAX = 120;
|
|
2
6
|
const STATUS_SEPARATOR = ' · ';
|
|
3
7
|
|
|
4
8
|
export function stripToolPrefix(name) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
const text = rawToolName(name);
|
|
10
|
+
const mcp = parseMcpToolName(text);
|
|
11
|
+
return mcp ? mcp.tool : text;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function rawToolName(name) {
|
|
15
|
+
return String(name || 'tool').replace(/^functions\./, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseMcpToolName(name) {
|
|
19
|
+
const text = rawToolName(name);
|
|
20
|
+
const match = /^mcp__(.+?)__(.+)$/.exec(text);
|
|
21
|
+
if (!match) return null;
|
|
22
|
+
return { server: match[1], tool: match[2] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isMcpToolName(name) {
|
|
26
|
+
return Boolean(parseMcpToolName(name));
|
|
8
27
|
}
|
|
9
28
|
|
|
10
29
|
export function normalizeToolName(name) {
|
|
@@ -61,6 +80,12 @@ function compactSlash(left, right) {
|
|
|
61
80
|
return a && b ? `${a}/${b}` : a || b;
|
|
62
81
|
}
|
|
63
82
|
|
|
83
|
+
function mcpToolTarget(name, max = DEFAULT_SUMMARY_MAX) {
|
|
84
|
+
const mcp = parseMcpToolName(name);
|
|
85
|
+
if (!mcp) return '';
|
|
86
|
+
return truncateToolText(compactSlash(mcp.server, mcp.tool), max);
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
function quoted(value, max) {
|
|
65
90
|
const text = truncateToolText(value || '', max);
|
|
66
91
|
return text ? `"${text}"` : '';
|
|
@@ -73,6 +98,52 @@ function firstText(...values) {
|
|
|
73
98
|
return '';
|
|
74
99
|
}
|
|
75
100
|
|
|
101
|
+
function splitToolSearchSelection(value) {
|
|
102
|
+
if (Array.isArray(value)) return value.map((item) => String(item || '').trim()).filter(Boolean);
|
|
103
|
+
return String(value || '')
|
|
104
|
+
.split(/[,\s]+/)
|
|
105
|
+
.map((item) => item.trim())
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function toolSearchTargetKind(value) {
|
|
110
|
+
const lower = String(value || '').trim().toLowerCase();
|
|
111
|
+
if (!lower) return '';
|
|
112
|
+
if (lower.startsWith('mcp__') || lower.includes('_mcp_') || lower.includes('mcp')) return 'MCP';
|
|
113
|
+
if (lower === 'skill' || lower.startsWith('skill:') || lower.startsWith('skill_') || lower.startsWith('skills_') || lower.includes('skill')) return 'Skills';
|
|
114
|
+
return 'Tools';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function orderedToolSearchKinds(kinds) {
|
|
118
|
+
const set = new Set((kinds || []).filter(Boolean));
|
|
119
|
+
const order = ['Tools', 'MCP', 'Skills'];
|
|
120
|
+
const out = order.filter((kind) => set.has(kind));
|
|
121
|
+
return out.length ? out : ['Tools'];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function inferToolSearchKinds(args = {}) {
|
|
125
|
+
const selected = splitToolSearchSelection(args.select);
|
|
126
|
+
if (selected.length) return orderedToolSearchKinds(selected.map(toolSearchTargetKind));
|
|
127
|
+
|
|
128
|
+
const query = firstText(args.query, args.q, args.text).toLowerCase();
|
|
129
|
+
const kinds = [];
|
|
130
|
+
if (/\bmcp\b|mcp__|mcp[-_\s]?server/.test(query)) kinds.push('MCP');
|
|
131
|
+
if (/\bskills?\b|skill_|use[-_\s]?skill/.test(query)) kinds.push('Skills');
|
|
132
|
+
return orderedToolSearchKinds(kinds);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function toolSearchDisplayLabel(args = {}) {
|
|
136
|
+
return `Load ${inferToolSearchKinds(args).join('/')}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function displayToolSearchTarget(value) {
|
|
140
|
+
const text = String(value || '').trim();
|
|
141
|
+
if (/^skill:/i.test(text)) return text.slice('skill:'.length);
|
|
142
|
+
const mcp = /^mcp__(.*?)__(.+)$/.exec(text);
|
|
143
|
+
if (mcp) return `${mcp[1]}.${mcp[2]}`;
|
|
144
|
+
return stripToolPrefix(text);
|
|
145
|
+
}
|
|
146
|
+
|
|
76
147
|
function titleizeToolName(name) {
|
|
77
148
|
return stripToolPrefix(name)
|
|
78
149
|
.split(/[_\s-]+/)
|
|
@@ -89,7 +160,6 @@ function titleizeToolName(name) {
|
|
|
89
160
|
|
|
90
161
|
const AGENT_DISPLAY_NAMES = new Map([
|
|
91
162
|
['explore', 'Explore'],
|
|
92
|
-
['web-researcher', 'Web Researcher'],
|
|
93
163
|
['maintainer', 'Maintainer'],
|
|
94
164
|
['worker', 'Worker'],
|
|
95
165
|
['heavy-worker', 'Heavy Worker'],
|
|
@@ -121,41 +191,22 @@ function displayAgentName(value) {
|
|
|
121
191
|
return AGENT_DISPLAY_NAMES.get(key) || titleizeDisplayName(text);
|
|
122
192
|
}
|
|
123
193
|
|
|
124
|
-
function displayModelName(model) {
|
|
125
|
-
const text = String(model
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
const family = titleizeDisplayName(newClaude[1]);
|
|
132
|
-
const minor = newClaude[3] && newClaude[3].length <= 2 ? `.${newClaude[3]}` : '';
|
|
133
|
-
return `${family} ${newClaude[2]}${minor}`;
|
|
134
|
-
}
|
|
135
|
-
const oldClaude = /^claude-(\d+)(?:-(\d+))?-(opus|sonnet|haiku)(?:-|$)/i.exec(lower);
|
|
136
|
-
if (oldClaude) {
|
|
137
|
-
const family = titleizeDisplayName(oldClaude[3]);
|
|
138
|
-
return `${family} ${oldClaude[1]}${oldClaude[2] ? `.${oldClaude[2]}` : ''}`;
|
|
139
|
-
}
|
|
140
|
-
if (lower.startsWith('gpt-')) {
|
|
141
|
-
return raw
|
|
142
|
-
.split('-')
|
|
143
|
-
.map((part, index) => (index === 0 ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1)))
|
|
144
|
-
.join('-');
|
|
145
|
-
}
|
|
146
|
-
if (lower.startsWith('grok-')) {
|
|
147
|
-
return raw
|
|
148
|
-
.split('-')
|
|
149
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
150
|
-
.join(' ');
|
|
151
|
-
}
|
|
152
|
-
return raw;
|
|
194
|
+
export function displayModelName(model, provider, displayHint) {
|
|
195
|
+
const text = String(model ?? '').trim();
|
|
196
|
+
const modelId = text
|
|
197
|
+
? (text.includes('/') ? (text.split('/').filter(Boolean).at(-1) || text) : text)
|
|
198
|
+
: '';
|
|
199
|
+
const shown = sharedDisplayModelName(modelId, provider, displayHint);
|
|
200
|
+
return shown || modelId;
|
|
153
201
|
}
|
|
154
202
|
|
|
155
203
|
function bridgeAgentModelSummary(args) {
|
|
204
|
+
const provider = firstText(args.provider, args.providerId, args.provider_id);
|
|
205
|
+
const modelId = firstText(args.model);
|
|
206
|
+
const displayHint = firstText(args.modelDisplay, args.model_display, args.displayModel);
|
|
156
207
|
return compactParts([
|
|
157
208
|
displayAgentName(firstText(args.agent, args.role, args.name, args.subagent_type)),
|
|
158
|
-
displayModelName(
|
|
209
|
+
displayModelName(modelId, provider, displayHint),
|
|
159
210
|
]);
|
|
160
211
|
}
|
|
161
212
|
|
|
@@ -186,6 +237,36 @@ function summarizePatch(patch, basePath) {
|
|
|
186
237
|
return text ? 'patch' : '';
|
|
187
238
|
}
|
|
188
239
|
|
|
240
|
+
function collectionCount(...values) {
|
|
241
|
+
for (const value of values) {
|
|
242
|
+
if (Array.isArray(value)) {
|
|
243
|
+
const count = value.filter((item) => item != null && String(item).trim()).length;
|
|
244
|
+
return count || value.length || 0;
|
|
245
|
+
}
|
|
246
|
+
if (value && typeof value === 'object') return 1;
|
|
247
|
+
if (value != null && String(value).trim()) return 1;
|
|
248
|
+
}
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function formatCountedUnit(count, singular, pluralText = `${singular}s`) {
|
|
253
|
+
const n = Math.max(0, Number(count || 0));
|
|
254
|
+
return n > 0 ? `${n} ${pluralize(n, singular, pluralText)}` : '';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function patchFileCount(args = {}) {
|
|
258
|
+
const patchText = String(args.patch ?? '');
|
|
259
|
+
if (patchText) {
|
|
260
|
+
const files = new Set();
|
|
261
|
+
for (const line of patchText.split('\n')) {
|
|
262
|
+
const match = /^\*\*\*\s+(?:Update|Add|Delete) File:\s+(.+)\s*$/.exec(line);
|
|
263
|
+
if (match) files.add(match[1].trim());
|
|
264
|
+
}
|
|
265
|
+
if (files.size > 0) return files.size;
|
|
266
|
+
}
|
|
267
|
+
return collectionCount(args.path, args.file, args.file_path, args.base_path);
|
|
268
|
+
}
|
|
269
|
+
|
|
189
270
|
function codeGraphLabel(args) {
|
|
190
271
|
const mode = String(args.mode || args.action || '').toLowerCase();
|
|
191
272
|
if (mode === 'prewarm' || mode === 'index' || mode === 'build' || mode === 'refresh') return 'Setup';
|
|
@@ -201,6 +282,7 @@ function codeGraphSummary(args, max) {
|
|
|
201
282
|
}
|
|
202
283
|
|
|
203
284
|
export function displayToolName(name, args = {}) {
|
|
285
|
+
if (isMcpToolName(name)) return 'MCP';
|
|
204
286
|
const normalized = normalizeToolName(name);
|
|
205
287
|
switch (normalized) {
|
|
206
288
|
case 'read':
|
|
@@ -209,24 +291,32 @@ export function displayToolName(name, args = {}) {
|
|
|
209
291
|
return 'Read';
|
|
210
292
|
case 'apply_patch': {
|
|
211
293
|
const parsed = parseToolArgs(args);
|
|
294
|
+
if (parsed && parsed.dry_run === true) return 'Check';
|
|
212
295
|
return parsed && parsed.old_string === '' ? 'Create' : 'Update';
|
|
213
296
|
}
|
|
214
297
|
case 'shell':
|
|
298
|
+
case 'bash':
|
|
215
299
|
case 'bash_session':
|
|
216
300
|
case 'shell_command':
|
|
217
|
-
case '
|
|
218
|
-
case 'trigger_schedule':
|
|
301
|
+
case 'job_wait':
|
|
219
302
|
return 'Run';
|
|
303
|
+
case 'task':
|
|
304
|
+
return 'Task';
|
|
220
305
|
case 'grep':
|
|
306
|
+
case 'find':
|
|
221
307
|
case 'glob':
|
|
222
|
-
case '
|
|
308
|
+
case 'list':
|
|
309
|
+
case 'ls':
|
|
223
310
|
return 'Search';
|
|
311
|
+
case 'trigger_schedule':
|
|
312
|
+
return 'Schedule';
|
|
313
|
+
case 'tool_search':
|
|
314
|
+
return toolSearchDisplayLabel(parseToolArgs(args));
|
|
224
315
|
case 'search':
|
|
225
316
|
case 'search_query':
|
|
226
317
|
case 'image_query':
|
|
227
318
|
case 'web_search':
|
|
228
319
|
case 'web_search_call':
|
|
229
|
-
case 'firecrawl_search':
|
|
230
320
|
return 'Web Search';
|
|
231
321
|
case 'explore':
|
|
232
322
|
return 'Explore';
|
|
@@ -282,9 +372,20 @@ export function displayToolName(name, args = {}) {
|
|
|
282
372
|
export function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}) {
|
|
283
373
|
const a = parseToolArgs(args);
|
|
284
374
|
if (!a || typeof a !== 'object') return '';
|
|
285
|
-
|
|
375
|
+
const normalized = normalizeToolName(name);
|
|
376
|
+
const mcpTarget = mcpToolTarget(name, max);
|
|
377
|
+
if (mcpTarget) {
|
|
378
|
+
return compactParts([
|
|
379
|
+
mcpTarget,
|
|
380
|
+
truncateToolText(firstText(a.query, a.q, a.text, a.prompt, a.path, a.uri, a.name, a.id, a.action), Math.min(max, 80)),
|
|
381
|
+
]);
|
|
382
|
+
}
|
|
383
|
+
switch (normalized) {
|
|
286
384
|
case 'read':
|
|
287
385
|
if (!a.path && !a.file_path) return '';
|
|
386
|
+
if (Array.isArray(a.path) || Array.isArray(a.file_path)) {
|
|
387
|
+
return formatCountedUnit(collectionCount(a.path, a.file_path), 'file');
|
|
388
|
+
}
|
|
288
389
|
return compactParts([
|
|
289
390
|
displayToolPath(a.path ?? a.file_path),
|
|
290
391
|
a.pages ? `pages ${a.pages}` : summarizeLineWindow(a),
|
|
@@ -294,19 +395,27 @@ export function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}
|
|
|
294
395
|
case 'apply_patch':
|
|
295
396
|
return summarizePatch(a.patch, a.base_path);
|
|
296
397
|
case 'shell':
|
|
398
|
+
case 'bash':
|
|
297
399
|
case 'bash_session':
|
|
298
400
|
case 'shell_command':
|
|
401
|
+
case 'job_wait':
|
|
299
402
|
return truncateCommand(a.description || a.command || a.cmd || '', max);
|
|
300
403
|
case 'task':
|
|
301
404
|
return compactParts([a.action || a.type || 'task', a.task_id || '']);
|
|
302
405
|
case 'list':
|
|
303
406
|
case 'ls':
|
|
407
|
+
if (Array.isArray(a.path) || Array.isArray(a.dir) || Array.isArray(a.cwd)) {
|
|
408
|
+
return formatCountedUnit(collectionCount(a.path, a.dir, a.cwd), 'directory', 'directories');
|
|
409
|
+
}
|
|
304
410
|
return compactParts([
|
|
305
411
|
displayToolPath(a.path ?? a.dir ?? a.cwd ?? ''),
|
|
306
412
|
a.head_limit || a.limit ? `${a.head_limit ?? a.limit} entries` : '',
|
|
307
413
|
]);
|
|
308
414
|
case 'grep':
|
|
309
415
|
if (!a.pattern && !a.query) return '';
|
|
416
|
+
if (Array.isArray(a.pattern) || Array.isArray(a.query)) {
|
|
417
|
+
return formatCountedUnit(collectionCount(a.pattern, a.query), 'pattern');
|
|
418
|
+
}
|
|
310
419
|
return compactParts([
|
|
311
420
|
`pattern: ${quoted(a.pattern ?? a.query, max)}`,
|
|
312
421
|
a.path ? `path: ${displayToolPath(a.path)}` : '',
|
|
@@ -314,23 +423,47 @@ export function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}
|
|
|
314
423
|
]);
|
|
315
424
|
case 'glob':
|
|
316
425
|
if (!a.pattern && !a.glob) return '';
|
|
426
|
+
if (Array.isArray(a.pattern) || Array.isArray(a.glob)) {
|
|
427
|
+
return formatCountedUnit(collectionCount(a.pattern, a.glob), 'glob');
|
|
428
|
+
}
|
|
317
429
|
return compactParts([
|
|
318
430
|
`pattern: ${quoted(a.pattern ?? a.glob, max)}`,
|
|
319
431
|
a.path ? `path: ${displayToolPath(a.path)}` : '',
|
|
320
432
|
]);
|
|
433
|
+
case 'find':
|
|
434
|
+
if (!a.query && !a.fuzzy) return '';
|
|
435
|
+
if (Array.isArray(a.query) || Array.isArray(a.fuzzy)) {
|
|
436
|
+
return formatCountedUnit(collectionCount(a.query, a.fuzzy), 'query', 'queries');
|
|
437
|
+
}
|
|
438
|
+
return compactParts([
|
|
439
|
+
quoted(a.query ?? a.fuzzy, max),
|
|
440
|
+
a.path ? `path: ${displayToolPath(a.path)}` : '',
|
|
441
|
+
]);
|
|
321
442
|
case 'search':
|
|
322
443
|
case 'search_query':
|
|
323
444
|
case 'image_query':
|
|
324
445
|
case 'web_search':
|
|
325
446
|
case 'web_search_call':
|
|
326
|
-
|
|
447
|
+
if (Array.isArray(a.query) || Array.isArray(a.keywords)) {
|
|
448
|
+
return formatCountedUnit(collectionCount(a.query, a.keywords), 'query', 'queries');
|
|
449
|
+
}
|
|
327
450
|
return quoted(a.query || a.keywords || '', max);
|
|
328
451
|
case 'explore':
|
|
452
|
+
if (Array.isArray(a.query) || Array.isArray(a.prompt) || Array.isArray(a.task) || Array.isArray(a.goal)) {
|
|
453
|
+
return formatCountedUnit(collectionCount(a.query, a.prompt, a.task, a.goal), 'query', 'queries');
|
|
454
|
+
}
|
|
329
455
|
return truncateSingleLine(firstText(a.query, a.prompt, a.task, a.goal, a.path), Math.min(max, 80));
|
|
330
456
|
case 'tool_search':
|
|
331
|
-
|
|
457
|
+
{
|
|
458
|
+
const selected = splitToolSearchSelection(a.select);
|
|
459
|
+
if (selected.length) return truncateToolText(selected.map(displayToolSearchTarget).join(', '), max);
|
|
460
|
+
return quoted(firstText(a.query, a.q, a.text), max);
|
|
461
|
+
}
|
|
332
462
|
case 'web_fetch':
|
|
333
463
|
case 'fetch':
|
|
464
|
+
if (Array.isArray(a.url) || Array.isArray(a.uri)) {
|
|
465
|
+
return formatCountedUnit(collectionCount(a.url, a.uri), 'URL', 'URLs');
|
|
466
|
+
}
|
|
334
467
|
return truncateToolText(a.url || a.uri || '', max);
|
|
335
468
|
case 'download_attachment':
|
|
336
469
|
return displayToolPath(a.filename || a.name || a.url || '');
|
|
@@ -396,7 +529,7 @@ export function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}
|
|
|
396
529
|
case 'skill_view':
|
|
397
530
|
case 'skills_list':
|
|
398
531
|
case 'use_skill':
|
|
399
|
-
return truncateToolText(firstText(a.name, a.skill, a.skill_name), max);
|
|
532
|
+
return truncateToolText(firstText(a.name, a.skill, a.skill_name, a.query, a.q, normalized === 'skills_list' ? 'all skills' : ''), max);
|
|
400
533
|
default: {
|
|
401
534
|
const primary = firstText(a.name, a.skill, a.query, a.title, a.path, a.file, a.target, a.id, a.action);
|
|
402
535
|
if (primary) return truncateToolText(primary, Math.min(max, 80));
|
|
@@ -477,10 +610,13 @@ function parseLineDelta(delta) {
|
|
|
477
610
|
|
|
478
611
|
function formatLineDelta(totals) {
|
|
479
612
|
if (!totals?.seen) return '';
|
|
613
|
+
const added = Number(totals.added) || 0;
|
|
614
|
+
const removed = Number(totals.removed) || 0;
|
|
615
|
+
if (added === 0 && removed === 0) return '';
|
|
480
616
|
const parts = [];
|
|
481
|
-
if (
|
|
482
|
-
if (
|
|
483
|
-
return parts.join(STATUS_SEPARATOR)
|
|
617
|
+
if (added > 0) parts.push(`+${added} ${pluralize(added, 'line')}`);
|
|
618
|
+
if (removed > 0) parts.push(`-${removed} ${pluralize(removed, 'line')}`);
|
|
619
|
+
return parts.join(STATUS_SEPARATOR);
|
|
484
620
|
}
|
|
485
621
|
|
|
486
622
|
function parseUpdateSummary(text) {
|
|
@@ -510,6 +646,10 @@ function looksLineOriented(text) {
|
|
|
510
646
|
}
|
|
511
647
|
|
|
512
648
|
function summarizeUpdateResult(text, args) {
|
|
649
|
+
// A dry_run patch validates without writing, so the collapsed detail must not
|
|
650
|
+
// claim a real mutation ("Updated/Created/Deleted foo.js"). Map every action
|
|
651
|
+
// to "Checked" wording, matching the dry-run header (Checking/Checked).
|
|
652
|
+
const isDryRun = parseToolArgs(args)?.dry_run === true;
|
|
513
653
|
const changed = [];
|
|
514
654
|
for (const line of String(text ?? '').split('\n')) {
|
|
515
655
|
const ok = /^\s*OK\s+(modify|add|delete|create)\s+(.+?)\s*$/i.exec(line);
|
|
@@ -526,8 +666,10 @@ function summarizeUpdateResult(text, args) {
|
|
|
526
666
|
}
|
|
527
667
|
if (changed.length === 1) {
|
|
528
668
|
const item = changed[0];
|
|
529
|
-
const action =
|
|
530
|
-
|
|
669
|
+
const action = isDryRun
|
|
670
|
+
? 'Checked'
|
|
671
|
+
: item.action === 'delete' ? 'Deleted' : item.action === 'add' || item.action === 'create' || item.action === 'created' ? 'Created' : 'Updated';
|
|
672
|
+
return compactParts([`${action} ${displayToolPath(item.path)}`, formatLineDelta(parseLineDelta(item.delta))]);
|
|
531
673
|
}
|
|
532
674
|
if (changed.length > 1) {
|
|
533
675
|
const totals = changed.reduce((acc, item) => {
|
|
@@ -537,12 +679,12 @@ function summarizeUpdateResult(text, args) {
|
|
|
537
679
|
acc.seen = acc.seen || delta.seen;
|
|
538
680
|
return acc;
|
|
539
681
|
}, { added: 0, removed: 0, seen: false });
|
|
540
|
-
return compactParts([
|
|
682
|
+
return compactParts([`${isDryRun ? 'Checked' : 'Updated'} ${changed.length} Files`, formatLineDelta(totals)]);
|
|
541
683
|
}
|
|
542
684
|
|
|
543
685
|
const parsedArgs = parseToolArgs(args);
|
|
544
686
|
const target = parsedArgs.path ?? parsedArgs.file ?? parsedArgs.file_path ?? '';
|
|
545
|
-
if (target) return
|
|
687
|
+
if (target) return `${isDryRun ? 'Checked' : 'Updated'} ${displayToolPath(target)}`;
|
|
546
688
|
return null;
|
|
547
689
|
}
|
|
548
690
|
|
|
@@ -552,21 +694,66 @@ function textBetweenTag(text, tag) {
|
|
|
552
694
|
return match ? match[1].trim() : '';
|
|
553
695
|
}
|
|
554
696
|
|
|
697
|
+
// Strip the most common inline-markdown markers so a one-line card summary
|
|
698
|
+
// reads as plain prose ("**not clean**" → "not clean", "`x`" → "x"). Block
|
|
699
|
+
// markers (#, >, -, 1.) at line start are dropped too. Whitespace is collapsed
|
|
700
|
+
// by the caller's truncateSingleLine.
|
|
701
|
+
function stripInlineMarkdown(value) {
|
|
702
|
+
return String(value ?? '')
|
|
703
|
+
.replace(/^\s{0,3}(?:#{1,6}\s+|>\s+|[-*+]\s+|\d+\.\s+)/, '')
|
|
704
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
705
|
+
.replace(/__([^_]+)__/g, '$1')
|
|
706
|
+
.replace(/(?<!\*)\*(?!\*)([^*]+)\*(?!\*)/g, '$1')
|
|
707
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
708
|
+
.trim();
|
|
709
|
+
}
|
|
710
|
+
|
|
555
711
|
function firstAgentResultLine(text) {
|
|
556
712
|
const finalAnswer = textBetweenTag(text, 'final-answer') || textBetweenTag(text, 'result');
|
|
557
713
|
const raw = finalAnswer || text;
|
|
558
714
|
for (const line of String(raw ?? '').split('\n')) {
|
|
559
715
|
const trimmed = line.trim();
|
|
560
716
|
if (!trimmed) continue;
|
|
561
|
-
if (/^
|
|
717
|
+
if (/^agent result\b/i.test(trimmed)) continue;
|
|
562
718
|
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;
|
|
563
|
-
if (/^(?:
|
|
719
|
+
if (/^(?:agent task|status|type|target|role|agent|preset|model|effort|fast|limits|session|task-id|task_id|notification|queueDepth):\s*/i.test(trimmed)) continue;
|
|
564
720
|
if (/^\[[a-z-]+:\s*[^\]]*\]$/i.test(trimmed)) continue;
|
|
565
|
-
return truncateSingleLine(trimmed,
|
|
721
|
+
return truncateSingleLine(trimmed, AGENT_SURFACE_BRIEF_MAX);
|
|
566
722
|
}
|
|
567
723
|
return '';
|
|
568
724
|
}
|
|
569
725
|
|
|
726
|
+
function summarizeGenericResult(text) {
|
|
727
|
+
const trimmed = String(text ?? '').trim();
|
|
728
|
+
if (!trimmed) return null;
|
|
729
|
+
if (/^(?:undefined|null)$/i.test(trimmed)) return null;
|
|
730
|
+
|
|
731
|
+
if (/^[\[{]/.test(trimmed)) {
|
|
732
|
+
try {
|
|
733
|
+
const parsed = JSON.parse(trimmed);
|
|
734
|
+
if (Array.isArray(parsed)) return `${parsed.length} ${pluralize(parsed.length, 'item')}`;
|
|
735
|
+
if (parsed && typeof parsed === 'object') {
|
|
736
|
+
const status = firstText(parsed.status, parsed.state, parsed.result, parsed.message);
|
|
737
|
+
if (status) return truncateSingleLine(titleStatus(status), 120);
|
|
738
|
+
if (parsed.cwd) return truncateSingleLine(parsed.cwd, 120);
|
|
739
|
+
if (typeof parsed.ok === 'boolean') return parsed.ok ? 'Ok' : 'Failed';
|
|
740
|
+
for (const key of ['items', 'results', 'resources', 'templates', 'providers', 'schedules', 'channels', 'tools']) {
|
|
741
|
+
if (Array.isArray(parsed[key])) return `${parsed[key].length} ${pluralize(parsed[key].length, (key.slice(0, -1) || 'item').toLowerCase())}`;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
} catch {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const line = firstAgentResultLine(text) || trimmed.split('\n').map((item) => item.trim()).find(Boolean) || '';
|
|
750
|
+
if (!line || line === '{' || line === '[') return null;
|
|
751
|
+
if (/^(ok|done|success|saved|sent|updated|reloaded|connected|enabled|disabled|active|inactive)$/i.test(line)) {
|
|
752
|
+
return titleStatus(line);
|
|
753
|
+
}
|
|
754
|
+
return truncateSingleLine(line, 120);
|
|
755
|
+
}
|
|
756
|
+
|
|
570
757
|
/**
|
|
571
758
|
* Derive a short semantic one-liner for a completed tool call using only the
|
|
572
759
|
* tool name, parsed args, and the raw result text. Returns null when nothing
|
|
@@ -576,6 +763,8 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
576
763
|
if (isError) return null;
|
|
577
764
|
const text = String(resultText ?? '');
|
|
578
765
|
const trimmed = text.trim();
|
|
766
|
+
if (/^(?:undefined|null)$/i.test(trimmed)) return null;
|
|
767
|
+
if (isMcpToolName(name)) return trimmed ? firstAgentResultLine(text) || null : null;
|
|
579
768
|
const normalized = normalizeToolName(name);
|
|
580
769
|
|
|
581
770
|
switch (normalized) {
|
|
@@ -585,7 +774,7 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
585
774
|
if (/^\[image:/i.test(trimmed)) return 'Image';
|
|
586
775
|
if (!trimmed) return null;
|
|
587
776
|
const n = text.split('\n').length;
|
|
588
|
-
return `${n} ${pluralize(n, '
|
|
777
|
+
return `${n} ${pluralize(n, 'line')}`;
|
|
589
778
|
}
|
|
590
779
|
case 'apply_patch': {
|
|
591
780
|
const updateSummary = summarizeUpdateResult(text, args);
|
|
@@ -613,17 +802,32 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
613
802
|
if (!trimmed || !looksLineOriented(text)) return null;
|
|
614
803
|
const n = countNonEmptyLines(text);
|
|
615
804
|
if (n === 0) return null;
|
|
616
|
-
return `${n} ${pluralize(n, '
|
|
805
|
+
return `${n} ${pluralize(n, 'match', 'matches')}`;
|
|
617
806
|
}
|
|
618
807
|
case 'glob': {
|
|
619
808
|
if (!trimmed || !looksLineOriented(text)) return null;
|
|
620
809
|
const n = countNonEmptyLines(text);
|
|
621
810
|
if (n === 0) return null;
|
|
622
|
-
return `${n} ${pluralize(n, '
|
|
811
|
+
return `${n} ${pluralize(n, 'file')}`;
|
|
812
|
+
}
|
|
813
|
+
case 'find': {
|
|
814
|
+
if (!trimmed || !looksLineOriented(text)) return null;
|
|
815
|
+
const n = countNonEmptyLines(text);
|
|
816
|
+
if (n === 0) return null;
|
|
817
|
+
return `${n} ${pluralize(n, 'candidate')}`;
|
|
818
|
+
}
|
|
819
|
+
case 'list':
|
|
820
|
+
case 'ls': {
|
|
821
|
+
if (!trimmed || !looksLineOriented(text)) return null;
|
|
822
|
+
const n = countNonEmptyLines(text);
|
|
823
|
+
if (n === 0) return null;
|
|
824
|
+
return `${n} ${pluralize(n, 'entry', 'entries')}`;
|
|
623
825
|
}
|
|
624
826
|
case 'shell':
|
|
827
|
+
case 'bash':
|
|
625
828
|
case 'bash_session':
|
|
626
|
-
case 'shell_command':
|
|
829
|
+
case 'shell_command':
|
|
830
|
+
case 'job_wait': {
|
|
627
831
|
if (!trimmed) return '(No Output)';
|
|
628
832
|
const job = /^\[(?:task_id|job):\s*([^\]]+)\]/mi.exec(text);
|
|
629
833
|
const status = /^\[status:\s*([^\]]+)\]/mi.exec(text);
|
|
@@ -635,11 +839,12 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
635
839
|
exit ? `Exit ${exit[1]}` : '',
|
|
636
840
|
]);
|
|
637
841
|
}
|
|
638
|
-
|
|
842
|
+
const firstLine = trimmed.split('\n').map((line) => line.trim()).find(Boolean) || trimmed;
|
|
843
|
+
return truncateSingleLine(firstLine, 120);
|
|
639
844
|
}
|
|
640
845
|
case 'code_graph': {
|
|
641
846
|
const match = /(\d+)\s+(references|definitions|symbols|callers|callees|results|matches)/i.exec(text);
|
|
642
|
-
if (match) return `${match[1]} ${
|
|
847
|
+
if (match) return `${match[1]} ${String(match[2]).toLowerCase()}`;
|
|
643
848
|
return null;
|
|
644
849
|
}
|
|
645
850
|
case 'web_fetch':
|
|
@@ -654,35 +859,94 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
654
859
|
if (status) return `HTTP ${status[1]}`;
|
|
655
860
|
return null;
|
|
656
861
|
}
|
|
862
|
+
case 'download_attachment':
|
|
863
|
+
return summarizeGenericResult(text);
|
|
657
864
|
case 'search': {
|
|
658
865
|
const match = /(\d+)\s+results?/i.exec(text);
|
|
659
866
|
if (match) {
|
|
660
867
|
const n = Number(match[1]);
|
|
661
|
-
return `${n} ${pluralize(n, '
|
|
868
|
+
return `${n} ${pluralize(n, 'result')}`;
|
|
662
869
|
}
|
|
663
870
|
return null;
|
|
664
871
|
}
|
|
665
872
|
case 'search_query':
|
|
666
873
|
case 'image_query':
|
|
667
874
|
case 'web_search':
|
|
668
|
-
case 'web_search_call':
|
|
669
|
-
case 'firecrawl_search': {
|
|
875
|
+
case 'web_search_call': {
|
|
670
876
|
const match = /(\d+)\s+results?/i.exec(text);
|
|
671
877
|
if (match) {
|
|
672
878
|
const n = Number(match[1]);
|
|
673
|
-
return `${n} ${pluralize(n, '
|
|
879
|
+
return `${n} ${pluralize(n, 'result')}`;
|
|
674
880
|
}
|
|
675
881
|
return trimmed ? firstAgentResultLine(text) || null : null;
|
|
676
882
|
}
|
|
677
883
|
case 'explore': {
|
|
678
884
|
return trimmed ? firstAgentResultLine(text) || null : null;
|
|
679
885
|
}
|
|
680
|
-
case '
|
|
886
|
+
case 'recall':
|
|
887
|
+
case 'search_memories':
|
|
888
|
+
case 'memory':
|
|
889
|
+
case 'remember':
|
|
890
|
+
case 'save_memory':
|
|
891
|
+
case 'update_memory':
|
|
892
|
+
case 'reply':
|
|
893
|
+
case 'react':
|
|
894
|
+
case 'edit_message':
|
|
895
|
+
case 'activate_channel_bridge':
|
|
896
|
+
case 'inject_command':
|
|
897
|
+
case 'request_user_input':
|
|
898
|
+
case 'update_plan':
|
|
899
|
+
case 'diagnostics':
|
|
900
|
+
case 'open_config':
|
|
901
|
+
case 'provider_status':
|
|
902
|
+
case 'channel_status':
|
|
903
|
+
case 'schedule_status':
|
|
904
|
+
case 'schedule_control':
|
|
905
|
+
case 'trigger_schedule':
|
|
906
|
+
case 'reload_config':
|
|
907
|
+
case 'cwd':
|
|
908
|
+
case 'list_mcp_resources':
|
|
909
|
+
case 'list_mcp_resource_templates':
|
|
910
|
+
return summarizeGenericResult(text);
|
|
911
|
+
case 'skill':
|
|
912
|
+
case 'skill_execute':
|
|
913
|
+
case 'skill_view':
|
|
914
|
+
case 'skills_list':
|
|
915
|
+
case 'use_skill': {
|
|
916
|
+
const parsedArgs = parseToolArgs(args);
|
|
917
|
+
const target = firstText(parsedArgs.name, parsedArgs.skill, parsedArgs.skill_name);
|
|
918
|
+
if (normalized === 'skills_list') {
|
|
919
|
+
const count = /(\d+)\s+skills?/i.exec(text);
|
|
920
|
+
if (count) return `${Number(count[1]) || count[1]} ${pluralize(Number(count[1]) || 0, 'skill')}`;
|
|
921
|
+
const lines = countNonEmptyLines(text);
|
|
922
|
+
return lines > 0 ? `${lines} ${pluralize(lines, 'skill')}` : null;
|
|
923
|
+
}
|
|
924
|
+
if (target) {
|
|
925
|
+
const verb = normalized === 'skill' || normalized === 'skill_view' ? 'Loaded' : 'Used';
|
|
926
|
+
return `${verb} ${truncateToolText(target, 80)}`;
|
|
927
|
+
}
|
|
928
|
+
return trimmed ? firstAgentResultLine(text) || null : null;
|
|
929
|
+
}
|
|
681
930
|
case 'agent':
|
|
682
931
|
case 'task': {
|
|
932
|
+
// Status-check (list/status) envelopes start with "agents: N" / "tasks: M"
|
|
933
|
+
// (or "(no agents or tasks)"). Collapse them to a tight count summary
|
|
934
|
+
// instead of leaking the raw "agents: 3 …" worker dump into the card.
|
|
935
|
+
if (/^\(no agents or tasks\)$/im.test(text)) return 'No agents or tasks';
|
|
936
|
+
const agentsCount = /^agents:\s*(\d+)/im.exec(text);
|
|
937
|
+
const tasksCount = /^tasks:\s*(\d+)/im.exec(text);
|
|
938
|
+
if (agentsCount || tasksCount) {
|
|
939
|
+
const a = agentsCount ? Number(agentsCount[1]) : 0;
|
|
940
|
+
const t = tasksCount ? Number(tasksCount[1]) : 0;
|
|
941
|
+
const parts = [];
|
|
942
|
+
if (agentsCount) parts.push(`${a} ${pluralize(a, 'agent')}`);
|
|
943
|
+
if (tasksCount && t > 0) parts.push(`${t} ${pluralize(t, 'task')}`);
|
|
944
|
+
return compactParts(parts) || 'No agents or tasks';
|
|
945
|
+
}
|
|
683
946
|
const answerLine = firstAgentResultLine(text);
|
|
684
|
-
|
|
685
|
-
|
|
947
|
+
// Agent/task result cards show only a one-liner; full report via ctrl+o.
|
|
948
|
+
if (answerLine) return truncateSingleLine(stripInlineMarkdown(answerLine), AGENT_SURFACE_BRIEF_MAX);
|
|
949
|
+
const task = /^agent task:\s*(\S+)/mi.exec(text);
|
|
686
950
|
const status = /^status:\s*([^\s(]+)/mi.exec(text);
|
|
687
951
|
const role = /^role:\s*(.+)$/mi.exec(text);
|
|
688
952
|
const preset = /^preset:\s*(.+)$/mi.exec(text);
|
|
@@ -694,6 +958,7 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
694
958
|
]);
|
|
695
959
|
if (agentModel) return agentModel;
|
|
696
960
|
const parts = [
|
|
961
|
+
task ? task[1] : '',
|
|
697
962
|
role ? role[1] : '',
|
|
698
963
|
preset ? preset[1] : '',
|
|
699
964
|
model ? model[1] : '',
|
|
@@ -701,7 +966,7 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
701
966
|
limits ? limits[1] : '',
|
|
702
967
|
].filter(Boolean);
|
|
703
968
|
if (parts.length) return compactParts(parts);
|
|
704
|
-
if (
|
|
969
|
+
if (task) return status ? `${task[1]} ${titleStatus(status[1])}` : task[1];
|
|
705
970
|
return null;
|
|
706
971
|
}
|
|
707
972
|
default:
|
|
@@ -713,6 +978,36 @@ export function isExplorerSurface(label) {
|
|
|
713
978
|
return label === 'Read' || label === 'Search';
|
|
714
979
|
}
|
|
715
980
|
|
|
981
|
+
function truncateAgentSurfaceBrief(value, max = AGENT_SURFACE_BRIEF_MAX) {
|
|
982
|
+
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
983
|
+
if (!text) return '';
|
|
984
|
+
if (text.length <= max) return text;
|
|
985
|
+
return `${text.slice(0, Math.max(1, max - 1))}\u2026`;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Tight one-liner for an agent/task card: prefer the inbound prompt/message for
|
|
989
|
+
// spawn/send, or a stripped first line of the result when a response landed.
|
|
990
|
+
export function summarizeAgentSurfaceBrief(name, args, resultText, { isError = false, isResponse = false } = {}) {
|
|
991
|
+
const a = parseToolArgs(args);
|
|
992
|
+
const action = String(a?.type || a?.action || '').toLowerCase();
|
|
993
|
+
const text = String(resultText ?? '').trim();
|
|
994
|
+
if (isResponse && text) {
|
|
995
|
+
const fromResult = summarizeToolResult(name, args, text, isError);
|
|
996
|
+
if (fromResult) return truncateAgentSurfaceBrief(stripInlineMarkdown(fromResult));
|
|
997
|
+
const line = firstAgentResultLine(text);
|
|
998
|
+
if (line) return truncateAgentSurfaceBrief(stripInlineMarkdown(line));
|
|
999
|
+
}
|
|
1000
|
+
const outbound = firstText(a?.prompt, a?.message);
|
|
1001
|
+
if (outbound && (action === 'spawn' || action === 'send' || !action)) {
|
|
1002
|
+
return truncateAgentSurfaceBrief(outbound);
|
|
1003
|
+
}
|
|
1004
|
+
if ((action === 'spawn' || action === 'send') && text && !isResponse) {
|
|
1005
|
+
const fromResult = summarizeToolResult(name, args, text, isError);
|
|
1006
|
+
if (fromResult) return truncateAgentSurfaceBrief(stripInlineMarkdown(fromResult));
|
|
1007
|
+
}
|
|
1008
|
+
return '';
|
|
1009
|
+
}
|
|
1010
|
+
|
|
716
1011
|
export function isMemorySurface(label) {
|
|
717
1012
|
return label === 'Memory';
|
|
718
1013
|
}
|
|
@@ -720,8 +1015,8 @@ export function isMemorySurface(label) {
|
|
|
720
1015
|
// ── Aggregate tool-card classification & formatting ──────────────
|
|
721
1016
|
|
|
722
1017
|
export const CATEGORY_ORDER = [
|
|
723
|
-
'Read', 'Search', 'Web Research', 'Memory', 'Explore',
|
|
724
|
-
'Patch', 'Shell', 'Agent', 'Channel', 'Setup', 'Other',
|
|
1018
|
+
'Read', 'Search', 'Load', 'MCP', 'Skill', 'Web Research', 'Memory', 'Explore',
|
|
1019
|
+
'Patch', 'Shell', 'Agent', 'Task', 'Schedule', 'Channel', 'Setup', 'Other',
|
|
725
1020
|
];
|
|
726
1021
|
|
|
727
1022
|
const TOOL_CATEGORY = new Map([
|
|
@@ -729,16 +1024,16 @@ const TOOL_CATEGORY = new Map([
|
|
|
729
1024
|
['view_image', 'Read'],
|
|
730
1025
|
['read_mcp_resource', 'Read'],
|
|
731
1026
|
['grep', 'Search'],
|
|
1027
|
+
['find', 'Search'],
|
|
732
1028
|
['glob', 'Search'],
|
|
733
1029
|
['list', 'Search'],
|
|
734
1030
|
['ls', 'Search'],
|
|
735
|
-
['tool_search', '
|
|
1031
|
+
['tool_search', 'Load'],
|
|
736
1032
|
['search', 'Web Research'],
|
|
737
1033
|
['web_search', 'Web Research'],
|
|
738
1034
|
['search_query', 'Web Research'],
|
|
739
1035
|
['image_query', 'Web Research'],
|
|
740
1036
|
['web_search_call', 'Web Research'],
|
|
741
|
-
['firecrawl_search', 'Web Research'],
|
|
742
1037
|
['web_fetch', 'Web Research'],
|
|
743
1038
|
['fetch', 'Web Research'],
|
|
744
1039
|
['download_attachment', 'Web Research'],
|
|
@@ -756,8 +1051,7 @@ const TOOL_CATEGORY = new Map([
|
|
|
756
1051
|
['shell_command', 'Shell'],
|
|
757
1052
|
['bash_session', 'Shell'],
|
|
758
1053
|
['job_wait', 'Shell'],
|
|
759
|
-
['task', '
|
|
760
|
-
['bridge', 'Agent'],
|
|
1054
|
+
['task', 'Task'],
|
|
761
1055
|
['agent', 'Agent'],
|
|
762
1056
|
['reply', 'Channel'],
|
|
763
1057
|
['react', 'Channel'],
|
|
@@ -776,16 +1070,17 @@ const TOOL_CATEGORY = new Map([
|
|
|
776
1070
|
['cwd', 'Setup'],
|
|
777
1071
|
['request_user_input', 'Setup'],
|
|
778
1072
|
['update_plan', 'Setup'],
|
|
779
|
-
['trigger_schedule', '
|
|
780
|
-
['skill', '
|
|
781
|
-
['skill_execute', '
|
|
782
|
-
['skill_view', '
|
|
783
|
-
['skills_list', '
|
|
784
|
-
['use_skill', '
|
|
1073
|
+
['trigger_schedule', 'Schedule'],
|
|
1074
|
+
['skill', 'Skill'],
|
|
1075
|
+
['skill_execute', 'Skill'],
|
|
1076
|
+
['skill_view', 'Skill'],
|
|
1077
|
+
['skills_list', 'Skill'],
|
|
1078
|
+
['use_skill', 'Skill'],
|
|
785
1079
|
]);
|
|
786
1080
|
|
|
787
1081
|
/** Return the aggregate category for a tool name + args. */
|
|
788
1082
|
export function classifyToolCategory(name, args = {}) {
|
|
1083
|
+
if (isMcpToolName(name)) return 'MCP';
|
|
789
1084
|
const normalized = normalizeToolName(name);
|
|
790
1085
|
if (normalized === 'code_graph') {
|
|
791
1086
|
const mode = String(args.mode || args.action || '').toLowerCase();
|
|
@@ -796,16 +1091,21 @@ export function classifyToolCategory(name, args = {}) {
|
|
|
796
1091
|
}
|
|
797
1092
|
|
|
798
1093
|
const CATEGORY_COPY = new Map([
|
|
799
|
-
['Read', { active: 'Reading', done: 'Read', noun: '
|
|
800
|
-
['Search', { active: 'Searching', done: 'Searched', noun: '
|
|
801
|
-
['
|
|
1094
|
+
['Read', { active: 'Reading', done: 'Read', noun: 'file' }],
|
|
1095
|
+
['Search', { active: 'Searching', done: 'Searched', noun: 'file' }],
|
|
1096
|
+
['Load', { active: 'Loading', done: 'Loaded', noun: 'tool' }],
|
|
1097
|
+
['MCP', { active: 'Using', done: 'Used', noun: 'MCP tool' }],
|
|
1098
|
+
['Skill', { active: 'Loading', done: 'Loaded', noun: 'skill' }],
|
|
1099
|
+
['Web Research', { active: 'Researching', done: 'Researched', noun: 'query', pluralNoun: 'queries' }],
|
|
802
1100
|
['Memory', { active: 'Checking', done: 'Checked', noun: 'memory item' }],
|
|
803
|
-
['Explore', { active: 'Exploring', done: 'Explored', noun: '
|
|
804
|
-
['Patch', { active: 'Editing', done: 'Edited', noun: '
|
|
1101
|
+
['Explore', { active: 'Exploring', done: 'Explored', noun: 'query', pluralNoun: 'queries' }],
|
|
1102
|
+
['Patch', { active: 'Editing', done: 'Edited', noun: 'file' }],
|
|
805
1103
|
['Shell', { active: 'Running', done: 'Ran', noun: 'command' }],
|
|
806
1104
|
['Agent', { active: 'Calling', done: 'Called', noun: 'agent' }],
|
|
1105
|
+
['Task', { active: 'Checking', done: 'Checked', noun: 'task' }],
|
|
1106
|
+
['Schedule', { active: 'Running', done: 'Ran', noun: 'schedule' }],
|
|
807
1107
|
['Channel', { active: 'Sending', done: 'Sent', noun: 'message' }],
|
|
808
|
-
['Setup', { active: 'Setting
|
|
1108
|
+
['Setup', { active: 'Setting up', done: 'Set up', noun: 'item' }],
|
|
809
1109
|
['Other', { active: 'Calling', done: 'Called', noun: 'tool' }],
|
|
810
1110
|
]);
|
|
811
1111
|
|
|
@@ -823,17 +1123,241 @@ function categoryNoun(category, count) {
|
|
|
823
1123
|
return pluralize(count, copy.noun, copy.pluralNoun || `${copy.noun}s`);
|
|
824
1124
|
}
|
|
825
1125
|
|
|
1126
|
+
function categoryCopy(category) {
|
|
1127
|
+
return CATEGORY_COPY.get(category) || CATEGORY_COPY.get('Other') || { active: 'Calling', done: 'Called', noun: 'tool' };
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function unitDescriptor(category, overrides = {}) {
|
|
1131
|
+
const copy = categoryCopy(category);
|
|
1132
|
+
return {
|
|
1133
|
+
category,
|
|
1134
|
+
active: overrides.active || copy.active,
|
|
1135
|
+
done: overrides.done || copy.done,
|
|
1136
|
+
noun: overrides.noun || copy.noun || 'item',
|
|
1137
|
+
pluralNoun: overrides.pluralNoun || copy.pluralNoun || `${overrides.noun || copy.noun || 'item'}s`,
|
|
1138
|
+
count: Math.max(1, Number(overrides.count || 1)),
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function queryCount(args, ...keys) {
|
|
1143
|
+
return collectionCount(...keys.map((key) => args?.[key]));
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
export function toolWorkUnit(name, args = {}, category = '') {
|
|
1147
|
+
const a = parseToolArgs(args);
|
|
1148
|
+
const normalized = normalizeToolName(name);
|
|
1149
|
+
const cat = category || classifyToolCategory(name, a);
|
|
1150
|
+
if (isMcpToolName(name)) {
|
|
1151
|
+
return unitDescriptor('MCP', { count: queryCount(a, 'query', 'q', 'text', 'prompt', 'path', 'uri', 'name', 'id', 'action') || 1 });
|
|
1152
|
+
}
|
|
1153
|
+
switch (normalized) {
|
|
1154
|
+
case 'read':
|
|
1155
|
+
return unitDescriptor('Read', { count: queryCount(a, 'path', 'paths', 'file_path', 'file', 'files') || 1, noun: 'file' });
|
|
1156
|
+
case 'view_image':
|
|
1157
|
+
return unitDescriptor('Read', { count: queryCount(a, 'path', 'file_path', 'file') || 1, noun: 'image' });
|
|
1158
|
+
case 'read_mcp_resource':
|
|
1159
|
+
return unitDescriptor('Read', { count: queryCount(a, 'uri', 'uris') || 1, noun: 'resource' });
|
|
1160
|
+
case 'apply_patch': {
|
|
1161
|
+
const creating = a.old_string === '';
|
|
1162
|
+
// A dry_run patch validates the diff WITHOUT writing any file, so the
|
|
1163
|
+
// header must not claim "Editing/Edited" (which made a pure validation
|
|
1164
|
+
// look like a real edit). Surface it as "Checking/Checked" instead.
|
|
1165
|
+
if (a.dry_run === true) {
|
|
1166
|
+
return unitDescriptor('Patch', {
|
|
1167
|
+
count: patchFileCount(a) || 1,
|
|
1168
|
+
active: 'Checking',
|
|
1169
|
+
done: 'Checked',
|
|
1170
|
+
noun: 'file',
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
return unitDescriptor('Patch', {
|
|
1174
|
+
count: patchFileCount(a) || 1,
|
|
1175
|
+
active: creating ? 'Creating' : 'Editing',
|
|
1176
|
+
done: creating ? 'Created' : 'Edited',
|
|
1177
|
+
noun: 'file',
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
case 'grep':
|
|
1181
|
+
return unitDescriptor('Search', { count: queryCount(a, 'pattern', 'patterns', 'query') || 1, active: 'Searching', done: 'Searched', noun: 'pattern' });
|
|
1182
|
+
case 'glob':
|
|
1183
|
+
return unitDescriptor('Search', { count: queryCount(a, 'pattern', 'patterns', 'glob', 'globs') || 1, active: 'Finding', done: 'Found', noun: 'glob' });
|
|
1184
|
+
case 'find':
|
|
1185
|
+
return unitDescriptor('Search', { count: queryCount(a, 'query', 'queries', 'fuzzy') || 1, active: 'Finding', done: 'Found', noun: 'query', pluralNoun: 'queries' });
|
|
1186
|
+
case 'list':
|
|
1187
|
+
case 'ls':
|
|
1188
|
+
return unitDescriptor('Search', { count: queryCount(a, 'path', 'paths', 'dir', 'dirs', 'cwd') || 1, active: 'Listing', done: 'Listed', noun: 'directory', pluralNoun: 'directories' });
|
|
1189
|
+
case 'tool_search': {
|
|
1190
|
+
const selected = splitToolSearchSelection(a.select);
|
|
1191
|
+
if (selected.length) return unitDescriptor('Load', { count: selected.length, noun: 'tool' });
|
|
1192
|
+
return unitDescriptor('Load', { count: queryCount(a, 'query', 'q', 'text') || 1, noun: 'query', pluralNoun: 'queries' });
|
|
1193
|
+
}
|
|
1194
|
+
case 'search':
|
|
1195
|
+
case 'search_query':
|
|
1196
|
+
case 'image_query':
|
|
1197
|
+
case 'web_search':
|
|
1198
|
+
case 'web_search_call':
|
|
1199
|
+
return unitDescriptor('Web Research', { count: queryCount(a, 'query', 'queries', 'keywords') || 1, noun: 'query', pluralNoun: 'queries' });
|
|
1200
|
+
case 'web_fetch':
|
|
1201
|
+
case 'fetch':
|
|
1202
|
+
return unitDescriptor('Web Research', { count: queryCount(a, 'url', 'urls', 'uri', 'uris') || 1, active: 'Fetching', done: 'Fetched', noun: 'URL', pluralNoun: 'URLs' });
|
|
1203
|
+
case 'download_attachment':
|
|
1204
|
+
return unitDescriptor('Web Research', { count: queryCount(a, 'url', 'urls', 'filename', 'name') || 1, active: 'Downloading', done: 'Downloaded', noun: 'attachment' });
|
|
1205
|
+
case 'recall':
|
|
1206
|
+
case 'recall_memory':
|
|
1207
|
+
case 'search_memories':
|
|
1208
|
+
return unitDescriptor('Memory', { count: queryCount(a, 'query', 'queries', 'text', 'input') || 1, noun: 'memory item', pluralNoun: 'memory items' });
|
|
1209
|
+
case 'remember':
|
|
1210
|
+
case 'save_memory':
|
|
1211
|
+
case 'update_memory':
|
|
1212
|
+
case 'memory':
|
|
1213
|
+
return unitDescriptor('Memory', { count: queryCount(a, 'entries', 'items', 'memories', 'query', 'text', 'value') || 1, active: 'Writing', done: 'Wrote', noun: 'memory item' });
|
|
1214
|
+
case 'explore':
|
|
1215
|
+
return unitDescriptor('Explore', { count: queryCount(a, 'query', 'queries', 'prompt', 'task', 'goal') || 1, noun: 'query', pluralNoun: 'queries' });
|
|
1216
|
+
case 'shell':
|
|
1217
|
+
case 'bash':
|
|
1218
|
+
case 'bash_session':
|
|
1219
|
+
case 'shell_command':
|
|
1220
|
+
case 'job_wait':
|
|
1221
|
+
return unitDescriptor('Shell', { count: queryCount(a, 'command', 'commands', 'cmd') || 1, noun: 'command' });
|
|
1222
|
+
case 'agent':
|
|
1223
|
+
case 'bridge':
|
|
1224
|
+
return unitDescriptor('Agent', { count: queryCount(a, 'agents', 'roles', 'role', 'tag', 'task_id', 'sessionId') || 1, noun: 'agent' });
|
|
1225
|
+
case 'task':
|
|
1226
|
+
return unitDescriptor('Task', { count: queryCount(a, 'task_id', 'task_ids', 'id', 'ids') || 1, noun: 'task' });
|
|
1227
|
+
case 'skill':
|
|
1228
|
+
case 'skill_execute':
|
|
1229
|
+
case 'skill_view':
|
|
1230
|
+
case 'skills_list':
|
|
1231
|
+
case 'use_skill':
|
|
1232
|
+
return unitDescriptor('Skill', { count: queryCount(a, 'name', 'skill', 'skill_name', 'query', 'q') || 1, noun: 'skill' });
|
|
1233
|
+
case 'reply':
|
|
1234
|
+
return unitDescriptor('Channel', { count: queryCount(a, 'messages', 'messageId', 'text') || 1, noun: 'message' });
|
|
1235
|
+
case 'react':
|
|
1236
|
+
return unitDescriptor('Channel', { count: queryCount(a, 'emoji', 'messageId') || 1, active: 'Reacting', done: 'Reacted', noun: 'reaction' });
|
|
1237
|
+
case 'edit_message':
|
|
1238
|
+
return unitDescriptor('Channel', { count: queryCount(a, 'messageId', 'text') || 1, active: 'Editing', done: 'Edited', noun: 'message' });
|
|
1239
|
+
case 'activate_channel_bridge':
|
|
1240
|
+
return unitDescriptor('Channel', { active: 'Toggling', done: 'Toggled', noun: 'channel bridge' });
|
|
1241
|
+
case 'inject_command':
|
|
1242
|
+
return unitDescriptor('Channel', { count: queryCount(a, 'command', 'text', 'name') || 1, active: 'Injecting', done: 'Injected', noun: 'command' });
|
|
1243
|
+
case 'trigger_schedule':
|
|
1244
|
+
return unitDescriptor('Schedule', { count: queryCount(a, 'name', 'id') || 1, noun: 'schedule' });
|
|
1245
|
+
case 'code_graph': {
|
|
1246
|
+
const mode = String(a.mode || a.action || '').toLowerCase();
|
|
1247
|
+
const searching = mode === 'search' || mode === 'find_symbol' || mode === 'references' || mode === 'callers' || mode === 'callees';
|
|
1248
|
+
return unitDescriptor(searching ? 'Search' : 'Read', {
|
|
1249
|
+
count: queryCount(a, 'symbols', 'symbol', 'query', 'file', 'path') || 1,
|
|
1250
|
+
active: searching ? 'Mapping' : 'Reading',
|
|
1251
|
+
done: searching ? 'Mapped' : 'Read',
|
|
1252
|
+
noun: searching ? 'symbol' : 'file',
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
case 'request_user_input':
|
|
1256
|
+
return unitDescriptor('Setup', { active: 'Asking', done: 'Asked', noun: 'user' });
|
|
1257
|
+
case 'update_plan':
|
|
1258
|
+
return unitDescriptor('Setup', { active: 'Updating', done: 'Updated', noun: 'plan' });
|
|
1259
|
+
case 'diagnostics':
|
|
1260
|
+
return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'diagnostic' });
|
|
1261
|
+
case 'open_config':
|
|
1262
|
+
return unitDescriptor('Setup', { active: 'Opening', done: 'Opened', noun: 'config' });
|
|
1263
|
+
case 'provider_status':
|
|
1264
|
+
return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'provider' });
|
|
1265
|
+
case 'channel_status':
|
|
1266
|
+
return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'channel' });
|
|
1267
|
+
case 'schedule_status':
|
|
1268
|
+
return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'schedule' });
|
|
1269
|
+
case 'schedule_control':
|
|
1270
|
+
return unitDescriptor('Setup', { active: 'Updating', done: 'Updated', noun: 'schedule' });
|
|
1271
|
+
case 'reload_config':
|
|
1272
|
+
return unitDescriptor('Setup', { active: 'Reloading', done: 'Reloaded', noun: 'config' });
|
|
1273
|
+
case 'list_mcp_resources':
|
|
1274
|
+
return unitDescriptor('Setup', { active: 'Listing', done: 'Listed', noun: 'MCP resource' });
|
|
1275
|
+
case 'list_mcp_resource_templates':
|
|
1276
|
+
return unitDescriptor('Setup', { active: 'Listing', done: 'Listed', noun: 'MCP resource template' });
|
|
1277
|
+
case 'cwd': {
|
|
1278
|
+
const action = String(a.action || a.type || '').toLowerCase();
|
|
1279
|
+
return action === 'set'
|
|
1280
|
+
? unitDescriptor('Setup', { active: 'Setting', done: 'Set', noun: 'working directory', pluralNoun: 'working directories' })
|
|
1281
|
+
: unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'working directory', pluralNoun: 'working directories' });
|
|
1282
|
+
}
|
|
1283
|
+
default:
|
|
1284
|
+
return unitDescriptor(cat, { count: queryCount(a, 'items', 'targets', 'query', 'path', 'name', 'id', 'action') || 1 });
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function lifecycleVerb(unit, pending, { stableVerbWidth = false } = {}) {
|
|
1289
|
+
const active = String(unit.active || '');
|
|
1290
|
+
const done = String(unit.done || '');
|
|
1291
|
+
const verb = pending ? active : done;
|
|
1292
|
+
if (!stableVerbWidth) return verb;
|
|
1293
|
+
return verb.padEnd(Math.max(active.length, done.length), ' ');
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
export function formatToolActionHeader(name, args = {}, { pending = false, count = 1, category = '', stableVerbWidth = false } = {}) {
|
|
1297
|
+
const unit = toolWorkUnit(name, args, category);
|
|
1298
|
+
const n = Math.max(1, Number(unit.count || count || 1));
|
|
1299
|
+
const verb = lifecycleVerb(unit, pending, { stableVerbWidth });
|
|
1300
|
+
return `${verb} ${n} ${pluralize(n, unit.noun, unit.pluralNoun)}`;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
export function aggregateToolCategoryEntry(name, args = {}, category = '') {
|
|
1304
|
+
const cat = category || classifyToolCategory(name, args);
|
|
1305
|
+
const unit = toolWorkUnit(name, args, cat);
|
|
1306
|
+
const key = [cat, unit.active, unit.done, unit.noun, unit.pluralNoun].join('|');
|
|
1307
|
+
return {
|
|
1308
|
+
key,
|
|
1309
|
+
category: cat,
|
|
1310
|
+
active: unit.active,
|
|
1311
|
+
done: unit.done,
|
|
1312
|
+
noun: unit.noun,
|
|
1313
|
+
pluralNoun: unit.pluralNoun,
|
|
1314
|
+
count: Math.max(1, Number(unit.count || 1)),
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
function aggregateCount(value) {
|
|
1319
|
+
if (value && typeof value === 'object') return Math.max(0, Number(value.count || 0));
|
|
1320
|
+
return Math.max(0, Number(value || 0));
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function aggregateDescriptor(key, value) {
|
|
1324
|
+
if (value && typeof value === 'object') {
|
|
1325
|
+
const category = value.category || String(key || '').split('|')[0] || 'Other';
|
|
1326
|
+
const copy = categoryCopy(category);
|
|
1327
|
+
const noun = value.noun || copy.noun || 'item';
|
|
1328
|
+
return {
|
|
1329
|
+
category,
|
|
1330
|
+
active: value.active || copy.active,
|
|
1331
|
+
done: value.done || copy.done,
|
|
1332
|
+
noun,
|
|
1333
|
+
pluralNoun: value.pluralNoun || copy.pluralNoun || `${noun}s`,
|
|
1334
|
+
count: aggregateCount(value),
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
const category = String(key || '');
|
|
1338
|
+
const copy = categoryCopy(category);
|
|
1339
|
+
const noun = copy.noun || 'item';
|
|
1340
|
+
return {
|
|
1341
|
+
category,
|
|
1342
|
+
active: copy.active,
|
|
1343
|
+
done: copy.done,
|
|
1344
|
+
noun,
|
|
1345
|
+
pluralNoun: copy.pluralNoun || `${noun}s`,
|
|
1346
|
+
count: aggregateCount(value),
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
826
1350
|
/**
|
|
827
1351
|
* Build a comma-separated header from per-category counts.
|
|
828
1352
|
* e.g. "Read 6 items, Searched 5 items, Called 1 agent"
|
|
829
1353
|
*/
|
|
830
|
-
export function formatAggregateHeader(categories, { pending = false, order = null } = {}) {
|
|
1354
|
+
export function formatAggregateHeader(categories, { pending = false, order = null, stableVerbWidth = false } = {}) {
|
|
831
1355
|
const categoryKeys = Object.keys(categories || {});
|
|
832
1356
|
const preferred = Array.isArray(order) && order.length ? order : categoryKeys;
|
|
833
1357
|
const seen = new Set();
|
|
834
1358
|
const ordered = [];
|
|
835
1359
|
const add = (cat) => {
|
|
836
|
-
if (!cat || seen.has(cat) || (categories[cat]
|
|
1360
|
+
if (!cat || seen.has(cat) || aggregateCount(categories[cat]) <= 0) return;
|
|
837
1361
|
seen.add(cat);
|
|
838
1362
|
ordered.push(cat);
|
|
839
1363
|
};
|
|
@@ -843,10 +1367,9 @@ export function formatAggregateHeader(categories, { pending = false, order = nul
|
|
|
843
1367
|
|
|
844
1368
|
return ordered
|
|
845
1369
|
.map((cat) => {
|
|
846
|
-
const
|
|
847
|
-
const label = pending
|
|
848
|
-
|
|
849
|
-
return `${label} ${count} ${categoryNoun(cat, count)}`;
|
|
1370
|
+
const item = aggregateDescriptor(cat, categories[cat]);
|
|
1371
|
+
const label = lifecycleVerb(item, pending, { stableVerbWidth });
|
|
1372
|
+
return `${label} ${item.count} ${pluralize(item.count, item.noun, item.pluralNoun)}`;
|
|
850
1373
|
})
|
|
851
1374
|
.join(', ');
|
|
852
1375
|
}
|
|
@@ -882,13 +1405,13 @@ export function formatAggregateDetail(summaries) {
|
|
|
882
1405
|
|
|
883
1406
|
let match = /^(?:Read\s+)?(\d+)\s+lines?$/i.exec(text);
|
|
884
1407
|
if (match) {
|
|
885
|
-
const metric = addMetric('read_lines', { count: 0, render: (m) => `${m.count} ${pluralize(m.count, '
|
|
1408
|
+
const metric = addMetric('read_lines', { count: 0, render: (m) => `${m.count} ${pluralize(m.count, 'line')}` });
|
|
886
1409
|
metric.count += Number(match[1]);
|
|
887
1410
|
continue;
|
|
888
1411
|
}
|
|
889
1412
|
|
|
890
1413
|
if (/^(?:Read\s+)?image$/i.test(text)) {
|
|
891
|
-
const metric = addMetric('read_images', { count: 0, render: (m) => `${m.count} ${pluralize(m.count, '
|
|
1414
|
+
const metric = addMetric('read_images', { count: 0, render: (m) => `${m.count} ${pluralize(m.count, 'image')}` });
|
|
892
1415
|
metric.count += 1;
|
|
893
1416
|
continue;
|
|
894
1417
|
}
|
|
@@ -899,7 +1422,7 @@ export function formatAggregateDetail(summaries) {
|
|
|
899
1422
|
const singular = nounRaw.endsWith('ies') ? `${nounRaw.slice(0, -3)}y` : nounRaw.endsWith('s') ? nounRaw.slice(0, -1) : nounRaw;
|
|
900
1423
|
const plural = nounRaw.endsWith('s') ? nounRaw : `${nounRaw}s`;
|
|
901
1424
|
const key = `found_${plural}`;
|
|
902
|
-
const metric = addMetric(key, { count: 0, singular, plural, render: (m) => `${m.count} ${pluralize(m.count,
|
|
1425
|
+
const metric = addMetric(key, { count: 0, singular, plural, render: (m) => `${m.count} ${pluralize(m.count, m.singular, m.plural)}` });
|
|
903
1426
|
metric.count += Number(match[1]);
|
|
904
1427
|
continue;
|
|
905
1428
|
}
|
|
@@ -922,10 +1445,16 @@ export function formatAggregateDetail(summaries) {
|
|
|
922
1445
|
removed: 0,
|
|
923
1446
|
seen: false,
|
|
924
1447
|
render: (m) => {
|
|
1448
|
+
// The aggregate header already carries the action + file count
|
|
1449
|
+
// (e.g. "Edited 2 files"), so the detail row shows only the merged
|
|
1450
|
+
// line delta. Fall back to the action + file/count summary only when
|
|
1451
|
+
// there is no +/- delta to show (e.g. pure create/delete).
|
|
1452
|
+
const delta = formatLineDelta(m);
|
|
1453
|
+
if (delta) return delta;
|
|
925
1454
|
const count = m.fileCount + m.files.size;
|
|
926
1455
|
const action = m.actions.size === 1 ? [...m.actions][0] : 'Updated';
|
|
927
|
-
const target = count === 1 && m.fileCount === 0 ? [...m.files][0] : `${count} ${pluralize(count, '
|
|
928
|
-
return
|
|
1456
|
+
const target = count === 1 && m.fileCount === 0 ? [...m.files][0] : `${count} ${pluralize(count, 'file')}`;
|
|
1457
|
+
return `${action} ${target}`;
|
|
929
1458
|
},
|
|
930
1459
|
});
|
|
931
1460
|
if (update.file) metric.files.add(update.file);
|