mixdog 0.8.0 → 0.9.0
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 +33 -27
- 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 +344 -0
- 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 +1223 -95
- 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 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- 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 +118 -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 +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -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 +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- 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 +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- 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 +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- 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 +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- 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/snapshot-store.mjs +1 -1
- 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 +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- 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/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- 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 +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -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 +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -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 +145 -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 -98
- 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 +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- 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 +367 -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 +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- 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 +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -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 +746 -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 +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- 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/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/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/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', '
|
|
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'],
|
|
@@ -751,15 +1046,12 @@ const TOOL_CATEGORY = new Map([
|
|
|
751
1046
|
['memory', 'Memory'],
|
|
752
1047
|
['explore', 'Explore'],
|
|
753
1048
|
['apply_patch', 'Patch'],
|
|
754
|
-
['write', 'Edit'],
|
|
755
|
-
['edit', 'Edit'],
|
|
756
1049
|
['bash', 'Shell'],
|
|
757
1050
|
['shell', 'Shell'],
|
|
758
1051
|
['shell_command', 'Shell'],
|
|
759
1052
|
['bash_session', 'Shell'],
|
|
760
1053
|
['job_wait', 'Shell'],
|
|
761
|
-
['task', '
|
|
762
|
-
['bridge', 'Agent'],
|
|
1054
|
+
['task', 'Task'],
|
|
763
1055
|
['agent', 'Agent'],
|
|
764
1056
|
['reply', 'Channel'],
|
|
765
1057
|
['react', 'Channel'],
|
|
@@ -778,16 +1070,17 @@ const TOOL_CATEGORY = new Map([
|
|
|
778
1070
|
['cwd', 'Setup'],
|
|
779
1071
|
['request_user_input', 'Setup'],
|
|
780
1072
|
['update_plan', 'Setup'],
|
|
781
|
-
['trigger_schedule', '
|
|
782
|
-
['skill', '
|
|
783
|
-
['skill_execute', '
|
|
784
|
-
['skill_view', '
|
|
785
|
-
['skills_list', '
|
|
786
|
-
['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'],
|
|
787
1079
|
]);
|
|
788
1080
|
|
|
789
1081
|
/** Return the aggregate category for a tool name + args. */
|
|
790
1082
|
export function classifyToolCategory(name, args = {}) {
|
|
1083
|
+
if (isMcpToolName(name)) return 'MCP';
|
|
791
1084
|
const normalized = normalizeToolName(name);
|
|
792
1085
|
if (normalized === 'code_graph') {
|
|
793
1086
|
const mode = String(args.mode || args.action || '').toLowerCase();
|
|
@@ -798,17 +1091,21 @@ export function classifyToolCategory(name, args = {}) {
|
|
|
798
1091
|
}
|
|
799
1092
|
|
|
800
1093
|
const CATEGORY_COPY = new Map([
|
|
801
|
-
['Read', { active: 'Reading', done: 'Read', noun: '
|
|
802
|
-
['Search', { active: 'Searching', done: 'Searched', noun: '
|
|
803
|
-
['
|
|
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' }],
|
|
804
1100
|
['Memory', { active: 'Checking', done: 'Checked', noun: 'memory item' }],
|
|
805
|
-
['Explore', { active: 'Exploring', done: 'Explored', noun: '
|
|
806
|
-
['Patch', { active: 'Editing', done: 'Edited', noun: '
|
|
807
|
-
['Edit', { active: 'Editing', done: 'Edited', noun: 'item' }],
|
|
1101
|
+
['Explore', { active: 'Exploring', done: 'Explored', noun: 'query', pluralNoun: 'queries' }],
|
|
1102
|
+
['Patch', { active: 'Editing', done: 'Edited', noun: 'file' }],
|
|
808
1103
|
['Shell', { active: 'Running', done: 'Ran', noun: 'command' }],
|
|
809
1104
|
['Agent', { active: 'Calling', done: 'Called', noun: 'agent' }],
|
|
1105
|
+
['Task', { active: 'Checking', done: 'Checked', noun: 'task' }],
|
|
1106
|
+
['Schedule', { active: 'Running', done: 'Ran', noun: 'schedule' }],
|
|
810
1107
|
['Channel', { active: 'Sending', done: 'Sent', noun: 'message' }],
|
|
811
|
-
['Setup', { active: 'Setting
|
|
1108
|
+
['Setup', { active: 'Setting up', done: 'Set up', noun: 'item' }],
|
|
812
1109
|
['Other', { active: 'Calling', done: 'Called', noun: 'tool' }],
|
|
813
1110
|
]);
|
|
814
1111
|
|
|
@@ -826,17 +1123,241 @@ function categoryNoun(category, count) {
|
|
|
826
1123
|
return pluralize(count, copy.noun, copy.pluralNoun || `${copy.noun}s`);
|
|
827
1124
|
}
|
|
828
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
|
+
|
|
829
1350
|
/**
|
|
830
1351
|
* Build a comma-separated header from per-category counts.
|
|
831
1352
|
* e.g. "Read 6 items, Searched 5 items, Called 1 agent"
|
|
832
1353
|
*/
|
|
833
|
-
export function formatAggregateHeader(categories, { pending = false, order = null } = {}) {
|
|
1354
|
+
export function formatAggregateHeader(categories, { pending = false, order = null, stableVerbWidth = false } = {}) {
|
|
834
1355
|
const categoryKeys = Object.keys(categories || {});
|
|
835
1356
|
const preferred = Array.isArray(order) && order.length ? order : categoryKeys;
|
|
836
1357
|
const seen = new Set();
|
|
837
1358
|
const ordered = [];
|
|
838
1359
|
const add = (cat) => {
|
|
839
|
-
if (!cat || seen.has(cat) || (categories[cat]
|
|
1360
|
+
if (!cat || seen.has(cat) || aggregateCount(categories[cat]) <= 0) return;
|
|
840
1361
|
seen.add(cat);
|
|
841
1362
|
ordered.push(cat);
|
|
842
1363
|
};
|
|
@@ -846,10 +1367,9 @@ export function formatAggregateHeader(categories, { pending = false, order = nul
|
|
|
846
1367
|
|
|
847
1368
|
return ordered
|
|
848
1369
|
.map((cat) => {
|
|
849
|
-
const
|
|
850
|
-
const label = pending
|
|
851
|
-
|
|
852
|
-
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)}`;
|
|
853
1373
|
})
|
|
854
1374
|
.join(', ');
|
|
855
1375
|
}
|
|
@@ -885,13 +1405,13 @@ export function formatAggregateDetail(summaries) {
|
|
|
885
1405
|
|
|
886
1406
|
let match = /^(?:Read\s+)?(\d+)\s+lines?$/i.exec(text);
|
|
887
1407
|
if (match) {
|
|
888
|
-
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')}` });
|
|
889
1409
|
metric.count += Number(match[1]);
|
|
890
1410
|
continue;
|
|
891
1411
|
}
|
|
892
1412
|
|
|
893
1413
|
if (/^(?:Read\s+)?image$/i.test(text)) {
|
|
894
|
-
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')}` });
|
|
895
1415
|
metric.count += 1;
|
|
896
1416
|
continue;
|
|
897
1417
|
}
|
|
@@ -902,7 +1422,7 @@ export function formatAggregateDetail(summaries) {
|
|
|
902
1422
|
const singular = nounRaw.endsWith('ies') ? `${nounRaw.slice(0, -3)}y` : nounRaw.endsWith('s') ? nounRaw.slice(0, -1) : nounRaw;
|
|
903
1423
|
const plural = nounRaw.endsWith('s') ? nounRaw : `${nounRaw}s`;
|
|
904
1424
|
const key = `found_${plural}`;
|
|
905
|
-
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)}` });
|
|
906
1426
|
metric.count += Number(match[1]);
|
|
907
1427
|
continue;
|
|
908
1428
|
}
|
|
@@ -925,10 +1445,16 @@ export function formatAggregateDetail(summaries) {
|
|
|
925
1445
|
removed: 0,
|
|
926
1446
|
seen: false,
|
|
927
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;
|
|
928
1454
|
const count = m.fileCount + m.files.size;
|
|
929
1455
|
const action = m.actions.size === 1 ? [...m.actions][0] : 'Updated';
|
|
930
|
-
const target = count === 1 && m.fileCount === 0 ? [...m.files][0] : `${count} ${pluralize(count, '
|
|
931
|
-
return
|
|
1456
|
+
const target = count === 1 && m.fileCount === 0 ? [...m.files][0] : `${count} ${pluralize(count, 'file')}`;
|
|
1457
|
+
return `${action} ${target}`;
|
|
932
1458
|
},
|
|
933
1459
|
});
|
|
934
1460
|
if (update.file) metric.files.add(update.file);
|