mixdog 0.9.2 → 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 +8 -3
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- 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/build-tui.mjs +13 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/hook-bus-test.mjs +191 -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/path-suffix-test.mjs +57 -0
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +207 -0
- 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 +30 -67
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +5 -2
- package/src/agents/heavy-worker/AGENT.md +21 -11
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +3 -2
- package/src/agents/worker/AGENT.md +21 -11
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +933 -3731
- 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/repl.mjs +5 -5
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/agent/30-explorer.md +8 -11
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +2 -9
- package/src/rules/shared/01-tool.md +11 -5
- 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/context/collect.mjs +51 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- 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 +97 -1343
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
- 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 +44 -1014
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- 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/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
- 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 +41 -1142
- 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 +303 -2119
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
- 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/providers/opencode-go-usage.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
- 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-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -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 +47 -0
- 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/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -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 +409 -1304
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -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/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
- 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/stall-policy.mjs +3 -3
- 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 +33 -42
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- 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 -87
- 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 +78 -304
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
- 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 +551 -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/keyword-match.mjs +82 -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 +1080 -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-columns.mjs +45 -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 +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
- 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 +1 -23
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- 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 +241 -894
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/crash-log.mjs +106 -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/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +9 -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/telegram-format.mjs +19 -22
- 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/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +258 -2050
- package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
- 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/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -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 +72 -837
- 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-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +20 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +238 -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 +6 -14
- 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/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/tool-execution-contract.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 +52 -2
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +291 -0
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-capabilities.mjs +130 -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 +126 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- 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/session-text.mjs +100 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +358 -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 +155 -677
- package/src/standalone/channel-worker.mjs +7 -9
- package/src/standalone/explore-tool.mjs +40 -12
- 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 +110 -746
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +125 -0
- package/src/standalone/provider-admin.mjs +15 -19
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1163 -7571
- 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 +259 -80
- 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 +56 -588
- 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/components/tool-output-format.mjs +2 -2
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +18034 -17188
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/agent-job-feed.mjs +133 -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/notification-plan.mjs +76 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -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/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +405 -1385
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +60 -10
- package/src/tui/keyboard-protocol.mjs +2 -2
- package/src/tui/lib/voice-recorder.mjs +35 -19
- package/src/tui/markdown/format-token.mjs +11 -9
- package/src/tui/paste-attachments.mjs +38 -0
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/tui/themes/base.mjs +2 -2
- package/src/tui/themes/kanagawa.mjs +4 -4
- package/src/tui/themes/teal.mjs +4 -5
- package/src/tui/themes/utils.mjs +1 -1
- 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 +77 -462
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -9
- package/src/workflows/sequential/WORKFLOW.md +16 -11
- package/src/workflows/solo/WORKFLOW.md +5 -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/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- 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/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/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,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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/queue-helpers.mjs - pure helpers for the prompt/notification
|
|
3
|
+
* queue: priority ordering, entry visibility/editability, prompt content
|
|
4
|
+
* flattening, session activity timestamps, and batch merging. Extracted from
|
|
5
|
+
* engine.mjs.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
agentJobResultText,
|
|
9
|
+
parseAgentJob,
|
|
10
|
+
parseSyntheticAgentMessage,
|
|
11
|
+
} from './agent-envelope.mjs';
|
|
12
|
+
|
|
13
|
+
const QUEUE_PRIORITY = { now: 0, next: 1, later: 2 };
|
|
14
|
+
|
|
15
|
+
export function queuePriorityValue(value) {
|
|
16
|
+
return QUEUE_PRIORITY[String(value || 'next')] ?? QUEUE_PRIORITY.next;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function defaultQueuePriority(mode) {
|
|
20
|
+
// Queue priority defaults:
|
|
21
|
+
// - user/bashed prompt input defaults to `next`, so it can be attached at the
|
|
22
|
+
// next model-send boundary while a turn is active.
|
|
23
|
+
// - task notifications default to `later`, unless the caller explicitly marks
|
|
24
|
+
// them urgent (e.g. interactive shell stall/completion).
|
|
25
|
+
return mode === 'task-notification' ? 'later' : 'next';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isQueuedEntryEditable(entry) {
|
|
29
|
+
const mode = entry?.mode || 'prompt';
|
|
30
|
+
return mode !== 'task-notification' && mode !== 'pending-resume';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isQueuedEntryVisible(entry) {
|
|
34
|
+
// state.queued drives the user-command wait list above the prompt. Background
|
|
35
|
+
// task completions stay in the internal pending queue, but should never look
|
|
36
|
+
// like commands typed by the user while they wait to be drained.
|
|
37
|
+
const mode = entry?.mode || 'prompt';
|
|
38
|
+
if (mode === 'pending-resume') return false;
|
|
39
|
+
return isQueuedEntryEditable(entry);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isSlashQueuedEntry(entry) {
|
|
43
|
+
if (entry?.skipSlashCommands) return false;
|
|
44
|
+
const text = promptContentText(entry?.content ?? entry?.text ?? '');
|
|
45
|
+
return text.trim().startsWith('/');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function firstQueueLine(text) {
|
|
49
|
+
return String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function shortTextFingerprint(text) {
|
|
53
|
+
const value = String(text || '').trim();
|
|
54
|
+
let hash = 2166136261;
|
|
55
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
56
|
+
hash ^= value.charCodeAt(i);
|
|
57
|
+
hash = Math.imul(hash, 16777619);
|
|
58
|
+
}
|
|
59
|
+
return (hash >>> 0).toString(36);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function notificationDisplayText(text) {
|
|
63
|
+
const parsed = parseAgentJob(text);
|
|
64
|
+
const result = agentJobResultText(text, parsed);
|
|
65
|
+
const synthetic = parseSyntheticAgentMessage(text);
|
|
66
|
+
return firstQueueLine(synthetic?.result || result || text) || 'agent notification';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function promptContentText(content) {
|
|
70
|
+
if (typeof content === 'string') return content;
|
|
71
|
+
if (Array.isArray(content)) {
|
|
72
|
+
return content.map((part) => {
|
|
73
|
+
if (typeof part === 'string') return part;
|
|
74
|
+
if (part?.type === 'text') return part.text || '';
|
|
75
|
+
if (part?.type === 'image') return '[Image]';
|
|
76
|
+
return part?.text || '';
|
|
77
|
+
}).filter(Boolean).join('\n');
|
|
78
|
+
}
|
|
79
|
+
return String(content ?? '');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function timestampMs(value) {
|
|
83
|
+
if (value == null || value === '') return 0;
|
|
84
|
+
const n = Number(value);
|
|
85
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
86
|
+
const parsed = Date.parse(String(value));
|
|
87
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function hasModelVisibleConversation(session) {
|
|
91
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
92
|
+
return messages.some((message) => {
|
|
93
|
+
const role = message?.role;
|
|
94
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'tool') return false;
|
|
95
|
+
const text = promptContentText(message.content).trim();
|
|
96
|
+
if (role === 'user' && text.startsWith('<system-reminder>')) return false;
|
|
97
|
+
if (role === 'assistant' && text === '.' && !Array.isArray(message.toolCalls)) return false;
|
|
98
|
+
return !!text || role === 'assistant' || role === 'tool';
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function sessionActivityTimestamp(session, fallback = 0) {
|
|
103
|
+
if (!hasModelVisibleConversation(session)) return 0;
|
|
104
|
+
return timestampMs(session?.lastUsedAt)
|
|
105
|
+
|| timestampMs(session?.updatedAt)
|
|
106
|
+
|| timestampMs(fallback);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function promptDisplayText(content, options = {}) {
|
|
110
|
+
if (typeof options.displayText === 'string') return options.displayText;
|
|
111
|
+
return promptContentText(content);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function mergePromptContents(entries) {
|
|
115
|
+
const batch = Array.isArray(entries) ? entries : [];
|
|
116
|
+
if (batch.every((entry) => typeof entry?.content === 'string')) {
|
|
117
|
+
return batch.map((entry) => entry.content).filter((text) => String(text || '').trim()).join('\n');
|
|
118
|
+
}
|
|
119
|
+
const parts = [];
|
|
120
|
+
for (const entry of batch) {
|
|
121
|
+
const content = entry?.content;
|
|
122
|
+
if (typeof content === 'string') {
|
|
123
|
+
if (content.trim()) parts.push({ type: 'text', text: content });
|
|
124
|
+
} else if (Array.isArray(content)) {
|
|
125
|
+
parts.push(...content);
|
|
126
|
+
}
|
|
127
|
+
parts.push({ type: 'text', text: '\n' });
|
|
128
|
+
}
|
|
129
|
+
while (parts.length && parts[parts.length - 1]?.type === 'text' && parts[parts.length - 1]?.text === '\n') parts.pop();
|
|
130
|
+
return parts.length === 1 && parts[0]?.type === 'text' ? parts[0].text : parts;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function mergePastedImages(entries) {
|
|
134
|
+
const out = {};
|
|
135
|
+
for (const entry of entries || []) {
|
|
136
|
+
const images = entry?.pastedImages;
|
|
137
|
+
if (!images || typeof images !== 'object') continue;
|
|
138
|
+
for (const [id, image] of Object.entries(images)) {
|
|
139
|
+
if (image) out[id] = image;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function mergePastedTexts(entries) {
|
|
146
|
+
const out = {};
|
|
147
|
+
for (const entry of entries || []) {
|
|
148
|
+
const texts = entry?.pastedTexts;
|
|
149
|
+
if (!texts || typeof texts !== 'object') continue;
|
|
150
|
+
for (const [id, text] of Object.entries(texts)) {
|
|
151
|
+
if (text) out[id] = text;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function callCommitCallbacks(entries) {
|
|
158
|
+
for (const entry of entries || []) {
|
|
159
|
+
try { entry?.onCommitted?.(); } catch {}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -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,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/session-stats.mjs - session-usage accumulator.
|
|
3
|
+
*
|
|
4
|
+
* Inlined (not imported from ui/statusline.mjs) so the engine has no static
|
|
5
|
+
* dependency on the vendored statusline closure. Extracted from engine.mjs.
|
|
6
|
+
*/
|
|
7
|
+
export function createSessionStats() {
|
|
8
|
+
return {
|
|
9
|
+
inputTokens: 0,
|
|
10
|
+
outputTokens: 0,
|
|
11
|
+
cachedTokens: 0,
|
|
12
|
+
cacheWriteTokens: 0,
|
|
13
|
+
promptTokens: 0,
|
|
14
|
+
latestInputTokens: 0,
|
|
15
|
+
latestOutputTokens: 0,
|
|
16
|
+
latestCachedTokens: 0,
|
|
17
|
+
latestCacheWriteTokens: 0,
|
|
18
|
+
latestPromptTokens: 0,
|
|
19
|
+
currentContextTokens: 0,
|
|
20
|
+
costUsd: 0,
|
|
21
|
+
turns: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function num(v) { const n = Number(v); return Number.isFinite(n) ? n : 0; }
|
|
26
|
+
|
|
27
|
+
export function applyUsageDelta(stats, delta = {}) {
|
|
28
|
+
if (!stats || !delta) return stats;
|
|
29
|
+
const inputTokens = num(delta.deltaInput);
|
|
30
|
+
const outputTokens = num(delta.deltaOutput);
|
|
31
|
+
const cachedTokens = num(delta.deltaCachedRead);
|
|
32
|
+
const cacheWriteTokens = num(delta.deltaCacheWrite);
|
|
33
|
+
const promptTokens = num(delta.deltaPrompt);
|
|
34
|
+
stats.inputTokens += inputTokens;
|
|
35
|
+
stats.outputTokens += outputTokens;
|
|
36
|
+
stats.cachedTokens += cachedTokens;
|
|
37
|
+
stats.cacheWriteTokens += cacheWriteTokens;
|
|
38
|
+
stats.promptTokens += promptTokens;
|
|
39
|
+
stats.latestInputTokens = inputTokens;
|
|
40
|
+
stats.latestOutputTokens = outputTokens;
|
|
41
|
+
stats.latestCachedTokens = cachedTokens;
|
|
42
|
+
stats.latestCacheWriteTokens = cacheWriteTokens;
|
|
43
|
+
stats.latestPromptTokens = promptTokens;
|
|
44
|
+
stats.costUsd += num(delta.costUsd);
|
|
45
|
+
return stats;
|
|
46
|
+
}
|
|
@@ -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,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/tool-call-fields.mjs - accessors for the varied tool-call /
|
|
3
|
+
* tool-result id/name/arg shapes across providers. Extracted from engine.mjs.
|
|
4
|
+
*/
|
|
5
|
+
export function toolCallId(call) {
|
|
6
|
+
return call?.id ?? call?.toolCallId ?? call?.tool_call_id ?? call?.call_id;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function toolResultCallId(message) {
|
|
10
|
+
return message?.toolCallId
|
|
11
|
+
?? message?.tool_call_id
|
|
12
|
+
?? message?.tool_use_id
|
|
13
|
+
?? message?.call_id
|
|
14
|
+
?? message?.id;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function toolCallName(call) {
|
|
18
|
+
return call?.name ?? call?.function?.name ?? call?.toolName ?? call?.tool_name ?? 'tool';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function toolCallArgs(call) {
|
|
22
|
+
return call?.arguments ?? call?.args ?? call?.input ?? call?.function?.arguments;
|
|
23
|
+
}
|
|
@@ -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
|
+
}
|