mixdog 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +355 -22
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +80 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +75 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -19,7 +19,14 @@
|
|
|
19
19
|
* so a shared long-lived pool is appropriate here.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import {
|
|
22
|
+
import { createRequire } from 'node:module'
|
|
23
|
+
|
|
24
|
+
const require = createRequire(import.meta.url)
|
|
25
|
+
let _undici = null
|
|
26
|
+
function undici() {
|
|
27
|
+
if (!_undici) _undici = require('undici')
|
|
28
|
+
return _undici
|
|
29
|
+
}
|
|
23
30
|
|
|
24
31
|
let _agent = null
|
|
25
32
|
let _globalInstalled = false
|
|
@@ -53,7 +60,7 @@ function proxyConfigured() {
|
|
|
53
60
|
if (env.HTTP_PROXY || env.HTTPS_PROXY || env.http_proxy || env.https_proxy) return true
|
|
54
61
|
if (env.NODE_USE_ENV_PROXY) return true
|
|
55
62
|
try {
|
|
56
|
-
const g = getGlobalDispatcher?.()
|
|
63
|
+
const g = undici().getGlobalDispatcher?.()
|
|
57
64
|
// Any non-default global dispatcher (constructor name other than the plain
|
|
58
65
|
// `Agent` undici installs by default) is treated as custom — ProxyAgent,
|
|
59
66
|
// EnvHttpProxyAgent, MockAgent, or a user subclass — and we step aside.
|
|
@@ -77,7 +84,7 @@ function proxyConfigured() {
|
|
|
77
84
|
export function getLlmDispatcher() {
|
|
78
85
|
if (proxyConfigured()) return undefined
|
|
79
86
|
if (!_agent) {
|
|
80
|
-
_agent = new Agent({
|
|
87
|
+
_agent = new (undici().Agent)({
|
|
81
88
|
keepAliveTimeout: envInt('MIXDOG_LLM_KEEPALIVE_MS', 60_000),
|
|
82
89
|
keepAliveMaxTimeout: envInt('MIXDOG_LLM_KEEPALIVE_MAX_MS', 90_000),
|
|
83
90
|
connections: envInt('MIXDOG_LLM_CONNECTIONS', 16),
|
|
@@ -87,7 +94,7 @@ export function getLlmDispatcher() {
|
|
|
87
94
|
// a per-request dispatcher throws UND_ERR_INVALID_ARG. Install globally once
|
|
88
95
|
// and omit the per-request dispatcher. See port-plan D7.
|
|
89
96
|
if (!_globalInstalled) {
|
|
90
|
-
try { setGlobalDispatcher(_agent); _globalInstalled = true } catch { /* fall back */ }
|
|
97
|
+
try { undici().setGlobalDispatcher(_agent); _globalInstalled = true } catch { /* fall back */ }
|
|
91
98
|
}
|
|
92
99
|
return _globalInstalled ? undefined : _agent
|
|
93
100
|
}
|
|
@@ -130,7 +137,7 @@ export function preconnect(origin) {
|
|
|
130
137
|
// A throwaway HEAD lands a pooled socket without fetching a body. Any
|
|
131
138
|
// failure (offline, DNS, 4xx/5xx) is irrelevant — the handshake is the
|
|
132
139
|
// point, and the real request will surface genuine errors.
|
|
133
|
-
|
|
140
|
+
undici().request(key, {
|
|
134
141
|
method: 'HEAD',
|
|
135
142
|
dispatcher: getLlmDispatcher(),
|
|
136
143
|
signal: AbortSignal.timeout(10_000),
|
|
@@ -2,32 +2,29 @@
|
|
|
2
2
|
* Canonical reader for registered schedules.
|
|
3
3
|
*
|
|
4
4
|
* `<Mixdog data dir>/schedules/<name>/` is the single source of truth.
|
|
5
|
-
* Each schedule directory contains `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Each schedule directory contains a single `SCHEDULE.md` — YAML-ish
|
|
6
|
+
* frontmatter (metadata: time/timezone/days/channel/model/enabled) plus a
|
|
7
|
+
* markdown body (the prompt). Both the setup UI (POST /schedules) and the
|
|
8
|
+
* `schedule-add` skill write the same one file; every reader —
|
|
8
9
|
* setup-server (GET /schedules), channels/lib/config.mjs (loadConfig),
|
|
9
10
|
* status/aggregator.mjs — must go through listSchedules() so a single
|
|
10
11
|
* entry shape is presented everywhere.
|
|
11
12
|
*
|
|
12
13
|
* The legacy `mixdog-config.json` `channels.schedules.items` /
|
|
13
14
|
* `channels.nonInteractive` / `channels.interactive` arrays are no longer
|
|
14
|
-
* consulted.
|
|
15
|
-
* — no in-code fallback.
|
|
15
|
+
* consulted. Clean cut: the old `config.json` + `instructions.md` pair is
|
|
16
|
+
* no longer read — no in-code fallback.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import { readFileSync, readdirSync } from 'fs';
|
|
19
20
|
import { join } from 'path';
|
|
20
21
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
22
|
+
import { readMarkdownDocument } from './markdown-frontmatter.mjs';
|
|
21
23
|
|
|
22
24
|
function schedulesDir() {
|
|
23
25
|
return join(resolvePluginData(), 'schedules');
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
function readJsonFile(path) {
|
|
27
|
-
try { return JSON.parse(readFileSync(path, 'utf8')); }
|
|
28
|
-
catch { return null; }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
28
|
function readTextFile(path) {
|
|
32
29
|
try { return readFileSync(path, 'utf8'); }
|
|
33
30
|
catch { return ''; }
|
|
@@ -36,13 +33,13 @@ function readTextFile(path) {
|
|
|
36
33
|
/**
|
|
37
34
|
* List every registered schedule.
|
|
38
35
|
*
|
|
39
|
-
* Return shape per entry: `{ name, ...
|
|
36
|
+
* Return shape per entry: `{ name, ...frontmatter, prompt }`.
|
|
40
37
|
* - `name` is the directory name (slug).
|
|
41
|
-
* - Spread of `
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* - `prompt` carries `
|
|
45
|
-
*
|
|
38
|
+
* - Spread of `SCHEDULE.md` frontmatter keys (time/timezone/days/channel/
|
|
39
|
+
* model/enabled). All frontmatter values arrive as strings; `enabled` is
|
|
40
|
+
* cast to a boolean here so downstream `s.enabled !== false` filters work.
|
|
41
|
+
* - `prompt` carries the `SCHEDULE.md` body (empty string when the file is
|
|
42
|
+
* missing — caller decides whether that is a hard error).
|
|
46
43
|
*
|
|
47
44
|
* Returns an empty array when the directory does not exist (fresh
|
|
48
45
|
* install with no schedules registered yet).
|
|
@@ -62,9 +59,14 @@ export function listSchedules() {
|
|
|
62
59
|
for (const ent of entries) {
|
|
63
60
|
if (!ent.isDirectory()) continue;
|
|
64
61
|
const name = ent.name;
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
62
|
+
const { frontmatter, body } = readMarkdownDocument(readTextFile(join(dir, name, 'SCHEDULE.md')));
|
|
63
|
+
const cfg = { ...frontmatter };
|
|
64
|
+
// Frontmatter is all-strings; cast enabled so `s.enabled !== false`
|
|
65
|
+
// filters (config.mjs, scheduler.mjs) treat `enabled: false` correctly.
|
|
66
|
+
if (Object.prototype.hasOwnProperty.call(cfg, 'enabled')) {
|
|
67
|
+
cfg.enabled = cfg.enabled !== 'false' && cfg.enabled !== false;
|
|
68
|
+
}
|
|
69
|
+
out.push({ name, ...cfg, prompt: body });
|
|
68
70
|
}
|
|
69
71
|
return out;
|
|
70
72
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Shared production of background-task notification envelopes.
|
|
2
|
+
//
|
|
3
|
+
// Producers (shell-jobs fire()/prompt-stall) and consumers (session
|
|
4
|
+
// ingest/manager detection, TUI parsers reading stored sessions) must agree
|
|
5
|
+
// byte-for-byte on the emitted wire format: bracket header fields, the blank
|
|
6
|
+
// line body separator, and the completion instruction wording. To keep one
|
|
7
|
+
// source of truth, the render helpers and the detection regexes live here.
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
toolCompletionInstruction,
|
|
11
|
+
isInternalRuntimeNotificationText,
|
|
12
|
+
isBracketedShellNotificationEnvelope,
|
|
13
|
+
backgroundTaskHeaderStatus,
|
|
14
|
+
} from './tool-execution-contract.mjs';
|
|
15
|
+
|
|
16
|
+
// Re-export the envelope *detection* predicate so producers and consumers can
|
|
17
|
+
// pull render + detect from one module. Detection primitives live in
|
|
18
|
+
// tool-execution-contract.mjs (shouldPersistModelVisibleToolCompletion et al.
|
|
19
|
+
// depend on them there); this keeps a single import surface without forking
|
|
20
|
+
// the regexes.
|
|
21
|
+
export {
|
|
22
|
+
isInternalRuntimeNotificationText,
|
|
23
|
+
isBracketedShellNotificationEnvelope,
|
|
24
|
+
backgroundTaskHeaderStatus,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Render the bracketed shell *completion* envelope body.
|
|
28
|
+
// Byte-compatible with the historical inline assembly in shell-jobs.fire().
|
|
29
|
+
export function renderShellCompletionEnvelope({
|
|
30
|
+
jobId,
|
|
31
|
+
status,
|
|
32
|
+
exitCode = null,
|
|
33
|
+
elapsedMs = null,
|
|
34
|
+
command = null,
|
|
35
|
+
summary = null,
|
|
36
|
+
stdoutPreview = null,
|
|
37
|
+
stderrPreview = null,
|
|
38
|
+
mergeStderr = false,
|
|
39
|
+
} = {}) {
|
|
40
|
+
const header = [
|
|
41
|
+
`[task_id: ${jobId}]`,
|
|
42
|
+
`[status: ${status}]`,
|
|
43
|
+
`[exit: ${exitCode === null ? 'n/a' : exitCode}]`,
|
|
44
|
+
elapsedMs !== null ? `[elapsed: ${elapsedMs} ms]` : null,
|
|
45
|
+
command ? `[command: ${command}]` : null,
|
|
46
|
+
].filter((l) => l !== null);
|
|
47
|
+
const bodySections = [
|
|
48
|
+
summary ? `Summary: ${summary}` : null,
|
|
49
|
+
stdoutPreview ? `\n[stdout preview]\n${stdoutPreview}` : null,
|
|
50
|
+
(mergeStderr !== true && stderrPreview) ? `\n[stderr preview]\n${stderrPreview}` : null,
|
|
51
|
+
].filter((l) => l !== null);
|
|
52
|
+
// Exactly one blank line separates the bracket header block from the body
|
|
53
|
+
// when any body section exists; no trailing blank line when there is none.
|
|
54
|
+
// (Previously the '' separator was filtered out by the value filter, so a
|
|
55
|
+
// summary-only envelope glued headers straight onto `Summary:` and read as
|
|
56
|
+
// bodyless to the `\n\s*\n` body detector.)
|
|
57
|
+
const lines = bodySections.length > 0
|
|
58
|
+
? [...header, '', ...bodySections]
|
|
59
|
+
: header;
|
|
60
|
+
return lines.join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build the shell completion instruction via the shared wording so all async
|
|
64
|
+
// surfaces read identically ("The async shell task … has finished …"). The
|
|
65
|
+
// exit detail is folded into the shared detail slot.
|
|
66
|
+
export function shellCompletionInstruction({ jobId, status, exitCode = null } = {}) {
|
|
67
|
+
return toolCompletionInstruction({
|
|
68
|
+
surface: 'shell',
|
|
69
|
+
id: jobId,
|
|
70
|
+
status,
|
|
71
|
+
detail: `exit ${exitCode === null ? 'n/a' : exitCode}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Render the bracketed shell *prompt-stall* progress envelope body.
|
|
76
|
+
// Byte-compatible with the historical inline assembly in maybeNotifyPromptStall.
|
|
77
|
+
export function renderShellPromptStallEnvelope({
|
|
78
|
+
jobId,
|
|
79
|
+
stalledMs,
|
|
80
|
+
elapsedMs = null,
|
|
81
|
+
command = null,
|
|
82
|
+
tailText = null,
|
|
83
|
+
} = {}) {
|
|
84
|
+
return [
|
|
85
|
+
`[task_id: ${jobId}]`,
|
|
86
|
+
'[status: running]',
|
|
87
|
+
`[stalled: no output growth for ${stalledMs} ms]`,
|
|
88
|
+
(elapsedMs !== null && elapsedMs >= 0) ? `[elapsed: ${elapsedMs} ms]` : null,
|
|
89
|
+
command ? `[command: ${command}]` : null,
|
|
90
|
+
'',
|
|
91
|
+
'This background shell task appears to be waiting for interactive input. Background tasks cannot answer prompts automatically; cancel it or rerun with non-interactive flags/input.',
|
|
92
|
+
tailText ? `\n${tailText}` : null,
|
|
93
|
+
].filter((line) => line !== null && line !== '').join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function shellPromptStallInstruction({ jobId } = {}) {
|
|
97
|
+
return `The background shell task ${jobId} appears to be waiting for interactive input; inspect the prompt, then cancel or rerun it non-interactively.`;
|
|
98
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
renderShellCompletionEnvelope,
|
|
5
|
+
shellCompletionInstruction,
|
|
6
|
+
renderShellPromptStallEnvelope,
|
|
7
|
+
shellPromptStallInstruction,
|
|
8
|
+
isInternalRuntimeNotificationText,
|
|
9
|
+
isBracketedShellNotificationEnvelope,
|
|
10
|
+
} from './task-notification-envelope.mjs';
|
|
11
|
+
|
|
12
|
+
test('completion envelope matches historical assembly plus the corrected blank-line separator', () => {
|
|
13
|
+
const jobId = 'shell_job_1';
|
|
14
|
+
const status = 'completed';
|
|
15
|
+
const exitCode = 0;
|
|
16
|
+
const elapsedMs = 1234;
|
|
17
|
+
const detail = {
|
|
18
|
+
command: 'npm test',
|
|
19
|
+
summary: 'ok',
|
|
20
|
+
stdoutPreview: 'out',
|
|
21
|
+
stderrPreview: 'err',
|
|
22
|
+
mergeStderr: false,
|
|
23
|
+
};
|
|
24
|
+
const expected = [
|
|
25
|
+
`[task_id: ${jobId}]`,
|
|
26
|
+
`[status: ${status}]`,
|
|
27
|
+
`[exit: ${exitCode === null ? 'n/a' : exitCode}]`,
|
|
28
|
+
elapsedMs !== null ? `[elapsed: ${elapsedMs} ms]` : null,
|
|
29
|
+
detail.command ? `[command: ${detail.command}]` : null,
|
|
30
|
+
// The historical inline assembly filtered this '' out, gluing `Summary:`
|
|
31
|
+
// onto the header block; the corrected renderer keeps exactly one blank
|
|
32
|
+
// line between the bracket headers and the body sections.
|
|
33
|
+
'',
|
|
34
|
+
detail.summary ? `Summary: ${detail.summary}` : null,
|
|
35
|
+
detail.stdoutPreview ? `\n[stdout preview]\n${detail.stdoutPreview}` : null,
|
|
36
|
+
(detail.mergeStderr !== true && detail.stderrPreview) ? `\n[stderr preview]\n${detail.stderrPreview}` : null,
|
|
37
|
+
].filter((l) => l !== null).join('\n');
|
|
38
|
+
const actual = renderShellCompletionEnvelope({ jobId, status, exitCode, elapsedMs, ...detail });
|
|
39
|
+
assert.equal(actual, expected);
|
|
40
|
+
assert.equal(isInternalRuntimeNotificationText(actual), true);
|
|
41
|
+
assert.equal(isBracketedShellNotificationEnvelope(actual), true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('summary-only completion envelope keeps one blank line before the body', () => {
|
|
45
|
+
const actual = renderShellCompletionEnvelope({
|
|
46
|
+
jobId: 'shell_job_1',
|
|
47
|
+
status: 'completed',
|
|
48
|
+
exitCode: 0,
|
|
49
|
+
summary: 'ok',
|
|
50
|
+
});
|
|
51
|
+
const expected = [
|
|
52
|
+
'[task_id: shell_job_1]',
|
|
53
|
+
'[status: completed]',
|
|
54
|
+
'[exit: 0]',
|
|
55
|
+
'',
|
|
56
|
+
'Summary: ok',
|
|
57
|
+
].join('\n');
|
|
58
|
+
assert.equal(actual, expected);
|
|
59
|
+
// The blank-line separator must survive so body detectors see a body.
|
|
60
|
+
assert.match(actual, /\n\s*\n/);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('bodyless completion envelope has no trailing blank line', () => {
|
|
64
|
+
const actual = renderShellCompletionEnvelope({
|
|
65
|
+
jobId: 'shell_job_1',
|
|
66
|
+
status: 'completed',
|
|
67
|
+
exitCode: 0,
|
|
68
|
+
});
|
|
69
|
+
assert.equal(actual, ['[task_id: shell_job_1]', '[status: completed]', '[exit: 0]'].join('\n'));
|
|
70
|
+
assert.doesNotMatch(actual, /\n\s*\n/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('completion instruction uses shared async wording + exit detail', () => {
|
|
74
|
+
assert.equal(
|
|
75
|
+
shellCompletionInstruction({ jobId: 'shell_job_1', status: 'completed', exitCode: 0 }),
|
|
76
|
+
'The async shell task shell_job_1 has finished (completed, exit 0) - review this result in your next step.',
|
|
77
|
+
);
|
|
78
|
+
assert.match(shellCompletionInstruction({ jobId: 'x', status: 'failed', exitCode: null }), /exit n\/a/);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('prompt-stall envelope is byte-compatible with historical inline assembly', () => {
|
|
82
|
+
const jobId = 'shell_job_1';
|
|
83
|
+
const stalledMs = 5000;
|
|
84
|
+
const elapsedMs = 6000;
|
|
85
|
+
const tail = { text: 'Password:' };
|
|
86
|
+
const detail = { command: 'ssh host' };
|
|
87
|
+
const expected = [
|
|
88
|
+
`[task_id: ${jobId}]`,
|
|
89
|
+
'[status: running]',
|
|
90
|
+
`[stalled: no output growth for ${stalledMs} ms]`,
|
|
91
|
+
elapsedMs >= 0 ? `[elapsed: ${elapsedMs} ms]` : null,
|
|
92
|
+
detail.command ? `[command: ${detail.command}]` : null,
|
|
93
|
+
'',
|
|
94
|
+
'This background shell task appears to be waiting for interactive input. Background tasks cannot answer prompts automatically; cancel it or rerun with non-interactive flags/input.',
|
|
95
|
+
tail.text ? `\n${tail.text}` : null,
|
|
96
|
+
].filter((line) => line !== null && line !== '').join('\n');
|
|
97
|
+
const actual = renderShellPromptStallEnvelope({ jobId, stalledMs, elapsedMs, command: detail.command, tailText: tail.text });
|
|
98
|
+
assert.equal(actual, expected);
|
|
99
|
+
assert.equal(isInternalRuntimeNotificationText(actual), true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('prompt-stall instruction wording unchanged', () => {
|
|
103
|
+
assert.equal(
|
|
104
|
+
shellPromptStallInstruction({ jobId: 'shell_job_1' }),
|
|
105
|
+
'The background shell task shell_job_1 appears to be waiting for interactive input; inspect the prompt, then cancel or rerun it non-interactively.',
|
|
106
|
+
);
|
|
107
|
+
});
|
|
@@ -33,7 +33,7 @@ function notificationResultBody(text) {
|
|
|
33
33
|
return match ? String(match[1] || '').trim() : '';
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function backgroundTaskHeaderStatus(text) {
|
|
36
|
+
export function backgroundTaskHeaderStatus(text) {
|
|
37
37
|
const match = /^status:\s*(\S+)/mi.exec(String(text || ''));
|
|
38
38
|
return clean(match?.[1]).toLowerCase();
|
|
39
39
|
}
|
|
@@ -86,7 +86,7 @@ export function shouldPersistModelVisibleToolCompletion(text, meta = {}) {
|
|
|
86
86
|
|
|
87
87
|
const BRACKETED_SHELL_STATUS_RE = /^\[status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled|error|timeout|done|success)\]/im;
|
|
88
88
|
|
|
89
|
-
function isBracketedShellNotificationEnvelope(text) {
|
|
89
|
+
export function isBracketedShellNotificationEnvelope(text) {
|
|
90
90
|
const value = String(text ?? '').trim();
|
|
91
91
|
if (!value) return false;
|
|
92
92
|
if (!/^\[task_id:\s*\S+\]/im.test(value)) return false;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { displayModelName as sharedDisplayModelName } from '../../ui/model-display.mjs';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
3
|
+
// Default cap for tool-arg summaries (header parenthetical, channel tool lines).
|
|
4
|
+
// Unified at 80 so every tool surface line — header arg summary and collapsed
|
|
5
|
+
// detail alike — shares one width ceiling; per-call sites still clamp lower
|
|
6
|
+
// (header 48, channel 50) where a tighter line is wanted.
|
|
7
|
+
const DEFAULT_SUMMARY_MAX = 80;
|
|
8
|
+
// Semantic cap for collapsed tool/agent/task card one-liners — the second row
|
|
9
|
+
// under the ⎿ gutter (spawn, send, response, generic/shell result summaries).
|
|
10
|
+
// Kept at 80 so every collapsed detail line is truncated to the same width
|
|
11
|
+
// regardless of terminal columns; ctrl+o expand still shows the full body.
|
|
12
|
+
export const AGENT_SURFACE_BRIEF_MAX = 80;
|
|
6
13
|
const STATUS_SEPARATOR = ' · ';
|
|
7
14
|
|
|
8
15
|
export function stripToolPrefix(name) {
|
|
@@ -205,7 +212,7 @@ function bridgeAgentModelSummary(args) {
|
|
|
205
212
|
const modelId = firstText(args.model);
|
|
206
213
|
const displayHint = firstText(args.modelDisplay, args.model_display, args.displayModel);
|
|
207
214
|
return compactParts([
|
|
208
|
-
displayAgentName(firstText(args.agent, args.
|
|
215
|
+
displayAgentName(firstText(args.agent, args.name, args.subagent_type)),
|
|
209
216
|
displayModelName(modelId, provider, displayHint),
|
|
210
217
|
]);
|
|
211
218
|
}
|
|
@@ -584,6 +591,22 @@ function countNonEmptyLines(text) {
|
|
|
584
591
|
.filter((line) => line.trim()).length;
|
|
585
592
|
}
|
|
586
593
|
|
|
594
|
+
// Zero-result recognizer (audit HIGH): result text that SAYS "nothing found"
|
|
595
|
+
// must summarize as an explicit zero, not be line-counted into "1 match".
|
|
596
|
+
// Matches the shapes emitted by grep/glob/find/list/recall backends:
|
|
597
|
+
// "(no matches)", "no matches found", "(no results)", "No results",
|
|
598
|
+
// "(no fuzzy match for \"...\")", "0 matches", "(empty)", "(no entries)".
|
|
599
|
+
function looksLikeZeroResultText(text) {
|
|
600
|
+
const trimmed = String(text ?? '').trim();
|
|
601
|
+
if (!trimmed) return false;
|
|
602
|
+
// Only trust short, single-line-ish payloads — a real listing that merely
|
|
603
|
+
// CONTAINS the words "no matches" somewhere must not be zeroed.
|
|
604
|
+
if (trimmed.length > 200 || trimmed.includes('\n')) return false;
|
|
605
|
+
return /^\(?\s*(?:no|0)\s+(?:fuzzy\s+)?(?:match(?:es)?|results?|files?|entries|candidates?|hits?)\b/i.test(trimmed)
|
|
606
|
+
|| /^\(?\s*(?:empty|none)\s*\)?$/i.test(trimmed)
|
|
607
|
+
|| /^no\s+\S+\s+(?:found|matched)\b/i.test(trimmed);
|
|
608
|
+
}
|
|
609
|
+
|
|
587
610
|
function splitPathAndDelta(value, explicitDelta = '') {
|
|
588
611
|
let path = String(value ?? '').trim();
|
|
589
612
|
let delta = String(explicitDelta ?? '').trim();
|
|
@@ -734,8 +757,8 @@ function summarizeGenericResult(text) {
|
|
|
734
757
|
if (Array.isArray(parsed)) return `${parsed.length} ${pluralize(parsed.length, 'item')}`;
|
|
735
758
|
if (parsed && typeof parsed === 'object') {
|
|
736
759
|
const status = firstText(parsed.status, parsed.state, parsed.result, parsed.message);
|
|
737
|
-
if (status) return truncateSingleLine(titleStatus(status),
|
|
738
|
-
if (parsed.cwd) return truncateSingleLine(parsed.cwd,
|
|
760
|
+
if (status) return truncateSingleLine(titleStatus(status), AGENT_SURFACE_BRIEF_MAX);
|
|
761
|
+
if (parsed.cwd) return truncateSingleLine(parsed.cwd, AGENT_SURFACE_BRIEF_MAX);
|
|
739
762
|
if (typeof parsed.ok === 'boolean') return parsed.ok ? 'Ok' : 'Failed';
|
|
740
763
|
for (const key of ['items', 'results', 'resources', 'templates', 'providers', 'schedules', 'channels', 'tools']) {
|
|
741
764
|
if (Array.isArray(parsed[key])) return `${parsed[key].length} ${pluralize(parsed[key].length, (key.slice(0, -1) || 'item').toLowerCase())}`;
|
|
@@ -751,7 +774,36 @@ function summarizeGenericResult(text) {
|
|
|
751
774
|
if (/^(ok|done|success|saved|sent|updated|reloaded|connected|enabled|disabled|active|inactive)$/i.test(line)) {
|
|
752
775
|
return titleStatus(line);
|
|
753
776
|
}
|
|
754
|
-
return truncateSingleLine(line,
|
|
777
|
+
return truncateSingleLine(line, AGENT_SURFACE_BRIEF_MAX);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Error-cause extractor (audit HIGH): when a tool result is an error, surface
|
|
781
|
+
// the actual cause line instead of a bare "Failed"/raw first line. Handles
|
|
782
|
+
// JSON error envelopes ({error|message|cause|detail}) and plain-text bodies
|
|
783
|
+
// (first line that carries error-ish signal, else first non-empty line).
|
|
784
|
+
// Exported for ToolExecution's collapsed detail row.
|
|
785
|
+
export function extractErrorCause(resultText) {
|
|
786
|
+
const text = String(resultText ?? '').trim();
|
|
787
|
+
if (!text) return '';
|
|
788
|
+
// JSON envelope: prefer explicit error-ish keys.
|
|
789
|
+
if (text.startsWith('{') || text.startsWith('[')) {
|
|
790
|
+
try {
|
|
791
|
+
const parsed = JSON.parse(text);
|
|
792
|
+
const obj = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
793
|
+
if (obj && typeof obj === 'object') {
|
|
794
|
+
const cause = firstText(
|
|
795
|
+
typeof obj.error === 'string' ? obj.error : obj.error?.message,
|
|
796
|
+
obj.message, obj.cause, obj.detail, obj.reason, obj.status,
|
|
797
|
+
);
|
|
798
|
+
if (cause) return truncateSingleLine(String(cause), AGENT_SURFACE_BRIEF_MAX);
|
|
799
|
+
}
|
|
800
|
+
} catch { /* fall through to text scan */ }
|
|
801
|
+
}
|
|
802
|
+
const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
803
|
+
// Prefer the first line that looks like an error statement.
|
|
804
|
+
const errorish = lines.find((l) => /\b(error|failed|failure|denied|refused|timed?\s*out|timeout|not\s+found|missing|invalid|cannot|can't|exception|exit\s+(?:code\s+)?[1-9])\b/i.test(l));
|
|
805
|
+
const picked = errorish || lines[0] || '';
|
|
806
|
+
return truncateSingleLine(stripInlineMarkdown(picked), AGENT_SURFACE_BRIEF_MAX);
|
|
755
807
|
}
|
|
756
808
|
|
|
757
809
|
/**
|
|
@@ -760,7 +812,13 @@ function summarizeGenericResult(text) {
|
|
|
760
812
|
* reliable can be derived, so the caller falls back to the raw result block.
|
|
761
813
|
*/
|
|
762
814
|
export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
763
|
-
if (isError)
|
|
815
|
+
if (isError) {
|
|
816
|
+
// Audit HIGH: errors used to disable semantic summaries entirely, leaving
|
|
817
|
+
// the UI with a raw first line or a bare "Failed". Surface the extracted
|
|
818
|
+
// cause so the collapsed card answers "why" without ctrl+o.
|
|
819
|
+
const cause = extractErrorCause(resultText);
|
|
820
|
+
return cause || null;
|
|
821
|
+
}
|
|
764
822
|
const text = String(resultText ?? '');
|
|
765
823
|
const trimmed = text.trim();
|
|
766
824
|
if (/^(?:undefined|null)$/i.test(trimmed)) return null;
|
|
@@ -800,18 +858,21 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
800
858
|
}
|
|
801
859
|
case 'grep': {
|
|
802
860
|
if (!trimmed || !looksLineOriented(text)) return null;
|
|
861
|
+
if (looksLikeZeroResultText(text)) return '0 matches';
|
|
803
862
|
const n = countNonEmptyLines(text);
|
|
804
863
|
if (n === 0) return null;
|
|
805
864
|
return `${n} ${pluralize(n, 'match', 'matches')}`;
|
|
806
865
|
}
|
|
807
866
|
case 'glob': {
|
|
808
867
|
if (!trimmed || !looksLineOriented(text)) return null;
|
|
868
|
+
if (looksLikeZeroResultText(text)) return '0 files';
|
|
809
869
|
const n = countNonEmptyLines(text);
|
|
810
870
|
if (n === 0) return null;
|
|
811
871
|
return `${n} ${pluralize(n, 'file')}`;
|
|
812
872
|
}
|
|
813
873
|
case 'find': {
|
|
814
874
|
if (!trimmed || !looksLineOriented(text)) return null;
|
|
875
|
+
if (looksLikeZeroResultText(text)) return '0 candidates';
|
|
815
876
|
const n = countNonEmptyLines(text);
|
|
816
877
|
if (n === 0) return null;
|
|
817
878
|
return `${n} ${pluralize(n, 'candidate')}`;
|
|
@@ -819,6 +880,7 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
819
880
|
case 'list':
|
|
820
881
|
case 'ls': {
|
|
821
882
|
if (!trimmed || !looksLineOriented(text)) return null;
|
|
883
|
+
if (looksLikeZeroResultText(text)) return '0 entries';
|
|
822
884
|
const n = countNonEmptyLines(text);
|
|
823
885
|
if (n === 0) return null;
|
|
824
886
|
return `${n} ${pluralize(n, 'entry', 'entries')}`;
|
|
@@ -840,15 +902,30 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
840
902
|
]);
|
|
841
903
|
}
|
|
842
904
|
const firstLine = trimmed.split('\n').map((line) => line.trim()).find(Boolean) || trimmed;
|
|
843
|
-
return truncateSingleLine(firstLine,
|
|
905
|
+
return truncateSingleLine(firstLine, AGENT_SURFACE_BRIEF_MAX);
|
|
844
906
|
}
|
|
845
907
|
case 'code_graph': {
|
|
846
908
|
const match = /(\d+)\s+(references|definitions|symbols|callers|callees|results|matches)/i.exec(text);
|
|
847
909
|
if (match) return `${match[1]} ${String(match[2]).toLowerCase()}`;
|
|
910
|
+
if (looksLikeZeroResultText(text)) return 'No results';
|
|
848
911
|
return null;
|
|
849
912
|
}
|
|
850
913
|
case 'web_fetch':
|
|
851
914
|
case 'fetch': {
|
|
915
|
+
// Audit HIGH: channel `fetch` (Discord message fetch — args carry
|
|
916
|
+
// channel/messageId/limit, never url/uri) was summarized as a WEB fetch,
|
|
917
|
+
// so its result missed both the status/size probes and fell to raw JSON.
|
|
918
|
+
// Route it to the generic JSON/text summarizer instead.
|
|
919
|
+
if (normalized === 'fetch') {
|
|
920
|
+
const a = parseToolArgs(args);
|
|
921
|
+
const isChannelFetch = !firstText(a.url, a.uri)
|
|
922
|
+
&& Boolean(firstText(a.channel, a.channelId, a.chatId, a.messageId) || a.limit != null);
|
|
923
|
+
if (isChannelFetch) {
|
|
924
|
+
const n = countNonEmptyLines(text);
|
|
925
|
+
if (trimmed && looksLineOriented(text) && n > 0) return `${n} ${pluralize(n, 'message')}`;
|
|
926
|
+
return summarizeGenericResult(text);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
852
929
|
// Status: require a status-like context (HTTP NNN, "Status: NNN",
|
|
853
930
|
// or "NNN OK"/"NNN Not Found") rather than any bare 3-digit number.
|
|
854
931
|
const status = /(?:HTTP[\s/]*\d?\.?\d?\s*|status[:\s]+)([1-5]\d{2})\b/i.exec(text)
|
|
@@ -885,7 +962,15 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
885
962
|
}
|
|
886
963
|
case 'recall':
|
|
887
964
|
case 'search_memories':
|
|
888
|
-
case 'memory':
|
|
965
|
+
case 'memory': {
|
|
966
|
+
if (!trimmed || trimmed === '(no results)' || looksLikeZeroResultText(text)) return 'No Results';
|
|
967
|
+
let n = 0;
|
|
968
|
+
for (const line of text.split('\n')) {
|
|
969
|
+
if (/#\d+\s*$/.test(line)) n += 1;
|
|
970
|
+
}
|
|
971
|
+
if (n > 0) return `${n} ${pluralize(n, 'Memory', 'Memories')}`;
|
|
972
|
+
return summarizeGenericResult(text);
|
|
973
|
+
}
|
|
889
974
|
case 'remember':
|
|
890
975
|
case 'save_memory':
|
|
891
976
|
case 'update_memory':
|
|
@@ -948,18 +1033,18 @@ export function summarizeToolResult(name, args, resultText, isError = false) {
|
|
|
948
1033
|
if (answerLine) return truncateSingleLine(stripInlineMarkdown(answerLine), AGENT_SURFACE_BRIEF_MAX);
|
|
949
1034
|
const task = /^agent task:\s*(\S+)/mi.exec(text);
|
|
950
1035
|
const status = /^status:\s*([^\s(]+)/mi.exec(text);
|
|
951
|
-
const
|
|
1036
|
+
const agent = /^agent:\s*(.+)$/mi.exec(text);
|
|
952
1037
|
const preset = /^preset:\s*(.+)$/mi.exec(text);
|
|
953
1038
|
const model = /^model:\s*(.+)$/mi.exec(text);
|
|
954
1039
|
const limits = /^limits:\s*(.+)$/mi.exec(text);
|
|
955
1040
|
const agentModel = compactParts([
|
|
956
|
-
displayAgentName(
|
|
1041
|
+
displayAgentName(agent ? agent[1] : ''),
|
|
957
1042
|
displayModelName(model ? model[1] : ''),
|
|
958
1043
|
]);
|
|
959
1044
|
if (agentModel) return agentModel;
|
|
960
1045
|
const parts = [
|
|
961
1046
|
task ? task[1] : '',
|
|
962
|
-
|
|
1047
|
+
agent ? agent[1] : '',
|
|
963
1048
|
preset ? preset[1] : '',
|
|
964
1049
|
model ? model[1] : '',
|
|
965
1050
|
status ? titleStatus(status[1]) : '',
|