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
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import {
|
|
4
|
-
formatExpandedResult,
|
|
5
|
-
wrapExpandedResultLines,
|
|
6
|
-
expandedResultBodyWidth,
|
|
7
|
-
inferLangFamily,
|
|
8
|
-
stripUnderlineAnsi,
|
|
9
|
-
linkifyUrls,
|
|
10
|
-
tryFormatJson,
|
|
11
|
-
} from './tool-output-format.mjs';
|
|
12
|
-
import stringWidth from 'string-width';
|
|
13
|
-
import { displayWidthWith } from '../display-width.mjs';
|
|
14
|
-
|
|
15
|
-
const stripAnsi = (s) => String(s).replace(/\x1b\[[0-9;]*m/g, '').replace(/\x1b\]8;;[^\x07]*\x07/g, '');
|
|
16
|
-
|
|
17
|
-
function withRenderLineCap(cap, fn) {
|
|
18
|
-
const prev = process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES;
|
|
19
|
-
const legacy = process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS;
|
|
20
|
-
process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES = String(cap);
|
|
21
|
-
delete process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS;
|
|
22
|
-
try {
|
|
23
|
-
return fn();
|
|
24
|
-
} finally {
|
|
25
|
-
if (prev === undefined) delete process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES;
|
|
26
|
-
else process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES = prev;
|
|
27
|
-
if (legacy === undefined) delete process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS;
|
|
28
|
-
else process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS = legacy;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
test('inferLangFamily maps known extensions, null otherwise', () => {
|
|
33
|
-
assert.equal(inferLangFamily('src/x.mjs'), 'js');
|
|
34
|
-
assert.equal(inferLangFamily('a.py'), 'py');
|
|
35
|
-
assert.equal(inferLangFamily('x.json'), 'json');
|
|
36
|
-
assert.equal(inferLangFamily('run.sh'), 'sh');
|
|
37
|
-
assert.equal(inferLangFamily('readme'), null);
|
|
38
|
-
assert.equal(inferLangFamily(''), null);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('read line-number gutter is split into a dim column and body is highlighted', () => {
|
|
42
|
-
const read = '900\u2192function f() {\n901\u2192 return 1;\n902\u2192}';
|
|
43
|
-
const out = formatExpandedResult(read, { pathArg: 'a.mjs' });
|
|
44
|
-
assert.equal(out.length, 3);
|
|
45
|
-
// gutter text survives, body keyword colored (output carries SGR escapes).
|
|
46
|
-
assert.ok(stripAnsi(out[0]).startsWith('900\u2192function f() {'));
|
|
47
|
-
assert.ok(out[0].includes('\x1b['), 'first line carries color escapes');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('non-ANSI tool output expands tabs so width math matches the terminal', () => {
|
|
51
|
-
// A raw \t is ZERO-width to string-width, so wrapExpandedResultLines would
|
|
52
|
-
// under-wrap a tab-bearing line and the terminal-expanded tab would bleed
|
|
53
|
-
// the row through the bottom prompt box on scroll. formatExpandedResult must
|
|
54
|
-
// normalize tabs to spaces for non-ANSI output.
|
|
55
|
-
const out = formatExpandedResult('\tindented line', { pathArg: 'a.txt' });
|
|
56
|
-
const plain = stripAnsi(out.join('\n'));
|
|
57
|
-
assert.ok(!plain.includes('\t'), 'no raw tab survives non-ANSI tool output');
|
|
58
|
-
const body = plain.split('\n').find((l) => l.includes('indented line'));
|
|
59
|
-
assert.ok(body, 'body line present');
|
|
60
|
-
assert.equal(stringWidth(body), body.length, 'visible width equals char length');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('shell ANSI output keeps raw control bytes (not normalized)', () => {
|
|
64
|
-
// ANSI/shell output legitimately carries control bytes in escape sequences;
|
|
65
|
-
// normalization must be skipped so those survive verbatim.
|
|
66
|
-
const sh = '\x1b[31mred\x1b[0m';
|
|
67
|
-
const out = formatExpandedResult(sh, { isShell: true });
|
|
68
|
-
assert.ok(out.join('').includes('\x1b['), 'escape sequences preserved');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('colored shell output still expands visible tabs (ANSI-aware normalize)', () => {
|
|
72
|
-
// A colored line with a raw \t in its VISIBLE text: the SGR escapes must be
|
|
73
|
-
// preserved verbatim while the tab is expanded, so the visible width matches
|
|
74
|
-
// the terminal and the row cannot bleed through the prompt box on scroll.
|
|
75
|
-
const sh = '\x1b[32m\tgreen tabbed\x1b[0m';
|
|
76
|
-
const out = formatExpandedResult(sh, { isShell: true });
|
|
77
|
-
const joined = out.join('\n');
|
|
78
|
-
assert.ok(joined.includes('\x1b['), 'color escapes preserved');
|
|
79
|
-
assert.ok(!joined.includes('\t'), 'visible tab expanded even in colored output');
|
|
80
|
-
const body = stripAnsi(joined).split('\n').find((l) => l.includes('green tabbed'));
|
|
81
|
-
assert.ok(body, 'body line present');
|
|
82
|
-
assert.equal(stringWidth(body), body.length, 'visible width equals char length');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('block syntax highlight skips tokenizer for over-long lines (index aligned)', () => {
|
|
86
|
-
// MAX_HIGHLIGHT_LINE_CHARS is 2000 in tool-output-format.mjs
|
|
87
|
-
const giant = 'x'.repeat(2001);
|
|
88
|
-
const read = `1\u2192const a = 1;\n2\u2192${giant}\n3\u2192const b = 2;`;
|
|
89
|
-
const out = formatExpandedResult(read, { pathArg: 'a.mjs' });
|
|
90
|
-
assert.equal(out.length, 3);
|
|
91
|
-
assert.ok(out[0].includes('\x1b['), 'short line before giant is highlighted');
|
|
92
|
-
assert.ok(out[2].includes('\x1b['), 'short line after giant is highlighted');
|
|
93
|
-
const plainMid = stripAnsi(out[1]);
|
|
94
|
-
assert.ok(plainMid.startsWith('2\u2192'), 'gutter preserved on giant line');
|
|
95
|
-
assert.ok(plainMid.endsWith(giant), 'giant body preserved');
|
|
96
|
-
const hljsSpans = (out[1].match(/\x1b\[[0-9;]*m/g) || []).length;
|
|
97
|
-
const shortSpans = (out[0].match(/\x1b\[[0-9;]*m/g) || []).length;
|
|
98
|
-
assert.ok(hljsSpans <= shortSpans + 2, 'giant line not fully tokenized like normal code');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('grep file:line: gutter is split too', () => {
|
|
102
|
-
const grep = 'src/a.mjs:5581: value: foo,';
|
|
103
|
-
const out = formatExpandedResult(grep, { pathArg: 'src/a.mjs' });
|
|
104
|
-
assert.equal(out.length, 1);
|
|
105
|
-
assert.ok(stripAnsi(out[0]).startsWith('src/a.mjs:5581:'));
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('grep Windows absolute path:line: gutter is split', () => {
|
|
109
|
-
const grep = 'C:\\Project\\mixdog\\src\\App.jsx:12: const x = 1;';
|
|
110
|
-
const out = formatExpandedResult(grep, { pathArg: 'src/App.jsx' });
|
|
111
|
-
assert.equal(out.length, 1);
|
|
112
|
-
const plain = stripAnsi(out[0]);
|
|
113
|
-
assert.ok(plain.startsWith('C:\\Project\\mixdog\\src\\App.jsx:12:'));
|
|
114
|
-
assert.ok(plain.includes('const x = 1;'), 'body preserved after gutter');
|
|
115
|
-
assert.ok(out[0].includes('\x1b['), 'body carries highlight escapes');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('grep bare line-number gutter still works', () => {
|
|
119
|
-
const grep = '12: return null;';
|
|
120
|
-
const out = formatExpandedResult(grep, { pathArg: 'a.mjs' });
|
|
121
|
-
assert.equal(out.length, 1);
|
|
122
|
-
assert.ok(stripAnsi(out[0]).startsWith('12:'));
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('https URLs are not treated as grep path:line: gutters', () => {
|
|
126
|
-
const line = 'see https://example.com:443/path for docs';
|
|
127
|
-
const out = formatExpandedResult(line, {});
|
|
128
|
-
assert.equal(out.length, 1);
|
|
129
|
-
assert.equal(stripAnsi(out[0]), line);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('JSON result is auto pretty-printed (size-capped, precision-safe)', () => {
|
|
133
|
-
const out = formatExpandedResult('{"a":1,"b":[2,3]}', {});
|
|
134
|
-
assert.ok(out.length > 1, 'single-line JSON expands to multiple rows');
|
|
135
|
-
assert.equal(stripAnsi(out[0]), '{');
|
|
136
|
-
// precision loss → keep original line
|
|
137
|
-
const big = '{"id":123456789012345678901234567890}';
|
|
138
|
-
assert.equal(tryFormatJson(big), big);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test('JSON with markdown-like string values stays JSON (no markdown lexer)', () => {
|
|
142
|
-
const raw = '["**literal**","`code`"]';
|
|
143
|
-
const out = formatExpandedResult(raw, {});
|
|
144
|
-
const visible = out.map(stripAnsi).join('\n');
|
|
145
|
-
assert.ok(visible.includes('**literal**'), 'array string keeps ** delimiters');
|
|
146
|
-
assert.ok(visible.includes('`code`'), 'array string keeps backticks');
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('JSON object with markdown-like values is not rendered as markdown', () => {
|
|
150
|
-
const raw = '{"msg":"**bold**","x":"`y`"}';
|
|
151
|
-
const out = formatExpandedResult(raw, {});
|
|
152
|
-
const visible = out.map(stripAnsi).join('\n');
|
|
153
|
-
assert.ok(visible.includes('**bold**'));
|
|
154
|
-
assert.ok(visible.includes('`y`'));
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('shell output keeps color ANSI but strips underline and never highlights', () => {
|
|
158
|
-
const sh = 'PASS \x1b[32mok\x1b[0m\n\x1b[4mfile.js\x1b[24m done';
|
|
159
|
-
const out = formatExpandedResult(sh, { isShell: true });
|
|
160
|
-
assert.ok(out[0].includes('\x1b[32m'), 'color ANSI preserved');
|
|
161
|
-
assert.ok(!out[1].includes('\x1b[4m'), 'underline stripped');
|
|
162
|
-
assert.equal(stripAnsi(out[1]), 'file.js done');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('unified diff is colored by line class', () => {
|
|
166
|
-
const diff = 'diff --git a/x b/x\n@@ -1 +1 @@\n-old\n+new\n ctx';
|
|
167
|
-
const out = formatExpandedResult(diff, {});
|
|
168
|
-
assert.equal(out.length, 5);
|
|
169
|
-
assert.ok(out.every((l) => l.includes('\x1b[')), 'every diff line colored');
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test('oversize output is capped with a truncation marker', () => {
|
|
173
|
-
const many = Array.from({ length: 5000 }, (_, i) => `line${i}`).join('\n');
|
|
174
|
-
const out = formatExpandedResult(many, {});
|
|
175
|
-
assert.ok(out.length <= 4001, 'logical cap keeps at most MAX_EXPANDED_LINES plus marker');
|
|
176
|
-
assert.ok(/truncated/.test(stripAnsi(out[out.length - 1])));
|
|
177
|
-
|
|
178
|
-
const sixHundred = Array.from({ length: 600 }, (_, i) => `row${i}`).join('\n');
|
|
179
|
-
const out600 = formatExpandedResult(sixHundred, {});
|
|
180
|
-
assert.equal(out600.length, 600, 'normal expanded output is not truncated at 80 logical lines');
|
|
181
|
-
|
|
182
|
-
const huge = 'x'.repeat(300 * 1024);
|
|
183
|
-
const out2 = formatExpandedResult(huge, {});
|
|
184
|
-
assert.ok(/truncated/.test(stripAnsi(out2[out2.length - 1])));
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('stripUnderlineAnsi removes only underline SGR', () => {
|
|
188
|
-
assert.equal(stripUnderlineAnsi('a\x1b[4mu\x1b[24mb'), 'aub');
|
|
189
|
-
assert.equal(stripUnderlineAnsi('a\x1b[32mg\x1b[0mb'), 'a\x1b[32mg\x1b[0mb');
|
|
190
|
-
assert.equal(stripUnderlineAnsi('x\x1b[4;31my'), 'x\x1b[31my');
|
|
191
|
-
assert.equal(stripUnderlineAnsi('x\x1b[42my'), 'x\x1b[42my');
|
|
192
|
-
assert.equal(stripUnderlineAnsi('x\x1b[31;4my'), 'x\x1b[31my');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test('linkifyUrls wraps bare URLs in OSC 8', () => {
|
|
196
|
-
const out = linkifyUrls('see https://x.com/y here');
|
|
197
|
-
assert.ok(out.includes('\x1b]8;;https://x.com/y\x07'));
|
|
198
|
-
assert.equal(linkifyUrls('no url here'), 'no url here');
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test('linkifyUrls keeps ANSI out of OSC 8 URL targets', () => {
|
|
202
|
-
const colored = 'go \x1b[31mhttps://example.com/a\x1b[0m end';
|
|
203
|
-
const out = linkifyUrls(colored);
|
|
204
|
-
const m = /\x1b]8;;([^\x07]+)\x07/.exec(out);
|
|
205
|
-
assert.ok(m, 'OSC 8 opener present');
|
|
206
|
-
assert.equal(m[1], 'https://example.com/a');
|
|
207
|
-
assert.ok(!m[1].includes('\x1b'), 'target URL has no escape bytes');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
function osc8UrlTargets(text) {
|
|
211
|
-
const targets = [];
|
|
212
|
-
const bel = /\x1b]8;;([^\x07]+)\x07/g;
|
|
213
|
-
const st = /\x1b]8;;((?:[^\x1b]|\x1b(?![\\]))+)\x1b\\/g;
|
|
214
|
-
let m;
|
|
215
|
-
while ((m = bel.exec(text)) !== null) targets.push(m[1]);
|
|
216
|
-
while ((m = st.exec(text)) !== null) targets.push(m[1]);
|
|
217
|
-
return targets;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
test('linkifyUrls does not re-linkify existing BEL OSC 8 hyperlinks', () => {
|
|
221
|
-
const belLink = '\x1b]8;;https://a.test\x07label\x1b]8;;\x07';
|
|
222
|
-
const out = linkifyUrls(belLink);
|
|
223
|
-
assert.equal(out, belLink);
|
|
224
|
-
assert.equal((out.match(/\x1b]8;;/g) || []).length, 2, 'single open/close pair');
|
|
225
|
-
for (const target of osc8UrlTargets(out)) {
|
|
226
|
-
assert.ok(!target.includes('\x1b'), 'target has no escape bytes');
|
|
227
|
-
assert.ok(!target.includes('\x1b]8;;'), 'no nested OSC in target');
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('linkifyUrls does not re-linkify existing ST OSC 8 hyperlinks', () => {
|
|
232
|
-
const stLink = '\x1b]8;;https://a.test\x1b\\label\x1b]8;;\x1b\\';
|
|
233
|
-
const out = linkifyUrls(stLink);
|
|
234
|
-
assert.equal(out, stLink);
|
|
235
|
-
assert.equal((out.match(/\x1b]8;;/g) || []).length, 2, 'single open/close pair');
|
|
236
|
-
for (const target of osc8UrlTargets(out)) {
|
|
237
|
-
assert.ok(!target.includes('\x1b'), 'target has no escape bytes');
|
|
238
|
-
assert.ok(!target.includes('\x1b]8;;'), 'no nested OSC in target');
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('empty input yields no lines', () => {
|
|
243
|
-
assert.deepEqual(formatExpandedResult('', {}), []);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test('wrapExpandedResultLines splits long logical rows to body width', () => {
|
|
247
|
-
const columns = 48;
|
|
248
|
-
const maxW = expandedResultBodyWidth(columns);
|
|
249
|
-
const longBody = 'alpha_beta_gamma_delta '.repeat(6).trimEnd();
|
|
250
|
-
const logical = formatExpandedResult(`900\u2192${longBody}`, { pathArg: 'a.mjs' });
|
|
251
|
-
assert.equal(logical.length, 1);
|
|
252
|
-
const physical = wrapExpandedResultLines(logical, columns);
|
|
253
|
-
assert.ok(physical.length > 1, 'long read line becomes multiple physical rows');
|
|
254
|
-
for (const row of physical) {
|
|
255
|
-
assert.ok(
|
|
256
|
-
stringWidth(stripAnsi(row)) <= maxW,
|
|
257
|
-
`row width ${stringWidth(stripAnsi(row))} exceeds budget ${maxW}`,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
for (let i = 1; i < physical.length; i++) {
|
|
261
|
-
assert.ok(/^\s+/.test(stripAnsi(physical[i])), 'wrapped continuations are indented');
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test('expanded markdown prose renders headings and emphasis (not raw #/**)', () => {
|
|
266
|
-
const md = '# Title\n\nSome **bold** and `inline`.\n';
|
|
267
|
-
const out = formatExpandedResult(md, {});
|
|
268
|
-
const visible = out.map(stripAnsi).join('\n');
|
|
269
|
-
assert.ok(!visible.includes('# Title'), 'ATX heading marker not shown raw');
|
|
270
|
-
assert.ok(visible.includes('Title'), 'heading text preserved');
|
|
271
|
-
assert.ok(visible.includes('bold'), 'strong text preserved');
|
|
272
|
-
assert.ok(!visible.includes('**'), 'emphasis delimiters stripped');
|
|
273
|
-
assert.ok(out.some((l) => l.includes('\x1b[')), 'markdown path applies theme colors');
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test('expanded fenced code in markdown path is highlighted', () => {
|
|
277
|
-
const md = '```js\nconst x = 1;\n```\n';
|
|
278
|
-
const out = formatExpandedResult(md, {});
|
|
279
|
-
const visible = out.map(stripAnsi).join('\n');
|
|
280
|
-
assert.ok(visible.includes('const x = 1;'), 'fence body preserved');
|
|
281
|
-
assert.ok(out.some((l) => l.includes('\x1b[')), 'fenced block carries ANSI');
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test('read gutter lines skip markdown mode and keep syntax highlight', () => {
|
|
285
|
-
const read = '1\u2192# not a heading\n2\u2192const y = 2;\n';
|
|
286
|
-
const out = formatExpandedResult(read, { pathArg: 'a.mjs' });
|
|
287
|
-
const visible = out.map(stripAnsi);
|
|
288
|
-
assert.ok(visible.some((l) => l.includes('1\u2192# not a heading')), 'source # kept on read lines');
|
|
289
|
-
assert.ok(visible.some((l) => l.includes('const y = 2;')), 'read body preserved');
|
|
290
|
-
assert.ok(out.some((l) => stripAnsi(l).includes('const y = 2;') && l.includes('\x1b[')), 'code line still highlighted');
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test('shell physical cap keeps newest rows across logical lines', () => {
|
|
294
|
-
withRenderLineCap(5, () => {
|
|
295
|
-
const long = 'W'.repeat(240);
|
|
296
|
-
const logical = formatExpandedResult(`${long}\nNEWEST_TAIL_LINE`, { isShell: true });
|
|
297
|
-
const physical = wrapExpandedResultLines(logical, 32, { isShell: true });
|
|
298
|
-
const visible = physical.map(stripAnsi);
|
|
299
|
-
assert.ok(visible.some((l) => l.includes('NEWEST_TAIL_LINE')), 'newest logical line survives rolling cap');
|
|
300
|
-
assert.ok(physical.length <= 5);
|
|
301
|
-
assert.ok(visible.some((l) => /omitted above/i.test(l)));
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test('exact physical cap row count is not truncated', () => {
|
|
306
|
-
withRenderLineCap(10, () => {
|
|
307
|
-
const logical = Array.from({ length: 10 }, (_, i) => `row-${i}`).map((l) => formatExpandedResult(l, { isShell: true })[0]);
|
|
308
|
-
const physical = wrapExpandedResultLines(logical, 80, { isShell: true });
|
|
309
|
-
assert.equal(physical.length, 10);
|
|
310
|
-
assert.ok(!physical.some((l) => /omitted above/i.test(stripAnsi(l))));
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
test('physical cap of 1 never returns more than one row', () => {
|
|
315
|
-
withRenderLineCap(1, () => {
|
|
316
|
-
const physical = wrapExpandedResultLines(formatExpandedResult('a\nb\nc', { isShell: true }), 80, { isShell: true });
|
|
317
|
-
assert.ok(physical.length <= 1);
|
|
318
|
-
const physicalNonShell = wrapExpandedResultLines(formatExpandedResult('a\nb\nc', {}), 80, { isShell: false });
|
|
319
|
-
assert.ok(physicalNonShell.length <= 1);
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test('shell physical cap omitted count includes marker slot', () => {
|
|
324
|
-
withRenderLineCap(5, () => {
|
|
325
|
-
const logical = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5'].map((l) => formatExpandedResult(l, { isShell: true })[0]);
|
|
326
|
-
const physical = wrapExpandedResultLines(logical, 80, { isShell: true });
|
|
327
|
-
const visible = physical.map(stripAnsi);
|
|
328
|
-
assert.equal(physical.length, 5);
|
|
329
|
-
assert.match(visible[0], /2 lines omitted above/);
|
|
330
|
-
assert.deepEqual(visible.slice(1), ['r2', 'r3', 'r4', 'r5']);
|
|
331
|
-
});
|
|
332
|
-
withRenderLineCap(1, () => {
|
|
333
|
-
const logical = Array.from({ length: 6 }, (_, i) => `row${i}`).map((l) => formatExpandedResult(l, { isShell: true })[0]);
|
|
334
|
-
const physical = wrapExpandedResultLines(logical, 80, { isShell: true });
|
|
335
|
-
assert.equal(physical.length, 1);
|
|
336
|
-
assert.match(stripAnsi(physical[0]), /6 lines omitted above/);
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test('shell logical truncation marker precedes retained tail', () => {
|
|
341
|
-
const many = Array.from({ length: 5000 }, (_, i) => `line${i}`).join('\n');
|
|
342
|
-
const out = formatExpandedResult(many, { isShell: true });
|
|
343
|
-
const visible = out.map(stripAnsi);
|
|
344
|
-
assert.ok(visible.length <= 4001);
|
|
345
|
-
assert.ok(/omitted above/i.test(visible[0]), 'shell marker is first row');
|
|
346
|
-
assert.ok(visible[visible.length - 1].includes('line4999'), 'newest logical line is last');
|
|
347
|
-
assert.ok(!visible[visible.length - 1].includes('re-read a narrower range'));
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
test('wide policy OFF: displayWidth is byte-for-byte identical to string-width for arrow lines', () => {
|
|
351
|
-
// Guards the "non-WT terminals unchanged" invariant: with the policy OFF
|
|
352
|
-
// (default in this test process — no WT_SESSION, no override), the arrow is
|
|
353
|
-
// still 1 cell, so wrap output must be identical to the string-width world.
|
|
354
|
-
const arrowLine = ' 12\u2192const x = 1; \u2190 done';
|
|
355
|
-
assert.equal(displayWidthWith(arrowLine, false), stringWidth(arrowLine),
|
|
356
|
-
'policy OFF must equal plain string-width');
|
|
357
|
-
const logical = formatExpandedResult(arrowLine, { pathArg: 'a.mjs' });
|
|
358
|
-
const physical = wrapExpandedResultLines(logical, 40);
|
|
359
|
-
const maxW = expandedResultBodyWidth(40);
|
|
360
|
-
for (const row of physical) {
|
|
361
|
-
assert.ok(stringWidth(stripAnsi(row)) <= maxW,
|
|
362
|
-
`row width ${stringWidth(stripAnsi(row))} exceeds ${maxW} (policy OFF)`);
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test('wide policy ON (forced): every emitted arrow row fits the display-width budget', async () => {
|
|
367
|
-
// The policy is resolved ONCE at module load, so forcing it requires a fresh
|
|
368
|
-
// process. Spawn a child with MIXDOG_TUI_AMBIGUOUS_WIDE=1 and assert that
|
|
369
|
-
// every wrapped read row (containing U+2192) measures <= expandedResultBodyWidth
|
|
370
|
-
// under the SAME wide policy the terminal will use. Without the clamp,
|
|
371
|
-
// string-width accepts arrow rows as fitting while the terminal renders them
|
|
372
|
-
// one cell wider, bleeding into the prompt box.
|
|
373
|
-
const { execFileSync } = await import('node:child_process');
|
|
374
|
-
const { fileURLToPath } = await import('node:url');
|
|
375
|
-
const modUrl = new URL('./tool-output-format.mjs', import.meta.url).href;
|
|
376
|
-
const dwUrl = new URL('../display-width.mjs', import.meta.url).href;
|
|
377
|
-
const script = `
|
|
378
|
-
import { formatExpandedResult, wrapExpandedResultLines, expandedResultBodyWidth } from ${JSON.stringify(modUrl)};
|
|
379
|
-
import { displayWidth, AMBIGUOUS_WIDE } from ${JSON.stringify(dwUrl)};
|
|
380
|
-
const strip = (s) => String(s).replace(/\\x1b\\[[0-9;]*m/g, '').replace(/\\x1b\\]8;;[^\\x07]*\\x07/g, '');
|
|
381
|
-
if (AMBIGUOUS_WIDE !== true) { console.log('POLICY_OFF'); process.exit(2); }
|
|
382
|
-
const columns = 30;
|
|
383
|
-
// Many arrows so a naive string-width fit accepts an over-wide row.
|
|
384
|
-
const body = 'x'.repeat(24) + ' ' + '\\u2192'.repeat(12) + ' tail';
|
|
385
|
-
const logical = formatExpandedResult('42\\u2192' + body, { pathArg: 'a.mjs' });
|
|
386
|
-
const physical = wrapExpandedResultLines(logical, columns);
|
|
387
|
-
const maxW = expandedResultBodyWidth(columns);
|
|
388
|
-
let bad = 0;
|
|
389
|
-
for (const row of physical) {
|
|
390
|
-
if (displayWidth(strip(row)) > maxW) bad++;
|
|
391
|
-
}
|
|
392
|
-
console.log(bad === 0 ? 'OK ' + physical.length : 'BAD ' + bad);
|
|
393
|
-
`;
|
|
394
|
-
const out = execFileSync(process.execPath, ['--input-type=module', '-e', script], {
|
|
395
|
-
env: { ...process.env, MIXDOG_TUI_AMBIGUOUS_WIDE: '1', WT_SESSION: '' },
|
|
396
|
-
encoding: 'utf8',
|
|
397
|
-
}).trim();
|
|
398
|
-
assert.ok(out.startsWith('OK'), `expected all rows within budget, got: ${out}`);
|
|
399
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import stringWidth from 'string-width';
|
|
4
|
-
import { displayWidthWith, isProblemCodePoint } from './display-width.mjs';
|
|
5
|
-
|
|
6
|
-
// Policy ON: the two problem ranges become 2 cells; everything else unchanged.
|
|
7
|
-
test('policy ON widens circled digits and arrows only', () => {
|
|
8
|
-
assert.equal(displayWidthWith('\u2460', true), 2); // ① circled digit
|
|
9
|
-
assert.equal(displayWidthWith('\u2192', true), 2); // → rightwards arrow
|
|
10
|
-
assert.equal(displayWidthWith('a', true), 1); // ASCII unchanged
|
|
11
|
-
// Box-drawing / figures must NOT be widened by the policy.
|
|
12
|
-
assert.equal(displayWidthWith('\u2514', true), stringWidth('\u2514')); // └
|
|
13
|
-
assert.equal(displayWidthWith('\u2502', true), stringWidth('\u2502')); // │
|
|
14
|
-
assert.equal(displayWidthWith('\u2500', true), stringWidth('\u2500')); // ─
|
|
15
|
-
assert.equal(displayWidthWith('\u25cf', true), stringWidth('\u25cf')); // ●
|
|
16
|
-
// Mixed string: base + one +1 per problem code point.
|
|
17
|
-
assert.equal(displayWidthWith('a\u2460b\u2192', true), stringWidth('a\u2460b\u2192') + 2);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Policy OFF: byte-for-byte identical to plain string-width.
|
|
21
|
-
test('policy OFF equals string-width', () => {
|
|
22
|
-
for (const s of ['\u2460', '\u2192', 'a', '\u2514', 'hello \u2192 world', '']) {
|
|
23
|
-
assert.equal(displayWidthWith(s, false), stringWidth(s));
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('problem range membership', () => {
|
|
28
|
-
assert.equal(isProblemCodePoint(0x2460), true);
|
|
29
|
-
assert.equal(isProblemCodePoint(0x24ff), true);
|
|
30
|
-
assert.equal(isProblemCodePoint(0x2190), true);
|
|
31
|
-
assert.equal(isProblemCodePoint(0x21ff), true);
|
|
32
|
-
assert.equal(isProblemCodePoint(0x2500), false); // box-drawing
|
|
33
|
-
assert.equal(isProblemCodePoint(0x245f), false); // just below range
|
|
34
|
-
assert.equal(isProblemCodePoint(0x2200), false); // just above arrows
|
|
35
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { resolveTuiRuntimeNotificationDelivery } from "./engine.mjs";
|
|
4
|
-
import { toolCompletionMeta } from "../runtime/shared/tool-execution-contract.mjs";
|
|
5
|
-
|
|
6
|
-
const completedBackgroundTask = [
|
|
7
|
-
"background task",
|
|
8
|
-
"task_id: task_agent_abc123",
|
|
9
|
-
"surface: agent",
|
|
10
|
-
"operation: run",
|
|
11
|
-
"status: completed",
|
|
12
|
-
"",
|
|
13
|
-
"worker finished successfully",
|
|
14
|
-
].join("\n");
|
|
15
|
-
|
|
16
|
-
const runningBackgroundTask = [
|
|
17
|
-
"background task",
|
|
18
|
-
"task_id: task_agent_abc123",
|
|
19
|
-
"surface: agent",
|
|
20
|
-
"operation: run",
|
|
21
|
-
"status: running",
|
|
22
|
-
].join("\n");
|
|
23
|
-
|
|
24
|
-
const statusOnlyCompletedBackgroundTask = [
|
|
25
|
-
"background task",
|
|
26
|
-
"task_id: task_agent_abc123",
|
|
27
|
-
"surface: agent",
|
|
28
|
-
"operation: run",
|
|
29
|
-
"status: completed",
|
|
30
|
-
].join("\n");
|
|
31
|
-
|
|
32
|
-
const agentTaskEnvelopeWithBody = [
|
|
33
|
-
"agent task: task_agent_xyz",
|
|
34
|
-
"status: completed",
|
|
35
|
-
"",
|
|
36
|
-
"done",
|
|
37
|
-
].join("\n");
|
|
38
|
-
|
|
39
|
-
const nestedAgentTaskInBackgroundTask = [
|
|
40
|
-
"background task",
|
|
41
|
-
"task_id: task_agent_abc123",
|
|
42
|
-
"surface: agent",
|
|
43
|
-
"operation: run",
|
|
44
|
-
"status: completed",
|
|
45
|
-
"",
|
|
46
|
-
"agent task: task_agent_nested",
|
|
47
|
-
"status: completed",
|
|
48
|
-
"",
|
|
49
|
-
"nested worker result",
|
|
50
|
-
].join("\n");
|
|
51
|
-
|
|
52
|
-
test("execution completion uses wrapper model content not raw enqueue text", () => {
|
|
53
|
-
const meta = toolCompletionMeta({
|
|
54
|
-
surface: "agent",
|
|
55
|
-
id: "task_agent_abc123",
|
|
56
|
-
status: "completed",
|
|
57
|
-
context: { callerSessionId: "sess_owner" },
|
|
58
|
-
});
|
|
59
|
-
const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, completedBackgroundTask);
|
|
60
|
-
assert.equal(delivery.action, "execution-ui");
|
|
61
|
-
assert.equal(delivery.displayText, completedBackgroundTask);
|
|
62
|
-
assert.ok(delivery.modelContent);
|
|
63
|
-
assert.match(delivery.modelContent, /worker finished successfully/);
|
|
64
|
-
assert.doesNotMatch(delivery.modelContent, /^background task\b/m);
|
|
65
|
-
assert.notEqual(delivery.modelContent, completedBackgroundTask);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("running execution notification is UI-only with empty model content", () => {
|
|
69
|
-
const meta = toolCompletionMeta({
|
|
70
|
-
surface: "agent",
|
|
71
|
-
id: "task_agent_abc123",
|
|
72
|
-
status: "running",
|
|
73
|
-
context: { callerSessionId: "sess_owner" },
|
|
74
|
-
});
|
|
75
|
-
const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, runningBackgroundTask);
|
|
76
|
-
assert.equal(delivery.action, "execution-ui");
|
|
77
|
-
assert.equal(delivery.modelContent, "");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("nested agent task body inside completed background task is not status-only", () => {
|
|
81
|
-
const meta = toolCompletionMeta({
|
|
82
|
-
surface: "agent",
|
|
83
|
-
id: "task_agent_abc123",
|
|
84
|
-
status: "completed",
|
|
85
|
-
context: { callerSessionId: "sess_owner" },
|
|
86
|
-
});
|
|
87
|
-
const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, nestedAgentTaskInBackgroundTask);
|
|
88
|
-
assert.equal(delivery.action, "execution-ui");
|
|
89
|
-
assert.ok(String(delivery.modelContent || "").trim());
|
|
90
|
-
assert.match(delivery.modelContent, /nested worker result/);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("agent task envelope with blank-line body is not status-only", () => {
|
|
94
|
-
const meta = toolCompletionMeta({
|
|
95
|
-
surface: "agent",
|
|
96
|
-
id: "task_agent_xyz",
|
|
97
|
-
status: "completed",
|
|
98
|
-
context: { callerSessionId: "sess_owner" },
|
|
99
|
-
});
|
|
100
|
-
const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, agentTaskEnvelopeWithBody);
|
|
101
|
-
assert.equal(delivery.action, "execution-ui");
|
|
102
|
-
assert.ok(String(delivery.modelContent || "").trim());
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("header-only completed background task is status-only", () => {
|
|
106
|
-
const meta = toolCompletionMeta({
|
|
107
|
-
surface: "agent",
|
|
108
|
-
id: "task_agent_abc123",
|
|
109
|
-
status: "completed",
|
|
110
|
-
context: { callerSessionId: "sess_owner" },
|
|
111
|
-
});
|
|
112
|
-
const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, statusOnlyCompletedBackgroundTask);
|
|
113
|
-
assert.equal(delivery.action, "status-only");
|
|
114
|
-
assert.equal(delivery.modelContent, "");
|
|
115
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import {
|
|
4
|
-
toolAggregateDetailFallback,
|
|
5
|
-
toolGroupedDisplayFallback,
|
|
6
|
-
toolResultText,
|
|
7
|
-
} from './engine.mjs';
|
|
8
|
-
|
|
9
|
-
test('toolResultText prefers nested content over empty scalar fields', () => {
|
|
10
|
-
assert.equal(
|
|
11
|
-
toolResultText({ text: '', content: [{ type: 'text', text: 'nested body' }] }),
|
|
12
|
-
'nested body',
|
|
13
|
-
);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('toolResultText unwraps nested tool_result blocks', () => {
|
|
17
|
-
assert.equal(
|
|
18
|
-
toolResultText({
|
|
19
|
-
type: 'tool_result',
|
|
20
|
-
content: [{ type: 'tool_result', content: 'inner payload' }],
|
|
21
|
-
}),
|
|
22
|
-
'inner payload',
|
|
23
|
-
);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('toolResultText preserves explicit empty tool_result clears', () => {
|
|
27
|
-
assert.equal(toolResultText({ type: 'tool_result', content: '' }), '');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('toolResultText preserves empty tool_result content arrays', () => {
|
|
31
|
-
assert.equal(toolResultText({ type: 'tool_result', content: [] }), '');
|
|
32
|
-
assert.notEqual(toolResultText({ type: 'tool_result', content: [] }), '{"type":"tool_result","content":[]}');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('toolResultText preserves empty typed text parts inside tool_result', () => {
|
|
36
|
-
assert.equal(
|
|
37
|
-
toolResultText({ type: 'tool_result', content: [{ type: 'text', text: '' }] }),
|
|
38
|
-
'',
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('toolResultText preserves empty object-shaped tool_result inner content', () => {
|
|
43
|
-
assert.equal(toolResultText({ type: 'tool_result', content: {} }), '');
|
|
44
|
-
assert.equal(
|
|
45
|
-
toolResultText({ type: 'tool_result', content: { type: 'text', text: '' } }),
|
|
46
|
-
'',
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('toolResultText still extracts plain strings and structured bodies', () => {
|
|
51
|
-
assert.equal(toolResultText('plain body'), 'plain body');
|
|
52
|
-
assert.equal(
|
|
53
|
-
toolResultText([{ type: 'text', text: 'line one' }, { type: 'text', text: 'line two' }]),
|
|
54
|
-
'line one\nline two',
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('toolResultText maps every top-level array element without using index as depth', () => {
|
|
59
|
-
const parts = Array.from({ length: 14 }, (_, i) => ({ type: 'text', text: `line-${i}` }));
|
|
60
|
-
const out = toolResultText(parts);
|
|
61
|
-
assert.match(out, /^line-0\n/);
|
|
62
|
-
assert.match(out, /\nline-13$/);
|
|
63
|
-
assert.equal(out.split('\n').length, 14);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('toolAggregateDetailFallback uses raw body when summary detail is empty', () => {
|
|
67
|
-
assert.equal(toolAggregateDetailFallback('', 'file contents here'), 'file contents here');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('toolGroupedDisplayFallback uses later extracted body when grouped text is empty', () => {
|
|
71
|
-
assert.equal(toolGroupedDisplayFallback('', '', 'recovered body'), 'recovered body');
|
|
72
|
-
assert.equal(toolGroupedDisplayFallback('group summary', '', 'later body'), 'group summary');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
|