mixdog 0.9.1 → 0.9.2

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.
Files changed (214) hide show
  1. package/package.json +8 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/background-task-meta-smoke.mjs +1 -1
  6. package/scripts/bench-run.mjs +262 -0
  7. package/scripts/compact-smoke.mjs +12 -0
  8. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  9. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  10. package/scripts/internal-comms-bench.mjs +727 -0
  11. package/scripts/internal-comms-smoke.mjs +75 -0
  12. package/scripts/lead-workflow-smoke.mjs +4 -4
  13. package/scripts/live-worker-smoke.mjs +9 -9
  14. package/scripts/output-style-bench.mjs +285 -0
  15. package/scripts/output-style-smoke.mjs +13 -10
  16. package/scripts/patch-replay.mjs +90 -0
  17. package/scripts/provider-stream-stall-test.mjs +276 -0
  18. package/scripts/provider-toolcall-test.mjs +599 -1
  19. package/scripts/routing-corpus.mjs +281 -0
  20. package/scripts/session-bench.mjs +1526 -0
  21. package/scripts/session-diag.mjs +595 -0
  22. package/scripts/task-bench.mjs +207 -0
  23. package/scripts/tool-failures.mjs +6 -6
  24. package/scripts/tool-smoke.mjs +306 -66
  25. package/scripts/toolcall-args-test.mjs +81 -0
  26. package/src/agents/debugger/AGENT.md +4 -4
  27. package/src/agents/heavy-worker/AGENT.md +4 -2
  28. package/src/agents/reviewer/AGENT.md +4 -4
  29. package/src/agents/worker/AGENT.md +4 -2
  30. package/src/app.mjs +10 -6
  31. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  32. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  33. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  34. package/src/headless-role.mjs +14 -14
  35. package/src/help.mjs +1 -0
  36. package/src/lib/rules-builder.cjs +32 -54
  37. package/src/mixdog-session-runtime.mjs +710 -318
  38. package/src/output-styles/default.md +12 -7
  39. package/src/output-styles/minimal.md +25 -0
  40. package/src/output-styles/oneline.md +21 -0
  41. package/src/output-styles/simple.md +10 -9
  42. package/src/repl.mjs +12 -2
  43. package/src/rules/agent/00-common.md +7 -5
  44. package/src/rules/agent/30-explorer.md +7 -8
  45. package/src/rules/lead/01-general.md +3 -1
  46. package/src/rules/lead/lead-tool.md +7 -0
  47. package/src/rules/shared/01-tool.md +17 -12
  48. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  51. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  52. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  53. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  54. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
  56. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  57. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  58. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
  59. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  61. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  62. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +332 -57
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +59 -32
  64. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
  65. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  66. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  67. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
  69. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +78 -3
  70. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +202 -98
  71. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +183 -20
  72. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  73. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
  74. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  75. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +15 -9
  76. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  77. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  78. package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
  79. package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
  80. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  81. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  82. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  83. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  84. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  85. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  86. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  87. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
  88. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  90. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  91. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  92. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  95. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
  96. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  97. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
  99. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  100. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
  101. package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
  102. package/src/runtime/channels/backends/discord.mjs +99 -9
  103. package/src/runtime/channels/backends/telegram.mjs +501 -0
  104. package/src/runtime/channels/index.mjs +441 -1224
  105. package/src/runtime/channels/lib/config.mjs +54 -2
  106. package/src/runtime/channels/lib/format.mjs +4 -2
  107. package/src/runtime/channels/lib/output-forwarder.mjs +80 -67
  108. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  109. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  110. package/src/runtime/channels/lib/telegram-format.mjs +283 -0
  111. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  112. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  113. package/src/runtime/channels/lib/webhook.mjs +59 -31
  114. package/src/runtime/channels/tool-defs.mjs +1 -1
  115. package/src/runtime/memory/index.mjs +184 -19
  116. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  117. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  118. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  119. package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
  120. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  121. package/src/runtime/memory/lib/memory.mjs +101 -4
  122. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  123. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  124. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  125. package/src/runtime/memory/tool-defs.mjs +6 -3
  126. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  127. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  128. package/src/runtime/shared/config.mjs +9 -0
  129. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  130. package/src/runtime/shared/schedules-store.mjs +21 -19
  131. package/src/runtime/shared/tool-surface.mjs +98 -13
  132. package/src/runtime/shared/transcript-writer.mjs +129 -0
  133. package/src/runtime/shared/update-checker.mjs +214 -0
  134. package/src/standalone/agent-tool.mjs +255 -109
  135. package/src/standalone/channel-admin.mjs +133 -40
  136. package/src/standalone/channel-worker.mjs +8 -291
  137. package/src/standalone/explore-tool.mjs +2 -2
  138. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  139. package/src/standalone/provider-admin.mjs +11 -0
  140. package/src/standalone/usage-dashboard.mjs +1 -1
  141. package/src/tui/App.jsx +2096 -732
  142. package/src/tui/components/ConfirmBar.jsx +47 -0
  143. package/src/tui/components/ContextPanel.jsx +5 -3
  144. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  145. package/src/tui/components/Markdown.jsx +22 -98
  146. package/src/tui/components/Message.jsx +14 -35
  147. package/src/tui/components/Picker.jsx +87 -12
  148. package/src/tui/components/PromptInput.jsx +83 -7
  149. package/src/tui/components/QueuedCommands.jsx +1 -1
  150. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  151. package/src/tui/components/Spinner.jsx +7 -7
  152. package/src/tui/components/StatusLine.jsx +40 -21
  153. package/src/tui/components/TextEntryPanel.jsx +51 -7
  154. package/src/tui/components/ToolExecution.jsx +170 -98
  155. package/src/tui/components/TurnDone.jsx +4 -4
  156. package/src/tui/components/UsagePanel.jsx +1 -1
  157. package/src/tui/components/tool-output-format.mjs +159 -21
  158. package/src/tui/components/tool-output-format.test.mjs +87 -0
  159. package/src/tui/display-width.mjs +69 -0
  160. package/src/tui/display-width.test.mjs +35 -0
  161. package/src/tui/dist/index.mjs +6965 -2391
  162. package/src/tui/engine.mjs +287 -126
  163. package/src/tui/index.jsx +117 -7
  164. package/src/tui/keyboard-protocol.mjs +42 -0
  165. package/src/tui/lib/voice-recorder.mjs +453 -0
  166. package/src/tui/markdown/format-token.mjs +129 -76
  167. package/src/tui/markdown/format-token.test.mjs +61 -19
  168. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  169. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  170. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  171. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  172. package/src/tui/markdown/table-layout.mjs +9 -9
  173. package/src/tui/paste-attachments.mjs +0 -11
  174. package/src/tui/prompt-history-store.mjs +129 -0
  175. package/src/tui/prompt-history-store.test.mjs +52 -0
  176. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  177. package/src/tui/theme.mjs +41 -657
  178. package/src/tui/themes/base.mjs +86 -0
  179. package/src/tui/themes/basic.mjs +85 -0
  180. package/src/tui/themes/catppuccin.mjs +72 -0
  181. package/src/tui/themes/dracula.mjs +70 -0
  182. package/src/tui/themes/everforest.mjs +71 -0
  183. package/src/tui/themes/gruvbox.mjs +71 -0
  184. package/src/tui/themes/index.mjs +71 -0
  185. package/src/tui/themes/indigo.mjs +78 -0
  186. package/src/tui/themes/kanagawa.mjs +80 -0
  187. package/src/tui/themes/light.mjs +81 -0
  188. package/src/tui/themes/nord.mjs +72 -0
  189. package/src/tui/themes/onedark.mjs +16 -0
  190. package/src/tui/themes/rosepine.mjs +70 -0
  191. package/src/tui/themes/teal.mjs +81 -0
  192. package/src/tui/themes/tokyonight.mjs +79 -0
  193. package/src/tui/themes/utils.mjs +106 -0
  194. package/src/tui/themes/warm.mjs +79 -0
  195. package/src/tui/transcript-tool-failures.mjs +13 -2
  196. package/src/ui/markdown.mjs +1 -1
  197. package/src/ui/model-display.mjs +2 -2
  198. package/src/ui/statusline.mjs +26 -27
  199. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  200. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  201. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  202. package/src/workflows/default/WORKFLOW.md +39 -12
  203. package/src/workflows/sequential/WORKFLOW.md +46 -0
  204. package/src/workflows/solo/WORKFLOW.md +7 -0
  205. package/vendor/ink/build/display-width.js +62 -0
  206. package/vendor/ink/build/ink.js +100 -12
  207. package/vendor/ink/build/measure-text.js +4 -1
  208. package/vendor/ink/build/output.js +115 -9
  209. package/vendor/ink/build/render-node-to-output.js +4 -1
  210. package/vendor/ink/build/render.js +4 -0
  211. package/src/output-styles/extreme-simple.md +0 -20
  212. package/src/rules/lead/04-workflow.md +0 -51
  213. package/src/workflows/default/workflow.json +0 -13
  214. 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