mixdog 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -38
- package/package.json +10 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/r5-orchestrated-task.json +7 -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-r13-clientmeta.json +464 -0
- package/scripts/bench/round-r14-betafeatures.json +466 -0
- package/scripts/bench/round-r15-fulldefault.json +462 -0
- package/scripts/bench/round-r16-sessionid.json +466 -0
- package/scripts/bench/round-r17-wirebytes.json +456 -0
- package/scripts/bench/round-r18-prewarm.json +468 -0
- package/scripts/bench/round-r19-clean.json +472 -0
- package/scripts/bench/round-r20-prewarm-clean.json +475 -0
- package/scripts/bench/round-r21-delta-retry.json +473 -0
- package/scripts/bench/round-r22-full-probe.json +693 -0
- package/scripts/bench/round-r23-itemprobe.json +701 -0
- package/scripts/bench/round-r24-shapefix.json +677 -0
- package/scripts/bench/round-r25-serial.json +464 -0
- package/scripts/bench/round-r26-parallel3.json +671 -0
- package/scripts/bench/round-r27-parallel10.json +894 -0
- package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
- package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
- package/scripts/bench/round-r30-instid.json +253 -0
- package/scripts/bench/round-r31-upgradeprobe.json +256 -0
- package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
- package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
- package/scripts/bench/round-r34-orchestrated.json +120 -0
- package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
- package/scripts/bench/round-r36-orchestrated-capped.json +128 -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 +251 -32
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +3 -4
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/model-catalog-audit.mjs +209 -0
- package/scripts/model-list-sanitize-test.mjs +37 -0
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +76 -13
- package/scripts/recall-quality-cases.json +12 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +25 -65
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +9 -8
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/scheduler-task/AGENT.md +2 -3
- package/src/agents/webhook-handler/AGENT.md +2 -3
- package/src/agents/worker/AGENT.md +10 -7
- package/src/app.mjs +12 -1
- package/src/headless-role.mjs +7 -1
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +647 -2056
- package/src/output-styles/default.md +30 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +8 -6
- package/src/output-styles/simple.md +21 -7
- package/src/rules/agent/00-common.md +6 -3
- package/src/rules/agent/30-explorer.md +16 -5
- package/src/rules/lead/01-general.md +5 -5
- package/src/rules/lead/lead-brief.md +15 -0
- package/src/rules/lead/lead-tool.md +6 -15
- package/src/rules/shared/01-tool.md +17 -21
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -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/provider-catalog-cache.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
- 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 +35 -44
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +27 -318
- package/src/runtime/channels/backends/telegram.mjs +8 -12
- package/src/runtime/channels/index.mjs +247 -701
- package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
- package/src/runtime/channels/lib/config.mjs +37 -149
- package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
- package/src/runtime/channels/lib/event-queue.mjs +78 -13
- package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
- package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/scheduler.mjs +27 -113
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -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 +43 -616
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +210 -1948
- package/src/runtime/memory/lib/core-memory-store.mjs +5 -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/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/ko-morph.mjs +195 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
- package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +802 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/config.mjs +14 -4
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
- package/src/runtime/shared/schedules-store.mjs +13 -3
- 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 +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +119 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +205 -0
- package/src/session-runtime/quick-search-models.mjs +47 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +352 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +1 -1
- 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 +138 -669
- package/src/standalone/channel-admin.mjs +102 -90
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +64 -14
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1059 -8110
- package/src/tui/app/app-format.mjs +213 -0
- package/src/tui/app/channel-pickers.mjs +508 -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 +356 -0
- package/src/tui/app/model-options.mjs +334 -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 +489 -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 +677 -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 +512 -0
- package/src/tui/app/use-transcript-window.mjs +607 -0
- package/src/tui/components/ConfirmBar.jsx +10 -7
- package/src/tui/components/Picker.jsx +64 -15
- package/src/tui/components/PromptInput.jsx +33 -102
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +69 -15
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +13553 -12384
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +170 -574
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +65 -1
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -18
- package/src/workflows/sequential/WORKFLOW.md +16 -18
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +112 -7
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/channels/lib/holidays.mjs +0 -138
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transcript-window.mjs — the transcript row-estimate + virtual-window engine,
|
|
3
|
+
* extracted verbatim from App.jsx. Owns ALL module-level caches for this
|
|
4
|
+
* cluster (variant-key, estimated-rows, measured-rows, sig-part). Pure module:
|
|
5
|
+
* no React, no App closures. The App imports the functions and (for the
|
|
6
|
+
* measured-height cache it writes from a layout effect) the shared cache +
|
|
7
|
+
* variant-key helper by name.
|
|
8
|
+
*
|
|
9
|
+
* Behavior is byte-for-byte the same as when these lived in App.jsx; only the
|
|
10
|
+
* home moved. The env-tunable constants and their comments are preserved.
|
|
11
|
+
*/
|
|
12
|
+
import {
|
|
13
|
+
measureMarkdownRenderedRows,
|
|
14
|
+
measureStreamingMarkdownRenderedRows,
|
|
15
|
+
} from '../markdown/measure-rendered-rows.mjs';
|
|
16
|
+
import { streamingLayoutText } from '../markdown/streaming-markdown.mjs';
|
|
17
|
+
import { displayWidth } from '../display-width.mjs';
|
|
18
|
+
import { formatToolSurface, normalizeToolName, parseToolArgs, summarizeAgentSurfaceBrief } from '../../runtime/shared/tool-surface.mjs';
|
|
19
|
+
import { isBackgroundErrorOnlyBody } from '../../runtime/shared/err-text.mjs';
|
|
20
|
+
import { formatExpandedResult, wrapExpandedResultLines } from '../components/tool-output-format.mjs';
|
|
21
|
+
import {
|
|
22
|
+
formatHookDenialDetail,
|
|
23
|
+
isHookApprovalDenialToolItem,
|
|
24
|
+
shouldSuppressFullyFailedToolItem,
|
|
25
|
+
toolItemResultText,
|
|
26
|
+
} from '../transcript-tool-failures.mjs';
|
|
27
|
+
|
|
28
|
+
function positiveIntEnv(name, fallback) {
|
|
29
|
+
const value = Number(process.env[name]);
|
|
30
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Per-keystroke render cost is proportional to the number of MOUNTED transcript
|
|
34
|
+
// items: ink's renderNodeToOutput still serializes (squashTextNodes/wrapText/
|
|
35
|
+
// output.write) every child even when an overflow:hidden viewport clips it
|
|
36
|
+
// off-screen — clipping only trims write coordinates, not the serialization. So
|
|
37
|
+
// the only lever for typing latency on a tall transcript is mounting fewer
|
|
38
|
+
// rows. The window keeps a small ITEM floor (so a few items stay mounted for
|
|
39
|
+
// stable scroll/overscan) but is otherwise driven by the viewport+overscan ROW
|
|
40
|
+
// span, not a large fixed item count. All three are env-tunable for A/B / revert.
|
|
41
|
+
export const TRANSCRIPT_WINDOW_MIN_ITEMS = positiveIntEnv('MIXDOG_TUI_TRANSCRIPT_WINDOW_MIN_ITEMS', 12);
|
|
42
|
+
export const TRANSCRIPT_WINDOW_OVERSCAN_ROWS = positiveIntEnv('MIXDOG_TUI_TRANSCRIPT_OVERSCAN_ROWS', 16);
|
|
43
|
+
|
|
44
|
+
// Hard cap on simultaneously MOUNTED transcript items. Every mounted child is
|
|
45
|
+
// fully serialized by ink each frame (clipping only trims write coords, not the
|
|
46
|
+
// serialize pass), so this cap is the dominant lever for per-frame render cost
|
|
47
|
+
// on a tall transcript. The viewport+overscan ROW span already drives the
|
|
48
|
+
// window; this cap only bounds the worst case (many short rows). 180 mounted
|
|
49
|
+
// rows is far more than any viewport needs and made each frame serialize a long
|
|
50
|
+
// tail of off-screen rows, so lower it to a value that still comfortably covers
|
|
51
|
+
// viewport + overscan on a large terminal. Env-tunable for A/B / revert.
|
|
52
|
+
export const TRANSCRIPT_WINDOW_MAX_ITEMS = positiveIntEnv('MIXDOG_TUI_TRANSCRIPT_WINDOW_ITEMS', 80);
|
|
53
|
+
export const SELECTION_PAINT_INTERVAL_MS = positiveIntEnv('MIXDOG_TUI_SELECTION_PAINT_MS', 24);
|
|
54
|
+
// Frame-coalesce edge-drag auto-scroll + wheel scroll: both paths accumulate
|
|
55
|
+
// deltas into one pending total and flush via a single scrollTranscriptRows
|
|
56
|
+
// call per this interval, instead of firing the (expensive: anchor recompute +
|
|
57
|
+
// selection repaint) scrollTranscriptRows on every mousemove/wheel tick.
|
|
58
|
+
export const SCROLL_COALESCE_MS = positiveIntEnv('MIXDOG_TUI_SCROLL_COALESCE_MS', 16);
|
|
59
|
+
export const PROMPT_HISTORY_LIMIT = 50;
|
|
60
|
+
|
|
61
|
+
// Parse a boolean env var that DEFAULTS ON. Any of 0/false/off/no (case-
|
|
62
|
+
// insensitive, trimmed) disables it; everything else (including unset) leaves it
|
|
63
|
+
// on. Used as the kill switch for the app-level measured-height feature below.
|
|
64
|
+
function boolEnvDefaultTrue(name) {
|
|
65
|
+
const raw = process.env[name];
|
|
66
|
+
if (raw == null) return true;
|
|
67
|
+
const v = String(raw).trim().toLowerCase();
|
|
68
|
+
return !(v === '0' || v === 'false' || v === 'off' || v === 'no');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const TRANSCRIPT_MEASURED_ROWS = boolEnvDefaultTrue('MIXDOG_TUI_TRANSCRIPT_MEASURED');
|
|
72
|
+
|
|
73
|
+
export function selectionRectsEqual(a, b) {
|
|
74
|
+
if (a === b) return true;
|
|
75
|
+
if (!a || !b) return false;
|
|
76
|
+
return a.mode === b.mode
|
|
77
|
+
&& a.x1 === b.x1
|
|
78
|
+
&& a.y1 === b.y1
|
|
79
|
+
&& a.x2 === b.x2
|
|
80
|
+
&& a.y2 === b.y2
|
|
81
|
+
&& a.clipY1 === b.clipY1
|
|
82
|
+
&& a.clipY2 === b.clipY2
|
|
83
|
+
&& a.captureText === b.captureText;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function shiftSelectionRectY(rect, deltaY) {
|
|
87
|
+
const dy = Math.round(Number(deltaY) || 0);
|
|
88
|
+
if (!rect || dy === 0) return rect || null;
|
|
89
|
+
return { ...rect, y1: rect.y1 + dy, y2: rect.y2 + dy };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Reading-order compare (row then col): -1 if a<b, 1 if a>b, 0 equal.
|
|
93
|
+
export function comparePoints(a, b) {
|
|
94
|
+
if (a.y !== b.y) return a.y < b.y ? -1 : 1;
|
|
95
|
+
if (a.x !== b.x) return a.x < b.x ? -1 : 1;
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Count how many terminal rows ONE logical line (no '\n') occupies once ink
|
|
100
|
+
// word-wraps it. Mirror the greedy word-wrap so the row estimate is never lower
|
|
101
|
+
// than what ink actually renders.
|
|
102
|
+
function wrappedLineRows(line, width) {
|
|
103
|
+
const text = String(line);
|
|
104
|
+
const full = displayWidth(text);
|
|
105
|
+
if (full === 0) return 1;
|
|
106
|
+
if (full <= width) return 1;
|
|
107
|
+
let rows = 1;
|
|
108
|
+
let col = 0;
|
|
109
|
+
for (const token of text.split(/(\s+)/)) {
|
|
110
|
+
if (!token) continue;
|
|
111
|
+
const tw = displayWidth(token);
|
|
112
|
+
if (tw === 0) continue;
|
|
113
|
+
if (tw > width) {
|
|
114
|
+
// Over-long unbreakable token: ink hard-splits it across rows.
|
|
115
|
+
if (col > 0) { rows++; col = 0; }
|
|
116
|
+
rows += Math.ceil(tw / width) - 1;
|
|
117
|
+
col = tw % width || width;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (col + tw > width) { rows++; col = tw; }
|
|
121
|
+
else { col += tw; }
|
|
122
|
+
}
|
|
123
|
+
return Math.max(1, rows);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function estimateWrappedRows(text, columns, reserve = 4) {
|
|
127
|
+
const width = Math.max(8, Number(columns || 80) - reserve);
|
|
128
|
+
const lines = String(text ?? '').split('\n');
|
|
129
|
+
return Math.max(1, lines.reduce((sum, line) => sum + wrappedLineRows(line, width), 0));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const SKILL_SURFACE_NAMES = new Set([
|
|
133
|
+
'skill', 'skill_execute', 'skill_view', 'skills_list', 'use_skill',
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
function isAgentResponseResultText(text) {
|
|
137
|
+
const value = String(text || '').trim();
|
|
138
|
+
if (!value) return false;
|
|
139
|
+
if (/^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)(?:\s*·\s*task_id:\s*\S+)?$/i.test(value)) return false;
|
|
140
|
+
if (/^(?:background task\b|agent task:|task_id:)/i.test(value) && !/\n\s*\n[\s\S]*\S/.test(value)) return false;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const BACKGROUND_TASK_TOOL_NAMES = new Set(['explore', 'search', 'shell', 'bash', 'bash_session', 'shell_command', 'task']);
|
|
145
|
+
|
|
146
|
+
function isBackgroundTaskToolName(normalizedName) {
|
|
147
|
+
return BACKGROUND_TASK_TOOL_NAMES.has(String(normalizedName || '').toLowerCase());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseBackgroundTaskResultForRows(value) {
|
|
151
|
+
const text = String(value || '').trim();
|
|
152
|
+
if (!text) return null;
|
|
153
|
+
const allLines = text.split('\n');
|
|
154
|
+
const start = allLines.findIndex((line) => line.trim() === 'background task');
|
|
155
|
+
if (start < 0) return null;
|
|
156
|
+
const rest = allLines.slice(start + 1);
|
|
157
|
+
const blank = rest.findIndex((line) => !line.trim());
|
|
158
|
+
const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
|
|
159
|
+
const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
|
|
160
|
+
const fields = {};
|
|
161
|
+
for (const line of headLines) {
|
|
162
|
+
const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
|
|
163
|
+
if (match) fields[match[1].toLowerCase()] = match[2].trim();
|
|
164
|
+
}
|
|
165
|
+
const status = String(fields.status || '').toLowerCase();
|
|
166
|
+
const error = String(fields.error || '').trim();
|
|
167
|
+
const errorOnlyBody = isBackgroundErrorOnlyBody(body, error);
|
|
168
|
+
return {
|
|
169
|
+
status,
|
|
170
|
+
body,
|
|
171
|
+
error,
|
|
172
|
+
errorOnlyBody,
|
|
173
|
+
hasResponse: Boolean(body) && !errorOnlyBody && !/^(running|pending|queued)$/i.test(status),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isBackgroundTaskResponseArgsForRows(normalizedName, args = {}) {
|
|
178
|
+
if (!isBackgroundTaskToolName(normalizedName)) return false;
|
|
179
|
+
const type = String(args?.type || args?.action || '').toLowerCase();
|
|
180
|
+
const status = String(args?.status || '').toLowerCase();
|
|
181
|
+
return type === 'result' || type === 'completion' || (/^(completed|cancelled|canceled)$/i.test(status) && Boolean(args?.task_id));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ToolExecution derives its background-task classification from
|
|
185
|
+
// formatToolSurface(name, args).args — parseToolArgs(args). The row estimate /
|
|
186
|
+
// variant key must read the SAME parsed shape. parseToolArgs already guards
|
|
187
|
+
// malformed input (returns {} / { value } without throwing).
|
|
188
|
+
function backgroundArgsForRows(rawArgs) {
|
|
189
|
+
const parsed = parseToolArgs(rawArgs);
|
|
190
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function toolItemPendingForRows(item) {
|
|
194
|
+
const count = Math.max(1, Number(item?.count || 1));
|
|
195
|
+
const done = Math.max(0, Math.min(count, Number(item?.completedCount || (item?.result == null ? 0 : count))));
|
|
196
|
+
return done < count;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const LEADING_STATUS_MARKER_LINE_RE = /^\[status:\s*[^\]]*\]\s*$/i;
|
|
200
|
+
|
|
201
|
+
function stripLeadingStatusMarkerFromTextForRows(text) {
|
|
202
|
+
const lines = String(text || '').split('\n');
|
|
203
|
+
if (lines.length > 0 && LEADING_STATUS_MARKER_LINE_RE.test(String(lines[0] ?? '').trim())) lines.shift();
|
|
204
|
+
return lines.join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function toolDisplayedResultTextForRows(item) {
|
|
208
|
+
const rt = item?.result == null ? '' : String(item.result).replace(/\s+$/, '');
|
|
209
|
+
const bgArgs = backgroundArgsForRows(item?.args);
|
|
210
|
+
const backgroundError = String(bgArgs.error || '');
|
|
211
|
+
const errorOnlyResult = Boolean(rt) && isBackgroundErrorOnlyBody(rt, backgroundError);
|
|
212
|
+
const normalizedName = String(normalizeToolName(item?.name) || '').toLowerCase();
|
|
213
|
+
if (!toolItemPendingForRows(item) && isBackgroundTaskToolName(normalizedName)) {
|
|
214
|
+
const meta = parseBackgroundTaskResultForRows(rt);
|
|
215
|
+
if (meta?.hasResponse && String(meta.body || '').trim()) {
|
|
216
|
+
return stripLeadingStatusMarkerFromTextForRows(String(meta.body));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return stripLeadingStatusMarkerFromTextForRows(errorOnlyResult ? '' : (rt || ''));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function toolHasDisplayResultForRows(item) {
|
|
223
|
+
const rt = item.result == null ? '' : String(item.result).replace(/\s+$/, '');
|
|
224
|
+
const trimmed = String(rt || '').trim();
|
|
225
|
+
if (!trimmed) return false;
|
|
226
|
+
const bgArgs = backgroundArgsForRows(item.args);
|
|
227
|
+
if (isBackgroundErrorOnlyBody(trimmed, bgArgs.error || '')) return false;
|
|
228
|
+
const normalizedName = String(normalizeToolName(item.name) || '').toLowerCase();
|
|
229
|
+
if (isBackgroundTaskToolName(normalizedName)) {
|
|
230
|
+
const meta = parseBackgroundTaskResultForRows(trimmed);
|
|
231
|
+
if (meta) return Boolean(meta.hasResponse && String(meta.body || '').trim());
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function toolExpandedRawTextForRows(item, rawRt) {
|
|
237
|
+
if (item?.aggregate) return rawRt;
|
|
238
|
+
const hasDisplayResult = toolHasDisplayResultForRows(item);
|
|
239
|
+
if (hasDisplayResult) return toolDisplayedResultTextForRows(item);
|
|
240
|
+
return stripLeadingStatusMarkerFromTextForRows(rawRt || '');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function toolHeaderFailureOnlyForRows(item, normalizedName, hasDisplayResult) {
|
|
244
|
+
if (hasDisplayResult) return false;
|
|
245
|
+
const bgArgs = backgroundArgsForRows(item.args);
|
|
246
|
+
const error = String(bgArgs.error || '').trim();
|
|
247
|
+
if (!error) return false;
|
|
248
|
+
if (normalizedName === 'agent') {
|
|
249
|
+
const pending = toolItemPendingForRows(item);
|
|
250
|
+
const isError = Boolean(item.isError);
|
|
251
|
+
const agentHeaderFailure = !pending && isError && error && !hasDisplayResult;
|
|
252
|
+
if (!agentHeaderFailure) return false;
|
|
253
|
+
const displayedResultText = toolDisplayedResultTextForRows(item);
|
|
254
|
+
const rt = item.result == null ? '' : String(item.result).replace(/\s+$/, '');
|
|
255
|
+
const isAgentResult = !pending && hasDisplayResult;
|
|
256
|
+
const isAgentResponse = isAgentResult && isAgentResponseResultText(rt);
|
|
257
|
+
const briefRaw = summarizeAgentSurfaceBrief(item.name, bgArgs, displayedResultText, {
|
|
258
|
+
isError,
|
|
259
|
+
isResponse: isAgentResponse,
|
|
260
|
+
});
|
|
261
|
+
const agentSurfaceBriefNonempty = Boolean(String(briefRaw || '').trim());
|
|
262
|
+
return !agentSurfaceBriefNonempty;
|
|
263
|
+
}
|
|
264
|
+
if (!isBackgroundTaskToolName(normalizedName) || !bgArgs.task_id) return false;
|
|
265
|
+
if (isBackgroundTaskResponseArgsForRows(normalizedName, bgArgs)) return false;
|
|
266
|
+
const status = String(bgArgs.status || '').toLowerCase();
|
|
267
|
+
return /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function toolArgPathForRows(item) {
|
|
271
|
+
const a = backgroundArgsForRows(item?.args);
|
|
272
|
+
return a?.path ?? a?.file_path ?? a?.file ?? '';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function isShellSurfaceForRows(normalizedName, label = '') {
|
|
276
|
+
const n = String(normalizedName || '').toLowerCase();
|
|
277
|
+
const l = String(label || '').toLowerCase();
|
|
278
|
+
return n === 'shell' || n === 'bash' || n === 'bash_session'
|
|
279
|
+
|| n === 'shell_command' || n === 'job_wait' || l === 'run';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function isShellSurfaceForToolItem(item, normalizedName) {
|
|
283
|
+
const label = formatToolSurface(item?.name, item?.args)?.label || '';
|
|
284
|
+
return isShellSurfaceForRows(normalizedName, label);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// EXPANDED tool bodies are post-processed by formatExpandedResult; the row
|
|
288
|
+
// estimate MUST run the SAME pipeline. Pass pathArg/isShell so the count matches.
|
|
289
|
+
function estimateToolRenderedResultRows(value, { pathArg = '', isShell = false, columns = 80 } = {}) {
|
|
290
|
+
const text = String(value ?? '').replace(/\s+$/, '');
|
|
291
|
+
if (!text) return 1;
|
|
292
|
+
try {
|
|
293
|
+
const logical = formatExpandedResult(text, { pathArg, isShell });
|
|
294
|
+
const rows = wrapExpandedResultLines(logical, columns, { isShell }).length;
|
|
295
|
+
return Math.max(1, rows);
|
|
296
|
+
} catch {
|
|
297
|
+
return Math.max(1, text.split('\n').length);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function estimateTranscriptItemRows(item, columns, toolOutputExpanded) {
|
|
302
|
+
if (!item) return 1;
|
|
303
|
+
switch (item.kind) {
|
|
304
|
+
case 'user':
|
|
305
|
+
return 1 + estimateWrappedRows(item.text, columns, 4);
|
|
306
|
+
case 'assistant':
|
|
307
|
+
// marginTop={1} (AssistantMessage <Box>) + rendered body height.
|
|
308
|
+
return 1 + (item.streaming
|
|
309
|
+
? measureStreamingMarkdownRenderedRows(item.text, columns, item.id)
|
|
310
|
+
: measureMarkdownRenderedRows(item.text, columns));
|
|
311
|
+
case 'tool': {
|
|
312
|
+
const TOOL_MARGIN_TOP = 1;
|
|
313
|
+
if (shouldSuppressFullyFailedToolItem(item)) return 0;
|
|
314
|
+
if (isHookApprovalDenialToolItem(item)) {
|
|
315
|
+
const detail = formatHookDenialDetail(toolItemResultText(item));
|
|
316
|
+
return TOOL_MARGIN_TOP + 1 + (detail ? 1 : 0);
|
|
317
|
+
}
|
|
318
|
+
const normalizedName = String(normalizeToolName(item.name) || '').toLowerCase();
|
|
319
|
+
const count = Math.max(1, Number(item.count || 1));
|
|
320
|
+
const done = Math.max(0, Math.min(count, Number(item.completedCount || (item.result == null ? 0 : count))));
|
|
321
|
+
const pending = done < count;
|
|
322
|
+
const isSkillSurface = !item.aggregate && SKILL_SURFACE_NAMES.has(normalizedName);
|
|
323
|
+
const isAgentSurface = normalizedName === 'agent';
|
|
324
|
+
const rt = item.result == null ? null : String(item.result).replace(/\s+$/, '');
|
|
325
|
+
const rawRt = item.rawResult == null ? null : String(item.rawResult).replace(/\s+$/, '');
|
|
326
|
+
const hasResult = item.result != null && Boolean(String(rt || '').trim());
|
|
327
|
+
const hasRawResult = item.rawResult != null && Boolean(String(rawRt || '').trim());
|
|
328
|
+
const expanded = toolOutputExpanded || item.expanded;
|
|
329
|
+
if (!expanded || pending) {
|
|
330
|
+
if (isSkillSurface) return TOOL_MARGIN_TOP + 1;
|
|
331
|
+
const hasDisplayResult = toolHasDisplayResultForRows(item);
|
|
332
|
+
if (toolHeaderFailureOnlyForRows(item, normalizedName, hasDisplayResult)) {
|
|
333
|
+
return TOOL_MARGIN_TOP + 1;
|
|
334
|
+
}
|
|
335
|
+
return TOOL_MARGIN_TOP + 1 + 1;
|
|
336
|
+
}
|
|
337
|
+
if (hasRawResult) {
|
|
338
|
+
const estimateText = toolExpandedRawTextForRows(item, rawRt);
|
|
339
|
+
const rawOpts = item.aggregate
|
|
340
|
+
? {}
|
|
341
|
+
: { pathArg: toolArgPathForRows(item), isShell: isShellSurfaceForToolItem(item, normalizedName) };
|
|
342
|
+
return TOOL_MARGIN_TOP + 1 + estimateToolRenderedResultRows(estimateText, { ...rawOpts, columns });
|
|
343
|
+
}
|
|
344
|
+
if (isAgentSurface && !hasResult) return TOOL_MARGIN_TOP + 1 + 1;
|
|
345
|
+
if (isSkillSurface && !hasResult) return TOOL_MARGIN_TOP + 1;
|
|
346
|
+
if (item.aggregate) {
|
|
347
|
+
if (hasRawResult) {
|
|
348
|
+
const resultRows = estimateToolRenderedResultRows(rawRt, { columns });
|
|
349
|
+
return TOOL_MARGIN_TOP + 1 + resultRows;
|
|
350
|
+
}
|
|
351
|
+
return TOOL_MARGIN_TOP + 1 + 1;
|
|
352
|
+
} else {
|
|
353
|
+
const backgroundMeta = hasResult && isBackgroundTaskToolName(normalizedName)
|
|
354
|
+
? parseBackgroundTaskResultForRows(rt)
|
|
355
|
+
: null;
|
|
356
|
+
const isBackgroundResult = hasResult && isBackgroundTaskToolName(normalizedName);
|
|
357
|
+
const isBackgroundResponse = isBackgroundResult
|
|
358
|
+
&& (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgsForRows(normalizedName, backgroundArgsForRows(item.args)));
|
|
359
|
+
const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
|
|
360
|
+
if (isBackgroundMetadataResult) {
|
|
361
|
+
const hasDisplayResult = toolHasDisplayResultForRows(item);
|
|
362
|
+
if (toolHeaderFailureOnlyForRows(item, normalizedName, hasDisplayResult)) {
|
|
363
|
+
return TOOL_MARGIN_TOP + 1;
|
|
364
|
+
}
|
|
365
|
+
return isSkillSurface ? TOOL_MARGIN_TOP + 1 : TOOL_MARGIN_TOP + 1 + 1;
|
|
366
|
+
}
|
|
367
|
+
const resultText = backgroundMeta?.hasResponse ? backgroundMeta.body : rt;
|
|
368
|
+
const resultRows = estimateToolRenderedResultRows(resultText, {
|
|
369
|
+
pathArg: toolArgPathForRows(item),
|
|
370
|
+
isShell: isShellSurfaceForToolItem(item, normalizedName),
|
|
371
|
+
columns,
|
|
372
|
+
});
|
|
373
|
+
return TOOL_MARGIN_TOP + 1 + resultRows;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
case 'notice':
|
|
377
|
+
return 1 + estimateWrappedRows(item.text, columns, 6);
|
|
378
|
+
case 'turndone':
|
|
379
|
+
case 'statusdone':
|
|
380
|
+
return 2;
|
|
381
|
+
default:
|
|
382
|
+
return 1;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function lowerBound(values, target) {
|
|
387
|
+
let lo = 0;
|
|
388
|
+
let hi = values.length;
|
|
389
|
+
while (lo < hi) {
|
|
390
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
391
|
+
if (values[mid] < target) lo = mid + 1;
|
|
392
|
+
else hi = mid;
|
|
393
|
+
}
|
|
394
|
+
return lo;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function upperBound(values, target) {
|
|
398
|
+
let lo = 0;
|
|
399
|
+
let hi = values.length;
|
|
400
|
+
while (lo < hi) {
|
|
401
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
402
|
+
if (values[mid] <= target) lo = mid + 1;
|
|
403
|
+
else hi = mid;
|
|
404
|
+
}
|
|
405
|
+
return lo;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Resolve the absolute scroll offset that keeps a captured reading anchor at the
|
|
409
|
+
// same screen position for the CURRENT prefix table. Pure so it can run during
|
|
410
|
+
// render AND in the post-commit layout effect.
|
|
411
|
+
export function resolveAnchorScrollOffset({ anchor, items, curPrefix, totalRows, viewRows, maxRows }) {
|
|
412
|
+
if (!anchor || anchor.id == null) return null;
|
|
413
|
+
if (!Array.isArray(curPrefix) || curPrefix.length <= 1) return null;
|
|
414
|
+
const list = Array.isArray(items) ? items : [];
|
|
415
|
+
let idx = -1;
|
|
416
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
417
|
+
if (list[i] && list[i].id === anchor.id) { idx = i; break; }
|
|
418
|
+
}
|
|
419
|
+
if (idx < 0 || idx > curPrefix.length - 2) return null;
|
|
420
|
+
const itemHeight = Math.max(0, (curPrefix[idx + 1] || 0) - (curPrefix[idx] || 0));
|
|
421
|
+
const clampedOffset = Math.max(0, Math.min(Number(anchor.offset) || 0, itemHeight));
|
|
422
|
+
const anchorRowCur = (curPrefix[idx] || 0) + clampedOffset;
|
|
423
|
+
return Math.max(0, Math.min(maxRows, totalRows - viewRows - anchorRowCur));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Cheap, stable height fingerprint for a text blob (length + newline count +
|
|
427
|
+
// FNV-1a hash), so a same-length edit that changes wrap/newline shape still
|
|
428
|
+
// invalidates the row/signature caches.
|
|
429
|
+
function fnv1a32(str) {
|
|
430
|
+
let h = 0x811c9dc5;
|
|
431
|
+
for (let i = 0; i < str.length; i++) {
|
|
432
|
+
h ^= str.charCodeAt(i);
|
|
433
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
434
|
+
}
|
|
435
|
+
return h >>> 0;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Two INDEPENDENT 32-bit rolling-hash steps folded into a 64-bit signature.
|
|
439
|
+
// fnvStepA is plain FNV-1a; fnvStepB uses a distinct seed/prime + xorshift
|
|
440
|
+
// finalizer so the two chains are decorrelated (see the App.jsx history note).
|
|
441
|
+
export function fnvStepA(hash, str) {
|
|
442
|
+
let h = hash >>> 0;
|
|
443
|
+
for (let i = 0; i < str.length; i++) {
|
|
444
|
+
h ^= str.charCodeAt(i);
|
|
445
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
446
|
+
}
|
|
447
|
+
return h >>> 0;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function fnvStepB(hash, str) {
|
|
451
|
+
let h = hash >>> 0;
|
|
452
|
+
for (let i = 0; i < str.length; i++) {
|
|
453
|
+
h = Math.imul(h ^ str.charCodeAt(i), 0x85ebca77) >>> 0;
|
|
454
|
+
h = (h ^ (h >>> 13)) >>> 0;
|
|
455
|
+
}
|
|
456
|
+
return h >>> 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function textShapeFingerprint(value) {
|
|
460
|
+
if (value == null) return 'z';
|
|
461
|
+
const text = String(value);
|
|
462
|
+
const len = text.length;
|
|
463
|
+
if (len === 0) return 'e';
|
|
464
|
+
let newlines = 0;
|
|
465
|
+
for (let i = 0; i < len; i++) {
|
|
466
|
+
if (text.charCodeAt(i) === 10) newlines++;
|
|
467
|
+
}
|
|
468
|
+
return `${len}.${newlines}.${fnv1a32(text).toString(36)}`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Identity-keyed memo for the variant key. Exported: the App's measured-height
|
|
472
|
+
// layout effect validates its cache writes on the SAME variant key.
|
|
473
|
+
const transcriptVariantKeyCache = new WeakMap();
|
|
474
|
+
|
|
475
|
+
export function transcriptItemVariantKey(item) {
|
|
476
|
+
if (item && typeof item === 'object') {
|
|
477
|
+
const cached = transcriptVariantKeyCache.get(item);
|
|
478
|
+
if (cached !== undefined) return cached;
|
|
479
|
+
const key = computeTranscriptItemVariantKey(item);
|
|
480
|
+
transcriptVariantKeyCache.set(item, key);
|
|
481
|
+
return key;
|
|
482
|
+
}
|
|
483
|
+
return computeTranscriptItemVariantKey(item);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function computeTranscriptItemVariantKey(item) {
|
|
487
|
+
const expanded = item.expanded ? 1 : 0;
|
|
488
|
+
if (item.kind === 'tool') {
|
|
489
|
+
const resultShape = textShapeFingerprint(item.result);
|
|
490
|
+
const rawShape = textShapeFingerprint(item.rawResult);
|
|
491
|
+
const count = Number(item.count ?? 0);
|
|
492
|
+
const completed = item.completedCount === undefined ? 'u' : Number(item.completedCount);
|
|
493
|
+
const errors = item.errorCount === undefined ? 'u' : Number(item.errorCount);
|
|
494
|
+
const isError = item.isError ? 1 : 0;
|
|
495
|
+
const normalizedName = String(normalizeToolName(item.name) || '').toLowerCase();
|
|
496
|
+
const aggregate = item.aggregate ? 1 : 0;
|
|
497
|
+
const bgArgs = backgroundArgsForRows(item.args);
|
|
498
|
+
const bgType = String(bgArgs.type || bgArgs.action || '');
|
|
499
|
+
const bgStatus = String(bgArgs.status || '');
|
|
500
|
+
const bgTaskId = bgArgs.task_id ? 1 : 0;
|
|
501
|
+
const bgPrompt = textShapeFingerprint(bgArgs.prompt);
|
|
502
|
+
const bgMessage = textShapeFingerprint(bgArgs.message);
|
|
503
|
+
const bgError = textShapeFingerprint(bgArgs.error);
|
|
504
|
+
return `x${expanded}:n${normalizedName}:g${aggregate}:r${resultShape}:R${rawShape}:c${count}:d${completed}:e${errors}:E${isError}:bt${bgType}:bs${bgStatus}:bk${bgTaskId}:bp${bgPrompt}:bm${bgMessage}:be${bgError}`;
|
|
505
|
+
}
|
|
506
|
+
return `x${expanded}:s${textShapeFingerprint(item.text ?? item.result ?? '')}`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Per-item ESTIMATED ROW COUNT cache for buildTranscriptRowIndex.
|
|
510
|
+
const transcriptRowsCache = new WeakMap();
|
|
511
|
+
|
|
512
|
+
// App-level MEASURED row heights (real per-item height cache). Exported so the
|
|
513
|
+
// App's per-commit layout effect can write/read/prune it; validated on the same
|
|
514
|
+
// (variantKey + columns + toolExpanded) tuple as the estimate caches.
|
|
515
|
+
export const transcriptMeasuredRowsCache = new WeakMap();
|
|
516
|
+
|
|
517
|
+
export function measuredTranscriptRows(item, columns, toolOutputExpanded) {
|
|
518
|
+
if (!TRANSCRIPT_MEASURED_ROWS || !item) return null;
|
|
519
|
+
if (shouldSuppressFullyFailedToolItem(item)) return 0;
|
|
520
|
+
if (item.kind === 'assistant' && item.streaming) return null;
|
|
521
|
+
const entry = transcriptMeasuredRowsCache.get(item);
|
|
522
|
+
if (!entry) return null;
|
|
523
|
+
if (entry.rows <= 0) return null;
|
|
524
|
+
if (entry.columns !== columns) return null;
|
|
525
|
+
if (entry.toolExpanded !== (toolOutputExpanded ? 1 : 0)) return null;
|
|
526
|
+
if (entry.variantKey !== transcriptItemVariantKey(item)) return null;
|
|
527
|
+
return entry.rows;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const STREAMING_ROW_QUANTUM = 1;
|
|
531
|
+
|
|
532
|
+
function assistantTextForStreamingRowEstimate(text) {
|
|
533
|
+
return streamingLayoutText(text);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function streamingEstimateRows(item, columns, toolOutputExpanded) {
|
|
537
|
+
const trimmedText = assistantTextForStreamingRowEstimate(item.text);
|
|
538
|
+
const estimateItem = trimmedText === item.text ? item : { ...item, text: trimmedText };
|
|
539
|
+
const raw = Math.max(1, Math.ceil(estimateTranscriptItemRows(estimateItem, columns, toolOutputExpanded)));
|
|
540
|
+
return Math.ceil(raw / STREAMING_ROW_QUANTUM) * STREAMING_ROW_QUANTUM;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded) {
|
|
544
|
+
if (!item) return Math.max(1, Math.ceil(estimateTranscriptItemRows(item, columns, toolOutputExpanded)));
|
|
545
|
+
if (shouldSuppressFullyFailedToolItem(item)) return 0;
|
|
546
|
+
if (item.kind === 'assistant' && item.streaming) {
|
|
547
|
+
return streamingEstimateRows(item, columns, toolOutputExpanded);
|
|
548
|
+
}
|
|
549
|
+
const variantKey = transcriptItemVariantKey(item);
|
|
550
|
+
const toolExpanded = toolOutputExpanded ? 1 : 0;
|
|
551
|
+
const cached = transcriptRowsCache.get(item);
|
|
552
|
+
if (cached
|
|
553
|
+
&& cached.columns === columns
|
|
554
|
+
&& cached.toolExpanded === toolExpanded
|
|
555
|
+
&& cached.variantKey === variantKey
|
|
556
|
+
&& cached.id === item.id
|
|
557
|
+
&& cached.kind === item.kind) {
|
|
558
|
+
return cached.rows;
|
|
559
|
+
}
|
|
560
|
+
const rows = Math.max(1, Math.ceil(estimateTranscriptItemRows(item, columns, toolOutputExpanded)));
|
|
561
|
+
transcriptRowsCache.set(item, { id: item.id, kind: item.kind, variantKey, columns, toolExpanded, rows });
|
|
562
|
+
return rows;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function buildTranscriptRowIndex(items, {
|
|
566
|
+
columns = 80,
|
|
567
|
+
toolOutputExpanded = false,
|
|
568
|
+
suppressMeasuredRowHeights = false,
|
|
569
|
+
} = {}) {
|
|
570
|
+
const allItems = Array.isArray(items) ? items : [];
|
|
571
|
+
const rows = new Array(allItems.length);
|
|
572
|
+
const prefixRows = new Array(allItems.length + 1);
|
|
573
|
+
prefixRows[0] = 0;
|
|
574
|
+
for (let i = 0; i < allItems.length; i++) {
|
|
575
|
+
const item = allItems[i];
|
|
576
|
+
const measured = suppressMeasuredRowHeights
|
|
577
|
+
? null
|
|
578
|
+
: measuredTranscriptRows(item, columns, toolOutputExpanded);
|
|
579
|
+
const rowCount = measured != null
|
|
580
|
+
? measured
|
|
581
|
+
: estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded);
|
|
582
|
+
rows[i] = rowCount;
|
|
583
|
+
prefixRows[i + 1] = prefixRows[i] + rowCount;
|
|
584
|
+
}
|
|
585
|
+
return { rows, prefixRows, totalRows: prefixRows[allItems.length] || 0 };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Stable signature for the transcript row-index / window memos. Changes only
|
|
589
|
+
// when transcript STRUCTURE changes or the streaming item's estimated height
|
|
590
|
+
// changes — not on every character. Per-item sigParts are identity-memoized.
|
|
591
|
+
const transcriptSigPartCache = new WeakMap();
|
|
592
|
+
|
|
593
|
+
export function transcriptStructureSignature(items, columns, toolOutputExpanded) {
|
|
594
|
+
const list = Array.isArray(items) ? items : [];
|
|
595
|
+
let hA = fnvStepA(0x811c9dc5, `${list.length}|${columns}|${toolOutputExpanded ? 1 : 0}`);
|
|
596
|
+
let hB = fnvStepB(0xcbf29ce4, `${list.length}|${columns}|${toolOutputExpanded ? 1 : 0}`);
|
|
597
|
+
for (let i = 0; i < list.length; i++) {
|
|
598
|
+
const it = list[i];
|
|
599
|
+
let sigPart;
|
|
600
|
+
if (!it) {
|
|
601
|
+
sigPart = '_';
|
|
602
|
+
} else if (it.kind === 'assistant' && it.streaming) {
|
|
603
|
+
sigPart = `a${it.id}:${streamingEstimateRows(it, columns, toolOutputExpanded)}`;
|
|
604
|
+
} else {
|
|
605
|
+
const variantKey = transcriptItemVariantKey(it);
|
|
606
|
+
const cached = transcriptSigPartCache.get(it);
|
|
607
|
+
if (cached
|
|
608
|
+
&& cached.variantKey === variantKey
|
|
609
|
+
&& cached.columns === columns
|
|
610
|
+
&& cached.id === it.id
|
|
611
|
+
&& cached.kind === it.kind) {
|
|
612
|
+
sigPart = cached.sigPart;
|
|
613
|
+
} else {
|
|
614
|
+
sigPart = `${it.kind?.[0] || '?'}${it.id}:${variantKey}`;
|
|
615
|
+
transcriptSigPartCache.set(it, { id: it.id, kind: it.kind, variantKey, columns, sigPart });
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
hA = fnvStepA(hA, `;${i};`);
|
|
619
|
+
hA = fnvStepA(hA, sigPart);
|
|
620
|
+
hB = fnvStepB(hB, `;${i};`);
|
|
621
|
+
hB = fnvStepB(hB, sigPart);
|
|
622
|
+
}
|
|
623
|
+
return `${hA.toString(36)}.${hB.toString(36)}`;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function transcriptRenderWindow(items, { scrollOffset = 0, viewportHeight = 24, columns = 80, toolOutputExpanded = false, rowIndex = null } = {}) {
|
|
627
|
+
const allItems = Array.isArray(items) ? items : [];
|
|
628
|
+
const itemCount = allItems.length;
|
|
629
|
+
const fallbackIndex = rowIndex?.prefixRows?.length === itemCount + 1
|
|
630
|
+
? rowIndex
|
|
631
|
+
: buildTranscriptRowIndex(allItems, { columns, toolOutputExpanded });
|
|
632
|
+
const totalRows = Math.max(0, fallbackIndex.totalRows || 0);
|
|
633
|
+
const viewRows = Math.max(1, Number(viewportHeight) || 24);
|
|
634
|
+
const maxScrollRows = Math.max(0, totalRows - viewRows);
|
|
635
|
+
const effectiveScrollOffset = Math.min(
|
|
636
|
+
maxScrollRows,
|
|
637
|
+
Math.max(0, Math.ceil(Number(scrollOffset) || 0)),
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
const bypassRowBudget = viewRows + TRANSCRIPT_WINDOW_OVERSCAN_ROWS * 2;
|
|
641
|
+
if (itemCount <= TRANSCRIPT_WINDOW_MIN_ITEMS || totalRows <= bypassRowBudget) {
|
|
642
|
+
return { startIndex: 0, endIndex: itemCount, items: allItems, bottomSpacerRows: 0, totalRows, maxScrollRows, effectiveScrollOffset };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const minItems = Math.min(TRANSCRIPT_WINDOW_MIN_ITEMS, itemCount);
|
|
646
|
+
const maxItems = Math.max(minItems, TRANSCRIPT_WINDOW_MAX_ITEMS);
|
|
647
|
+
const prefixRows = fallbackIndex.prefixRows;
|
|
648
|
+
const visibleTop = Math.max(0, totalRows - effectiveScrollOffset - viewRows);
|
|
649
|
+
const visibleBottom = Math.min(totalRows, totalRows - effectiveScrollOffset);
|
|
650
|
+
const desiredTop = Math.max(0, visibleTop - TRANSCRIPT_WINDOW_OVERSCAN_ROWS);
|
|
651
|
+
const desiredBottom = Math.min(totalRows, visibleBottom + TRANSCRIPT_WINDOW_OVERSCAN_ROWS);
|
|
652
|
+
|
|
653
|
+
let startIndex = Math.max(0, upperBound(prefixRows, desiredTop) - 1);
|
|
654
|
+
let endIndex = Math.min(itemCount, Math.max(startIndex + 1, lowerBound(prefixRows, Math.max(desiredBottom, desiredTop + 1))));
|
|
655
|
+
|
|
656
|
+
while (endIndex - startIndex < minItems && startIndex > 0) startIndex--;
|
|
657
|
+
while (endIndex - startIndex < minItems && endIndex < itemCount) endIndex++;
|
|
658
|
+
|
|
659
|
+
if (endIndex - startIndex > maxItems) {
|
|
660
|
+
const visibleStartIndex = Math.max(0, upperBound(prefixRows, visibleTop) - 1);
|
|
661
|
+
const visibleEndIndex = Math.min(itemCount, Math.max(visibleStartIndex + 1, lowerBound(prefixRows, Math.max(visibleBottom, visibleTop + 1))));
|
|
662
|
+
startIndex = Math.max(0, Math.min(visibleStartIndex, itemCount - maxItems));
|
|
663
|
+
endIndex = Math.min(itemCount, Math.max(visibleEndIndex, startIndex + maxItems));
|
|
664
|
+
if (endIndex - startIndex > maxItems) startIndex = Math.max(0, endIndex - maxItems);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const bottomSpacerRows = Math.max(0, totalRows - (prefixRows[endIndex] || totalRows));
|
|
668
|
+
return {
|
|
669
|
+
startIndex,
|
|
670
|
+
endIndex,
|
|
671
|
+
items: allItems.slice(startIndex, endIndex),
|
|
672
|
+
bottomSpacerRows,
|
|
673
|
+
totalRows,
|
|
674
|
+
maxScrollRows,
|
|
675
|
+
effectiveScrollOffset,
|
|
676
|
+
};
|
|
677
|
+
}
|