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
|
@@ -66,7 +66,7 @@ function codePointTokenWeight(cp) {
|
|
|
66
66
|
// surrogate-pair emoji are scored once, at the high emoji weight), takes the
|
|
67
67
|
// max of the weighted sum and the chars/4 ASCII floor, then applies the safety
|
|
68
68
|
// multiplier. Always overcounts relative to chars/4 for non-ASCII text.
|
|
69
|
-
function estimateTokens(text) {
|
|
69
|
+
export function estimateTokens(text) {
|
|
70
70
|
const s = String(text ?? '');
|
|
71
71
|
if (s.length === 0) return 0;
|
|
72
72
|
let weighted = 0;
|
|
@@ -74,7 +74,7 @@ function estimateTokens(text) {
|
|
|
74
74
|
const asciiFloor = s.length / 4; // never below the legacy chars/4 lower bound
|
|
75
75
|
return Math.ceil(Math.max(weighted, asciiFloor) * TOKEN_ESTIMATE_SAFETY_MULTIPLIER);
|
|
76
76
|
}
|
|
77
|
-
function messageEstimateText(m) {
|
|
77
|
+
export function messageEstimateText(m) {
|
|
78
78
|
if (!m || typeof m !== 'object') return '';
|
|
79
79
|
let text = typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? '');
|
|
80
80
|
if (m.role === 'assistant' && Array.isArray(m.toolCalls) && m.toolCalls.length) {
|
|
@@ -84,13 +84,260 @@ function messageEstimateText(m) {
|
|
|
84
84
|
if (m.role === 'tool' && m.toolCallId) text += `\n${m.toolCallId}`;
|
|
85
85
|
return text;
|
|
86
86
|
}
|
|
87
|
-
function estimateMessageTokens(m) {
|
|
87
|
+
export function estimateMessageTokens(m) {
|
|
88
88
|
return estimateTokens(messageEstimateText(m)) + 4;
|
|
89
89
|
}
|
|
90
90
|
export function estimateMessagesTokens(messages) {
|
|
91
91
|
return messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
export const DEFAULT_COMPACTION_BUFFER_TOKENS = 0;
|
|
95
|
+
export const DEFAULT_COMPACTION_BUFFER_RATIO = 0.1;
|
|
96
|
+
export const MAX_COMPACTION_BUFFER_RATIO = 0.25;
|
|
97
|
+
export const DEFAULT_COMPACTION_KEEP_TOKENS = 8_000;
|
|
98
|
+
const LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO = 0.1;
|
|
99
|
+
|
|
100
|
+
export function positiveTokenInt(value) {
|
|
101
|
+
const n = Number(value);
|
|
102
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function envTokenInt(name) {
|
|
106
|
+
return positiveTokenInt(process.env[name]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function normalizeCompactionBufferRatio(value, fallback = DEFAULT_COMPACTION_BUFFER_RATIO) {
|
|
110
|
+
const n = Number(value);
|
|
111
|
+
if (Number.isFinite(n) && n > 0) return n > 1 ? n / 100 : n;
|
|
112
|
+
return fallback;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Percent-named inputs (bufferPercent / bufferPct / *_BUFFER_PERCENT) carry a
|
|
116
|
+
// PERCENT: 1 means 1% (0.01). Ratio-named inputs (bufferRatio / bufferFraction)
|
|
117
|
+
// carry a fraction: 0.01 means 1%, and a legacy value > 1 is read as a percent.
|
|
118
|
+
export function resolveBufferRatioCandidate(percentInputs = [], ratioInputs = []) {
|
|
119
|
+
for (const raw of percentInputs) {
|
|
120
|
+
const n = Number(raw);
|
|
121
|
+
if (Number.isFinite(n) && n > 0) return Math.min(1, n / 100);
|
|
122
|
+
}
|
|
123
|
+
for (const raw of ratioInputs) {
|
|
124
|
+
const n = Number(raw);
|
|
125
|
+
if (Number.isFinite(n) && n > 0) return n > 1 ? n / 100 : n;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function resolveCompactBufferRatio(cfg = {}) {
|
|
131
|
+
const resolved = resolveBufferRatioCandidate(
|
|
132
|
+
[cfg.bufferPercent, cfg.bufferPct, process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT],
|
|
133
|
+
[cfg.bufferRatio, cfg.bufferFraction, process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO],
|
|
134
|
+
);
|
|
135
|
+
return normalizeCompactionBufferRatio(resolved, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function compactionBufferTokensForBoundary(boundaryTokens, opts = {}) {
|
|
139
|
+
const boundary = Math.max(0, Math.floor(Number(boundaryTokens) || 0));
|
|
140
|
+
const explicit = Math.max(0, Math.floor(Number(opts.explicitTokens) || 0));
|
|
141
|
+
if (!boundary) return explicit;
|
|
142
|
+
const maxRatio = normalizeCompactionBufferRatio(opts.maxRatio, MAX_COMPACTION_BUFFER_RATIO);
|
|
143
|
+
const cap = Math.max(0, Math.floor(boundary * maxRatio));
|
|
144
|
+
if (explicit > 0) return Math.max(0, Math.min(explicit, cap));
|
|
145
|
+
const ratio = normalizeCompactionBufferRatio(opts.ratio, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
146
|
+
return Math.max(0, Math.min(Math.floor(boundary * ratio), cap));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function isPersistedZeroBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
150
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
151
|
+
if (!boundary) return false;
|
|
152
|
+
if (envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')) return false;
|
|
153
|
+
for (const envName of ['MIXDOG_AGENT_COMPACT_BUFFER_PERCENT', 'MIXDOG_AGENT_COMPACT_BUFFER_RATIO']) {
|
|
154
|
+
const n = Number(process.env[envName]);
|
|
155
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
156
|
+
}
|
|
157
|
+
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
158
|
+
const n = Number(cfg?.[key]);
|
|
159
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
160
|
+
}
|
|
161
|
+
const ratio = Number(cfg?.bufferRatio);
|
|
162
|
+
if (Number.isFinite(ratio) && ratio > 0) return false;
|
|
163
|
+
const explicitTokens = Number(cfg?.bufferTokens ?? cfg?.buffer);
|
|
164
|
+
if (!Number.isFinite(explicitTokens) || explicitTokens !== 0) return false;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function isLegacyDefaultBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
169
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
170
|
+
if (!boundary) return false;
|
|
171
|
+
if (envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')) return false;
|
|
172
|
+
for (const envName of ['MIXDOG_AGENT_COMPACT_BUFFER_PERCENT', 'MIXDOG_AGENT_COMPACT_BUFFER_RATIO']) {
|
|
173
|
+
const n = Number(process.env[envName]);
|
|
174
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
175
|
+
}
|
|
176
|
+
// Percent/fraction-named fields are operator config. Legacy/default
|
|
177
|
+
// telemetry persisted bufferTokens + bufferRatio after a check/compact pass.
|
|
178
|
+
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
179
|
+
const n = Number(cfg?.[key]);
|
|
180
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
181
|
+
}
|
|
182
|
+
const explicitTokens = positiveTokenInt(cfg?.bufferTokens ?? cfg?.buffer);
|
|
183
|
+
const ratio = Number(cfg?.bufferRatio);
|
|
184
|
+
if (!explicitTokens || !Number.isFinite(ratio) || Math.abs(ratio - LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO) > 1e-9) return false;
|
|
185
|
+
const expectedTokens = Math.floor(boundary * LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
186
|
+
const cfgBoundary = positiveTokenInt(cfg?.boundaryTokens);
|
|
187
|
+
const cfgTrigger = positiveTokenInt(cfg?.triggerTokens);
|
|
188
|
+
return explicitTokens === expectedTokens
|
|
189
|
+
|| (cfgBoundary === boundary && cfgTrigger > 0 && explicitTokens === Math.max(0, boundary - cfgTrigger));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function compactBufferConfigForBoundary(cfg = {}, boundaryTokens = 0) {
|
|
193
|
+
const base = cfg || {};
|
|
194
|
+
if (!isLegacyDefaultBufferTelemetry(base, boundaryTokens)
|
|
195
|
+
&& !isPersistedZeroBufferTelemetry(base, boundaryTokens)) {
|
|
196
|
+
return base;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
...base,
|
|
200
|
+
bufferTokens: null,
|
|
201
|
+
buffer: null,
|
|
202
|
+
bufferRatio: null,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function resolveCompactBufferTokens(boundaryTokens, cfg = {}, opts = {}) {
|
|
207
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
208
|
+
const effectiveCfg = compactBufferConfigForBoundary(cfg, boundary);
|
|
209
|
+
const configured = positiveTokenInt(effectiveCfg.bufferTokens ?? effectiveCfg.buffer)
|
|
210
|
+
|| envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')
|
|
211
|
+
|| 0;
|
|
212
|
+
if (!boundary) return configured || positiveTokenInt(opts.defaultTokens) || DEFAULT_COMPACTION_BUFFER_TOKENS;
|
|
213
|
+
return compactionBufferTokensForBoundary(boundary, {
|
|
214
|
+
explicitTokens: configured,
|
|
215
|
+
ratio: resolveCompactBufferRatio(effectiveCfg),
|
|
216
|
+
maxRatio: opts.maxRatio ?? MAX_COMPACTION_BUFFER_RATIO,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function resolveCompactTriggerTokens(sessionOrConfig = {}, boundaryTokens = 0) {
|
|
221
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
222
|
+
if (!boundary) return null;
|
|
223
|
+
const cfg = sessionOrConfig?.compaction || sessionOrConfig || {};
|
|
224
|
+
const autoLimit = positiveTokenInt(sessionOrConfig?.autoCompactTokenLimit ?? cfg?.autoCompactTokenLimit);
|
|
225
|
+
if (autoLimit && autoLimit < boundary) return Math.max(1, autoLimit);
|
|
226
|
+
const buffer = resolveCompactBufferTokens(boundary, cfg);
|
|
227
|
+
return Math.max(1, boundary - buffer);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function stripSystemReminder(text) {
|
|
231
|
+
return String(text || '')
|
|
232
|
+
.replace(/^\s*<system-reminder>\s*/i, '')
|
|
233
|
+
.replace(/\s*<\/system-reminder>\s*$/i, '')
|
|
234
|
+
.trim();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function splitMarkdownSections(text) {
|
|
238
|
+
const sections = [];
|
|
239
|
+
let current = [];
|
|
240
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
241
|
+
if (/^#\s+/.test(line) && current.length) {
|
|
242
|
+
const body = current.join('\n').trim();
|
|
243
|
+
if (body) sections.push(body);
|
|
244
|
+
current = [line];
|
|
245
|
+
} else {
|
|
246
|
+
current.push(line);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const tail = current.join('\n').trim();
|
|
250
|
+
if (tail) sections.push(tail);
|
|
251
|
+
return sections;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function reminderSectionBucket(section) {
|
|
255
|
+
const heading = String(section.match(/^#\s+([^\n]+)/)?.[1] || '').trim().toLowerCase();
|
|
256
|
+
if (heading.includes('core memory')) return 'memory';
|
|
257
|
+
if (heading.includes('active workflow') || heading.includes('available agents') || heading.includes('workflow')) return 'workflow';
|
|
258
|
+
if (heading.includes('workspace')) return 'workspace';
|
|
259
|
+
if (heading.includes('environment')) return 'environment';
|
|
260
|
+
return 'other';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function summarizeContextMessages(messages) {
|
|
264
|
+
const rows = {
|
|
265
|
+
system: { count: 0, tokens: 0 },
|
|
266
|
+
user: { count: 0, tokens: 0 },
|
|
267
|
+
assistant: { count: 0, tokens: 0 },
|
|
268
|
+
tool: { count: 0, tokens: 0 },
|
|
269
|
+
other: { count: 0, tokens: 0 },
|
|
270
|
+
};
|
|
271
|
+
const semantic = {
|
|
272
|
+
system: { count: 0, tokens: 0 },
|
|
273
|
+
chat: { count: 0, tokens: 0 },
|
|
274
|
+
assistant: { count: 0, tokens: 0 },
|
|
275
|
+
toolResults: { count: 0, tokens: 0 },
|
|
276
|
+
reminders: { count: 0, tokens: 0, otherTokens: 0 },
|
|
277
|
+
workflow: { tokens: 0 },
|
|
278
|
+
memory: { tokens: 0 },
|
|
279
|
+
workspace: { tokens: 0 },
|
|
280
|
+
environment: { tokens: 0 },
|
|
281
|
+
other: { tokens: 0 },
|
|
282
|
+
};
|
|
283
|
+
let toolCallCount = 0;
|
|
284
|
+
let toolCallTokens = 0;
|
|
285
|
+
let toolResultCount = 0;
|
|
286
|
+
let toolResultTokens = 0;
|
|
287
|
+
for (const message of messages || []) {
|
|
288
|
+
const role = rows[message?.role] ? message.role : 'other';
|
|
289
|
+
const text = messageEstimateText(message);
|
|
290
|
+
const tokens = estimateMessageTokens(message);
|
|
291
|
+
rows[role].count += 1;
|
|
292
|
+
rows[role].tokens += tokens;
|
|
293
|
+
if (role === 'system') {
|
|
294
|
+
semantic.system.count += 1;
|
|
295
|
+
semantic.system.tokens += tokens;
|
|
296
|
+
} else if (role === 'user') {
|
|
297
|
+
if (String(text || '').trim().startsWith('<system-reminder>')) {
|
|
298
|
+
semantic.reminders.count += 1;
|
|
299
|
+
semantic.reminders.tokens += tokens;
|
|
300
|
+
let sectionTokens = 0;
|
|
301
|
+
for (const section of splitMarkdownSections(stripSystemReminder(text))) {
|
|
302
|
+
const bucket = reminderSectionBucket(section);
|
|
303
|
+
const sectionTokenCount = estimateTokens(section);
|
|
304
|
+
semantic[bucket].tokens += sectionTokenCount;
|
|
305
|
+
sectionTokens += sectionTokenCount;
|
|
306
|
+
}
|
|
307
|
+
semantic.reminders.otherTokens += Math.max(0, tokens - sectionTokens);
|
|
308
|
+
} else {
|
|
309
|
+
semantic.chat.count += 1;
|
|
310
|
+
semantic.chat.tokens += tokens;
|
|
311
|
+
}
|
|
312
|
+
} else if (role === 'assistant') {
|
|
313
|
+
semantic.assistant.count += 1;
|
|
314
|
+
semantic.assistant.tokens += tokens;
|
|
315
|
+
} else if (role === 'tool') {
|
|
316
|
+
semantic.toolResults.count += 1;
|
|
317
|
+
semantic.toolResults.tokens += tokens;
|
|
318
|
+
}
|
|
319
|
+
if (message?.role === 'assistant' && Array.isArray(message.toolCalls) && message.toolCalls.length) {
|
|
320
|
+
toolCallCount += message.toolCalls.length;
|
|
321
|
+
try { toolCallTokens += estimateTokens(JSON.stringify(message.toolCalls)); }
|
|
322
|
+
catch { toolCallTokens += estimateTokens(`[${message.toolCalls.length} tool calls]`); }
|
|
323
|
+
}
|
|
324
|
+
if (message?.role === 'tool') {
|
|
325
|
+
toolResultCount += 1;
|
|
326
|
+
toolResultTokens += tokens;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
count: Array.isArray(messages) ? messages.length : 0,
|
|
331
|
+
estimatedTokens: Array.isArray(messages) ? estimateMessagesTokens(messages) : 0,
|
|
332
|
+
roles: rows,
|
|
333
|
+
semantic,
|
|
334
|
+
toolCallCount,
|
|
335
|
+
toolCallTokens,
|
|
336
|
+
toolResultCount,
|
|
337
|
+
toolResultTokens,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
94
341
|
// Per-request overhead the provider injects that never appears in the
|
|
95
342
|
// `messages` array: function-calling preamble + system-prompt framing the
|
|
96
343
|
// provider wraps around the request. The chars/4 message estimate misses all
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Compaction debug/diagnostic helpers extracted from loop.mjs.
|
|
2
|
+
import { estimateMessagesTokens } from '../context-utils.mjs';
|
|
3
|
+
|
|
4
|
+
export function estimateMessagesTokensSafe(messages) {
|
|
5
|
+
try { return estimateMessagesTokens(messages); }
|
|
6
|
+
catch { return null; }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function compactDebugEnabled() {
|
|
10
|
+
return String(process.env.MIXDOG_COMPACT_DEBUG || '').trim() === '1';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function compactDiagnosticError(err) {
|
|
14
|
+
if (!err) return null;
|
|
15
|
+
const text = String(err?.message || err);
|
|
16
|
+
return text.length > 500 ? `${text.slice(0, 499)}…` : text;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function compactByteLength(text) {
|
|
20
|
+
try { return Buffer.byteLength(String(text || ''), 'utf8'); }
|
|
21
|
+
catch { return String(text || '').length; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function compactDebugLog(scope, details = {}) {
|
|
25
|
+
if (!compactDebugEnabled()) return;
|
|
26
|
+
try { process.stderr.write(`[compact] ${scope} ${JSON.stringify(details)}\n`); }
|
|
27
|
+
catch { /* best-effort diagnostics only */ }
|
|
28
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Compaction policy resolution, pressure/target budgeting, telemetry
|
|
2
|
+
// persistence, and event emission — extracted from loop.mjs.
|
|
3
|
+
// runRecallFastTrackCompact stays in the loop (it drives the recall pipeline
|
|
4
|
+
// against live session state).
|
|
5
|
+
import {
|
|
6
|
+
estimateRequestReserveTokens,
|
|
7
|
+
resolveCompactBufferRatio,
|
|
8
|
+
resolveCompactBufferTokens,
|
|
9
|
+
} from '../context-utils.mjs';
|
|
10
|
+
import {
|
|
11
|
+
compactTypeIsRecallFastTrack,
|
|
12
|
+
compactTypeIsSemantic,
|
|
13
|
+
normalizeCompactType,
|
|
14
|
+
DEFAULT_COMPACT_TYPE,
|
|
15
|
+
DEFAULT_COMPACTION_KEEP_TOKENS,
|
|
16
|
+
} from '../compact.mjs';
|
|
17
|
+
import { positiveTokenInt, envFlag, envTokenInt } from './env.mjs';
|
|
18
|
+
|
|
19
|
+
const COMPACT_SAFETY_PERCENT = 1.00;
|
|
20
|
+
const COMPACT_TARGET_RATIO = 0.02;
|
|
21
|
+
const COMPACT_TARGET_MIN_TOKENS = 4_000;
|
|
22
|
+
const COMPACT_TARGET_MAX_TOKENS = 16_000;
|
|
23
|
+
|
|
24
|
+
function resolveSemanticCompactSetting(sessionRef, cfg = {}) {
|
|
25
|
+
if (process.env.MIXDOG_AGENT_COMPACT_SEMANTIC !== undefined) return envFlag('MIXDOG_AGENT_COMPACT_SEMANTIC', true);
|
|
26
|
+
if (cfg.semantic === false || cfg.semantic === 'false' || cfg.semantic === 'off') return false;
|
|
27
|
+
if (cfg.semantic === true || cfg.semantic === 'true' || cfg.semantic === 'on') return true;
|
|
28
|
+
// The compact type is already explicit (`semantic` by default). Honor it
|
|
29
|
+
// directly instead of substituting another compaction path.
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveCompactTypeSetting(_sessionRef, cfg = {}) {
|
|
34
|
+
const configured = process.env.MIXDOG_AGENT_COMPACT_TYPE
|
|
35
|
+
?? process.env.MIXDOG_COMPACT_TYPE
|
|
36
|
+
?? cfg.type
|
|
37
|
+
?? cfg.compactType
|
|
38
|
+
?? cfg.compact_type;
|
|
39
|
+
return normalizeCompactType(configured, DEFAULT_COMPACT_TYPE);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveCompactTargetRatio(cfg = {}) {
|
|
43
|
+
const raw = cfg.targetPercent
|
|
44
|
+
?? cfg.targetPct
|
|
45
|
+
?? cfg.targetRatio
|
|
46
|
+
?? cfg.targetFraction
|
|
47
|
+
?? process.env.MIXDOG_AGENT_COMPACT_TARGET_PERCENT
|
|
48
|
+
?? process.env.MIXDOG_COMPACT_TARGET_PERCENT
|
|
49
|
+
?? COMPACT_TARGET_RATIO;
|
|
50
|
+
const n = Number(raw);
|
|
51
|
+
if (!Number.isFinite(n) || n <= 0) return COMPACT_TARGET_RATIO;
|
|
52
|
+
return n > 1 ? n / 100 : n;
|
|
53
|
+
}
|
|
54
|
+
function resolveCompactTargetTokens(boundaryTokens, cfg = {}) {
|
|
55
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
56
|
+
if (!boundary) return null;
|
|
57
|
+
const explicit = positiveTokenInt(cfg.targetTokens ?? cfg.target)
|
|
58
|
+
|| envTokenInt('MIXDOG_AGENT_COMPACT_TARGET_TOKENS')
|
|
59
|
+
|| envTokenInt('MIXDOG_COMPACT_TARGET_TOKENS');
|
|
60
|
+
if (explicit) return Math.max(1, Math.min(boundary, explicit));
|
|
61
|
+
const minTarget = Math.min(boundary, positiveTokenInt(cfg.targetMinTokens ?? cfg.minTargetTokens)
|
|
62
|
+
|| envTokenInt('MIXDOG_AGENT_COMPACT_TARGET_MIN_TOKENS')
|
|
63
|
+
|| envTokenInt('MIXDOG_COMPACT_TARGET_MIN_TOKENS')
|
|
64
|
+
|| COMPACT_TARGET_MIN_TOKENS);
|
|
65
|
+
const maxTarget = Math.min(boundary, positiveTokenInt(cfg.targetMaxTokens ?? cfg.maxTargetTokens)
|
|
66
|
+
|| envTokenInt('MIXDOG_AGENT_COMPACT_TARGET_MAX_TOKENS')
|
|
67
|
+
|| envTokenInt('MIXDOG_COMPACT_TARGET_MAX_TOKENS')
|
|
68
|
+
|| COMPACT_TARGET_MAX_TOKENS);
|
|
69
|
+
const byRatio = Math.max(1, Math.floor(boundary * resolveCompactTargetRatio(cfg)));
|
|
70
|
+
return Math.max(1, Math.min(boundary, maxTarget, Math.max(minTarget, byRatio)));
|
|
71
|
+
}
|
|
72
|
+
function resolveCompactKeepTokens(cfg = {}) {
|
|
73
|
+
return positiveTokenInt(cfg.keepTokens ?? cfg.keep?.tokens ?? cfg.preserveRecentTokens)
|
|
74
|
+
|| envTokenInt('MIXDOG_AGENT_COMPACT_KEEP_TOKENS')
|
|
75
|
+
|| DEFAULT_COMPACTION_KEEP_TOKENS;
|
|
76
|
+
}
|
|
77
|
+
export function resolveWorkerCompactPolicy(sessionRef, tools) {
|
|
78
|
+
if (!sessionRef) return null;
|
|
79
|
+
const cfg = sessionRef.compaction || {};
|
|
80
|
+
const auto = cfg.auto !== false && envFlag('MIXDOG_AGENT_COMPACT_AUTO', true);
|
|
81
|
+
if (!auto) return { auto: false };
|
|
82
|
+
const contextWindow = positiveTokenInt(sessionRef.contextWindow ?? cfg.contextWindow);
|
|
83
|
+
const explicitBoundary = positiveTokenInt(sessionRef.compactBoundaryTokens ?? cfg.boundaryTokens);
|
|
84
|
+
const autoLimit = positiveTokenInt(sessionRef.autoCompactTokenLimit ?? cfg.autoCompactTokenLimit);
|
|
85
|
+
const boundaryTokens = explicitBoundary && contextWindow
|
|
86
|
+
? Math.min(explicitBoundary, contextWindow)
|
|
87
|
+
: (explicitBoundary || contextWindow || autoLimit);
|
|
88
|
+
if (!boundaryTokens) return null;
|
|
89
|
+
const compactBoundaryTokens = Math.max(1, Math.floor(boundaryTokens * COMPACT_SAFETY_PERCENT));
|
|
90
|
+
// Only an explicit auto-compact limit STRICTLY BELOW the boundary acts as
|
|
91
|
+
// the trigger. A persisted value == boundary (legacy derived full-window
|
|
92
|
+
// autoCompactTokenLimit) would set autoTriggerTokens == boundary and
|
|
93
|
+
// collapse/override the default trigger, so it is ignored in favor of the
|
|
94
|
+
// default boundary trigger.
|
|
95
|
+
const autoTriggerTokens = autoLimit && autoLimit < compactBoundaryTokens ? Math.max(1, autoLimit) : null;
|
|
96
|
+
// Sanitized explicit limit: only a sub-boundary value is a real auto-compact
|
|
97
|
+
// limit. Anything >= boundary is a legacy derived full-window artifact and
|
|
98
|
+
// is reported as null so rememberCompactTelemetry does not re-persist it
|
|
99
|
+
// back onto the session and re-collapse the buffer on the next turn.
|
|
100
|
+
const explicitAutoCompactTokenLimit = autoTriggerTokens;
|
|
101
|
+
const bufferTokens = autoTriggerTokens
|
|
102
|
+
? Math.max(0, compactBoundaryTokens - autoTriggerTokens)
|
|
103
|
+
: resolveCompactBufferTokens(compactBoundaryTokens, cfg);
|
|
104
|
+
const bufferRatio = compactBoundaryTokens ? (bufferTokens / compactBoundaryTokens) : resolveCompactBufferRatio(cfg);
|
|
105
|
+
const triggerTokens = autoTriggerTokens || Math.max(1, compactBoundaryTokens - bufferTokens);
|
|
106
|
+
const configuredReserve = positiveTokenInt(cfg.reservedTokens)
|
|
107
|
+
|| envTokenInt('MIXDOG_AGENT_COMPACT_RESERVED_TOKENS')
|
|
108
|
+
|| 0;
|
|
109
|
+
const requestReserve = estimateRequestReserveTokens(tools);
|
|
110
|
+
const keepTokens = resolveCompactKeepTokens(cfg);
|
|
111
|
+
const compactType = resolveCompactTypeSetting(sessionRef, cfg);
|
|
112
|
+
return {
|
|
113
|
+
auto: true,
|
|
114
|
+
type: compactType,
|
|
115
|
+
compactType,
|
|
116
|
+
prune: cfg.prune === true || envFlag('MIXDOG_AGENT_COMPACT_PRUNE', false),
|
|
117
|
+
boundaryTokens: compactBoundaryTokens,
|
|
118
|
+
triggerTokens,
|
|
119
|
+
bufferTokens,
|
|
120
|
+
bufferRatio,
|
|
121
|
+
contextWindow,
|
|
122
|
+
rawContextWindow: positiveTokenInt(sessionRef.rawContextWindow ?? cfg.rawContextWindow) || contextWindow,
|
|
123
|
+
effectiveContextWindowPercent: Number.isFinite(Number(sessionRef.effectiveContextWindowPercent ?? cfg.effectiveContextWindowPercent))
|
|
124
|
+
? Number(sessionRef.effectiveContextWindowPercent ?? cfg.effectiveContextWindowPercent)
|
|
125
|
+
: null,
|
|
126
|
+
autoCompactTokenLimit: explicitAutoCompactTokenLimit,
|
|
127
|
+
semantic: compactTypeIsSemantic(compactType) && resolveSemanticCompactSetting(sessionRef, cfg),
|
|
128
|
+
recallFastTrack: compactTypeIsRecallFastTrack(compactType),
|
|
129
|
+
semanticTimeoutMs: positiveTokenInt(cfg.timeoutMs) || envTokenInt('MIXDOG_AGENT_COMPACT_TIMEOUT_MS') || 30_000,
|
|
130
|
+
tailTurns: positiveTokenInt(cfg.tailTurns) || envTokenInt('MIXDOG_AGENT_COMPACT_TAIL_TURNS') || 2,
|
|
131
|
+
keepTokens,
|
|
132
|
+
preserveRecentTokens: positiveTokenInt(cfg.preserveRecentTokens) || envTokenInt('MIXDOG_AGENT_COMPACT_PRESERVE_RECENT_TOKENS') || keepTokens,
|
|
133
|
+
reserveTokens: requestReserve + configuredReserve,
|
|
134
|
+
requestReserveTokens: requestReserve,
|
|
135
|
+
configuredReserveTokens: configuredReserve,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** Transcript + request reserve only (never provider lastContextTokens). */
|
|
139
|
+
function compactPressureTokens(messageTokensEst, policy) {
|
|
140
|
+
if (messageTokensEst === null) return 0;
|
|
141
|
+
return Math.max(0, messageTokensEst + (policy?.reserveTokens || 0));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Telemetry pressure when a reactive overflow retry forces the next compact. */
|
|
145
|
+
export function compactionTelemetryPressureTokens(messageTokensEst, policy, { reactivePending = false } = {}) {
|
|
146
|
+
const base = compactPressureTokens(messageTokensEst, policy);
|
|
147
|
+
if (!reactivePending) return base;
|
|
148
|
+
const floor = positiveTokenInt(policy?.triggerTokens) || positiveTokenInt(policy?.boundaryTokens) || 0;
|
|
149
|
+
return floor ? Math.max(base, floor) : base;
|
|
150
|
+
}
|
|
151
|
+
export function compactTargetBudget(policy) {
|
|
152
|
+
const boundary = positiveTokenInt(policy?.boundaryTokens);
|
|
153
|
+
if (!boundary) return null;
|
|
154
|
+
const reserve = Math.max(0, Number(policy?.reserveTokens) || 0);
|
|
155
|
+
const targetEffective = resolveCompactTargetTokens(boundary, policy) || boundary;
|
|
156
|
+
return Math.max(1, Math.min(boundary, targetEffective + reserve));
|
|
157
|
+
}
|
|
158
|
+
export function shouldCompactForSession(messageTokensEst, policy, { forceReactive = false } = {}) {
|
|
159
|
+
if (!policy?.auto || !policy.boundaryTokens) return false;
|
|
160
|
+
if (forceReactive) return true;
|
|
161
|
+
if (messageTokensEst === null) return true;
|
|
162
|
+
return compactPressureTokens(messageTokensEst, policy) >= (policy.triggerTokens || policy.boundaryTokens);
|
|
163
|
+
}
|
|
164
|
+
export function countPrunedToolOutputs(before, after) {
|
|
165
|
+
if (!Array.isArray(before) || !Array.isArray(after)) return 0;
|
|
166
|
+
let count = 0;
|
|
167
|
+
const n = Math.min(before.length, after.length);
|
|
168
|
+
for (let i = 0; i < n; i += 1) {
|
|
169
|
+
if (before[i]?.role !== 'tool' || after[i]?.role !== 'tool') continue;
|
|
170
|
+
if (before[i]?.content !== after[i]?.content && after[i]?.compactedKind === 'tool_output_prune') count += 1;
|
|
171
|
+
}
|
|
172
|
+
return count;
|
|
173
|
+
}
|
|
174
|
+
export function rememberCompactTelemetry(sessionRef, policy, meta = {}) {
|
|
175
|
+
if (!sessionRef || !policy) return;
|
|
176
|
+
const prev = sessionRef.compaction && typeof sessionRef.compaction === 'object'
|
|
177
|
+
? sessionRef.compaction
|
|
178
|
+
: {};
|
|
179
|
+
const changed = meta.compactChanged === true || meta.pruneCount > 0;
|
|
180
|
+
sessionRef.compaction = {
|
|
181
|
+
...prev,
|
|
182
|
+
auto: policy.auto !== false,
|
|
183
|
+
prune: policy.prune === true,
|
|
184
|
+
reservedTokens: policy.configuredReserveTokens || prev.reservedTokens || null,
|
|
185
|
+
requestReserveTokens: policy.requestReserveTokens || 0,
|
|
186
|
+
reserveTokens: policy.reserveTokens || 0,
|
|
187
|
+
boundaryTokens: policy.boundaryTokens || null,
|
|
188
|
+
triggerTokens: policy.triggerTokens || null,
|
|
189
|
+
bufferTokens: policy.bufferTokens || 0,
|
|
190
|
+
bufferRatio: policy.bufferRatio ?? prev.bufferRatio ?? null,
|
|
191
|
+
contextWindow: policy.contextWindow || null,
|
|
192
|
+
rawContextWindow: policy.rawContextWindow || null,
|
|
193
|
+
effectiveContextWindowPercent: policy.effectiveContextWindowPercent ?? null,
|
|
194
|
+
autoCompactTokenLimit: policy.autoCompactTokenLimit || null,
|
|
195
|
+
type: policy.compactType || policy.type || DEFAULT_COMPACT_TYPE,
|
|
196
|
+
compactType: policy.compactType || policy.type || DEFAULT_COMPACT_TYPE,
|
|
197
|
+
semantic: policy.semantic === true ? 'auto' : false,
|
|
198
|
+
recallFastTrack: policy.recallFastTrack === true,
|
|
199
|
+
semanticModel: policy.semanticModel || null,
|
|
200
|
+
semanticTimeoutMs: policy.semanticTimeoutMs || null,
|
|
201
|
+
tailTurns: policy.tailTurns || null,
|
|
202
|
+
keepTokens: policy.keepTokens || null,
|
|
203
|
+
preserveRecentTokens: policy.preserveRecentTokens || null,
|
|
204
|
+
lastCheckedAt: Date.now(),
|
|
205
|
+
lastBeforeTokens: meta.beforeTokens ?? null,
|
|
206
|
+
lastAfterTokens: meta.afterTokens ?? null,
|
|
207
|
+
lastPressureTokens: meta.pressureTokens ?? null,
|
|
208
|
+
currentEstimatedTokens: meta.pressureTokens ?? prev.currentEstimatedTokens ?? null,
|
|
209
|
+
lastApiRequestTokens: positiveTokenInt(sessionRef?.lastContextTokens) || prev.lastApiRequestTokens || null,
|
|
210
|
+
lastStage: meta.stage || prev.lastStage || null,
|
|
211
|
+
lastChanged: changed,
|
|
212
|
+
lastTrigger: meta.trigger || prev.lastTrigger || null,
|
|
213
|
+
lastSemantic: meta.semanticCompact === true,
|
|
214
|
+
lastSemanticError: Object.hasOwn(meta, 'semanticError')
|
|
215
|
+
? (meta.semanticError ?? null)
|
|
216
|
+
: (prev.lastSemanticError ?? null),
|
|
217
|
+
lastRecallFastTrack: meta.recallFastTrack === true,
|
|
218
|
+
lastRecallFastTrackError: Object.hasOwn(meta, 'recallFastTrackError')
|
|
219
|
+
? (meta.recallFastTrackError ?? null)
|
|
220
|
+
: (prev.lastRecallFastTrackError ?? null),
|
|
221
|
+
lastError: Object.hasOwn(meta, 'compactError') || Object.hasOwn(meta, 'lastError')
|
|
222
|
+
? (meta.compactError ?? meta.lastError ?? null)
|
|
223
|
+
: (prev.lastError ?? null),
|
|
224
|
+
lastPruneCount: meta.pruneCount || 0,
|
|
225
|
+
lastDurationMs: meta.durationMs != null && Number.isFinite(Number(meta.durationMs))
|
|
226
|
+
? Math.max(0, Math.round(Number(meta.durationMs)))
|
|
227
|
+
: null,
|
|
228
|
+
compactCount: (prev.compactCount || 0) + (changed ? 1 : 0),
|
|
229
|
+
};
|
|
230
|
+
if (changed) {
|
|
231
|
+
const changedAt = Date.now();
|
|
232
|
+
sessionRef.compaction.lastChangedAt = changedAt;
|
|
233
|
+
sessionRef.compaction.lastCompactAt = changedAt;
|
|
234
|
+
sessionRef.lastContextTokensStaleAfterCompact = true;
|
|
235
|
+
}
|
|
236
|
+
sessionRef.contextWindow = policy.contextWindow || sessionRef.contextWindow;
|
|
237
|
+
sessionRef.rawContextWindow = policy.rawContextWindow || sessionRef.rawContextWindow;
|
|
238
|
+
sessionRef.compactBoundaryTokens = policy.boundaryTokens || sessionRef.compactBoundaryTokens || null;
|
|
239
|
+
// Persist only the sanitized (sub-boundary) explicit limit. policy.autoCompactTokenLimit
|
|
240
|
+
// is already null for legacy derived full-window values, so a stale
|
|
241
|
+
// boundary-sized autoCompactTokenLimit on the session is cleared here rather
|
|
242
|
+
// than carried forward to re-collapse the buffer next turn.
|
|
243
|
+
{
|
|
244
|
+
const _boundary = positiveTokenInt(sessionRef.compactBoundaryTokens);
|
|
245
|
+
const _prevLimit = positiveTokenInt(sessionRef.autoCompactTokenLimit);
|
|
246
|
+
const _keepPrev = _prevLimit && (!_boundary || _prevLimit < _boundary) ? _prevLimit : null;
|
|
247
|
+
sessionRef.autoCompactTokenLimit = policy.autoCompactTokenLimit || _keepPrev || null;
|
|
248
|
+
}
|
|
249
|
+
if (policy.effectiveContextWindowPercent !== null) {
|
|
250
|
+
sessionRef.effectiveContextWindowPercent = policy.effectiveContextWindowPercent;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function emitCompactEvent(opts, event = {}) {
|
|
255
|
+
if (!opts || typeof opts.onCompactEvent !== 'function') return;
|
|
256
|
+
try { opts.onCompactEvent({ ts: Date.now(), ...event }); }
|
|
257
|
+
catch { /* best-effort UI/log hook */ }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function compactEventType(policy, fallback = DEFAULT_COMPACT_TYPE) {
|
|
261
|
+
return policy?.compactType || policy?.type || fallback;
|
|
262
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Agent context-overflow error, extracted from loop.mjs. Raised when the latest
|
|
2
|
+
// turn cannot fit the target context budget even after compaction.
|
|
3
|
+
|
|
4
|
+
export class AgentContextOverflowError extends Error {
|
|
5
|
+
constructor({ stage, sessionId, provider, model, contextWindow, budgetTokens, reserveTokens, messageTokensEst }, cause) {
|
|
6
|
+
const target = [provider, model].filter(Boolean).join('/') || 'target model';
|
|
7
|
+
const causeMsg = cause && cause.message ? `: ${cause.message}` : '';
|
|
8
|
+
super(
|
|
9
|
+
`agent context overflow (${target}, stage=${stage || 'compact'}): ` +
|
|
10
|
+
`latest turn cannot fit target context budget=${budgetTokens ?? 'unknown'} ` +
|
|
11
|
+
`reserve=${reserveTokens ?? 'unknown'} contextWindow=${contextWindow ?? 'unknown'} ` +
|
|
12
|
+
`messageTokensEst=${messageTokensEst ?? 'unknown'}${causeMsg}`,
|
|
13
|
+
);
|
|
14
|
+
this.name = 'AgentContextOverflowError';
|
|
15
|
+
this.code = 'AGENT_CONTEXT_OVERFLOW';
|
|
16
|
+
this.sessionId = sessionId || null;
|
|
17
|
+
this.provider = provider || null;
|
|
18
|
+
this.model = model || null;
|
|
19
|
+
this.contextWindow = contextWindow ?? null;
|
|
20
|
+
this.budgetTokens = budgetTokens ?? null;
|
|
21
|
+
this.reserveTokens = reserveTokens ?? null;
|
|
22
|
+
this.messageTokensEst = messageTokensEst ?? null;
|
|
23
|
+
if (cause) this.cause = cause;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function agentContextOverflowError({ stage, sessionId, sessionRef, model, budgetTokens, reserveTokens, messageTokensEst }, cause) {
|
|
28
|
+
return new AgentContextOverflowError({
|
|
29
|
+
stage,
|
|
30
|
+
sessionId,
|
|
31
|
+
provider: sessionRef?.provider || null,
|
|
32
|
+
model: sessionRef?.model || model || null,
|
|
33
|
+
contextWindow: sessionRef?.contextWindow ?? null,
|
|
34
|
+
budgetTokens,
|
|
35
|
+
reserveTokens,
|
|
36
|
+
messageTokensEst,
|
|
37
|
+
}, cause);
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Env-var / token parsing helpers extracted from loop.mjs.
|
|
2
|
+
|
|
3
|
+
export function positiveTokenInt(value) {
|
|
4
|
+
const n = Number(value);
|
|
5
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
6
|
+
}
|
|
7
|
+
export function envFlag(name, fallback = false) {
|
|
8
|
+
const v = process.env[name];
|
|
9
|
+
if (v === undefined) return fallback;
|
|
10
|
+
return !['0', 'false', 'off', 'no'].includes(String(v).trim().toLowerCase());
|
|
11
|
+
}
|
|
12
|
+
export function envTokenInt(name) {
|
|
13
|
+
return positiveTokenInt(process.env[name]);
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Hidden (sub-agent) role registry, extracted from loop.mjs.
|
|
2
|
+
// Source of truth: defaults/agents.json — read once and cached. HIDDEN_AGENT_NAMES
|
|
3
|
+
// is built eagerly so it stays in sync with the declarative registry (no
|
|
4
|
+
// hardcoded duplicate).
|
|
5
|
+
import { readFileSync as _readFileSync } from 'fs';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { dirname, resolve as resolvePath } from 'path';
|
|
8
|
+
|
|
9
|
+
const _AGENTS_JSON = resolvePath(dirname(fileURLToPath(import.meta.url)), '../../../../../defaults/agents.json');
|
|
10
|
+
let _hiddenAgentsCache = null;
|
|
11
|
+
function _getHiddenAgents() {
|
|
12
|
+
if (_hiddenAgentsCache) return _hiddenAgentsCache;
|
|
13
|
+
try {
|
|
14
|
+
_hiddenAgentsCache = JSON.parse(_readFileSync(_AGENTS_JSON, 'utf8'));
|
|
15
|
+
} catch { _hiddenAgentsCache = { agents: [] }; }
|
|
16
|
+
return _hiddenAgentsCache;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const HIDDEN_AGENT_NAMES = new Set(
|
|
20
|
+
(_getHiddenAgents().agents || []).map((r) => r && r.agent).filter((n) => typeof n === 'string' && n.length > 0)
|
|
21
|
+
);
|