mixdog 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +23 -101
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/agent-job-feed.mjs — agent-job / runtime-notification plumbing
|
|
3
|
+
* extracted from createEngineSession (engine.mjs) as a dependency-injection
|
|
4
|
+
* factory.
|
|
5
|
+
*
|
|
6
|
+
* Owns: the agent-job card result patch (updateAgentJobCard), the
|
|
7
|
+
* execution-pending-resume kick trio (kick / flushDeferred / schedule), and the
|
|
8
|
+
* runtime.onNotification subscription that routes runtime notifications into the
|
|
9
|
+
* store (status refresh, execution-ui synthetic items, model-visible enqueue).
|
|
10
|
+
*
|
|
11
|
+
* These handlers mutate live session state and drive the queue, so
|
|
12
|
+
* state/set/enqueue/drain/pushUserOrSyntheticItem/patchItem/etc are threaded via
|
|
13
|
+
* the factory argument (getters/callbacks) — never stale snapshots. Every body
|
|
14
|
+
* is the original engine.mjs logic verbatim.
|
|
15
|
+
*/
|
|
16
|
+
import {
|
|
17
|
+
parseAgentJob,
|
|
18
|
+
agentJobResultText,
|
|
19
|
+
agentArgsWithResultMetadata,
|
|
20
|
+
} from './agent-envelope.mjs';
|
|
21
|
+
import { toolErrorDisplay } from './tool-result-text.mjs';
|
|
22
|
+
import {
|
|
23
|
+
notificationQueueKey,
|
|
24
|
+
resolveTuiRuntimeNotificationDelivery,
|
|
25
|
+
} from './notification-plan.mjs';
|
|
26
|
+
|
|
27
|
+
export function createAgentJobFeed({
|
|
28
|
+
runtime,
|
|
29
|
+
getState,
|
|
30
|
+
set,
|
|
31
|
+
nextId,
|
|
32
|
+
getDisposed,
|
|
33
|
+
patchItem,
|
|
34
|
+
enqueue,
|
|
35
|
+
drain,
|
|
36
|
+
pushUserOrSyntheticItem,
|
|
37
|
+
makeQueueEntry,
|
|
38
|
+
getPending,
|
|
39
|
+
agentStatusState,
|
|
40
|
+
displayedExecutionNotificationKeys,
|
|
41
|
+
}) {
|
|
42
|
+
let executionResumeKickDeferred = false;
|
|
43
|
+
|
|
44
|
+
function kickExecutionPendingResume() {
|
|
45
|
+
if (getDisposed()) return;
|
|
46
|
+
if (getState().busy) {
|
|
47
|
+
executionResumeKickDeferred = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const pending = getPending();
|
|
51
|
+
if (pending.some((entry) => entry.mode === 'pending-resume')) {
|
|
52
|
+
executionResumeKickDeferred = true;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
executionResumeKickDeferred = false;
|
|
56
|
+
pending.push(makeQueueEntry('', { mode: 'pending-resume', priority: 'next' }));
|
|
57
|
+
void drain();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function flushDeferredExecutionPendingResumeKick() {
|
|
61
|
+
if (!executionResumeKickDeferred || getDisposed() || getState().busy) return;
|
|
62
|
+
kickExecutionPendingResume();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function scheduleExecutionPendingResumeKick() {
|
|
66
|
+
// notifyFnForSession enqueues the model-visible body after onNotification
|
|
67
|
+
// returns; defer the kick so askSession pre-drain sees session pending.
|
|
68
|
+
queueMicrotask(() => kickExecutionPendingResume());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function updateAgentJobCard(itemId, text, isError = false) {
|
|
72
|
+
const parsed = parseAgentJob(text);
|
|
73
|
+
const current = getState().items.find((it) => it.id === itemId);
|
|
74
|
+
const rawDisplayText = agentJobResultText(text, parsed) || String(text ?? '').trim();
|
|
75
|
+
const displayText = isError ? toolErrorDisplay(rawDisplayText, 'agent') : rawDisplayText;
|
|
76
|
+
patchItem(itemId, {
|
|
77
|
+
result: displayText,
|
|
78
|
+
text: displayText,
|
|
79
|
+
isError,
|
|
80
|
+
errorCount: isError ? 1 : 0,
|
|
81
|
+
...(parsed ? { args: agentArgsWithResultMetadata(current?.args, parsed) } : {}),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function subscribeRuntimeNotifications() {
|
|
86
|
+
if (typeof runtime.onNotification !== 'function') return null;
|
|
87
|
+
return runtime.onNotification((event) => {
|
|
88
|
+
if (getDisposed()) return;
|
|
89
|
+
const text = String(event?.content ?? event?.text ?? event ?? '').trim();
|
|
90
|
+
if (!text) return;
|
|
91
|
+
const parsed = parseAgentJob(text);
|
|
92
|
+
const notificationKey = notificationQueueKey(event, text, parsed);
|
|
93
|
+
const delivery = resolveTuiRuntimeNotificationDelivery(event, text);
|
|
94
|
+
if (delivery.action === 'ignore') return;
|
|
95
|
+
if (delivery.action === 'status-only') {
|
|
96
|
+
if (parsed?.taskId) set(agentStatusState({ force: true }));
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (delivery.action === 'execution-ui') {
|
|
100
|
+
const firstDelivery = !notificationKey || !displayedExecutionNotificationKeys.has(notificationKey);
|
|
101
|
+
if (firstDelivery) {
|
|
102
|
+
if (notificationKey) displayedExecutionNotificationKeys.add(notificationKey);
|
|
103
|
+
pushUserOrSyntheticItem(delivery.displayText, nextId());
|
|
104
|
+
}
|
|
105
|
+
if (parsed?.taskId) set(agentStatusState({ force: true }));
|
|
106
|
+
if (String(delivery.modelContent || '').trim()) {
|
|
107
|
+
scheduleExecutionPendingResumeKick();
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (parsed?.taskId) {
|
|
112
|
+
set(agentStatusState({ force: true }));
|
|
113
|
+
}
|
|
114
|
+
const modelContent = String(delivery.modelContent ?? delivery.displayText ?? text).trim();
|
|
115
|
+
if (!modelContent) return true;
|
|
116
|
+
enqueue(modelContent, {
|
|
117
|
+
mode: 'task-notification',
|
|
118
|
+
priority: 'next',
|
|
119
|
+
key: notificationKey || undefined,
|
|
120
|
+
displayText: delivery.displayText || text,
|
|
121
|
+
});
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
kickExecutionPendingResume,
|
|
128
|
+
flushDeferredExecutionPendingResumeKick,
|
|
129
|
+
scheduleExecutionPendingResumeKick,
|
|
130
|
+
updateAgentJobCard,
|
|
131
|
+
subscribeRuntimeNotifications,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/notification-plan.mjs - pure runtime.onNotification delivery plan.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from engine.mjs (no behavior change). These are stateless helpers
|
|
5
|
+
* that decide how a runtime notification envelope should be delivered to the
|
|
6
|
+
* TUI store (dedupe key + display/model routing). No closures over engine state.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
isStatusOnlyAgentCompletionNotification,
|
|
10
|
+
parseAgentJob,
|
|
11
|
+
parseAgentResultEnvelope,
|
|
12
|
+
parseBackgroundTaskEnvelope,
|
|
13
|
+
parseSyntheticAgentMessage,
|
|
14
|
+
} from './agent-envelope.mjs';
|
|
15
|
+
import { shortTextFingerprint } from './queue-helpers.mjs';
|
|
16
|
+
import { modelVisibleToolCompletionMessage } from '../../runtime/shared/tool-execution-contract.mjs';
|
|
17
|
+
|
|
18
|
+
export function notificationQueueKey(event, text, parsed) {
|
|
19
|
+
const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
|
|
20
|
+
const synthetic = parseSyntheticAgentMessage(text);
|
|
21
|
+
if (synthetic?.name === 'agent' && String(synthetic.args?.type || '').toLowerCase() === 'result') {
|
|
22
|
+
const taskId = String(synthetic.args?.task_id || '').trim();
|
|
23
|
+
const executionId = String(meta.execution_id || '').trim();
|
|
24
|
+
const tag = String(synthetic.args?.tag || '').trim();
|
|
25
|
+
const resultId = taskId
|
|
26
|
+
? `task:${taskId}`
|
|
27
|
+
: executionId
|
|
28
|
+
? `exec:${executionId}`
|
|
29
|
+
: tag
|
|
30
|
+
? `tag:${tag}:${shortTextFingerprint(synthetic.result || text)}`
|
|
31
|
+
: '';
|
|
32
|
+
const agent = String(synthetic.args?.agent || '').trim();
|
|
33
|
+
if (resultId || agent) return ['agent-result', resultId, agent].filter(Boolean).join(':');
|
|
34
|
+
}
|
|
35
|
+
const id = String(meta.execution_id || parsed?.taskId || '').trim();
|
|
36
|
+
if (!id) return '';
|
|
37
|
+
const type = String(meta.type || '').trim();
|
|
38
|
+
const status = String(meta.status || parsed?.status || '').trim();
|
|
39
|
+
const fallbackKind = String(text || '').split('\n', 1)[0]?.trim() || 'notification';
|
|
40
|
+
// Distinguish a body-carrying completion from a header-only preview that
|
|
41
|
+
// shares the same id/type/status. An early agent preview can arrive before
|
|
42
|
+
// the session is persisted (no result body); the canonical notification that
|
|
43
|
+
// follows DOES carry the body. Without this dimension the bodyless preview
|
|
44
|
+
// would claim the dedupe key and suppress the real result. A blank-line gap
|
|
45
|
+
// separates the task header block from the result body in the envelope.
|
|
46
|
+
const hasBody = /\n\s*\n[\s\S]*\S/.test(String(text || '')) ? 'b1' : 'b0';
|
|
47
|
+
return [id, type || fallbackKind, status, hasBody].filter(Boolean).join(':');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isExecutionNotification(event, text, parsed) {
|
|
51
|
+
const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
|
|
52
|
+
if (meta.execution_id || meta.execution_surface) return true;
|
|
53
|
+
if (parseAgentResultEnvelope(text)) return true;
|
|
54
|
+
if (parseBackgroundTaskEnvelope(text)) return true;
|
|
55
|
+
return Boolean(parsed?.taskId && /^(?:agent task:|task_id:)/mi.test(String(text || '')));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Pure delivery plan for runtime.onNotification execution envelopes (tests + handler). */
|
|
59
|
+
export function resolveTuiRuntimeNotificationDelivery(event, text) {
|
|
60
|
+
const trimmed = String(text ?? '').trim();
|
|
61
|
+
if (!trimmed) return { action: 'ignore' };
|
|
62
|
+
const parsed = parseAgentJob(trimmed);
|
|
63
|
+
const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
|
|
64
|
+
if (!isExecutionNotification(event, trimmed, parsed)) {
|
|
65
|
+
return { action: 'enqueue', displayText: trimmed, modelContent: trimmed };
|
|
66
|
+
}
|
|
67
|
+
if (isStatusOnlyAgentCompletionNotification(trimmed)) {
|
|
68
|
+
return { action: 'status-only', displayText: trimmed, modelContent: '' };
|
|
69
|
+
}
|
|
70
|
+
const modelContent = modelVisibleToolCompletionMessage(trimmed, meta);
|
|
71
|
+
return {
|
|
72
|
+
action: 'execution-ui',
|
|
73
|
+
displayText: trimmed,
|
|
74
|
+
modelContent,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/render-timing.mjs - render-throttle timing helper.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from engine.mjs (no behavior change).
|
|
5
|
+
*
|
|
6
|
+
* Ink renders through a maxFps throttle (120fps in index.jsx, ≈8.3ms). A plain
|
|
7
|
+
* setImmediate only yields to the event loop; if Ink already painted within the
|
|
8
|
+
* current throttle window, the next paint may still be queued and our following
|
|
9
|
+
* transcript mutation can coalesce into the same visible frame. Wait just past
|
|
10
|
+
* one render window when we intentionally split transcript commits for visual
|
|
11
|
+
* stability (preamble frame → tool-card frame).
|
|
12
|
+
*/
|
|
13
|
+
export const RENDER_THROTTLE_FLUSH_MS = 12;
|
|
14
|
+
|
|
15
|
+
export const yieldToRenderer = () => new Promise((resolve) => {
|
|
16
|
+
setTimeout(resolve, RENDER_THROTTLE_FLUSH_MS);
|
|
17
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/tool-approval.mjs — tool-approval queue plumbing extracted
|
|
3
|
+
* from createEngineSession (engine.mjs) as a dependency-injection factory.
|
|
4
|
+
*
|
|
5
|
+
* These handlers drive a small FIFO of hook approval requests and reflect the
|
|
6
|
+
* active one into store state (state.toolApproval). They need live session
|
|
7
|
+
* state, so the closure identifiers (state/set/nextId/disposed/timeout) are
|
|
8
|
+
* threaded through the factory argument via getters — never stale snapshots.
|
|
9
|
+
* Every body is the original engine.mjs logic verbatim.
|
|
10
|
+
*/
|
|
11
|
+
export function createToolApproval({ getState, set, nextId, getDisposed, timeoutMs }) {
|
|
12
|
+
const toolApprovalQueue = [];
|
|
13
|
+
let activeToolApproval = null;
|
|
14
|
+
|
|
15
|
+
function normalizeToolApprovalRequest(input = {}, id = nextId()) {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
const requestTimeoutMs = timeoutMs;
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
name: String(input?.name || input?.tool_name || 'tool'),
|
|
21
|
+
args: input?.args ?? input?.tool_input ?? null,
|
|
22
|
+
cwd: input?.cwd || null,
|
|
23
|
+
sessionId: input?.sessionId || input?.session_id || null,
|
|
24
|
+
toolCallId: input?.toolCallId || input?.tool_use_id || null,
|
|
25
|
+
reason: String(input?.reason || input?.message || 'approval requested by hook').trim(),
|
|
26
|
+
requestedAt: now,
|
|
27
|
+
timeoutMs: requestTimeoutMs,
|
|
28
|
+
expiresAt: now + requestTimeoutMs,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function presentNextToolApproval() {
|
|
32
|
+
if (activeToolApproval || getDisposed()) return;
|
|
33
|
+
const entry = toolApprovalQueue.shift();
|
|
34
|
+
if (!entry) {
|
|
35
|
+
if (getState().toolApproval) set({ toolApproval: null });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
activeToolApproval = entry;
|
|
39
|
+
entry.timer = setTimeout(() => {
|
|
40
|
+
finishToolApproval(entry.id, false, 'approval timed out');
|
|
41
|
+
}, entry.request.timeoutMs);
|
|
42
|
+
entry.timer.unref?.();
|
|
43
|
+
set({ toolApproval: entry.request });
|
|
44
|
+
}
|
|
45
|
+
function finishToolApproval(id, approved, reason = '') {
|
|
46
|
+
const targetId = String(id || '');
|
|
47
|
+
if (activeToolApproval && activeToolApproval.id === targetId) {
|
|
48
|
+
const entry = activeToolApproval;
|
|
49
|
+
activeToolApproval = null;
|
|
50
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
51
|
+
set({ toolApproval: null });
|
|
52
|
+
try { entry.resolve({ approved: approved === true, reason: String(reason || '') }); } catch {}
|
|
53
|
+
presentNextToolApproval();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const index = toolApprovalQueue.findIndex((entry) => entry.id === targetId);
|
|
57
|
+
if (index >= 0) {
|
|
58
|
+
const [entry] = toolApprovalQueue.splice(index, 1);
|
|
59
|
+
if (entry?.timer) clearTimeout(entry.timer);
|
|
60
|
+
try { entry.resolve({ approved: approved === true, reason: String(reason || '') }); } catch {}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function denyAllToolApprovals(reason = 'approval cancelled') {
|
|
66
|
+
if (activeToolApproval) {
|
|
67
|
+
const entry = activeToolApproval;
|
|
68
|
+
activeToolApproval = null;
|
|
69
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
70
|
+
try { entry.resolve({ approved: false, reason }); } catch {}
|
|
71
|
+
}
|
|
72
|
+
while (toolApprovalQueue.length > 0) {
|
|
73
|
+
const entry = toolApprovalQueue.shift();
|
|
74
|
+
if (entry?.timer) clearTimeout(entry.timer);
|
|
75
|
+
try { entry.resolve({ approved: false, reason }); } catch {}
|
|
76
|
+
}
|
|
77
|
+
if (getState().toolApproval) set({ toolApproval: null });
|
|
78
|
+
}
|
|
79
|
+
function requestToolApproval(input = {}) {
|
|
80
|
+
if (getDisposed()) return Promise.resolve({ approved: false, reason: 'runtime disposed' });
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const id = nextId();
|
|
83
|
+
toolApprovalQueue.push({ id, request: normalizeToolApprovalRequest(input, id), resolve, timer: null });
|
|
84
|
+
presentNextToolApproval();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
presentNextToolApproval,
|
|
90
|
+
finishToolApproval,
|
|
91
|
+
denyAllToolApprovals,
|
|
92
|
+
requestToolApproval,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/tool-card-results.mjs — the tool-card result state machine
|
|
3
|
+
* (patchToolCardResult + flushToolResults) extracted from createEngineSession
|
|
4
|
+
* (engine.mjs) as a dependency-injection factory.
|
|
5
|
+
*
|
|
6
|
+
* These handlers own the per-turn accounting that reflects tool results into
|
|
7
|
+
* store items: aggregate cards, non-aggregate/legacy agent-job cards, grouped
|
|
8
|
+
* fallbacks, and the finalize/cancelled sweeps. They mutate live session state,
|
|
9
|
+
* so state/set/patchItem/markToolCallDone/updateAgentJobCard are threaded via
|
|
10
|
+
* the factory argument (getters/callbacks) — never stale snapshots. Every body
|
|
11
|
+
* is the original engine.mjs logic verbatim.
|
|
12
|
+
*/
|
|
13
|
+
import { summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
|
|
14
|
+
import { toolResultText, toolErrorDisplay, toolGroupedDisplayFallback } from './tool-result-text.mjs';
|
|
15
|
+
import { toolResultCallId } from './tool-call-fields.mjs';
|
|
16
|
+
import { parseAgentJob, agentArgsWithResultMetadata, toolResultStatus, isErrorToolStatus } from './agent-envelope.mjs';
|
|
17
|
+
import {
|
|
18
|
+
withCancelledResultMarker,
|
|
19
|
+
groupedToolResultText,
|
|
20
|
+
aggregateRawResult,
|
|
21
|
+
aggregateSummaries,
|
|
22
|
+
assignAggregateSummaryOrder,
|
|
23
|
+
} from './tool-result-status.mjs';
|
|
24
|
+
import { formatAggregateDetail } from '../../runtime/shared/tool-surface.mjs';
|
|
25
|
+
|
|
26
|
+
export function createToolCardResults({
|
|
27
|
+
getState,
|
|
28
|
+
set,
|
|
29
|
+
patchItem,
|
|
30
|
+
markToolCallDone,
|
|
31
|
+
updateAgentJobCard,
|
|
32
|
+
agentStatusState,
|
|
33
|
+
}) {
|
|
34
|
+
function patchToolCardResult(card, message, toolGroups, done) {
|
|
35
|
+
if (!card || card.done) return false;
|
|
36
|
+
const callId = toolResultCallId(message) || card.callId;
|
|
37
|
+
if (callId && done.has(callId)) return false;
|
|
38
|
+
// Any resolving call clears its active-summary entry (keyed by the same
|
|
39
|
+
// callKey used at markToolCallActive; card.callId holds it for both branches).
|
|
40
|
+
markToolCallDone(card.callId);
|
|
41
|
+
// A result for this card arrived (possibly before its deferred push delay
|
|
42
|
+
// elapsed) — surface the card now so the patch below has a live item and the
|
|
43
|
+
// fast tool paints a completed card directly, no pending placeholder stage.
|
|
44
|
+
// ensureVisible flushes this card AND every earlier-created still-deferred
|
|
45
|
+
// card in order, so transcript order always matches call order.
|
|
46
|
+
(card.aggregate?.ensureVisible || card.ensureVisible)?.();
|
|
47
|
+
const rawText = toolResultText(message?.content);
|
|
48
|
+
const isError = message?.isError === true || message?.toolKind === 'error' || /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText));
|
|
49
|
+
const text = isError ? toolErrorDisplay(rawText, card?.name || 'tool') : rawText;
|
|
50
|
+
|
|
51
|
+
// Aggregate card handling — collect semantic summaries per call
|
|
52
|
+
const aggregate = card.aggregate;
|
|
53
|
+
if (aggregate && card.itemId === aggregate.itemId) {
|
|
54
|
+
const callRec = callId ? aggregate.calls.get(callId) : null;
|
|
55
|
+
if (!callRec) return false;
|
|
56
|
+
if (callRec.resolved) {
|
|
57
|
+
card.done = true;
|
|
58
|
+
if (callId) done.add(callId);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
callRec.summary = !isError ? summarizeToolResult(callRec.name, callRec.args, rawText, isError) : null;
|
|
62
|
+
assignAggregateSummaryOrder(aggregate, callRec);
|
|
63
|
+
callRec.isError = isError;
|
|
64
|
+
callRec.resultText = text;
|
|
65
|
+
callRec.resolved = true;
|
|
66
|
+
const allCalls = [...aggregate.calls.values()];
|
|
67
|
+
const completed = allCalls.filter((r) => r.resolved).length;
|
|
68
|
+
const errors = allCalls.filter((r) => r.isError).length;
|
|
69
|
+
// Collapsed detail carries the merged per-call count summary
|
|
70
|
+
// ("512 lines, 6 matches, 3 files") so the finished card answers "how
|
|
71
|
+
// much" without ctrl+o. Failures keep a bare 'N Ok · N Failed' status so
|
|
72
|
+
// an error stays visible while collapsed.
|
|
73
|
+
const succeeded = completed - errors;
|
|
74
|
+
const detailText = errors > 0
|
|
75
|
+
? (succeeded > 0 ? `${succeeded} Ok · ${errors} Failed` : `${errors} Failed`)
|
|
76
|
+
: formatAggregateDetail(aggregateSummaries(aggregate));
|
|
77
|
+
const currentItem = getState().items.find((it) => it.id === card.itemId);
|
|
78
|
+
const earlyCompleted = allCalls.filter((r) => r.resolved || r.completedEarly).length;
|
|
79
|
+
const visualCompleted = Math.max(completed, earlyCompleted, Math.min(allCalls.length, Number(currentItem?.completedCount || 0)));
|
|
80
|
+
const rawResult = aggregateRawResult(allCalls);
|
|
81
|
+
// The numbered+labelled raw (rawResult) is preserved for ctrl+o expansion.
|
|
82
|
+
const displayDetail = detailText;
|
|
83
|
+
patchItem(card.itemId, {
|
|
84
|
+
result: displayDetail,
|
|
85
|
+
text: displayDetail,
|
|
86
|
+
rawResult: rawResult || null,
|
|
87
|
+
isError: errors > 0,
|
|
88
|
+
errorCount: errors,
|
|
89
|
+
count: allCalls.length,
|
|
90
|
+
completedCount: visualCompleted,
|
|
91
|
+
completedAt: Number(currentItem?.completedAt) || Date.now(),
|
|
92
|
+
});
|
|
93
|
+
card.done = true;
|
|
94
|
+
if (callId) done.add(callId);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Non-aggregate (legacy agent-job cards, etc.)
|
|
99
|
+
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
|
|
100
|
+
group.completed = Math.min(group.count, group.completed + 1);
|
|
101
|
+
group.errors += isError ? 1 : 0;
|
|
102
|
+
group.results.push({ text, isError });
|
|
103
|
+
toolGroups.set(card.itemId, group);
|
|
104
|
+
const resultText = groupedToolResultText(group);
|
|
105
|
+
const displayResult = toolGroupedDisplayFallback(resultText, text, rawText);
|
|
106
|
+
const patch = {
|
|
107
|
+
result: displayResult,
|
|
108
|
+
text: displayResult,
|
|
109
|
+
isError: group.errors > 0,
|
|
110
|
+
errorCount: group.errors,
|
|
111
|
+
count: group.count,
|
|
112
|
+
completedCount: group.completed,
|
|
113
|
+
completedAt: Date.now(),
|
|
114
|
+
};
|
|
115
|
+
if (group.count <= 1) {
|
|
116
|
+
const body = String(text || rawText || '').trim();
|
|
117
|
+
if (body) patch.rawResult = text || rawText;
|
|
118
|
+
const parsedAgent = parseAgentJob(rawText);
|
|
119
|
+
if (parsedAgent) {
|
|
120
|
+
patch.args = agentArgsWithResultMetadata(getState().items.find((it) => it.id === card.itemId)?.args, parsedAgent);
|
|
121
|
+
set(agentStatusState({ force: true }));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
patchItem(card.itemId, patch);
|
|
125
|
+
if (group.count <= 1) updateAgentJobCard(card.itemId, rawText, isError);
|
|
126
|
+
card.done = true;
|
|
127
|
+
if (callId) done.add(callId);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const flushToolResults = (messages, toolCards, cardByCallId, toolGroups, done, { finalize = false, cancelled = false } = {}) => {
|
|
132
|
+
const results = [];
|
|
133
|
+
for (const m of messages || []) {
|
|
134
|
+
if (!m || m.role !== 'tool') continue;
|
|
135
|
+
const callId = toolResultCallId(m);
|
|
136
|
+
results.push({ message: m, callId, used: false });
|
|
137
|
+
if (!callId || done.has(callId)) continue;
|
|
138
|
+
const card = cardByCallId.get(callId);
|
|
139
|
+
if (patchToolCardResult(card, m, toolGroups, done)) {
|
|
140
|
+
results[results.length - 1].used = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const openCards = (toolCards || []).filter((card) => !card.done);
|
|
145
|
+
if (openCards.length === 0) return;
|
|
146
|
+
|
|
147
|
+
const unusedResults = results.filter((result) => !result.used);
|
|
148
|
+
const fallbackResults = unusedResults.slice(-openCards.length);
|
|
149
|
+
for (let i = 0; i < fallbackResults.length; i++) {
|
|
150
|
+
const card = openCards[i];
|
|
151
|
+
const result = fallbackResults[i];
|
|
152
|
+
if (!card || !result || card.done) continue;
|
|
153
|
+
if (patchToolCardResult(card, result.message, toolGroups, done)) {
|
|
154
|
+
if (result.callId) done.add(result.callId);
|
|
155
|
+
result.used = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!finalize) return;
|
|
160
|
+
for (const card of toolCards || []) {
|
|
161
|
+
if (card.done) continue;
|
|
162
|
+
// Finalize must surface any still-deferred card before patching its result
|
|
163
|
+
// so the completed/cancelled card is never silently dropped.
|
|
164
|
+
(card.aggregate?.ensureVisible || card.ensureVisible)?.();
|
|
165
|
+
// Aggregate finalize — mark any remaining calls as done
|
|
166
|
+
const aggregate = card.aggregate;
|
|
167
|
+
if (aggregate && card.itemId === aggregate.itemId) {
|
|
168
|
+
const allCalls = [...aggregate.calls.values()];
|
|
169
|
+
// Never let a call that truly never resolved be presented as a real
|
|
170
|
+
// completion. Stamp it resolved so completedCount reflects an honest
|
|
171
|
+
// (if degenerate) accounting instead of manufacturing success out of
|
|
172
|
+
// a call that never came back. A record already marked completedEarly
|
|
173
|
+
// (via __earlyNotify) already carries a real isError/resultText/summary
|
|
174
|
+
// from its actual result — preserve those; only blank-fill for calls
|
|
175
|
+
// truly never heard from (no completedEarly, no resolved).
|
|
176
|
+
for (const rec of allCalls) {
|
|
177
|
+
if (rec.resolved) continue;
|
|
178
|
+
rec.resolved = true;
|
|
179
|
+
if (!rec.completedEarly) {
|
|
180
|
+
rec.isError = false;
|
|
181
|
+
rec.resultText = rec.resultText || '';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const completed = allCalls.filter((r) => r.resolved).length;
|
|
185
|
+
const totalCompleted = completed;
|
|
186
|
+
const errors = allCalls.filter((r) => r.isError).length;
|
|
187
|
+
const succeeded = completed - errors;
|
|
188
|
+
const rawResult = aggregateRawResult(allCalls);
|
|
189
|
+
// Collapsed detail carries the merged per-call count summary; failures
|
|
190
|
+
// keep a bare 'N Ok · N Failed' status. Raw is kept for ctrl+o.
|
|
191
|
+
let displayDetail = errors > 0
|
|
192
|
+
? (succeeded > 0 ? `${succeeded} Ok · ${errors} Failed` : `${errors} Failed`)
|
|
193
|
+
: formatAggregateDetail(aggregateSummaries(aggregate));
|
|
194
|
+
if (cancelled) {
|
|
195
|
+
// Cancelled aggregates MUST keep the [status: cancelled] marker on the
|
|
196
|
+
// result so terminalStatus parsing resolves to 'cancelled'. Only normal
|
|
197
|
+
// completions drop the summary; cancelled ones prepend the marker.
|
|
198
|
+
const currentItem = getState().items.find((it) => it.id === card.itemId);
|
|
199
|
+
displayDetail = withCancelledResultMarker(displayDetail, currentItem);
|
|
200
|
+
}
|
|
201
|
+
patchItem(card.itemId, {
|
|
202
|
+
result: displayDetail,
|
|
203
|
+
text: displayDetail,
|
|
204
|
+
rawResult: rawResult || null,
|
|
205
|
+
isError: errors > 0,
|
|
206
|
+
errorCount: errors,
|
|
207
|
+
count: allCalls.length,
|
|
208
|
+
completedCount: totalCompleted,
|
|
209
|
+
completedAt: Date.now(),
|
|
210
|
+
});
|
|
211
|
+
for (const sibling of toolCards || []) {
|
|
212
|
+
if (sibling.itemId !== card.itemId) continue;
|
|
213
|
+
sibling.done = true;
|
|
214
|
+
if (sibling.callId) done.add(sibling.callId);
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Non-aggregate finalize
|
|
219
|
+
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
|
|
220
|
+
group.completed = Math.min(group.count, group.completed + 1);
|
|
221
|
+
toolGroups.set(card.itemId, group);
|
|
222
|
+
let resultText = groupedToolResultText(group);
|
|
223
|
+
if (cancelled) {
|
|
224
|
+
const currentItem = getState().items.find((it) => it.id === card.itemId);
|
|
225
|
+
resultText = withCancelledResultMarker(resultText, currentItem);
|
|
226
|
+
}
|
|
227
|
+
patchItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, count: group.count, completedCount: group.completed, completedAt: Date.now() });
|
|
228
|
+
card.done = true;
|
|
229
|
+
if (card.callId) done.add(card.callId);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return { patchToolCardResult, flushToolResults };
|
|
234
|
+
}
|