mixdog 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +23 -101
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
// Summarization system prompt, schema validation/repair, semantic + recall
|
|
2
|
+
// fast-track summary-message construction and fitting, root-block fitting, and
|
|
3
|
+
// the recall query builder. Extracted verbatim from compact.mjs
|
|
4
|
+
// (behavior-preserving).
|
|
5
|
+
import { estimateMessagesTokens } from '../context-utils.mjs';
|
|
6
|
+
import {
|
|
7
|
+
SUMMARY_PREFIX,
|
|
8
|
+
COMPACT_TYPE_SEMANTIC,
|
|
9
|
+
COMPACT_TYPE_RECALL_FASTTRACK,
|
|
10
|
+
} from './constants.mjs';
|
|
11
|
+
import {
|
|
12
|
+
extractText,
|
|
13
|
+
truncateMiddle,
|
|
14
|
+
toolCallSummary,
|
|
15
|
+
toolCallArgBudget,
|
|
16
|
+
toolResultId,
|
|
17
|
+
} from './text-utils.mjs';
|
|
18
|
+
import {
|
|
19
|
+
compactHeader,
|
|
20
|
+
makeSummaryMessage,
|
|
21
|
+
isProtectedContextUserMessage,
|
|
22
|
+
isInjectedSkillBodyMessage,
|
|
23
|
+
} from './messages.mjs';
|
|
24
|
+
import {
|
|
25
|
+
summaryIsSchemaValid,
|
|
26
|
+
summaryHasUnrecognizedHeadings,
|
|
27
|
+
repairSemanticSummary,
|
|
28
|
+
minimalSchemaSummary,
|
|
29
|
+
truncateSummaryBySections,
|
|
30
|
+
} from './summary-schema.mjs';
|
|
31
|
+
|
|
32
|
+
export { repairSemanticSummary } from './summary-schema.mjs';
|
|
33
|
+
|
|
34
|
+
const COMPACTION_INPUT_MAX_CHARS = 2_000;
|
|
35
|
+
|
|
36
|
+
export const COMPACTION_SYSTEM_PROMPT = [
|
|
37
|
+
'You are an anchored context summarization assistant for coding sessions.',
|
|
38
|
+
'',
|
|
39
|
+
'Summarize only the conversation history you are given. The newest turns may be kept verbatim outside your summary, so focus on the older context that still matters for continuing the work.',
|
|
40
|
+
'',
|
|
41
|
+
'If the prompt includes a <previous-summary> block, treat it as the current anchored summary. Update it with the new history by preserving still-true details, removing stale details, and merging in new facts.',
|
|
42
|
+
'',
|
|
43
|
+
'Always follow the exact output structure requested by the user prompt. Keep every section, preserve exact file paths and identifiers when known, and prefer terse bullets over paragraphs.',
|
|
44
|
+
'',
|
|
45
|
+
'Do not answer the conversation itself. Do not mention that you are summarizing, compacting, or merging context. Respond in the same language as the conversation.',
|
|
46
|
+
].join('\n');
|
|
47
|
+
const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
|
|
48
|
+
<template>
|
|
49
|
+
## Goal
|
|
50
|
+
- [single-sentence task summary]
|
|
51
|
+
|
|
52
|
+
## Constraints & Preferences
|
|
53
|
+
- [user constraints, preferences, specs, or "(none)"]
|
|
54
|
+
|
|
55
|
+
## Progress
|
|
56
|
+
### Done
|
|
57
|
+
- [completed work or "(none)"]
|
|
58
|
+
|
|
59
|
+
### In Progress
|
|
60
|
+
- [current work or "(none)"]
|
|
61
|
+
|
|
62
|
+
### Blocked
|
|
63
|
+
- [blockers or "(none)"]
|
|
64
|
+
|
|
65
|
+
## Key Decisions
|
|
66
|
+
- [decision and why, or "(none)"]
|
|
67
|
+
|
|
68
|
+
## Next Steps
|
|
69
|
+
- [ordered next actions or "(none)"]
|
|
70
|
+
|
|
71
|
+
## Critical Context
|
|
72
|
+
- [important technical facts, errors, open questions, or "(none)"]
|
|
73
|
+
|
|
74
|
+
## Relevant Files
|
|
75
|
+
- [file or directory path: why it matters, or "(none)"]
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
Rules:
|
|
79
|
+
- Keep every section, even when empty.
|
|
80
|
+
- Use terse bullets, not prose paragraphs.
|
|
81
|
+
- Preserve exact file paths, commands, error strings, and identifiers when known.
|
|
82
|
+
- Use the same language as the active user thread when it is clear.
|
|
83
|
+
- Do not mention the summary process or that context was compacted.`;
|
|
84
|
+
|
|
85
|
+
function transcriptLineForCompaction(m, index, perMessageChars) {
|
|
86
|
+
const role = m?.role || 'unknown';
|
|
87
|
+
const text = truncateMiddle(extractText(m).trim(), perMessageChars);
|
|
88
|
+
const meta = `${toolCallSummary(m, toolCallArgBudget(perMessageChars))}${toolResultId(m)}`;
|
|
89
|
+
if (!text) return `${index + 1}. ${role}${meta}`;
|
|
90
|
+
return `${index + 1}. ${role}${meta}:\n${text}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function buildCompactionPrompt({ head, previousSummary, preservedFacts }, perMessageChars) {
|
|
94
|
+
const lines = [
|
|
95
|
+
previousSummary
|
|
96
|
+
? 'Update the anchored summary below using the conversation history that follows. Preserve still-true details, remove stale details, and merge in the new facts.'
|
|
97
|
+
: 'Create a new anchored summary from the conversation history below.',
|
|
98
|
+
SUMMARY_TEMPLATE,
|
|
99
|
+
];
|
|
100
|
+
if (previousSummary) {
|
|
101
|
+
lines.push('', '<previous-summary>', previousSummary, '</previous-summary>');
|
|
102
|
+
}
|
|
103
|
+
if (preservedFacts) {
|
|
104
|
+
lines.push('', '<preserved-facts>', preservedFacts, '</preserved-facts>');
|
|
105
|
+
}
|
|
106
|
+
lines.push('', '<conversation-history>');
|
|
107
|
+
if (head.length === 0) {
|
|
108
|
+
lines.push('[No additional older messages before the preserved recent tail.]');
|
|
109
|
+
} else {
|
|
110
|
+
for (let i = 0; i < head.length; i += 1) {
|
|
111
|
+
lines.push(transcriptLineForCompaction(head[i], i, perMessageChars));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
lines.push('</conversation-history>');
|
|
115
|
+
return lines.join('\n');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function estimateCompactionPromptTokens(input, perMessageChars) {
|
|
119
|
+
const prompt = buildCompactionPrompt(input, perMessageChars);
|
|
120
|
+
return estimateMessagesTokens([
|
|
121
|
+
{ role: 'system', content: COMPACTION_SYSTEM_PROMPT },
|
|
122
|
+
{ role: 'user', content: prompt },
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function previousSummaryBodyForCompactionPrompt(previousSummary) {
|
|
127
|
+
const text = String(previousSummary || '').trim();
|
|
128
|
+
if (!text) return '';
|
|
129
|
+
return stripNestedSummaryHeaderLines(text);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function priorSummaryNeedsNormalization(text) {
|
|
133
|
+
const body = String(text || '').trim();
|
|
134
|
+
if (!body) return false;
|
|
135
|
+
if (!/^##\s+/m.test(body)) return true;
|
|
136
|
+
if (!summaryIsSchemaValid(body)) return true;
|
|
137
|
+
return summaryHasUnrecognizedHeadings(body);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizePriorSummaryForCompactionPrompt(fullBody) {
|
|
141
|
+
const text = String(fullBody || '').trim();
|
|
142
|
+
if (!text) return '';
|
|
143
|
+
if (!priorSummaryNeedsNormalization(text)) return text;
|
|
144
|
+
return repairSemanticSummary(text, { head: [], tail: [] });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Shrink or drop a prior anchored summary so the compaction provider prompt fits
|
|
148
|
+
// the call budget. Unstructured/legacy priors are repaired first; section
|
|
149
|
+
// anchors are preserved via truncateSummaryBySections;
|
|
150
|
+
// the last resort is omitting <previous-summary> entirely.
|
|
151
|
+
function fitPreviousSummaryForCompactionPrompt(input, perMessageChars, targetTokens) {
|
|
152
|
+
if (!input?.previousSummary) return input;
|
|
153
|
+
const fullBody = normalizePriorSummaryForCompactionPrompt(
|
|
154
|
+
previousSummaryBodyForCompactionPrompt(input.previousSummary),
|
|
155
|
+
);
|
|
156
|
+
const withSummary = (summaryText) => {
|
|
157
|
+
const trimmed = String(summaryText || '').trim();
|
|
158
|
+
if (!trimmed) return { ...input, previousSummary: null };
|
|
159
|
+
return { ...input, previousSummary: trimmed };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
if (estimateCompactionPromptTokens(withSummary(fullBody), perMessageChars) <= targetTokens) {
|
|
163
|
+
return withSummary(fullBody);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (fullBody) {
|
|
167
|
+
let lo = 0;
|
|
168
|
+
let hi = fullBody.length;
|
|
169
|
+
let bestChars = -1;
|
|
170
|
+
while (lo <= hi) {
|
|
171
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
172
|
+
const truncated = truncateSummaryBySections(fullBody, mid);
|
|
173
|
+
const candidate = withSummary(truncated);
|
|
174
|
+
if (estimateCompactionPromptTokens(candidate, perMessageChars) <= targetTokens) {
|
|
175
|
+
bestChars = mid;
|
|
176
|
+
lo = mid + 1;
|
|
177
|
+
} else {
|
|
178
|
+
hi = mid - 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (bestChars >= 0) {
|
|
182
|
+
return withSummary(truncateSummaryBySections(fullBody, bestChars));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const minimalPrior = minimalSchemaSummary();
|
|
187
|
+
if (estimateCompactionPromptTokens(withSummary(minimalPrior), perMessageChars) <= targetTokens) {
|
|
188
|
+
return withSummary(minimalPrior);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const withoutPrior = withSummary(null);
|
|
192
|
+
if (estimateCompactionPromptTokens(withoutPrior, perMessageChars) <= targetTokens) {
|
|
193
|
+
return withoutPrior;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function fitCompactionPrompt(input, targetTokens) {
|
|
200
|
+
const tryFit = (withFacts) => {
|
|
201
|
+
const baseInp = withFacts ? input : { ...input, preservedFacts: null };
|
|
202
|
+
|
|
203
|
+
const fitAt = (perMessageChars) => {
|
|
204
|
+
let inp = baseInp;
|
|
205
|
+
if (estimateCompactionPromptTokens(inp, perMessageChars) > targetTokens) {
|
|
206
|
+
const fitted = fitPreviousSummaryForCompactionPrompt(inp, perMessageChars, targetTokens);
|
|
207
|
+
if (!fitted) return null;
|
|
208
|
+
inp = fitted;
|
|
209
|
+
if (estimateCompactionPromptTokens(inp, perMessageChars) > targetTokens) return null;
|
|
210
|
+
}
|
|
211
|
+
return buildCompactionPrompt(inp, perMessageChars);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const minimalPrompt = fitAt(0);
|
|
215
|
+
if (!minimalPrompt) return null;
|
|
216
|
+
|
|
217
|
+
let maxText = 0;
|
|
218
|
+
for (const m of baseInp.head) maxText = Math.max(maxText, extractText(m).length);
|
|
219
|
+
let lo = 0;
|
|
220
|
+
let hi = Math.min(COMPACTION_INPUT_MAX_CHARS, Math.max(maxText, 0));
|
|
221
|
+
let best = minimalPrompt;
|
|
222
|
+
while (lo <= hi) {
|
|
223
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
224
|
+
const candidate = fitAt(mid);
|
|
225
|
+
if (candidate) {
|
|
226
|
+
best = candidate;
|
|
227
|
+
lo = mid + 1;
|
|
228
|
+
} else {
|
|
229
|
+
hi = mid - 1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return best;
|
|
233
|
+
};
|
|
234
|
+
if (input.preservedFacts) {
|
|
235
|
+
const withFacts = tryFit(true);
|
|
236
|
+
if (withFacts && estimateMessagesTokens([
|
|
237
|
+
{ role: 'system', content: COMPACTION_SYSTEM_PROMPT },
|
|
238
|
+
{ role: 'user', content: withFacts },
|
|
239
|
+
]) <= targetTokens) return withFacts;
|
|
240
|
+
}
|
|
241
|
+
const fitted = tryFit(false);
|
|
242
|
+
if (fitted) return fitted;
|
|
243
|
+
|
|
244
|
+
// Emergency deterministic reduction: even at perMessageChars=0 the prompt can
|
|
245
|
+
// overflow when the head carries a very large NUMBER of messages (each still
|
|
246
|
+
// emits a `N. role` line). Keep only the newest K head messages and collapse
|
|
247
|
+
// the rest into a single `[K older messages omitted]` stub line, binary
|
|
248
|
+
// searching the largest K that fits. This bounds the head by COUNT, not just
|
|
249
|
+
// per-message chars, so a huge-head transcript still yields a minimal prompt
|
|
250
|
+
// instead of null (which surfaced as a hard compaction throw).
|
|
251
|
+
const head = Array.isArray(input.head) ? input.head : [];
|
|
252
|
+
const baseNoFacts = { ...input, preservedFacts: null };
|
|
253
|
+
const buildReduced = (k) => {
|
|
254
|
+
const kept = k > 0 ? head.slice(head.length - k) : [];
|
|
255
|
+
const omitted = head.length - kept.length;
|
|
256
|
+
const stubHead = omitted > 0
|
|
257
|
+
? [{ role: 'user', content: `[${omitted} older messages omitted]` }, ...kept]
|
|
258
|
+
: kept;
|
|
259
|
+
let inp = { ...baseNoFacts, head: stubHead };
|
|
260
|
+
// Also shrink/drop a prior <previous-summary> (same as the normal fitAt
|
|
261
|
+
// path) — a large prior summary can keep the prompt over budget even at
|
|
262
|
+
// K=0. fitPreviousSummaryForCompactionPrompt is a no-op when there is no
|
|
263
|
+
// previousSummary, so this is safe for the summary-less case.
|
|
264
|
+
if (estimateCompactionPromptTokens(inp, 0) > targetTokens) {
|
|
265
|
+
const fitted = fitPreviousSummaryForCompactionPrompt(inp, 0, targetTokens);
|
|
266
|
+
if (!fitted) return null;
|
|
267
|
+
inp = fitted;
|
|
268
|
+
if (estimateCompactionPromptTokens(inp, 0) > targetTokens) return null;
|
|
269
|
+
}
|
|
270
|
+
return buildCompactionPrompt(inp, 0);
|
|
271
|
+
};
|
|
272
|
+
let lo = 0;
|
|
273
|
+
let hi = head.length;
|
|
274
|
+
let best = null;
|
|
275
|
+
while (lo <= hi) {
|
|
276
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
277
|
+
const candidate = buildReduced(mid);
|
|
278
|
+
if (candidate) { best = candidate; lo = mid + 1; }
|
|
279
|
+
else hi = mid - 1;
|
|
280
|
+
}
|
|
281
|
+
return best;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function extractResponseText(response) {
|
|
285
|
+
if (!response) return '';
|
|
286
|
+
if (typeof response.content === 'string') return response.content.trim();
|
|
287
|
+
if (Array.isArray(response.content)) {
|
|
288
|
+
return response.content
|
|
289
|
+
.map((item) => {
|
|
290
|
+
if (typeof item === 'string') return item;
|
|
291
|
+
if (typeof item?.text === 'string') return item.text;
|
|
292
|
+
if (typeof item?.content === 'string') return item.content;
|
|
293
|
+
return '';
|
|
294
|
+
})
|
|
295
|
+
.filter(Boolean)
|
|
296
|
+
.join('\n')
|
|
297
|
+
.trim();
|
|
298
|
+
}
|
|
299
|
+
return '';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Validate the provider summary against the required template sections; when it
|
|
303
|
+
// is missing ANY required section anchor (fully or partially malformed) repair
|
|
304
|
+
// it deterministically so a non-empty-but-broken response is never injected as
|
|
305
|
+
// the sole summary. Returns { summary, repaired }.
|
|
306
|
+
export function enforceSemanticSummarySchema(summary, ctx = {}) {
|
|
307
|
+
const text = String(summary || '').trim();
|
|
308
|
+
if (!text) return { summary: text, repaired: false };
|
|
309
|
+
if (summaryIsSchemaValid(text)) {
|
|
310
|
+
return { summary: text, repaired: false };
|
|
311
|
+
}
|
|
312
|
+
return { summary: repairSemanticSummary(text, ctx), repaired: true };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function makeSemanticSummaryMessage(oldHistory, summary, semanticMeta = {}, preservedFacts = '') {
|
|
316
|
+
const header = compactHeader(oldHistory);
|
|
317
|
+
header.push(`compact_type=${COMPACT_TYPE_SEMANTIC}`);
|
|
318
|
+
header.push(`semantic=true provider=${semanticMeta.provider || 'unknown'} model=${semanticMeta.model || 'unknown'}`);
|
|
319
|
+
const facts = String(preservedFacts || '').trim();
|
|
320
|
+
const body = String(summary || '').trim();
|
|
321
|
+
const parts = [header.join('\n')];
|
|
322
|
+
if (facts) parts.push(facts);
|
|
323
|
+
if (body) parts.push(body);
|
|
324
|
+
return makeSummaryMessage(parts.join('\n\n'));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function buildRecallFastTrackQuery(messages, opts = {}) {
|
|
328
|
+
const maxChars = Math.max(200, Number(opts.maxChars) || 2_000);
|
|
329
|
+
const hints = String(opts.hints || 'current task decisions constraints file paths changed files verification failures next steps').trim();
|
|
330
|
+
let latestUser = '';
|
|
331
|
+
const recent = [];
|
|
332
|
+
const input = Array.isArray(messages) ? messages : [];
|
|
333
|
+
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
334
|
+
const m = input[i];
|
|
335
|
+
const text = extractText(m).trim();
|
|
336
|
+
if (!text) continue;
|
|
337
|
+
if (recent.length < 6) recent.unshift(text);
|
|
338
|
+
if (!latestUser && m?.role === 'user' && !isProtectedContextUserMessage(m) && !isInjectedSkillBodyMessage(m)) {
|
|
339
|
+
latestUser = text;
|
|
340
|
+
}
|
|
341
|
+
if (latestUser && recent.length >= 6) break;
|
|
342
|
+
}
|
|
343
|
+
const parts = [latestUser, hints, recent.join('\n')]
|
|
344
|
+
.map((s) => String(s || '').trim())
|
|
345
|
+
.filter(Boolean);
|
|
346
|
+
return truncateMiddle([...new Set(parts)].join('\n'), maxChars);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Fit the structured semantic summary into the remaining token budget WITHOUT
|
|
350
|
+
// dropping any required section. The incoming `summary` is already schema-valid
|
|
351
|
+
// (enforceSemanticSummarySchema ran upstream); here we shrink section bodies via
|
|
352
|
+
// section-aware truncation, fall back to a headings-only schema-valid summary,
|
|
353
|
+
// and finally revalidate so the injected SUMMARY_PREFIX message always carries
|
|
354
|
+
// every required anchor. Returns null only when even the minimal schema-valid
|
|
355
|
+
// summary cannot fit (caller throws).
|
|
356
|
+
export function fitSemanticSummaryMessage(oldHistory, summary, remainingTokens, semanticMeta, preservedFacts = '') {
|
|
357
|
+
const tryFit = (factsText) => {
|
|
358
|
+
const text = String(summary || '').trim();
|
|
359
|
+
// Minimal schema-valid body (headings + "(none)"). If even this does
|
|
360
|
+
// not fit, this facts variant cannot produce a valid message.
|
|
361
|
+
const minimalBody = text ? minimalSchemaSummary() : '';
|
|
362
|
+
const minimal = makeSemanticSummaryMessage(oldHistory, minimalBody, semanticMeta, factsText);
|
|
363
|
+
if (estimateMessagesTokens([minimal]) > remainingTokens) return null;
|
|
364
|
+
if (!text) return minimal;
|
|
365
|
+
// Binary search the per-section body budget; keep all anchors intact.
|
|
366
|
+
let lo = 0;
|
|
367
|
+
let hi = text.length;
|
|
368
|
+
let best = minimal;
|
|
369
|
+
while (lo <= hi) {
|
|
370
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
371
|
+
const body = truncateSummaryBySections(text, mid);
|
|
372
|
+
const candidate = makeSemanticSummaryMessage(oldHistory, body, semanticMeta, factsText);
|
|
373
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens && summaryIsSchemaValid(body)) {
|
|
374
|
+
best = candidate;
|
|
375
|
+
lo = mid + 1;
|
|
376
|
+
} else {
|
|
377
|
+
hi = mid - 1;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return best;
|
|
381
|
+
};
|
|
382
|
+
let result = null;
|
|
383
|
+
if (preservedFacts) result = tryFit(preservedFacts);
|
|
384
|
+
if (!result) result = tryFit('');
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export const RECALL_TAIL_TRUNCATION_MARKER = '[... truncated during recall tail preservation ...]';
|
|
389
|
+
export const RECALL_TAIL_SHORT_TRUNCATION_MARKER = '[truncated]';
|
|
390
|
+
|
|
391
|
+
const PRIOR_COMPACTED_CONTEXT_OPEN = '<prior-compacted-context>';
|
|
392
|
+
const PRIOR_COMPACTED_CONTEXT_CLOSE = '</prior-compacted-context>';
|
|
393
|
+
|
|
394
|
+
function formatPriorCompactedContextBlock(priorText) {
|
|
395
|
+
const prior = String(priorText || '').trim();
|
|
396
|
+
if (!prior) return '';
|
|
397
|
+
return `${PRIOR_COMPACTED_CONTEXT_OPEN}\n${prior}\n${PRIOR_COMPACTED_CONTEXT_CLOSE}`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function stripNestedSummaryHeaderLines(text) {
|
|
401
|
+
const lines = String(text ?? '').split('\n');
|
|
402
|
+
const out = [];
|
|
403
|
+
for (const line of lines) {
|
|
404
|
+
if (line.startsWith(SUMMARY_PREFIX)) continue;
|
|
405
|
+
if (/^messages=\d+\s+(?:sha256=|compact_type=)/.test(line.trim())) continue;
|
|
406
|
+
if (/^compact_type=/.test(line.trim())) continue;
|
|
407
|
+
out.push(line);
|
|
408
|
+
}
|
|
409
|
+
return out.join('\n').trim();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function makeRecallFastTrackSummaryMessageParts(oldHistory, recallPart, priorPart, recallMeta = {}) {
|
|
413
|
+
const header = compactHeader(oldHistory);
|
|
414
|
+
header.push(`compact_type=${COMPACT_TYPE_RECALL_FASTTRACK} source=recall-fasttrack query_sha=${recallMeta.querySha || 'none'}`);
|
|
415
|
+
const parts = [header.join('\n')];
|
|
416
|
+
const priorBlock = formatPriorCompactedContextBlock(priorPart);
|
|
417
|
+
if (priorBlock) parts.push(priorBlock);
|
|
418
|
+
const recall = String(recallPart || '').trim();
|
|
419
|
+
if (recall) parts.push(recall);
|
|
420
|
+
return makeSummaryMessage(parts.join('\n\n'));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function fitRecallFastTrackSummaryMessage(oldHistory, recallText, remainingTokens, recallMeta = {}, priorPart = '') {
|
|
424
|
+
const recall = String(recallText || '').trim();
|
|
425
|
+
const prior = String(priorPart || '').trim();
|
|
426
|
+
|
|
427
|
+
let fittedPrior = prior;
|
|
428
|
+
if (prior) {
|
|
429
|
+
let lo = 0;
|
|
430
|
+
let hi = prior.length;
|
|
431
|
+
let bestPriorLen = 0;
|
|
432
|
+
while (lo <= hi) {
|
|
433
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
434
|
+
const candidate = makeRecallFastTrackSummaryMessageParts(oldHistory, '', prior.slice(0, mid), recallMeta);
|
|
435
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens) {
|
|
436
|
+
bestPriorLen = mid;
|
|
437
|
+
lo = mid + 1;
|
|
438
|
+
} else {
|
|
439
|
+
hi = mid - 1;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
fittedPrior = prior.slice(0, bestPriorLen);
|
|
443
|
+
if (!fittedPrior && prior) {
|
|
444
|
+
const markerOnly = makeRecallFastTrackSummaryMessageParts(oldHistory, '', RECALL_TAIL_TRUNCATION_MARKER, recallMeta);
|
|
445
|
+
if (estimateMessagesTokens([markerOnly]) <= remainingTokens) {
|
|
446
|
+
fittedPrior = RECALL_TAIL_TRUNCATION_MARKER;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const minimal = makeRecallFastTrackSummaryMessageParts(oldHistory, '', fittedPrior, recallMeta);
|
|
452
|
+
if (estimateMessagesTokens([minimal]) > remainingTokens) return null;
|
|
453
|
+
if (!recall) return minimal;
|
|
454
|
+
|
|
455
|
+
const { preamble, blocks } = splitRecallRootBlocks(recall);
|
|
456
|
+
if (blocks.length > 0) {
|
|
457
|
+
// Root-block granularity fit: drop the OLDEST blocks WHOLE (never cut
|
|
458
|
+
// a `# chunk` / `# raw_pending` / `# raw_terminal` block mid-entry);
|
|
459
|
+
// dropping more leading blocks only shrinks the body, so binary-search
|
|
460
|
+
// the minimal drop count (mirrors fitRecallRootsMessage).
|
|
461
|
+
let loB = 0;
|
|
462
|
+
let hiB = blocks.length;
|
|
463
|
+
let bestLo = -1;
|
|
464
|
+
while (loB <= hiB) {
|
|
465
|
+
const midB = Math.floor((loB + hiB) / 2);
|
|
466
|
+
const body = [preamble, ...blocks.slice(midB)].filter(Boolean).join('\n\n');
|
|
467
|
+
const candidate = makeRecallFastTrackSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
|
|
468
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens) {
|
|
469
|
+
bestLo = midB;
|
|
470
|
+
hiB = midB - 1;
|
|
471
|
+
} else {
|
|
472
|
+
loB = midB + 1;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (bestLo >= 0) {
|
|
476
|
+
const body = [preamble, ...blocks.slice(bestLo)].filter(Boolean).join('\n\n');
|
|
477
|
+
return makeRecallFastTrackSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
|
|
478
|
+
}
|
|
479
|
+
// Even zero blocks (preamble alone) overflows alongside the fitted
|
|
480
|
+
// prior - try preamble alone, else the no-recall minimal message.
|
|
481
|
+
if (preamble) {
|
|
482
|
+
const preambleOnly = makeRecallFastTrackSummaryMessageParts(oldHistory, preamble, fittedPrior, recallMeta);
|
|
483
|
+
if (estimateMessagesTokens([preambleOnly]) <= remainingTokens) return preambleOnly;
|
|
484
|
+
}
|
|
485
|
+
return minimal;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// No parseable root-block boundaries (plain recall text): fall back to the
|
|
489
|
+
// character-slice binary search below.
|
|
490
|
+
|
|
491
|
+
let lo = 0;
|
|
492
|
+
let hi = recall.length;
|
|
493
|
+
let best = minimal;
|
|
494
|
+
while (lo <= hi) {
|
|
495
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
496
|
+
const candidate = makeRecallFastTrackSummaryMessageParts(oldHistory, recall.slice(0, mid), fittedPrior, recallMeta);
|
|
497
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens) {
|
|
498
|
+
best = candidate;
|
|
499
|
+
lo = mid + 1;
|
|
500
|
+
} else {
|
|
501
|
+
hi = mid - 1;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return best;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// --- Smart-compact root-based fitting (arrival-time replacement) -----------
|
|
508
|
+
//
|
|
509
|
+
// dump_session_roots (memory/index.mjs dumpSessionRootChunks) renders chunks
|
|
510
|
+
// TIME-ORDERED (oldest first; chunks.sort by sourceTurn/ts/id ascending),
|
|
511
|
+
// each root/raw block starting with one of:
|
|
512
|
+
// # chunk N root=ID[ category=X]
|
|
513
|
+
// # raw_pending N id=ID
|
|
514
|
+
// # raw_terminal N id=ID
|
|
515
|
+
// and blocks joined by "\n\n". runRecallFastTrackForSession additionally
|
|
516
|
+
// prepends a "session_id=..." / cycle1-drain-status preamble before the dump
|
|
517
|
+
// text (also "\n\n"-joined) — preserved verbatim as a non-block segment.
|
|
518
|
+
//
|
|
519
|
+
// Unlike fitRecallFastTrackSummaryMessage (character-slice binary search,
|
|
520
|
+
// used by the LLM-summary-free but still-mid-turn recall-fasttrack compact
|
|
521
|
+
// path), the smart-compact arrival path must never cut a root block
|
|
522
|
+
// mid-entry — losing half a root's content silently corrupts that entry.
|
|
523
|
+
// This splitter finds block boundaries by the label pattern (robust to
|
|
524
|
+
// blank lines inside member/raw content, since it anchors on the distinctive
|
|
525
|
+
// "# chunk /raw_pending/raw_terminal" line rather than a blank-line split).
|
|
526
|
+
const RECALL_ROOT_BLOCK_HEADER_RE = /^# (?:chunk \d+ root=\d+(?: category=\S+)?|raw_pending \d+ id=\d+|raw_terminal \d+ id=\d+)[ \t]*$/;
|
|
527
|
+
|
|
528
|
+
export function splitRecallRootBlocks(text) {
|
|
529
|
+
const value = String(text || '');
|
|
530
|
+
if (!value.trim()) return { preamble: '', blocks: [] };
|
|
531
|
+
const re = new RegExp(RECALL_ROOT_BLOCK_HEADER_RE.source, 'gm');
|
|
532
|
+
const starts = [];
|
|
533
|
+
let m;
|
|
534
|
+
while ((m = re.exec(value)) !== null) {
|
|
535
|
+
starts.push(m.index);
|
|
536
|
+
if (re.lastIndex === m.index) re.lastIndex += 1; // zero-width guard, defensive
|
|
537
|
+
}
|
|
538
|
+
if (starts.length === 0) return { preamble: value.trim(), blocks: [] };
|
|
539
|
+
const preamble = value.slice(0, starts[0]).trim();
|
|
540
|
+
const blocks = [];
|
|
541
|
+
for (let i = 0; i < starts.length; i += 1) {
|
|
542
|
+
const start = starts[i];
|
|
543
|
+
const end = i + 1 < starts.length ? starts[i + 1] : value.length;
|
|
544
|
+
const raw = value.slice(start, end).trim();
|
|
545
|
+
if (raw) blocks.push(raw);
|
|
546
|
+
}
|
|
547
|
+
return { preamble, blocks };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Minimal-header summary-message wrapper for the smart-compact roots path.
|
|
551
|
+
// Keeps the SUMMARY_PREFIX anchor (isSummaryMessage / selectCompactionWindow
|
|
552
|
+
// / clear-preserve / TUI all key off startsWith(SUMMARY_PREFIX)) but skips the
|
|
553
|
+
// full sha256/roleCounts header line that fitRecallFastTrackSummaryMessage
|
|
554
|
+
// computes — smart-arrival replacement is a lightweight prefix swap, not the
|
|
555
|
+
// anchored LLM-summary compact, so a heavy per-call header is unneeded cost.
|
|
556
|
+
function makeRecallRootsSummaryMessageParts(oldHistory, rootsPart, priorPart, recallMeta = {}) {
|
|
557
|
+
const header = `${SUMMARY_PREFIX}\nmessages=${(oldHistory || []).length} compact_type=${COMPACT_TYPE_RECALL_FASTTRACK} source=smart-arrival query_sha=${recallMeta.querySha || 'none'}`;
|
|
558
|
+
const parts = [header];
|
|
559
|
+
const priorBlock = formatPriorCompactedContextBlock(priorPart);
|
|
560
|
+
if (priorBlock) parts.push(priorBlock);
|
|
561
|
+
const roots = String(rootsPart || '').trim();
|
|
562
|
+
if (roots) parts.push(roots);
|
|
563
|
+
return makeSummaryMessage(parts.join('\n\n'));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Root-block-aware fit for the smart-compact arrival path (Step1). Mirrors
|
|
567
|
+
// fitRecallFastTrackSummaryMessage's prior-block binary-search fit, but the
|
|
568
|
+
// recall body is fit at ROOT-BLOCK granularity: when the full set of root
|
|
569
|
+
// blocks (kept in original time order) exceeds remainingTokens, the OLDEST
|
|
570
|
+
// blocks are dropped WHOLE (never character-truncated mid-block) until the
|
|
571
|
+
// remaining (newest-biased) suffix fits. Because dropping more leading
|
|
572
|
+
// blocks can only shrink (never grow) the serialized size, the minimal-drop
|
|
573
|
+
// threshold is found via binary search on the drop count.
|
|
574
|
+
export function fitRecallRootsMessage(oldHistory, recallText, remainingTokens, recallMeta = {}, priorPart = '') {
|
|
575
|
+
const prior = String(priorPart || '').trim();
|
|
576
|
+
|
|
577
|
+
let fittedPrior = prior;
|
|
578
|
+
if (prior) {
|
|
579
|
+
let lo = 0;
|
|
580
|
+
let hi = prior.length;
|
|
581
|
+
let bestPriorLen = 0;
|
|
582
|
+
while (lo <= hi) {
|
|
583
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
584
|
+
const candidate = makeRecallRootsSummaryMessageParts(oldHistory, '', prior.slice(0, mid), recallMeta);
|
|
585
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens) {
|
|
586
|
+
bestPriorLen = mid;
|
|
587
|
+
lo = mid + 1;
|
|
588
|
+
} else {
|
|
589
|
+
hi = mid - 1;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
fittedPrior = prior.slice(0, bestPriorLen);
|
|
593
|
+
if (!fittedPrior && prior) {
|
|
594
|
+
const markerOnly = makeRecallRootsSummaryMessageParts(oldHistory, '', RECALL_TAIL_TRUNCATION_MARKER, recallMeta);
|
|
595
|
+
if (estimateMessagesTokens([markerOnly]) <= remainingTokens) {
|
|
596
|
+
fittedPrior = RECALL_TAIL_TRUNCATION_MARKER;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const minimal = makeRecallRootsSummaryMessageParts(oldHistory, '', fittedPrior, recallMeta);
|
|
602
|
+
if (estimateMessagesTokens([minimal]) > remainingTokens) return null;
|
|
603
|
+
|
|
604
|
+
const { preamble, blocks } = splitRecallRootBlocks(recallText);
|
|
605
|
+
if (blocks.length === 0) {
|
|
606
|
+
// No parseable root-block boundaries (empty / non-dump recallText) —
|
|
607
|
+
// degrade to keep-whole-if-it-fits, else drop entirely. Never
|
|
608
|
+
// mid-truncates: a non-block body is treated as a single atomic unit.
|
|
609
|
+
const whole = String(recallText || '').trim();
|
|
610
|
+
if (!whole) return minimal;
|
|
611
|
+
const full = makeRecallRootsSummaryMessageParts(oldHistory, whole, fittedPrior, recallMeta);
|
|
612
|
+
if (estimateMessagesTokens([full]) <= remainingTokens) return full;
|
|
613
|
+
return minimal;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
let loB = 0;
|
|
617
|
+
let hiB = blocks.length;
|
|
618
|
+
let bestLo = -1;
|
|
619
|
+
while (loB <= hiB) {
|
|
620
|
+
const mid = Math.floor((loB + hiB) / 2);
|
|
621
|
+
const kept = blocks.slice(mid);
|
|
622
|
+
const body = [preamble, ...kept].filter(Boolean).join('\n\n');
|
|
623
|
+
const candidate = makeRecallRootsSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
|
|
624
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens) {
|
|
625
|
+
bestLo = mid;
|
|
626
|
+
hiB = mid - 1;
|
|
627
|
+
} else {
|
|
628
|
+
loB = mid + 1;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (bestLo >= 0) {
|
|
632
|
+
const kept = blocks.slice(bestLo);
|
|
633
|
+
const body = [preamble, ...kept].filter(Boolean).join('\n\n');
|
|
634
|
+
return makeRecallRootsSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
|
|
635
|
+
}
|
|
636
|
+
// Even zero root blocks (preamble alone) overflows alongside the fitted
|
|
637
|
+
// prior — try preamble alone, else the no-recall minimal header.
|
|
638
|
+
if (preamble) {
|
|
639
|
+
const preambleOnly = makeRecallRootsSummaryMessageParts(oldHistory, preamble, fittedPrior, recallMeta);
|
|
640
|
+
if (estimateMessagesTokens([preambleOnly]) <= remainingTokens) return preambleOnly;
|
|
641
|
+
}
|
|
642
|
+
return minimal;
|
|
643
|
+
}
|