mixdog 0.9.1 → 0.9.3
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 +9 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +355 -22
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +80 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +75 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -4,6 +4,12 @@ import {
|
|
|
4
4
|
dedupToolResultBodies,
|
|
5
5
|
reconcileDedupStubs,
|
|
6
6
|
estimateMessagesTokens,
|
|
7
|
+
DEFAULT_COMPACTION_BUFFER_TOKENS,
|
|
8
|
+
DEFAULT_COMPACTION_BUFFER_RATIO,
|
|
9
|
+
MAX_COMPACTION_BUFFER_RATIO,
|
|
10
|
+
DEFAULT_COMPACTION_KEEP_TOKENS,
|
|
11
|
+
normalizeCompactionBufferRatio,
|
|
12
|
+
compactionBufferTokensForBoundary,
|
|
7
13
|
} from './context-utils.mjs';
|
|
8
14
|
|
|
9
15
|
export const SUMMARY_PREFIX = 'A previous model worked on this task and produced the compacted handoff summary below. Build on the work already done and avoid duplicating it; treat the summary as authoritative context for continuing the task. You also retain the preserved recent turns that follow.';
|
|
@@ -15,10 +21,14 @@ export const SUMMARY_PREFIX = 'A previous model worked on this task and produced
|
|
|
15
21
|
// to tune headroom. Telemetry-persisted bufferTokens/bufferRatio of zero is not
|
|
16
22
|
// operator config; loop/manager strip it and reapply this default (see
|
|
17
23
|
// compactBufferConfigForBoundary).
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
export {
|
|
25
|
+
DEFAULT_COMPACTION_BUFFER_TOKENS,
|
|
26
|
+
DEFAULT_COMPACTION_BUFFER_RATIO,
|
|
27
|
+
MAX_COMPACTION_BUFFER_RATIO,
|
|
28
|
+
DEFAULT_COMPACTION_KEEP_TOKENS,
|
|
29
|
+
normalizeCompactionBufferRatio,
|
|
30
|
+
compactionBufferTokensForBoundary,
|
|
31
|
+
};
|
|
22
32
|
export const SUMMARY_OUTPUT_TOKENS = 4_096;
|
|
23
33
|
// Minimum room the generated summary needs after the mandatory (system +
|
|
24
34
|
// preserved tail) cost is accounted for. When the configured target budget is
|
|
@@ -221,6 +231,20 @@ function isProtectedContextUserMessage(m) {
|
|
|
221
231
|
return m.content.trimStart().startsWith('<system-reminder>');
|
|
222
232
|
}
|
|
223
233
|
|
|
234
|
+
// An injected Skill-body user message (the general newMessages channel carries
|
|
235
|
+
// the full SKILL.md body as a role:'user' message after the Skill tool_result).
|
|
236
|
+
// Like isSummaryMessage / isProtectedContextUserMessage, it is detected by
|
|
237
|
+
// content prefix (the `<skill>` envelope from buildSkillResultEnvelope) so the
|
|
238
|
+
// check survives even if the synthetic `meta` field is dropped during a tail
|
|
239
|
+
// rebuild. It is NOT the human's latest prompt and must be excluded from
|
|
240
|
+
// "latest human request" selection (deriveCurrentRequest /
|
|
241
|
+
// buildRecallFastTrackQuery). The `meta:'skill'` marker is also honoured.
|
|
242
|
+
function isInjectedSkillBodyMessage(m) {
|
|
243
|
+
if (m?.role !== 'user') return false;
|
|
244
|
+
if (m.meta === 'skill') return true;
|
|
245
|
+
return typeof m.content === 'string' && m.content.trimStart().startsWith('<skill>');
|
|
246
|
+
}
|
|
247
|
+
|
|
224
248
|
function isProtectedContextAckMessage(m) {
|
|
225
249
|
return m?.role === 'assistant'
|
|
226
250
|
&& typeof m.content === 'string'
|
|
@@ -365,29 +389,26 @@ export function redactToolCallSecretsInMessages(messages) {
|
|
|
365
389
|
return messages.map((m) => redactMessageToolCallSecrets(m));
|
|
366
390
|
}
|
|
367
391
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
export function
|
|
375
|
-
const boundary = Math.max(0, Math.floor(Number(boundaryTokens) || 0));
|
|
376
|
-
const explicit = Math.max(0, Math.floor(Number(opts.explicitTokens) || 0));
|
|
377
|
-
if (!boundary) return explicit;
|
|
378
|
-
const maxRatio = normalizeCompactionBufferRatio(opts.maxRatio, MAX_COMPACTION_BUFFER_RATIO);
|
|
379
|
-
const cap = Math.max(0, Math.floor(boundary * maxRatio));
|
|
380
|
-
if (explicit > 0) return Math.max(0, Math.min(explicit, cap));
|
|
381
|
-
const ratio = normalizeCompactionBufferRatio(opts.ratio, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
382
|
-
return Math.max(0, Math.min(Math.floor(boundary * ratio), cap));
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function effectiveBudget(budgetTokens, opts) {
|
|
392
|
+
// Floor for the reserve-adjusted compact budget. When the tool-schema/request
|
|
393
|
+
// reserve rivals the whole budget (huge agent tool surfaces), subtracting the
|
|
394
|
+
// full reserve could leave a degenerate target; keep enough room to attempt a
|
|
395
|
+
// summary and let the final fit check decide. Logged as degraded because a
|
|
396
|
+
// floored budget can still overflow on the next send.
|
|
397
|
+
const MIN_EFFECTIVE_COMPACT_BUDGET_TOKENS = 1024;
|
|
398
|
+
export function effectiveBudget(budgetTokens, opts) {
|
|
386
399
|
if (!(budgetTokens > 0)) throw new Error('compact: budgetTokens must be > 0');
|
|
387
400
|
const reserve = Number(opts?.reserveTokens) || 0;
|
|
388
401
|
if (reserve <= 0) return budgetTokens;
|
|
389
|
-
|
|
390
|
-
|
|
402
|
+
// Subtract the FULL reserve so an accepted compact actually fits next to
|
|
403
|
+
// the request reserve on the following send. The previous 50%-of-budget cap
|
|
404
|
+
// under-reserved large tool surfaces (agent sessions): a compact could be
|
|
405
|
+
// "accepted" at budget/2 while the true remaining room was smaller, then
|
|
406
|
+
// overflow immediately on the next request.
|
|
407
|
+
const remaining = budgetTokens - reserve;
|
|
408
|
+
if (remaining >= MIN_EFFECTIVE_COMPACT_BUDGET_TOKENS) return remaining;
|
|
409
|
+
const floored = Math.max(1, Math.min(budgetTokens, MIN_EFFECTIVE_COMPACT_BUDGET_TOKENS));
|
|
410
|
+
try { process.stderr.write(`[compact] degraded budget: reserve=${reserve} leaves ${remaining} of budget=${budgetTokens}; flooring to ${floored}\n`); } catch { /* best-effort */ }
|
|
411
|
+
return floored;
|
|
391
412
|
}
|
|
392
413
|
|
|
393
414
|
const PRUNE_TOOL_OUTPUT_MAX_CHARS = 2_000;
|
|
@@ -434,6 +455,39 @@ export function compactTypeIsRecallFastTrack(value) {
|
|
|
434
455
|
return normalizeCompactType(value) === COMPACT_TYPE_RECALL_FASTTRACK;
|
|
435
456
|
}
|
|
436
457
|
|
|
458
|
+
function compactDebugEnabled() {
|
|
459
|
+
return String(process.env.MIXDOG_COMPACT_DEBUG || '').trim() === '1';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function compactDebugLog(scope, details = {}) {
|
|
463
|
+
if (!compactDebugEnabled()) return;
|
|
464
|
+
try {
|
|
465
|
+
process.stderr.write(`[compact] ${scope} ${JSON.stringify(details)}\n`);
|
|
466
|
+
} catch { /* best-effort diagnostics only */ }
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function safeEstimateMessagesTokens(messages) {
|
|
470
|
+
try { return estimateMessagesTokens(messages); }
|
|
471
|
+
catch { return null; }
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function textByteLength(text) {
|
|
475
|
+
try { return Buffer.byteLength(String(text || ''), 'utf8'); }
|
|
476
|
+
catch { return String(text || '').length; }
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function messageContentHasMarker(m, marker) {
|
|
480
|
+
if (!m || !marker) return false;
|
|
481
|
+
if (typeof m.content === 'string') return m.content.includes(marker);
|
|
482
|
+
if (Array.isArray(m.content)) {
|
|
483
|
+
return m.content.some((part) => {
|
|
484
|
+
if (!part || typeof part !== 'object') return false;
|
|
485
|
+
return String(part.text || part.content || '').includes(marker);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
437
491
|
// Count raw (unchunked) pending rows still present in a dump_session_roots
|
|
438
492
|
// payload. recall-fasttrack must keep cycle1-chunking until this reaches 0 so
|
|
439
493
|
// the injected root is the chunked summary, not the raw transcript tail.
|
|
@@ -679,6 +733,39 @@ export function pruneToolOutputs(messages, budgetTokens, opts = {}) {
|
|
|
679
733
|
return reconcileDedupStubs(result);
|
|
680
734
|
}
|
|
681
735
|
|
|
736
|
+
// Anchor-independent tool-output prune (loop overflow safety net).
|
|
737
|
+
//
|
|
738
|
+
// pruneToolOutputs protects the most-recent tailTurns of USER-anchored history,
|
|
739
|
+
// so a single-turn transcript with no user boundary yields protectFrom=0 and
|
|
740
|
+
// prunes nothing. This variant needs no user anchor: it middle-truncates the
|
|
741
|
+
// OLDEST oversized tool_result bodies first, walking forward, until the
|
|
742
|
+
// transcript fits the budget. The newest tool_result is truncated last (and
|
|
743
|
+
// only if still necessary) so fresh state is preserved as long as possible.
|
|
744
|
+
// Structure/pairing is preserved (only string content shrinks), and the result
|
|
745
|
+
// is re-reconciled so tool pairing stays provider-valid.
|
|
746
|
+
export function pruneToolOutputsUnanchored(messages, budgetTokens, opts = {}) {
|
|
747
|
+
const budget = effectiveBudget(budgetTokens, opts);
|
|
748
|
+
let result = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(messages)));
|
|
749
|
+
if (estimateMessagesTokens(result) <= budget) return result;
|
|
750
|
+
|
|
751
|
+
const maxChars = Math.max(256, Number(opts?.maxToolOutputChars) || PRUNE_TOOL_OUTPUT_MAX_CHARS);
|
|
752
|
+
// Oldest -> newest so recent tool output survives longest. No user-turn
|
|
753
|
+
// protection: every oversized tool_result is a candidate.
|
|
754
|
+
for (let i = 0; i < result.length; i += 1) {
|
|
755
|
+
const m = result[i];
|
|
756
|
+
if (m?.role !== 'tool' || typeof m.content !== 'string') continue;
|
|
757
|
+
if (m.content.length <= maxChars) continue;
|
|
758
|
+
result[i] = {
|
|
759
|
+
...m,
|
|
760
|
+
content: pruneToolOutputText(m.content, maxChars, m.toolCallId),
|
|
761
|
+
compacted: true,
|
|
762
|
+
compactedKind: 'tool_output_prune',
|
|
763
|
+
};
|
|
764
|
+
if (estimateMessagesTokens(result) <= budget) break;
|
|
765
|
+
}
|
|
766
|
+
return reconcileDedupStubs(result);
|
|
767
|
+
}
|
|
768
|
+
|
|
682
769
|
function preserveRecentBudget(budget, opts = {}) {
|
|
683
770
|
const maxForBudget = Math.max(1, Math.floor(Number(budget || 0) * 0.8));
|
|
684
771
|
const explicit = Number(opts.preserveRecentTokens ?? opts.keepTokens);
|
|
@@ -743,14 +830,29 @@ function splitLiveCompactionContext(messages) {
|
|
|
743
830
|
return { system: protectedPrefix, live, previousSummary, sanitized };
|
|
744
831
|
}
|
|
745
832
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
833
|
+
// A tail may begin ONLY at an index that is not a tool result: a tool result
|
|
834
|
+
// must stay paired with the assistant tool_call that precedes it, so it can
|
|
835
|
+
// never be the first message of the preserved tail. Every other role (user /
|
|
836
|
+
// assistant / developer / ...) is a valid tail boundary. This replaces the old
|
|
837
|
+
// "the tail must begin at a real user turn" rule, which threw whenever the
|
|
838
|
+
// recent window carried no user message (single-turn agent sessions whose tail
|
|
839
|
+
// is assistant/tool only).
|
|
840
|
+
function findValidCutIndices(live) {
|
|
841
|
+
const out = [];
|
|
842
|
+
for (let i = 0; i < live.length; i += 1) {
|
|
843
|
+
if (live[i]?.role === 'tool') continue;
|
|
844
|
+
out.push(i);
|
|
845
|
+
}
|
|
846
|
+
return out;
|
|
847
|
+
}
|
|
749
848
|
|
|
750
|
-
|
|
751
|
-
|
|
849
|
+
// User-anchored path (unchanged behaviour): keep up to tailTurns recent turns
|
|
850
|
+
// bounded by recentBudget, splitting the newest turn's suffix when it alone is
|
|
851
|
+
// too large. Preserved verbatim so Lead / normal sessions with real user turns
|
|
852
|
+
// compact exactly as before.
|
|
853
|
+
function selectTailStartByTurns(live, recentBudget, tailTurns, previousSummary, opts) {
|
|
752
854
|
const indexedTurns = indexLiveTurns(live);
|
|
753
|
-
if (indexedTurns.length === 0)
|
|
855
|
+
if (indexedTurns.length === 0) return live.length;
|
|
754
856
|
|
|
755
857
|
let tailStartIdx = live.length;
|
|
756
858
|
let keptTurns = 0;
|
|
@@ -786,20 +888,90 @@ function selectCompactionWindow(messages, budget, opts = {}) {
|
|
|
786
888
|
break;
|
|
787
889
|
}
|
|
788
890
|
|
|
891
|
+
if (opts.force === true && !previousSummary && tailStartIdx <= 0) {
|
|
892
|
+
if (indexedTurns.length >= 2) {
|
|
893
|
+
tailStartIdx = indexedTurns[1].start;
|
|
894
|
+
} else if (indexedTurns.length === 1) {
|
|
895
|
+
const onlyTurn = indexedTurns[0];
|
|
896
|
+
const splitIdx = splitTurnStartIndexForBudget(onlyTurn, recentBudget);
|
|
897
|
+
if (splitIdx > onlyTurn.start && splitIdx < onlyTurn.end) {
|
|
898
|
+
tailStartIdx = splitIdx;
|
|
899
|
+
} else if (onlyTurn.end > onlyTurn.start + 1) {
|
|
900
|
+
tailStartIdx = onlyTurn.start + 1;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return tailStartIdx;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// No-user path: pick the tail boundary from valid cut points. Walk newest ->
|
|
908
|
+
// oldest, growing the tail across valid cut points while its suffix still fits
|
|
909
|
+
// recentBudget, and stop before it overflows. Never anchors on a user turn, so
|
|
910
|
+
// an assistant/tool-only single-turn transcript still yields a head to
|
|
911
|
+
// summarize and a paired tail to keep.
|
|
912
|
+
function selectTailStartByCutPoint(live, recentBudget, previousSummary) {
|
|
913
|
+
const validCuts = findValidCutIndices(live);
|
|
914
|
+
if (validCuts.length === 0) return live.length; // degenerate: only tool results
|
|
915
|
+
|
|
916
|
+
let chosen = null;
|
|
917
|
+
for (let k = validCuts.length - 1; k >= 0; k -= 1) {
|
|
918
|
+
const idx = validCuts[k];
|
|
919
|
+
if (estimateMessagesTokens(live.slice(idx)) <= recentBudget) {
|
|
920
|
+
chosen = idx; // fits — try to grow the tail toward an older cut
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
break; // this cut overflows recentBudget; keep the previous (newer) choice
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (chosen === null) {
|
|
927
|
+
// Even the newest valid cut's suffix exceeds recentBudget (a single huge
|
|
928
|
+
// message run). Keep the minimal tail from the newest valid cut so a head
|
|
929
|
+
// remains to summarize; if that cut is at index 0 there is nothing to
|
|
930
|
+
// split off, so keep everything in the head instead. The oversized tail
|
|
931
|
+
// is tolerated downstream (mandatory-cost budget raise) rather than
|
|
932
|
+
// throwing.
|
|
933
|
+
const newestCut = validCuts[validCuts.length - 1];
|
|
934
|
+
return newestCut > 0 ? newestCut : live.length;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (chosen <= 0) {
|
|
938
|
+
// Whole transcript would become the tail => nothing to compact. With no
|
|
939
|
+
// prior summary to build on, pull the tail start forward to the next
|
|
940
|
+
// valid cut so the leading message(s) become the compactable head.
|
|
941
|
+
if (!previousSummary && validCuts.length >= 2) return validCuts[1];
|
|
942
|
+
// Only ONE valid cut (or a leading tool run before it) and no prior
|
|
943
|
+
// summary: there is no older cut to pull forward to. Returning 0 would
|
|
944
|
+
// make the whole transcript the tail with an empty head, and
|
|
945
|
+
// semanticCompactMessages throws on head.length===0 && !previousSummary.
|
|
946
|
+
// Keep everything in the HEAD instead (empty tail) so a head remains to
|
|
947
|
+
// summarize; an empty tail is valid downstream (mandatory = system+tail).
|
|
948
|
+
if (!previousSummary && validCuts.length < 2) return live.length;
|
|
949
|
+
return chosen;
|
|
950
|
+
}
|
|
951
|
+
return chosen;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function selectCompactionWindow(messages, budget, opts = {}) {
|
|
955
|
+
const { system, live, previousSummary } = splitLiveCompactionContext(messages);
|
|
956
|
+
const tailTurns = Math.max(1, Number(opts.tailTurns) || DEFAULT_TAIL_TURNS);
|
|
957
|
+
const recentBudget = preserveRecentBudget(budget, opts);
|
|
958
|
+
|
|
959
|
+
const tailStartIdx = userIndexes(live).length
|
|
960
|
+
? selectTailStartByTurns(live, recentBudget, tailTurns, previousSummary, opts)
|
|
961
|
+
: selectTailStartByCutPoint(live, recentBudget, previousSummary);
|
|
962
|
+
|
|
789
963
|
const head = live.slice(0, tailStartIdx);
|
|
790
964
|
let tail = live.slice(tailStartIdx);
|
|
965
|
+
// sanitizeToolPairs/dedup/reconcile repairs any orphan tool_result the cut
|
|
966
|
+
// may have left; because valid cut points never start on a tool result, an
|
|
967
|
+
// assistant tool_call and its trailing tool_results always land on the same
|
|
968
|
+
// side of the boundary, so pairing stays provider-valid.
|
|
791
969
|
tail = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(tail)));
|
|
792
970
|
|
|
971
|
+
// Only a genuinely empty live window is unrecoverable. Absence of a user
|
|
972
|
+
// turn in the tail is no longer an error.
|
|
793
973
|
if (!head.length && !tail.length) {
|
|
794
|
-
throw new Error('semanticCompactMessages:
|
|
795
|
-
}
|
|
796
|
-
if (tail.length && !tail.some((m) => m?.role === 'user')) {
|
|
797
|
-
const lastUserIdx = live.findLastIndex
|
|
798
|
-
? live.findLastIndex((m) => m?.role === 'user')
|
|
799
|
-
: (() => { for (let i = live.length - 1; i >= 0; i -= 1) { if (live[i]?.role === 'user') return i; } return -1; })();
|
|
800
|
-
if (lastUserIdx < 0 || lastUserIdx >= tailStartIdx) {
|
|
801
|
-
throw new Error('semanticCompactMessages: no user turn to preserve');
|
|
802
|
-
}
|
|
974
|
+
throw new Error('semanticCompactMessages: nothing to compact (empty live window)');
|
|
803
975
|
}
|
|
804
976
|
|
|
805
977
|
const preservedFacts = extractPreservedFacts(head);
|
|
@@ -970,7 +1142,47 @@ function fitCompactionPrompt(input, targetTokens) {
|
|
|
970
1142
|
{ role: 'user', content: withFacts },
|
|
971
1143
|
]) <= targetTokens) return withFacts;
|
|
972
1144
|
}
|
|
973
|
-
|
|
1145
|
+
const fitted = tryFit(false);
|
|
1146
|
+
if (fitted) return fitted;
|
|
1147
|
+
|
|
1148
|
+
// Emergency deterministic reduction: even at perMessageChars=0 the prompt can
|
|
1149
|
+
// overflow when the head carries a very large NUMBER of messages (each still
|
|
1150
|
+
// emits a `N. role` line). Keep only the newest K head messages and collapse
|
|
1151
|
+
// the rest into a single `[K older messages omitted]` stub line, binary
|
|
1152
|
+
// searching the largest K that fits. This bounds the head by COUNT, not just
|
|
1153
|
+
// per-message chars, so a huge-head transcript still yields a minimal prompt
|
|
1154
|
+
// instead of null (which surfaced as a hard compaction throw).
|
|
1155
|
+
const head = Array.isArray(input.head) ? input.head : [];
|
|
1156
|
+
const baseNoFacts = { ...input, preservedFacts: null };
|
|
1157
|
+
const buildReduced = (k) => {
|
|
1158
|
+
const kept = k > 0 ? head.slice(head.length - k) : [];
|
|
1159
|
+
const omitted = head.length - kept.length;
|
|
1160
|
+
const stubHead = omitted > 0
|
|
1161
|
+
? [{ role: 'user', content: `[${omitted} older messages omitted]` }, ...kept]
|
|
1162
|
+
: kept;
|
|
1163
|
+
let inp = { ...baseNoFacts, head: stubHead };
|
|
1164
|
+
// Also shrink/drop a prior <previous-summary> (same as the normal fitAt
|
|
1165
|
+
// path) — a large prior summary can keep the prompt over budget even at
|
|
1166
|
+
// K=0. fitPreviousSummaryForCompactionPrompt is a no-op when there is no
|
|
1167
|
+
// previousSummary, so this is safe for the summary-less case.
|
|
1168
|
+
if (estimateCompactionPromptTokens(inp, 0) > targetTokens) {
|
|
1169
|
+
const fitted = fitPreviousSummaryForCompactionPrompt(inp, 0, targetTokens);
|
|
1170
|
+
if (!fitted) return null;
|
|
1171
|
+
inp = fitted;
|
|
1172
|
+
if (estimateCompactionPromptTokens(inp, 0) > targetTokens) return null;
|
|
1173
|
+
}
|
|
1174
|
+
return buildCompactionPrompt(inp, 0);
|
|
1175
|
+
};
|
|
1176
|
+
let lo = 0;
|
|
1177
|
+
let hi = head.length;
|
|
1178
|
+
let best = null;
|
|
1179
|
+
while (lo <= hi) {
|
|
1180
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
1181
|
+
const candidate = buildReduced(mid);
|
|
1182
|
+
if (candidate) { best = candidate; lo = mid + 1; }
|
|
1183
|
+
else hi = mid - 1;
|
|
1184
|
+
}
|
|
1185
|
+
return best;
|
|
974
1186
|
}
|
|
975
1187
|
|
|
976
1188
|
function extractResponseText(response) {
|
|
@@ -1072,7 +1284,7 @@ function deriveRelevantFilesBullets(head) {
|
|
|
1072
1284
|
function deriveCurrentRequest(messages) {
|
|
1073
1285
|
for (let i = (Array.isArray(messages) ? messages.length : 0) - 1; i >= 0; i -= 1) {
|
|
1074
1286
|
const m = messages[i];
|
|
1075
|
-
if (m?.role === 'user' && !isProtectedContextUserMessage(m)) {
|
|
1287
|
+
if (m?.role === 'user' && !isProtectedContextUserMessage(m) && !isInjectedSkillBodyMessage(m)) {
|
|
1076
1288
|
const text = truncateMiddle(extractText(m).trim(), 400);
|
|
1077
1289
|
if (text) return text;
|
|
1078
1290
|
}
|
|
@@ -1263,7 +1475,7 @@ export function buildRecallFastTrackQuery(messages, opts = {}) {
|
|
|
1263
1475
|
const text = extractText(m).trim();
|
|
1264
1476
|
if (!text) continue;
|
|
1265
1477
|
if (recent.length < 6) recent.unshift(text);
|
|
1266
|
-
if (!latestUser && m?.role === 'user' && !isProtectedContextUserMessage(m)) {
|
|
1478
|
+
if (!latestUser && m?.role === 'user' && !isProtectedContextUserMessage(m) && !isInjectedSkillBodyMessage(m)) {
|
|
1267
1479
|
latestUser = text;
|
|
1268
1480
|
}
|
|
1269
1481
|
if (latestUser && recent.length >= 6) break;
|
|
@@ -1443,6 +1655,144 @@ function fitRecallFastTrackSummaryMessage(oldHistory, recallText, remainingToken
|
|
|
1443
1655
|
return best;
|
|
1444
1656
|
}
|
|
1445
1657
|
|
|
1658
|
+
// --- Smart-compact root-based fitting (arrival-time replacement) -----------
|
|
1659
|
+
//
|
|
1660
|
+
// dump_session_roots (memory/index.mjs dumpSessionRootChunks) renders chunks
|
|
1661
|
+
// TIME-ORDERED (oldest first; chunks.sort by sourceTurn/ts/id ascending),
|
|
1662
|
+
// each root/raw block starting with one of:
|
|
1663
|
+
// # chunk N root=ID[ category=X]
|
|
1664
|
+
// # raw_pending N id=ID
|
|
1665
|
+
// # raw_terminal N id=ID
|
|
1666
|
+
// and blocks joined by "\n\n". runRecallFastTrackForSession additionally
|
|
1667
|
+
// prepends a "session_id=..." / cycle1-drain-status preamble before the dump
|
|
1668
|
+
// text (also "\n\n"-joined) — preserved verbatim as a non-block segment.
|
|
1669
|
+
//
|
|
1670
|
+
// Unlike fitRecallFastTrackSummaryMessage (character-slice binary search,
|
|
1671
|
+
// used by the LLM-summary-free but still-mid-turn recall-fasttrack compact
|
|
1672
|
+
// path), the smart-compact arrival path must never cut a root block
|
|
1673
|
+
// mid-entry — losing half a root's content silently corrupts that entry.
|
|
1674
|
+
// This splitter finds block boundaries by the label pattern (robust to
|
|
1675
|
+
// blank lines inside member/raw content, since it anchors on the distinctive
|
|
1676
|
+
// "# chunk /raw_pending/raw_terminal" line rather than a blank-line split).
|
|
1677
|
+
const RECALL_ROOT_BLOCK_HEADER_RE = /^# (?:chunk \d+ root=\d+(?: category=\S+)?|raw_pending \d+ id=\d+|raw_terminal \d+ id=\d+)[ \t]*$/;
|
|
1678
|
+
|
|
1679
|
+
export function splitRecallRootBlocks(text) {
|
|
1680
|
+
const value = String(text || '');
|
|
1681
|
+
if (!value.trim()) return { preamble: '', blocks: [] };
|
|
1682
|
+
const re = new RegExp(RECALL_ROOT_BLOCK_HEADER_RE.source, 'gm');
|
|
1683
|
+
const starts = [];
|
|
1684
|
+
let m;
|
|
1685
|
+
while ((m = re.exec(value)) !== null) {
|
|
1686
|
+
starts.push(m.index);
|
|
1687
|
+
if (re.lastIndex === m.index) re.lastIndex += 1; // zero-width guard, defensive
|
|
1688
|
+
}
|
|
1689
|
+
if (starts.length === 0) return { preamble: value.trim(), blocks: [] };
|
|
1690
|
+
const preamble = value.slice(0, starts[0]).trim();
|
|
1691
|
+
const blocks = [];
|
|
1692
|
+
for (let i = 0; i < starts.length; i += 1) {
|
|
1693
|
+
const start = starts[i];
|
|
1694
|
+
const end = i + 1 < starts.length ? starts[i + 1] : value.length;
|
|
1695
|
+
const raw = value.slice(start, end).trim();
|
|
1696
|
+
if (raw) blocks.push(raw);
|
|
1697
|
+
}
|
|
1698
|
+
return { preamble, blocks };
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// Minimal-header summary-message wrapper for the smart-compact roots path.
|
|
1702
|
+
// Keeps the SUMMARY_PREFIX anchor (isSummaryMessage / selectCompactionWindow
|
|
1703
|
+
// / clear-preserve / TUI all key off startsWith(SUMMARY_PREFIX)) but skips the
|
|
1704
|
+
// full sha256/roleCounts header line that fitRecallFastTrackSummaryMessage
|
|
1705
|
+
// computes — smart-arrival replacement is a lightweight prefix swap, not the
|
|
1706
|
+
// anchored LLM-summary compact, so a heavy per-call header is unneeded cost.
|
|
1707
|
+
function makeRecallRootsSummaryMessageParts(oldHistory, rootsPart, priorPart, recallMeta = {}) {
|
|
1708
|
+
const header = `${SUMMARY_PREFIX}\nmessages=${(oldHistory || []).length} compact_type=${COMPACT_TYPE_RECALL_FASTTRACK} source=smart-arrival query_sha=${recallMeta.querySha || 'none'}`;
|
|
1709
|
+
const parts = [header];
|
|
1710
|
+
const priorBlock = formatPriorCompactedContextBlock(priorPart);
|
|
1711
|
+
if (priorBlock) parts.push(priorBlock);
|
|
1712
|
+
const roots = String(rootsPart || '').trim();
|
|
1713
|
+
if (roots) parts.push(roots);
|
|
1714
|
+
return makeSummaryMessage(parts.join('\n\n'));
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Root-block-aware fit for the smart-compact arrival path (Step1). Mirrors
|
|
1718
|
+
// fitRecallFastTrackSummaryMessage's prior-block binary-search fit, but the
|
|
1719
|
+
// recall body is fit at ROOT-BLOCK granularity: when the full set of root
|
|
1720
|
+
// blocks (kept in original time order) exceeds remainingTokens, the OLDEST
|
|
1721
|
+
// blocks are dropped WHOLE (never character-truncated mid-block) until the
|
|
1722
|
+
// remaining (newest-biased) suffix fits. Because dropping more leading
|
|
1723
|
+
// blocks can only shrink (never grow) the serialized size, the minimal-drop
|
|
1724
|
+
// threshold is found via binary search on the drop count.
|
|
1725
|
+
export function fitRecallRootsMessage(oldHistory, recallText, remainingTokens, recallMeta = {}, priorPart = '') {
|
|
1726
|
+
const prior = String(priorPart || '').trim();
|
|
1727
|
+
|
|
1728
|
+
let fittedPrior = prior;
|
|
1729
|
+
if (prior) {
|
|
1730
|
+
let lo = 0;
|
|
1731
|
+
let hi = prior.length;
|
|
1732
|
+
let bestPriorLen = 0;
|
|
1733
|
+
while (lo <= hi) {
|
|
1734
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
1735
|
+
const candidate = makeRecallRootsSummaryMessageParts(oldHistory, '', prior.slice(0, mid), recallMeta);
|
|
1736
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens) {
|
|
1737
|
+
bestPriorLen = mid;
|
|
1738
|
+
lo = mid + 1;
|
|
1739
|
+
} else {
|
|
1740
|
+
hi = mid - 1;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
fittedPrior = prior.slice(0, bestPriorLen);
|
|
1744
|
+
if (!fittedPrior && prior) {
|
|
1745
|
+
const markerOnly = makeRecallRootsSummaryMessageParts(oldHistory, '', RECALL_TAIL_TRUNCATION_MARKER, recallMeta);
|
|
1746
|
+
if (estimateMessagesTokens([markerOnly]) <= remainingTokens) {
|
|
1747
|
+
fittedPrior = RECALL_TAIL_TRUNCATION_MARKER;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const minimal = makeRecallRootsSummaryMessageParts(oldHistory, '', fittedPrior, recallMeta);
|
|
1753
|
+
if (estimateMessagesTokens([minimal]) > remainingTokens) return null;
|
|
1754
|
+
|
|
1755
|
+
const { preamble, blocks } = splitRecallRootBlocks(recallText);
|
|
1756
|
+
if (blocks.length === 0) {
|
|
1757
|
+
// No parseable root-block boundaries (empty / non-dump recallText) —
|
|
1758
|
+
// degrade to keep-whole-if-it-fits, else drop entirely. Never
|
|
1759
|
+
// mid-truncates: a non-block body is treated as a single atomic unit.
|
|
1760
|
+
const whole = String(recallText || '').trim();
|
|
1761
|
+
if (!whole) return minimal;
|
|
1762
|
+
const full = makeRecallRootsSummaryMessageParts(oldHistory, whole, fittedPrior, recallMeta);
|
|
1763
|
+
if (estimateMessagesTokens([full]) <= remainingTokens) return full;
|
|
1764
|
+
return minimal;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
let loB = 0;
|
|
1768
|
+
let hiB = blocks.length;
|
|
1769
|
+
let bestLo = -1;
|
|
1770
|
+
while (loB <= hiB) {
|
|
1771
|
+
const mid = Math.floor((loB + hiB) / 2);
|
|
1772
|
+
const kept = blocks.slice(mid);
|
|
1773
|
+
const body = [preamble, ...kept].filter(Boolean).join('\n\n');
|
|
1774
|
+
const candidate = makeRecallRootsSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
|
|
1775
|
+
if (estimateMessagesTokens([candidate]) <= remainingTokens) {
|
|
1776
|
+
bestLo = mid;
|
|
1777
|
+
hiB = mid - 1;
|
|
1778
|
+
} else {
|
|
1779
|
+
loB = mid + 1;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
if (bestLo >= 0) {
|
|
1783
|
+
const kept = blocks.slice(bestLo);
|
|
1784
|
+
const body = [preamble, ...kept].filter(Boolean).join('\n\n');
|
|
1785
|
+
return makeRecallRootsSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
|
|
1786
|
+
}
|
|
1787
|
+
// Even zero root blocks (preamble alone) overflows alongside the fitted
|
|
1788
|
+
// prior — try preamble alone, else the no-recall minimal header.
|
|
1789
|
+
if (preamble) {
|
|
1790
|
+
const preambleOnly = makeRecallRootsSummaryMessageParts(oldHistory, preamble, fittedPrior, recallMeta);
|
|
1791
|
+
if (estimateMessagesTokens([preambleOnly]) <= remainingTokens) return preambleOnly;
|
|
1792
|
+
}
|
|
1793
|
+
return minimal;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1446
1796
|
function combinedSignal(parent, timeoutMs) {
|
|
1447
1797
|
const ms = Number(timeoutMs);
|
|
1448
1798
|
if (!Number.isFinite(ms) || ms <= 0) return parent || undefined;
|
|
@@ -1455,13 +1805,29 @@ export async function semanticCompactMessages(provider, messages, model, budgetT
|
|
|
1455
1805
|
if (!provider || typeof provider.send !== 'function') {
|
|
1456
1806
|
throw new Error('semanticCompactMessages: provider.send is required');
|
|
1457
1807
|
}
|
|
1808
|
+
const startedAt = Date.now();
|
|
1458
1809
|
let budget = effectiveBudget(budgetTokens, opts);
|
|
1459
1810
|
const baseSanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(messages)));
|
|
1811
|
+
const baseTokens = safeEstimateMessagesTokens(baseSanitized);
|
|
1460
1812
|
// No-op fast path: if the original sanitized transcript already fits and we
|
|
1461
1813
|
// are not forced, return it UNCHANGED (no preserved-tail redaction applied)
|
|
1462
1814
|
// to keep prior no-compaction semantics.
|
|
1463
|
-
if (
|
|
1464
|
-
return {
|
|
1815
|
+
if (baseTokens != null && baseTokens <= budget && opts.force !== true) {
|
|
1816
|
+
return {
|
|
1817
|
+
messages: baseSanitized,
|
|
1818
|
+
usage: null,
|
|
1819
|
+
semantic: false,
|
|
1820
|
+
compactType: COMPACT_TYPE_SEMANTIC,
|
|
1821
|
+
diagnostics: {
|
|
1822
|
+
noOp: true,
|
|
1823
|
+
reason: 'fits_budget',
|
|
1824
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
1825
|
+
baseMessages: baseSanitized.length,
|
|
1826
|
+
baseTokens,
|
|
1827
|
+
budgetTokens: budget,
|
|
1828
|
+
durationMs: Date.now() - startedAt,
|
|
1829
|
+
},
|
|
1830
|
+
};
|
|
1465
1831
|
}
|
|
1466
1832
|
// Compaction will proceed: redact sensitive tool-call argument VALUES before
|
|
1467
1833
|
// window selection so the preserved tail/system that survive verbatim are
|
|
@@ -1478,6 +1844,7 @@ export async function semanticCompactMessages(provider, messages, model, budgetT
|
|
|
1478
1844
|
|
|
1479
1845
|
const mandatory = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs([...selected.system, ...selected.tail])));
|
|
1480
1846
|
const mandatoryCost = estimateMessagesTokens(mandatory);
|
|
1847
|
+
const originalBudget = budget;
|
|
1481
1848
|
// The preserved tail is kept verbatim and the head is replaced by a much
|
|
1482
1849
|
// smaller summary, so the compacted result is always smaller than the
|
|
1483
1850
|
// input regardless of how the configured target budget compares to the
|
|
@@ -1487,6 +1854,7 @@ export async function semanticCompactMessages(provider, messages, model, budgetT
|
|
|
1487
1854
|
if (mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS > budget) {
|
|
1488
1855
|
budget = mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS;
|
|
1489
1856
|
}
|
|
1857
|
+
const budgetRaisedBy = Math.max(0, budget - originalBudget);
|
|
1490
1858
|
|
|
1491
1859
|
const callBudget = Math.max(1, Math.floor((opts.compactionInputBudgetTokens || budget) * COMPACTION_PROMPT_HEADROOM));
|
|
1492
1860
|
const prompt = fitCompactionPrompt(selected, callBudget);
|
|
@@ -1553,6 +1921,41 @@ export async function semanticCompactMessages(provider, messages, model, budgetT
|
|
|
1553
1921
|
if (finalTokens > budget) {
|
|
1554
1922
|
throw new Error(`semanticCompactMessages: compacted result exceeds budget=${budget} (result=${finalTokens})`);
|
|
1555
1923
|
}
|
|
1924
|
+
const diagnostics = {
|
|
1925
|
+
noOp: false,
|
|
1926
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
1927
|
+
baseMessages: baseSanitized.length,
|
|
1928
|
+
baseTokens,
|
|
1929
|
+
systemMessages: selected.system.length,
|
|
1930
|
+
headMessages: selected.head.length,
|
|
1931
|
+
originalHeadMessages: selected.originalHead.length,
|
|
1932
|
+
tailMessages: selected.tail.length,
|
|
1933
|
+
mandatoryMessages: mandatory.length,
|
|
1934
|
+
finalMessages: result.length,
|
|
1935
|
+
systemTokens: safeEstimateMessagesTokens(selected.system),
|
|
1936
|
+
headTokens: safeEstimateMessagesTokens(selected.head),
|
|
1937
|
+
tailTokens: safeEstimateMessagesTokens(selected.tail),
|
|
1938
|
+
mandatoryCost,
|
|
1939
|
+
finalTokens,
|
|
1940
|
+
originalBudgetTokens: originalBudget,
|
|
1941
|
+
budgetTokens: budget,
|
|
1942
|
+
budgetRaised: budgetRaisedBy > 0,
|
|
1943
|
+
budgetRaisedBy,
|
|
1944
|
+
remainingTokens: budget - mandatoryCost,
|
|
1945
|
+
callBudgetTokens: callBudget,
|
|
1946
|
+
promptChars: String(prompt || '').length,
|
|
1947
|
+
promptBytes: textByteLength(prompt),
|
|
1948
|
+
promptTokens: safeEstimateMessagesTokens([
|
|
1949
|
+
{ role: 'system', content: COMPACTION_SYSTEM_PROMPT },
|
|
1950
|
+
{ role: 'user', content: prompt },
|
|
1951
|
+
]),
|
|
1952
|
+
summaryChars: String(summary || '').length,
|
|
1953
|
+
rawSummaryChars: String(rawSummary || '').length,
|
|
1954
|
+
summaryRepaired: enforced.repaired === true,
|
|
1955
|
+
previousSummary: !!selected.previousSummary,
|
|
1956
|
+
durationMs: Date.now() - startedAt,
|
|
1957
|
+
};
|
|
1958
|
+
compactDebugLog('semantic result', diagnostics);
|
|
1556
1959
|
return {
|
|
1557
1960
|
messages: result,
|
|
1558
1961
|
usage: response?.usage || null,
|
|
@@ -1561,6 +1964,7 @@ export async function semanticCompactMessages(provider, messages, model, budgetT
|
|
|
1561
1964
|
compactType: COMPACT_TYPE_SEMANTIC,
|
|
1562
1965
|
summary,
|
|
1563
1966
|
summaryRepaired: enforced.repaired === true,
|
|
1967
|
+
diagnostics,
|
|
1564
1968
|
};
|
|
1565
1969
|
}
|
|
1566
1970
|
|
|
@@ -1664,7 +2068,54 @@ function fitSingleRecallTurnToCap(turn, cap) {
|
|
|
1664
2068
|
function truncateTailToCap(messages, cap) {
|
|
1665
2069
|
const turn = Array.isArray(messages) ? messages : [];
|
|
1666
2070
|
if (turn.length === 0) return [];
|
|
1667
|
-
|
|
2071
|
+
// No user anchor in this turn: keep the NEWEST messages that fit `cap`,
|
|
2072
|
+
// walking backward. (Previously this delegated back to
|
|
2073
|
+
// fitSingleRecallTurnToCap, which re-entered here on a no-user turn —
|
|
2074
|
+
// infinite mutual recursion. Unreachable while a no-user tail threw upstream;
|
|
2075
|
+
// now that a no-user tail is allowed, this path must terminate on its own.)
|
|
2076
|
+
let out = [];
|
|
2077
|
+
let startIdx = turn.length; // index in `turn` where `out` begins
|
|
2078
|
+
for (let i = turn.length - 1; i >= 0; i -= 1) {
|
|
2079
|
+
const candidate = [turn[i], ...out];
|
|
2080
|
+
if (estimateMessagesTokens(candidate) <= cap) {
|
|
2081
|
+
out = candidate;
|
|
2082
|
+
startIdx = i;
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
if (out.length === 0) {
|
|
2086
|
+
// Even the newest single message exceeds cap: middle-truncate its
|
|
2087
|
+
// string content so at least one message survives.
|
|
2088
|
+
const m = turn[i];
|
|
2089
|
+
const text = typeof m?.content === 'string' ? m.content : extractText(m);
|
|
2090
|
+
const truncated = truncateMessageForRecallTail(text, Math.max(1, cap * RECALL_TAIL_CHARS_PER_TOKEN));
|
|
2091
|
+
out = [{ ...m, content: truncated }];
|
|
2092
|
+
startIdx = i;
|
|
2093
|
+
}
|
|
2094
|
+
break;
|
|
2095
|
+
}
|
|
2096
|
+
// A leading tool_result with no preceding assistant tool_call is an orphan
|
|
2097
|
+
// that sanitizeToolPairs drops — which could empty the whole tail. Extend the
|
|
2098
|
+
// window backward to swallow the preceding non-tool boundary (the assistant
|
|
2099
|
+
// that owns the tool_call), so the pair survives sanitize. Bounded by
|
|
2100
|
+
// startIdx so it always terminates.
|
|
2101
|
+
while (startIdx > 0 && out[0]?.role === 'tool') {
|
|
2102
|
+
startIdx -= 1;
|
|
2103
|
+
out = [turn[startIdx], ...out];
|
|
2104
|
+
}
|
|
2105
|
+
let sanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(out)));
|
|
2106
|
+
// Final guard: if sanitize still emptied the tail but the turn has a non-tool
|
|
2107
|
+
// message, rebuild from the newest non-tool message forward so the tail is
|
|
2108
|
+
// never empty when preservable content exists.
|
|
2109
|
+
if (sanitized.length === 0) {
|
|
2110
|
+
let nt = -1;
|
|
2111
|
+
for (let i = turn.length - 1; i >= 0; i -= 1) {
|
|
2112
|
+
if (turn[i]?.role !== 'tool') { nt = i; break; }
|
|
2113
|
+
}
|
|
2114
|
+
if (nt >= 0) {
|
|
2115
|
+
sanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(turn.slice(nt))));
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return sanitized;
|
|
1668
2119
|
}
|
|
1669
2120
|
|
|
1670
2121
|
function stripNestedSummaryHeaderLines(text) {
|
|
@@ -1713,10 +2164,10 @@ function selectRecallPreservedTail(live, opts = {}) {
|
|
|
1713
2164
|
tail = fitSingleRecallTurnToCap(kept[kept.length - 1], cap);
|
|
1714
2165
|
}
|
|
1715
2166
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
2167
|
+
// A no-user tail is valid: a single-turn agent session may keep only
|
|
2168
|
+
// assistant/tool structure recently. Mirror the semantic cut-point model —
|
|
2169
|
+
// preserve the recent structured turn(s) verbatim without demanding a user
|
|
2170
|
+
// anchor rather than throwing. tool-pairing is already reconciled above.
|
|
1720
2171
|
const tailStartIdx = recallTailStartIndex(msgs, tail);
|
|
1721
2172
|
const head = msgs.slice(0, tailStartIdx);
|
|
1722
2173
|
return { tail, head, tailStartIdx };
|
|
@@ -1730,10 +2181,26 @@ function selectRecallTailUserMessages(tail, opts = {}) {
|
|
|
1730
2181
|
}
|
|
1731
2182
|
|
|
1732
2183
|
function _recallFastTrackCompactMessages(messages, budgetTokens, opts = {}) {
|
|
2184
|
+
const startedAt = Date.now();
|
|
1733
2185
|
let budget = effectiveBudget(budgetTokens, opts);
|
|
1734
2186
|
const baseSanitized = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs(messages)));
|
|
1735
|
-
|
|
1736
|
-
|
|
2187
|
+
const baseTokens = safeEstimateMessagesTokens(baseSanitized);
|
|
2188
|
+
if (baseTokens != null && baseTokens <= budget && opts.force !== true) {
|
|
2189
|
+
return {
|
|
2190
|
+
messages: baseSanitized,
|
|
2191
|
+
recallFastTrack: false,
|
|
2192
|
+
compactType: COMPACT_TYPE_RECALL_FASTTRACK,
|
|
2193
|
+
query: opts.query || '',
|
|
2194
|
+
diagnostics: {
|
|
2195
|
+
noOp: true,
|
|
2196
|
+
reason: 'fits_budget',
|
|
2197
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
2198
|
+
baseMessages: baseSanitized.length,
|
|
2199
|
+
baseTokens,
|
|
2200
|
+
budgetTokens: budget,
|
|
2201
|
+
durationMs: Date.now() - startedAt,
|
|
2202
|
+
},
|
|
2203
|
+
};
|
|
1737
2204
|
}
|
|
1738
2205
|
const sanitized = redactToolCallSecretsInMessages(baseSanitized);
|
|
1739
2206
|
|
|
@@ -1751,9 +2218,11 @@ function _recallFastTrackCompactMessages(messages, budgetTokens, opts = {}) {
|
|
|
1751
2218
|
|
|
1752
2219
|
const mandatory = reconcileDedupStubs(dedupToolResultBodies(sanitizeToolPairs([...safeSystem, ...recallTail])));
|
|
1753
2220
|
const mandatoryCost = estimateMessagesTokens(mandatory);
|
|
2221
|
+
const originalBudget = budget;
|
|
1754
2222
|
if (mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS > budget) {
|
|
1755
2223
|
budget = mandatoryCost + COMPACT_SUMMARY_MIN_ROOM_TOKENS;
|
|
1756
2224
|
}
|
|
2225
|
+
const budgetRaisedBy = Math.max(0, budget - originalBudget);
|
|
1757
2226
|
|
|
1758
2227
|
if (!recallFit.recall && !recallFit.prior && opts.allowEmptyRecall !== true) {
|
|
1759
2228
|
throw new Error('recallFastTrackCompactMessages: recall text is empty');
|
|
@@ -1779,10 +2248,50 @@ function _recallFastTrackCompactMessages(messages, budgetTokens, opts = {}) {
|
|
|
1779
2248
|
if (finalTokens > budget) {
|
|
1780
2249
|
throw new Error(`recallFastTrackCompactMessages: compacted result exceeds budget=${budget} (result=${finalTokens})`);
|
|
1781
2250
|
}
|
|
2251
|
+
const summaryContent = String(summaryMessage?.content || '');
|
|
2252
|
+
const diagnostics = {
|
|
2253
|
+
noOp: false,
|
|
2254
|
+
inputMessages: Array.isArray(messages) ? messages.length : 0,
|
|
2255
|
+
baseMessages: baseSanitized.length,
|
|
2256
|
+
baseTokens,
|
|
2257
|
+
systemMessages: safeSystem.length,
|
|
2258
|
+
liveMessages: live.length,
|
|
2259
|
+
headMessages: recallHead.length,
|
|
2260
|
+
tailMessages: recallTail.length,
|
|
2261
|
+
mandatoryMessages: mandatory.length,
|
|
2262
|
+
finalMessages: result.length,
|
|
2263
|
+
systemTokens: safeEstimateMessagesTokens(safeSystem),
|
|
2264
|
+
liveTokens: safeEstimateMessagesTokens(live),
|
|
2265
|
+
headTokens: safeEstimateMessagesTokens(recallHead),
|
|
2266
|
+
tailTokens: safeEstimateMessagesTokens(recallTail),
|
|
2267
|
+
mandatoryCost,
|
|
2268
|
+
finalTokens,
|
|
2269
|
+
originalBudgetTokens: originalBudget,
|
|
2270
|
+
budgetTokens: budget,
|
|
2271
|
+
budgetRaised: budgetRaisedBy > 0,
|
|
2272
|
+
budgetRaisedBy,
|
|
2273
|
+
remainingTokens: budget - mandatoryCost,
|
|
2274
|
+
recallChars: recallFit.recall.length,
|
|
2275
|
+
recallBytes: textByteLength(recallFit.recall),
|
|
2276
|
+
priorChars: recallFit.prior.length,
|
|
2277
|
+
priorBytes: textByteLength(recallFit.prior),
|
|
2278
|
+
summaryMessageChars: summaryContent.length,
|
|
2279
|
+
summaryMessageBytes: textByteLength(summaryContent),
|
|
2280
|
+
recallEmpty: !recallFit.recall,
|
|
2281
|
+
priorEmpty: !recallFit.prior,
|
|
2282
|
+
recallTruncatedInSummary: !!recallFit.recall && !summaryContent.includes(recallFit.recall),
|
|
2283
|
+
priorTruncatedInSummary: !!recallFit.prior && !summaryContent.includes(recallFit.prior),
|
|
2284
|
+
tailTruncated: recallTail.some((m) => messageContentHasMarker(m, RECALL_TAIL_TRUNCATION_MARKER) || messageContentHasMarker(m, RECALL_TAIL_SHORT_TRUNCATION_MARKER)),
|
|
2285
|
+
tailOptions: recallTailOpts,
|
|
2286
|
+
previousSummary: !!previousSummary,
|
|
2287
|
+
durationMs: Date.now() - startedAt,
|
|
2288
|
+
};
|
|
2289
|
+
compactDebugLog('recall-fasttrack result', diagnostics);
|
|
1782
2290
|
return {
|
|
1783
2291
|
messages: result,
|
|
1784
2292
|
recallFastTrack: true,
|
|
1785
2293
|
compactType: COMPACT_TYPE_RECALL_FASTTRACK,
|
|
1786
2294
|
query: opts.query || '',
|
|
2295
|
+
diagnostics,
|
|
1787
2296
|
};
|
|
1788
2297
|
}
|