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,749 @@
|
|
|
1
|
+
// Compaction window selection, recall-tail preservation, and the semantic +
|
|
2
|
+
// recall fast-track compaction entry points. Extracted verbatim from
|
|
3
|
+
// compact.mjs (behavior-preserving).
|
|
4
|
+
import {
|
|
5
|
+
sanitizeToolPairs,
|
|
6
|
+
dedupToolResultBodies,
|
|
7
|
+
reconcileDedupStubs,
|
|
8
|
+
estimateMessagesTokens,
|
|
9
|
+
DEFAULT_COMPACTION_KEEP_TOKENS,
|
|
10
|
+
} from '../context-utils.mjs';
|
|
11
|
+
import {
|
|
12
|
+
SUMMARY_OUTPUT_TOKENS,
|
|
13
|
+
COMPACT_SUMMARY_MIN_ROOM_TOKENS,
|
|
14
|
+
COMPACT_TYPE_SEMANTIC,
|
|
15
|
+
COMPACT_TYPE_RECALL_FASTTRACK,
|
|
16
|
+
compactDebugLog,
|
|
17
|
+
} from './constants.mjs';
|
|
18
|
+
import {
|
|
19
|
+
extractText,
|
|
20
|
+
safeEstimateMessagesTokens,
|
|
21
|
+
textByteLength,
|
|
22
|
+
messageContentHasMarker,
|
|
23
|
+
redactToolCallSecretsInMessages,
|
|
24
|
+
} from './text-utils.mjs';
|
|
25
|
+
import {
|
|
26
|
+
isSummaryMessage,
|
|
27
|
+
splitProtectedContext,
|
|
28
|
+
} from './messages.mjs';
|
|
29
|
+
import {
|
|
30
|
+
effectiveBudget,
|
|
31
|
+
extractPreservedFacts,
|
|
32
|
+
preserveRecentBudget,
|
|
33
|
+
} from './budget.mjs';
|
|
34
|
+
import {
|
|
35
|
+
COMPACTION_SYSTEM_PROMPT,
|
|
36
|
+
fitCompactionPrompt,
|
|
37
|
+
extractResponseText,
|
|
38
|
+
enforceSemanticSummarySchema,
|
|
39
|
+
fitSemanticSummaryMessage,
|
|
40
|
+
fitRecallFastTrackSummaryMessage,
|
|
41
|
+
stripNestedSummaryHeaderLines,
|
|
42
|
+
RECALL_TAIL_TRUNCATION_MARKER,
|
|
43
|
+
RECALL_TAIL_SHORT_TRUNCATION_MARKER,
|
|
44
|
+
} from './summary.mjs';
|
|
45
|
+
|
|
46
|
+
const DEFAULT_TAIL_TURNS = 2;
|
|
47
|
+
const COMPACTION_PROMPT_HEADROOM = 0.85;
|
|
48
|
+
|
|
49
|
+
function userIndexes(messages) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
52
|
+
if (messages[i]?.role === 'user') out.push(i);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function splitTailIntoTurns(messages) {
|
|
58
|
+
const turns = [];
|
|
59
|
+
let current = null;
|
|
60
|
+
for (const m of messages) {
|
|
61
|
+
if (isSummaryMessage(m)) continue;
|
|
62
|
+
if (m?.role === 'user') {
|
|
63
|
+
if (current) turns.push(current);
|
|
64
|
+
current = [m];
|
|
65
|
+
} else {
|
|
66
|
+
if (!current) current = [];
|
|
67
|
+
current.push(m);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (current && current.length) turns.push(current);
|
|
71
|
+
return turns;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function indexLiveTurns(live) {
|
|
75
|
+
const turns = splitTailIntoTurns(live);
|
|
76
|
+
const indexed = [];
|
|
77
|
+
let scan = 0;
|
|
78
|
+
for (const messages of turns) {
|
|
79
|
+
while (scan < live.length && live[scan] !== messages[0]) scan += 1;
|
|
80
|
+
const start = scan;
|
|
81
|
+
const end = start + messages.length;
|
|
82
|
+
indexed.push({ start, end, messages });
|
|
83
|
+
scan = end;
|
|
84
|
+
}
|
|
85
|
+
return indexed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function splitTurnStartIndexForBudget(turn, budget) {
|
|
89
|
+
const { start, end, messages } = turn;
|
|
90
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
91
|
+
const suffixStart = start + i;
|
|
92
|
+
const suffix = messages.slice(i);
|
|
93
|
+
if (suffix.length > 0 && estimateMessagesTokens(suffix) <= budget) {
|
|
94
|
+
return suffixStart;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return end;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function splitLiveCompactionContext(messages) {
|
|
101
|
+
const sanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(messages)));
|
|
102
|
+
const { protectedPrefix, conversation: nonSystem } = splitProtectedContext(sanitized);
|
|
103
|
+
let previousSummary = null;
|
|
104
|
+
for (let i = nonSystem.length - 1; i >= 0; i -= 1) {
|
|
105
|
+
if (isSummaryMessage(nonSystem[i])) {
|
|
106
|
+
previousSummary = nonSystem[i].content;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const live = nonSystem.filter((m) => !isSummaryMessage(m));
|
|
111
|
+
return { system: protectedPrefix, live, previousSummary, sanitized };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A tail may begin ONLY at an index that is not a tool result: a tool result
|
|
115
|
+
// must stay paired with the assistant tool_call that precedes it, so it can
|
|
116
|
+
// never be the first message of the preserved tail. Every other role (user /
|
|
117
|
+
// assistant / developer / ...) is a valid tail boundary. This replaces the old
|
|
118
|
+
// "the tail must begin at a real user turn" rule, which threw whenever the
|
|
119
|
+
// recent window carried no user message (single-turn agent sessions whose tail
|
|
120
|
+
// is assistant/tool only).
|
|
121
|
+
function findValidCutIndices(live) {
|
|
122
|
+
const out = [];
|
|
123
|
+
for (let i = 0; i < live.length; i += 1) {
|
|
124
|
+
if (live[i]?.role === 'tool') continue;
|
|
125
|
+
out.push(i);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// User-anchored path (unchanged behaviour): keep up to tailTurns recent turns
|
|
131
|
+
// bounded by recentBudget, splitting the newest turn's suffix when it alone is
|
|
132
|
+
// too large. Preserved verbatim so Lead / normal sessions with real user turns
|
|
133
|
+
// compact exactly as before.
|
|
134
|
+
function selectTailStartByTurns(live, recentBudget, tailTurns, previousSummary, opts) {
|
|
135
|
+
const indexedTurns = indexLiveTurns(live);
|
|
136
|
+
if (indexedTurns.length === 0) return live.length;
|
|
137
|
+
|
|
138
|
+
let tailStartIdx = live.length;
|
|
139
|
+
let keptTurns = 0;
|
|
140
|
+
|
|
141
|
+
for (let t = indexedTurns.length - 1; t >= 0; t -= 1) {
|
|
142
|
+
if (keptTurns >= tailTurns) break;
|
|
143
|
+
const turn = indexedTurns[t];
|
|
144
|
+
const tailFromTurn = live.slice(turn.start);
|
|
145
|
+
if (keptTurns === 0) {
|
|
146
|
+
if (estimateMessagesTokens(tailFromTurn) <= recentBudget) {
|
|
147
|
+
tailStartIdx = turn.start;
|
|
148
|
+
keptTurns += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const splitIdx = splitTurnStartIndexForBudget(turn, recentBudget);
|
|
152
|
+
if (splitIdx < turn.end) {
|
|
153
|
+
tailStartIdx = splitIdx;
|
|
154
|
+
keptTurns += 1;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
// Newest turn has no fitting suffix: keep entire live transcript in head for summarization.
|
|
158
|
+
tailStartIdx = live.length;
|
|
159
|
+
keptTurns = 0;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
const candidateStart = turn.start;
|
|
163
|
+
const candidateTail = live.slice(candidateStart);
|
|
164
|
+
if (estimateMessagesTokens(candidateTail) <= recentBudget) {
|
|
165
|
+
tailStartIdx = candidateStart;
|
|
166
|
+
keptTurns += 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (opts.force === true && !previousSummary && tailStartIdx <= 0) {
|
|
173
|
+
if (indexedTurns.length >= 2) {
|
|
174
|
+
tailStartIdx = indexedTurns[1].start;
|
|
175
|
+
} else if (indexedTurns.length === 1) {
|
|
176
|
+
const onlyTurn = indexedTurns[0];
|
|
177
|
+
const splitIdx = splitTurnStartIndexForBudget(onlyTurn, recentBudget);
|
|
178
|
+
if (splitIdx > onlyTurn.start && splitIdx < onlyTurn.end) {
|
|
179
|
+
tailStartIdx = splitIdx;
|
|
180
|
+
} else if (onlyTurn.end > onlyTurn.start + 1) {
|
|
181
|
+
tailStartIdx = onlyTurn.start + 1;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return tailStartIdx;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// No-user path: pick the tail boundary from valid cut points. Walk newest ->
|
|
189
|
+
// oldest, growing the tail across valid cut points while its suffix still fits
|
|
190
|
+
// recentBudget, and stop before it overflows. Never anchors on a user turn, so
|
|
191
|
+
// an assistant/tool-only single-turn transcript still yields a head to
|
|
192
|
+
// summarize and a paired tail to keep.
|
|
193
|
+
function selectTailStartByCutPoint(live, recentBudget, previousSummary) {
|
|
194
|
+
const validCuts = findValidCutIndices(live);
|
|
195
|
+
if (validCuts.length === 0) return live.length; // degenerate: only tool results
|
|
196
|
+
|
|
197
|
+
let chosen = null;
|
|
198
|
+
for (let k = validCuts.length - 1; k >= 0; k -= 1) {
|
|
199
|
+
const idx = validCuts[k];
|
|
200
|
+
if (estimateMessagesTokens(live.slice(idx)) <= recentBudget) {
|
|
201
|
+
chosen = idx; // fits — try to grow the tail toward an older cut
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
break; // this cut overflows recentBudget; keep the previous (newer) choice
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (chosen === null) {
|
|
208
|
+
// Even the newest valid cut's suffix exceeds recentBudget (a single huge
|
|
209
|
+
// message run). Keep the minimal tail from the newest valid cut so a head
|
|
210
|
+
// remains to summarize; if that cut is at index 0 there is nothing to
|
|
211
|
+
// split off, so keep everything in the head instead. The oversized tail
|
|
212
|
+
// is tolerated downstream (mandatory-cost budget raise) rather than
|
|
213
|
+
// throwing.
|
|
214
|
+
const newestCut = validCuts[validCuts.length - 1];
|
|
215
|
+
return newestCut > 0 ? newestCut : live.length;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (chosen <= 0) {
|
|
219
|
+
// Whole transcript would become the tail => nothing to compact. With no
|
|
220
|
+
// prior summary to build on, pull the tail start forward to the next
|
|
221
|
+
// valid cut so the leading message(s) become the compactable head.
|
|
222
|
+
if (!previousSummary && validCuts.length >= 2) return validCuts[1];
|
|
223
|
+
// Only ONE valid cut (or a leading tool run before it) and no prior
|
|
224
|
+
// summary: there is no older cut to pull forward to. Returning 0 would
|
|
225
|
+
// make the whole transcript the tail with an empty head, and
|
|
226
|
+
// semanticCompactMessages throws on head.length===0 && !previousSummary.
|
|
227
|
+
// Keep everything in the HEAD instead (empty tail) so a head remains to
|
|
228
|
+
// summarize; an empty tail is valid downstream (mandatory = system+tail).
|
|
229
|
+
if (!previousSummary && validCuts.length < 2) return live.length;
|
|
230
|
+
return chosen;
|
|
231
|
+
}
|
|
232
|
+
return chosen;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function selectCompactionWindow(messages, budget, opts = {}) {
|
|
236
|
+
const { system, live, previousSummary } = splitLiveCompactionContext(messages);
|
|
237
|
+
const tailTurns = Math.max(1, Number(opts.tailTurns) || DEFAULT_TAIL_TURNS);
|
|
238
|
+
const recentBudget = preserveRecentBudget(budget, opts);
|
|
239
|
+
|
|
240
|
+
const tailStartIdx = userIndexes(live).length
|
|
241
|
+
? selectTailStartByTurns(live, recentBudget, tailTurns, previousSummary, opts)
|
|
242
|
+
: selectTailStartByCutPoint(live, recentBudget, previousSummary);
|
|
243
|
+
|
|
244
|
+
const head = live.slice(0, tailStartIdx);
|
|
245
|
+
let tail = live.slice(tailStartIdx);
|
|
246
|
+
// sanitizeToolPairs/dedup/reconcile repairs any orphan tool_result the cut
|
|
247
|
+
// may have left; because valid cut points never start on a tool result, an
|
|
248
|
+
// assistant tool_call and its trailing tool_results always land on the same
|
|
249
|
+
// side of the boundary, so pairing stays provider-valid.
|
|
250
|
+
tail = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(tail)));
|
|
251
|
+
|
|
252
|
+
// Only a genuinely empty live window is unrecoverable. Absence of a user
|
|
253
|
+
// turn in the tail is no longer an error.
|
|
254
|
+
if (!head.length && !tail.length) {
|
|
255
|
+
throw new Error('semanticCompactMessages: nothing to compact (empty live window)');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const preservedFacts = extractPreservedFacts(head);
|
|
259
|
+
const originalHead = head;
|
|
260
|
+
return {
|
|
261
|
+
system,
|
|
262
|
+
head,
|
|
263
|
+
tail,
|
|
264
|
+
previousSummary,
|
|
265
|
+
originalHead,
|
|
266
|
+
preservedFacts,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function combinedSignal(parent, timeoutMs) {
|
|
271
|
+
const ms = Number(timeoutMs);
|
|
272
|
+
if (!Number.isFinite(ms) || ms <= 0) return parent || undefined;
|
|
273
|
+
const timeout = AbortSignal.timeout(Math.floor(ms));
|
|
274
|
+
if (parent && typeof AbortSignal.any === 'function') return AbortSignal.any([parent, timeout]);
|
|
275
|
+
return timeout;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function semanticCompactMessages(provider, messages, model, budgetTokens, opts = {}) {
|
|
279
|
+
if (!provider || typeof provider.send !== 'function') {
|
|
280
|
+
throw new Error('semanticCompactMessages: provider.send is required');
|
|
281
|
+
}
|
|
282
|
+
const startedAt = Date.now();
|
|
283
|
+
let budget = effectiveBudget(budgetTokens, opts);
|
|
284
|
+
const baseSanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(messages)));
|
|
285
|
+
const baseTokens = safeEstimateMessagesTokens(baseSanitized);
|
|
286
|
+
// No-op fast path: if the original sanitized transcript already fits and we
|
|
287
|
+
// are not forced, return it UNCHANGED (no preserved-tail redaction applied)
|
|
288
|
+
// to keep prior no-compaction semantics.
|
|
289
|
+
if (baseTokens != null && baseTokens <= budget && opts.force !== true) {
|
|
290
|
+
return {
|
|
291
|
+
messages: baseSanitized,
|
|
292
|
+
usage: null,
|
|
293
|
+
semantic: false,
|
|
294
|
+
compactType: COMPACT_TYPE_SEMANTIC,
|
|
295
|
+
diagnostics: {
|
|
296
|
+
noOp: true,
|
|
297
|
+
reason: 'fits_budget',
|
|
298
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
299
|
+
baseMessages: baseSanitized.length,
|
|
300
|
+
baseTokens,
|
|
301
|
+
budgetTokens: budget,
|
|
302
|
+
durationMs: Date.now() - startedAt,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
// Compaction will proceed: redact sensitive tool-call argument VALUES before
|
|
307
|
+
// window selection so the preserved tail/system that survive verbatim are
|
|
308
|
+
// measured AND emitted in their redacted form. Head prompt normalizers
|
|
309
|
+
// (toolCallSummary/normalizeToolArgValue) still apply on top for the
|
|
310
|
+
// summarized head. Redaction is shape-preserving, so tool-pair structure
|
|
311
|
+
// stays provider-valid.
|
|
312
|
+
const sanitized = redactToolCallSecretsInMessages(baseSanitized);
|
|
313
|
+
|
|
314
|
+
const selected = selectCompactionWindow(sanitized, budget, opts);
|
|
315
|
+
if (selected.head.length === 0 && !selected.previousSummary) {
|
|
316
|
+
throw new Error('semanticCompactMessages: no compactable prior history before preserved tail');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const mandatory = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs([...selected.system, ...selected.tail])));
|
|
320
|
+
const mandatoryCost = estimateMessagesTokens(mandatory);
|
|
321
|
+
const originalBudget = budget;
|
|
322
|
+
// The preserved tail is kept verbatim and the head is replaced by a much
|
|
323
|
+
// smaller summary, so the compacted result is always smaller than the
|
|
324
|
+
// input regardless of how the configured target budget compares to the
|
|
325
|
+
// mandatory cost. When the budget cannot even hold what we must keep, raise
|
|
326
|
+
// it to fit (mandatory + summary room) rather than refusing — a refusal
|
|
327
|
+
// here was the source of auto-clear / overflow compact failures.
|
|
328
|
+
if (mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS > budget) {
|
|
329
|
+
budget = mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS;
|
|
330
|
+
}
|
|
331
|
+
const budgetRaisedBy = Math.max(0, budget - originalBudget);
|
|
332
|
+
|
|
333
|
+
const callBudget = Math.max(1, Math.floor((opts.compactionInputBudgetTokens || budget) * COMPACTION_PROMPT_HEADROOM));
|
|
334
|
+
const prompt = fitCompactionPrompt(selected, callBudget);
|
|
335
|
+
if (!prompt) {
|
|
336
|
+
throw new Error(`semanticCompactMessages: compaction prompt cannot fit call budget=${callBudget}`);
|
|
337
|
+
}
|
|
338
|
+
const compactModel = model;
|
|
339
|
+
const sendOpts = {
|
|
340
|
+
...(opts.sendOpts || {}),
|
|
341
|
+
thinkingBudgetTokens: undefined,
|
|
342
|
+
xaiReasoningEffort: undefined,
|
|
343
|
+
reasoningEffort: undefined,
|
|
344
|
+
effort: 'low',
|
|
345
|
+
fast: opts.fast ?? opts.sendOpts?.fast ?? true,
|
|
346
|
+
maxOutputTokens: opts.maxOutputTokens || SUMMARY_OUTPUT_TOKENS,
|
|
347
|
+
providerState: undefined,
|
|
348
|
+
onToolCall: undefined,
|
|
349
|
+
onToolResult: undefined,
|
|
350
|
+
onTextDelta: undefined,
|
|
351
|
+
onReasoningDelta: undefined,
|
|
352
|
+
onUsageDelta: undefined,
|
|
353
|
+
onStreamDelta: undefined,
|
|
354
|
+
onStageChange: undefined,
|
|
355
|
+
drainSteering: undefined,
|
|
356
|
+
onSteerMessage: undefined,
|
|
357
|
+
signal: combinedSignal(opts.signal || opts.sendOpts?.signal || null, opts.timeoutMs || 30_000),
|
|
358
|
+
};
|
|
359
|
+
if (opts.sessionId) sendOpts.sessionId = `${opts.sessionId}:compact`;
|
|
360
|
+
if (opts.promptCacheKey || opts.sendOpts?.promptCacheKey) {
|
|
361
|
+
sendOpts.promptCacheKey = `${opts.promptCacheKey || opts.sendOpts.promptCacheKey}:compact`;
|
|
362
|
+
}
|
|
363
|
+
if (opts.providerCacheKey || opts.sendOpts?.providerCacheKey) {
|
|
364
|
+
sendOpts.providerCacheKey = `${opts.providerCacheKey || opts.sendOpts.providerCacheKey}:compact`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const response = await provider.send([
|
|
368
|
+
{ role: 'system', content: COMPACTION_SYSTEM_PROMPT },
|
|
369
|
+
{ role: 'user', content: prompt },
|
|
370
|
+
], compactModel, undefined, sendOpts);
|
|
371
|
+
const rawSummary = extractResponseText(response);
|
|
372
|
+
if (!rawSummary) throw new Error('semanticCompactMessages: compaction agent returned empty summary');
|
|
373
|
+
// Lightweight schema enforcement: a non-empty but malformed provider
|
|
374
|
+
// response (missing the required template sections) is deterministically
|
|
375
|
+
// repaired into the structured anchored shape rather than injected blindly.
|
|
376
|
+
const enforced = enforceSemanticSummarySchema(rawSummary, { head: selected.head, tail: selected.tail });
|
|
377
|
+
const summary = enforced.summary;
|
|
378
|
+
|
|
379
|
+
const oldHistory = selected.originalHead;
|
|
380
|
+
const semanticMeta = {
|
|
381
|
+
provider: opts.providerName || provider.name || null,
|
|
382
|
+
model: compactModel,
|
|
383
|
+
};
|
|
384
|
+
const summaryMessage = fitSemanticSummaryMessage(oldHistory, summary, budget - mandatoryCost, semanticMeta, selected.preservedFacts);
|
|
385
|
+
if (!summaryMessage) {
|
|
386
|
+
throw new Error(`semanticCompactMessages: summary cannot fit remaining budget=${budget - mandatoryCost}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// selected.system / selected.tail already carry redacted tool-call args
|
|
390
|
+
// (sanitized was redacted before window selection), so the preserved tail
|
|
391
|
+
// is both measured and emitted in redacted form.
|
|
392
|
+
let result = sanitizeToolPairs([...selected.system, summaryMessage, ...selected.tail]);
|
|
393
|
+
result = reconcileDedupStubs(dedupToolResultBodies(result));
|
|
394
|
+
const finalTokens = estimateMessagesTokens(result);
|
|
395
|
+
if (finalTokens > budget) {
|
|
396
|
+
throw new Error(`semanticCompactMessages: compacted result exceeds budget=${budget} (result=${finalTokens})`);
|
|
397
|
+
}
|
|
398
|
+
const diagnostics = {
|
|
399
|
+
noOp: false,
|
|
400
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
401
|
+
baseMessages: baseSanitized.length,
|
|
402
|
+
baseTokens,
|
|
403
|
+
systemMessages: selected.system.length,
|
|
404
|
+
headMessages: selected.head.length,
|
|
405
|
+
originalHeadMessages: selected.originalHead.length,
|
|
406
|
+
tailMessages: selected.tail.length,
|
|
407
|
+
mandatoryMessages: mandatory.length,
|
|
408
|
+
finalMessages: result.length,
|
|
409
|
+
systemTokens: safeEstimateMessagesTokens(selected.system),
|
|
410
|
+
headTokens: safeEstimateMessagesTokens(selected.head),
|
|
411
|
+
tailTokens: safeEstimateMessagesTokens(selected.tail),
|
|
412
|
+
mandatoryCost,
|
|
413
|
+
finalTokens,
|
|
414
|
+
originalBudgetTokens: originalBudget,
|
|
415
|
+
budgetTokens: budget,
|
|
416
|
+
budgetRaised: budgetRaisedBy > 0,
|
|
417
|
+
budgetRaisedBy,
|
|
418
|
+
remainingTokens: budget - mandatoryCost,
|
|
419
|
+
callBudgetTokens: callBudget,
|
|
420
|
+
promptChars: String(prompt || '').length,
|
|
421
|
+
promptBytes: textByteLength(prompt),
|
|
422
|
+
promptTokens: safeEstimateMessagesTokens([
|
|
423
|
+
{ role: 'system', content: COMPACTION_SYSTEM_PROMPT },
|
|
424
|
+
{ role: 'user', content: prompt },
|
|
425
|
+
]),
|
|
426
|
+
summaryChars: String(summary || '').length,
|
|
427
|
+
rawSummaryChars: String(rawSummary || '').length,
|
|
428
|
+
summaryRepaired: enforced.repaired === true,
|
|
429
|
+
previousSummary: !!selected.previousSummary,
|
|
430
|
+
durationMs: Date.now() - startedAt,
|
|
431
|
+
};
|
|
432
|
+
compactDebugLog('semantic result', diagnostics);
|
|
433
|
+
return {
|
|
434
|
+
messages: result,
|
|
435
|
+
usage: response?.usage || null,
|
|
436
|
+
providerState: response?.providerState,
|
|
437
|
+
semantic: true,
|
|
438
|
+
compactType: COMPACT_TYPE_SEMANTIC,
|
|
439
|
+
summary,
|
|
440
|
+
summaryRepaired: enforced.repaired === true,
|
|
441
|
+
diagnostics,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function recallFastTrackCompactMessages(messages, budgetTokens, opts = {}) {
|
|
446
|
+
return _recallFastTrackCompactMessages(messages, budgetTokens, opts);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Recall fast-track (type 2) tail policy: preserve the most recent turns of the
|
|
450
|
+
// live conversation VERBATIM and STRUCTURED, keeping role semantics for
|
|
451
|
+
// user / assistant / tool / system / developer instead of collapsing the tail
|
|
452
|
+
// to user-only. The chunk summary anchors older history; the preserved tail
|
|
453
|
+
// keeps recent assistant reasoning, tool_calls, and tool_results so fresh
|
|
454
|
+
// state is not silently dropped.
|
|
455
|
+
//
|
|
456
|
+
// Turns are anchored on user-role boundaries: each turn = a user message plus
|
|
457
|
+
// the assistant/tool/system/developer messages that follow it (a leading run of
|
|
458
|
+
// non-user messages before the first user boundary is treated as its own
|
|
459
|
+
// partial turn so nothing is lost). We keep the newest RECALL_TAIL_USER_MAX
|
|
460
|
+
// turns; if the kept set exceeds RECALL_TAIL_TOKEN_CAP we drop whole oldest
|
|
461
|
+
// turns first, then middle-truncate the oldest surviving messages' string
|
|
462
|
+
// content so the set fits while leaving the newest message whole.
|
|
463
|
+
//
|
|
464
|
+
// Partial tool_call/tool_result pairs that truncation might leave behind are
|
|
465
|
+
// repaired by sanitizeToolPairs/reconcileDedupStubs in the caller, so pairing
|
|
466
|
+
// stays valid even after trimming.
|
|
467
|
+
const RECALL_TAIL_USER_MAX = 2;
|
|
468
|
+
const RECALL_TAIL_TOKEN_CAP = DEFAULT_COMPACTION_KEEP_TOKENS; // 8k
|
|
469
|
+
// Rough chars-per-token used only to size a truncation target; the real fit is
|
|
470
|
+
// re-checked with estimateMessagesTokens below.
|
|
471
|
+
const RECALL_TAIL_CHARS_PER_TOKEN = 4;
|
|
472
|
+
|
|
473
|
+
function truncateMessageForRecallTail(text, maxChars) {
|
|
474
|
+
const marker = RECALL_TAIL_TRUNCATION_MARKER;
|
|
475
|
+
const value = String(text ?? '').replace(/\r\n/g, '\n');
|
|
476
|
+
if (value.length <= maxChars) return value;
|
|
477
|
+
if (maxChars <= 0) return RECALL_TAIL_SHORT_TRUNCATION_MARKER;
|
|
478
|
+
if (maxChars < marker.length) return RECALL_TAIL_SHORT_TRUNCATION_MARKER;
|
|
479
|
+
const room = maxChars - marker.length;
|
|
480
|
+
const head = Math.ceil(room * 0.35);
|
|
481
|
+
const tailPart = Math.floor(room * 0.65);
|
|
482
|
+
return `${value.slice(0, head)}${marker}${value.slice(value.length - tailPart)}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function fitRecallUserMessageToCap(userMsg, cap, following = []) {
|
|
486
|
+
const followCost = estimateMessagesTokens(following);
|
|
487
|
+
const room = Math.max(1, cap - followCost);
|
|
488
|
+
const base = { ...userMsg };
|
|
489
|
+
const raw = typeof base.content === 'string' ? base.content : extractText(base);
|
|
490
|
+
if (estimateMessagesTokens([{ ...base, content: raw }]) <= room) return { ...base, content: raw };
|
|
491
|
+
|
|
492
|
+
let lo = 0;
|
|
493
|
+
let hi = raw.length;
|
|
494
|
+
let best = truncateMessageForRecallTail(raw, 0);
|
|
495
|
+
while (lo <= hi) {
|
|
496
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
497
|
+
const candidateText = truncateMessageForRecallTail(raw, mid);
|
|
498
|
+
const candidate = { ...base, content: candidateText };
|
|
499
|
+
if (estimateMessagesTokens([candidate]) <= room) {
|
|
500
|
+
best = candidateText;
|
|
501
|
+
lo = mid + 1;
|
|
502
|
+
} else {
|
|
503
|
+
hi = mid - 1;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return { ...base, content: best };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function fitSingleRecallTurnToCap(turn, cap) {
|
|
510
|
+
const userIdx = turn.findIndex((m) => m?.role === 'user');
|
|
511
|
+
if (userIdx < 0) {
|
|
512
|
+
return truncateTailToCap(turn, cap);
|
|
513
|
+
}
|
|
514
|
+
const userMsg = turn[userIdx];
|
|
515
|
+
const following = turn.slice(userIdx + 1);
|
|
516
|
+
const fittedUser = fitRecallUserMessageToCap(userMsg, cap, following);
|
|
517
|
+
let out = [fittedUser];
|
|
518
|
+
for (const m of following) {
|
|
519
|
+
const candidate = [...out, m];
|
|
520
|
+
if (estimateMessagesTokens(candidate) <= cap) out.push(m);
|
|
521
|
+
}
|
|
522
|
+
return reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(out)));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function truncateTailToCap(messages, cap) {
|
|
526
|
+
const turn = Array.isArray(messages) ? messages : [];
|
|
527
|
+
if (turn.length === 0) return [];
|
|
528
|
+
// No user anchor in this turn: keep the NEWEST messages that fit `cap`,
|
|
529
|
+
// walking backward. (Previously this delegated back to
|
|
530
|
+
// fitSingleRecallTurnToCap, which re-entered here on a no-user turn —
|
|
531
|
+
// infinite mutual recursion. Unreachable while a no-user tail threw upstream;
|
|
532
|
+
// now that a no-user tail is allowed, this path must terminate on its own.)
|
|
533
|
+
let out = [];
|
|
534
|
+
let startIdx = turn.length; // index in `turn` where `out` begins
|
|
535
|
+
for (let i = turn.length - 1; i >= 0; i -= 1) {
|
|
536
|
+
const candidate = [turn[i], ...out];
|
|
537
|
+
if (estimateMessagesTokens(candidate) <= cap) {
|
|
538
|
+
out = candidate;
|
|
539
|
+
startIdx = i;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (out.length === 0) {
|
|
543
|
+
// Even the newest single message exceeds cap: middle-truncate its
|
|
544
|
+
// string content so at least one message survives.
|
|
545
|
+
const m = turn[i];
|
|
546
|
+
const text = typeof m?.content === 'string' ? m.content : extractText(m);
|
|
547
|
+
const truncated = truncateMessageForRecallTail(text, Math.max(1, cap * RECALL_TAIL_CHARS_PER_TOKEN));
|
|
548
|
+
out = [{ ...m, content: truncated }];
|
|
549
|
+
startIdx = i;
|
|
550
|
+
}
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
// A leading tool_result with no preceding assistant tool_call is an orphan
|
|
554
|
+
// that sanitizeToolPairs drops — which could empty the whole tail. Extend the
|
|
555
|
+
// window backward to swallow the preceding non-tool boundary (the assistant
|
|
556
|
+
// that owns the tool_call), so the pair survives sanitize. Bounded by
|
|
557
|
+
// startIdx so it always terminates.
|
|
558
|
+
while (startIdx > 0 && out[0]?.role === 'tool') {
|
|
559
|
+
startIdx -= 1;
|
|
560
|
+
out = [turn[startIdx], ...out];
|
|
561
|
+
}
|
|
562
|
+
let sanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(out)));
|
|
563
|
+
// Final guard: if sanitize still emptied the tail but the turn has a non-tool
|
|
564
|
+
// message, rebuild from the newest non-tool message forward so the tail is
|
|
565
|
+
// never empty when preservable content exists.
|
|
566
|
+
if (sanitized.length === 0) {
|
|
567
|
+
let nt = -1;
|
|
568
|
+
for (let i = turn.length - 1; i >= 0; i -= 1) {
|
|
569
|
+
if (turn[i]?.role !== 'tool') { nt = i; break; }
|
|
570
|
+
}
|
|
571
|
+
if (nt >= 0) {
|
|
572
|
+
sanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(turn.slice(nt))));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return sanitized;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function splitRecallFitInputs(recallText, previousSummary) {
|
|
579
|
+
return {
|
|
580
|
+
recall: String(recallText || '').trim(),
|
|
581
|
+
prior: previousSummary ? stripNestedSummaryHeaderLines(previousSummary) : '',
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function recallTailStartIndex(live, tail) {
|
|
586
|
+
if (!tail.length) return live.length;
|
|
587
|
+
const first = tail[0];
|
|
588
|
+
const idx = live.indexOf(first);
|
|
589
|
+
if (idx >= 0) return idx;
|
|
590
|
+
return Math.max(0, live.length - tail.length);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function selectRecallPreservedTail(live, opts = {}) {
|
|
594
|
+
const msgs = (Array.isArray(live) ? live : []).filter((m) => m && !isSummaryMessage(m));
|
|
595
|
+
if (msgs.length === 0) return { tail: [], head: [], tailStartIdx: 0 };
|
|
596
|
+
const maxTurns = Math.max(1, Number(opts.maxUsers) || RECALL_TAIL_USER_MAX);
|
|
597
|
+
const cap = Math.max(1, Number(opts.tokenCap) || RECALL_TAIL_TOKEN_CAP);
|
|
598
|
+
const turns = splitTailIntoTurns(msgs);
|
|
599
|
+
if (turns.length === 0) return { tail: [], head: msgs, tailStartIdx: 0 };
|
|
600
|
+
|
|
601
|
+
let kept = turns.slice(-maxTurns);
|
|
602
|
+
while (kept.length > 1 && estimateMessagesTokens(kept.flat()) > cap) {
|
|
603
|
+
kept = kept.slice(1);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
let tail;
|
|
607
|
+
if (estimateMessagesTokens(kept.flat()) <= cap) {
|
|
608
|
+
tail = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(kept.flat())));
|
|
609
|
+
} else {
|
|
610
|
+
tail = fitSingleRecallTurnToCap(kept[kept.length - 1], cap);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// A no-user tail is valid: a single-turn agent session may keep only
|
|
614
|
+
// assistant/tool structure recently. Mirror the semantic cut-point model —
|
|
615
|
+
// preserve the recent structured turn(s) verbatim without demanding a user
|
|
616
|
+
// anchor rather than throwing. tool-pairing is already reconciled above.
|
|
617
|
+
const tailStartIdx = recallTailStartIndex(msgs, tail);
|
|
618
|
+
const head = msgs.slice(0, tailStartIdx);
|
|
619
|
+
return { tail, head, tailStartIdx };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function _recallFastTrackCompactMessages(messages, budgetTokens, opts = {}) {
|
|
623
|
+
const startedAt = Date.now();
|
|
624
|
+
let budget = effectiveBudget(budgetTokens, opts);
|
|
625
|
+
const baseSanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(messages)));
|
|
626
|
+
const baseTokens = safeEstimateMessagesTokens(baseSanitized);
|
|
627
|
+
if (baseTokens != null && baseTokens <= budget && opts.force !== true) {
|
|
628
|
+
return {
|
|
629
|
+
messages: baseSanitized,
|
|
630
|
+
recallFastTrack: false,
|
|
631
|
+
compactType: COMPACT_TYPE_RECALL_FASTTRACK,
|
|
632
|
+
query: opts.query || '',
|
|
633
|
+
diagnostics: {
|
|
634
|
+
noOp: true,
|
|
635
|
+
reason: 'fits_budget',
|
|
636
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
637
|
+
baseMessages: baseSanitized.length,
|
|
638
|
+
baseTokens,
|
|
639
|
+
budgetTokens: budget,
|
|
640
|
+
durationMs: Date.now() - startedAt,
|
|
641
|
+
},
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
const sanitized = redactToolCallSecretsInMessages(baseSanitized);
|
|
645
|
+
|
|
646
|
+
const { system: safeSystem, live, previousSummary } = splitLiveCompactionContext(sanitized);
|
|
647
|
+
const recallTailOpts = {
|
|
648
|
+
maxUsers: opts.recallTailMaxUsers ?? opts.tailTurns ?? RECALL_TAIL_USER_MAX,
|
|
649
|
+
tokenCap: opts.recallTailTokenCap ?? preserveRecentBudget(budget, opts),
|
|
650
|
+
};
|
|
651
|
+
const { tail: recallTail, head: recallHead } = selectRecallPreservedTail(live, recallTailOpts);
|
|
652
|
+
const recallFit = splitRecallFitInputs(opts.recallText, previousSummary);
|
|
653
|
+
if (recallHead.length === 0 && !previousSummary
|
|
654
|
+
&& !(recallFit.recall || recallFit.prior || opts.allowEmptyRecall === true)) {
|
|
655
|
+
throw new Error('recallFastTrackCompactMessages: no compactable prior history before preserved tail');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const mandatory = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs([...safeSystem, ...recallTail])));
|
|
659
|
+
const mandatoryCost = estimateMessagesTokens(mandatory);
|
|
660
|
+
const originalBudget = budget;
|
|
661
|
+
if (mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS > budget) {
|
|
662
|
+
budget = mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS;
|
|
663
|
+
}
|
|
664
|
+
const budgetRaisedBy = Math.max(0, budget - originalBudget);
|
|
665
|
+
|
|
666
|
+
if (!recallFit.recall && !recallFit.prior && opts.allowEmptyRecall !== true) {
|
|
667
|
+
throw new Error('recallFastTrackCompactMessages: recall text is empty');
|
|
668
|
+
}
|
|
669
|
+
const oldHistory = recallHead;
|
|
670
|
+
const recallMeta = {
|
|
671
|
+
querySha: opts.querySha || null,
|
|
672
|
+
};
|
|
673
|
+
// Recall injection room is capped separately from the mandatory
|
|
674
|
+
// (system + preserved tail) budget: recall text never gets more than
|
|
675
|
+
// opts.recallTokenCap (5% of context window, floor 2048 tokens, set by
|
|
676
|
+
// the caller); the remaining budget stays reserved for live
|
|
677
|
+
// conversation. Only applied when the cap is a positive finite number,
|
|
678
|
+
// preserving current uncapped behavior otherwise.
|
|
679
|
+
const recallRoomUncapped = budget - mandatoryCost;
|
|
680
|
+
const recallTokenCap = Number(opts.recallTokenCap);
|
|
681
|
+
const recallRoom = (Number.isFinite(recallTokenCap) && recallTokenCap > 0)
|
|
682
|
+
? Math.min(recallRoomUncapped, recallTokenCap)
|
|
683
|
+
: recallRoomUncapped;
|
|
684
|
+
const summaryMessage = fitRecallFastTrackSummaryMessage(
|
|
685
|
+
oldHistory,
|
|
686
|
+
recallFit.recall,
|
|
687
|
+
recallRoom,
|
|
688
|
+
recallMeta,
|
|
689
|
+
recallFit.prior,
|
|
690
|
+
);
|
|
691
|
+
if (!summaryMessage) {
|
|
692
|
+
throw new Error(`recallFastTrackCompactMessages: summary cannot fit remaining budget=${recallRoom}`);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
let result = sanitizeToolPairs([...safeSystem, summaryMessage, ...recallTail]);
|
|
696
|
+
result = reconcileDedupStubs(dedupToolResultBodies(result));
|
|
697
|
+
const finalTokens = estimateMessagesTokens(result);
|
|
698
|
+
if (finalTokens > budget) {
|
|
699
|
+
throw new Error(`recallFastTrackCompactMessages: compacted result exceeds budget=${budget} (result=${finalTokens})`);
|
|
700
|
+
}
|
|
701
|
+
const summaryContent = String(summaryMessage?.content || '');
|
|
702
|
+
const diagnostics = {
|
|
703
|
+
noOp: false,
|
|
704
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
705
|
+
baseMessages: baseSanitized.length,
|
|
706
|
+
baseTokens,
|
|
707
|
+
systemMessages: safeSystem.length,
|
|
708
|
+
liveMessages: live.length,
|
|
709
|
+
headMessages: recallHead.length,
|
|
710
|
+
tailMessages: recallTail.length,
|
|
711
|
+
mandatoryMessages: mandatory.length,
|
|
712
|
+
finalMessages: result.length,
|
|
713
|
+
systemTokens: safeEstimateMessagesTokens(safeSystem),
|
|
714
|
+
liveTokens: safeEstimateMessagesTokens(live),
|
|
715
|
+
headTokens: safeEstimateMessagesTokens(recallHead),
|
|
716
|
+
tailTokens: safeEstimateMessagesTokens(recallTail),
|
|
717
|
+
mandatoryCost,
|
|
718
|
+
finalTokens,
|
|
719
|
+
originalBudgetTokens: originalBudget,
|
|
720
|
+
budgetTokens: budget,
|
|
721
|
+
budgetRaised: budgetRaisedBy > 0,
|
|
722
|
+
budgetRaisedBy,
|
|
723
|
+
remainingTokens: budget - mandatoryCost,
|
|
724
|
+
recallTokenCap: (Number.isFinite(recallTokenCap) && recallTokenCap > 0) ? recallTokenCap : null,
|
|
725
|
+
recallRoom,
|
|
726
|
+
recallChars: recallFit.recall.length,
|
|
727
|
+
recallBytes: textByteLength(recallFit.recall),
|
|
728
|
+
priorChars: recallFit.prior.length,
|
|
729
|
+
priorBytes: textByteLength(recallFit.prior),
|
|
730
|
+
summaryMessageChars: summaryContent.length,
|
|
731
|
+
summaryMessageBytes: textByteLength(summaryContent),
|
|
732
|
+
recallEmpty: !recallFit.recall,
|
|
733
|
+
priorEmpty: !recallFit.prior,
|
|
734
|
+
recallTruncatedInSummary: !!recallFit.recall && !summaryContent.includes(recallFit.recall),
|
|
735
|
+
priorTruncatedInSummary: !!recallFit.prior && !summaryContent.includes(recallFit.prior),
|
|
736
|
+
tailTruncated: recallTail.some((m) => messageContentHasMarker(m, RECALL_TAIL_TRUNCATION_MARKER) || messageContentHasMarker(m, RECALL_TAIL_SHORT_TRUNCATION_MARKER)),
|
|
737
|
+
tailOptions: recallTailOpts,
|
|
738
|
+
previousSummary: !!previousSummary,
|
|
739
|
+
durationMs: Date.now() - startedAt,
|
|
740
|
+
};
|
|
741
|
+
compactDebugLog('recall-fasttrack result', diagnostics);
|
|
742
|
+
return {
|
|
743
|
+
messages: result,
|
|
744
|
+
recallFastTrack: true,
|
|
745
|
+
compactType: COMPACT_TYPE_RECALL_FASTTRACK,
|
|
746
|
+
query: opts.query || '',
|
|
747
|
+
diagnostics,
|
|
748
|
+
};
|
|
749
|
+
}
|