mixdog 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,471 @@
1
+ // Session compaction runner — recall-fasttrack / semantic / prune-fallback.
2
+ // Extracted verbatim from manager.mjs (behavior-preserving). Self-contained:
3
+ // operates on a live `session` object + opts, using pure compact/context
4
+ // helpers. No runtime-liveness (_runtimeState) coupling — manager.mjs still
5
+ // owns scheduling / stage gating and simply calls runSessionCompaction().
6
+ import { createHash } from 'crypto';
7
+ import { getProvider } from '../../providers/registry.mjs';
8
+ import {
9
+ recallFastTrackCompactMessages,
10
+ semanticCompactMessages,
11
+ pruneToolOutputsUnanchored,
12
+ effectiveBudget as compactEffectiveBudget,
13
+ compactTypeIsRecallFastTrack,
14
+ compactTypeIsSemantic,
15
+ drainSessionCycle1,
16
+ } from '../compact.mjs';
17
+ import { estimateMessagesTokens, estimateRequestReserveTokens, estimateTranscriptContextUsage, resolveCompactBufferRatio } from '../context-utils.mjs';
18
+ import { executeInternalTool } from '../../internal-tools.mjs';
19
+ import {
20
+ positiveContextWindow,
21
+ compactTriggerForSession,
22
+ compactTargetBudget,
23
+ semanticCompactionEnabledForSession,
24
+ compactTypeForSession,
25
+ } from './context-meta.mjs';
26
+ import { uncachedInputTokensForProvider } from './usage-metrics.mjs';
27
+
28
+ // 'compacting' is a transient in-flight stage written just before semantic /
29
+ // recall-fasttrack compaction runs. If the process crashes or only partially
30
+ // saves while it is set, a later load/resume reads a session that is NOT
31
+ // actually compacting but whose UI marker (App.jsx / ContextPanel) shows
32
+ // "Compacting conversation" permanently. Normalize that stale transient stage
33
+ // to 'interrupted' so the surface recovers. Terminal stages (post_turn /
34
+ // manual / auto_clear / *_failed / overflow_failed) are intentionally left as
35
+ // the durable record of the last real outcome.
36
+ export function normalizeStaleCompactingStage(session) {
37
+ const c = session?.compaction;
38
+ if (!c || typeof c !== 'object') return false;
39
+ if (c.lastStage !== 'compacting' && c.inProgress !== true) return false;
40
+ c.lastStage = 'interrupted';
41
+ c.inProgress = false;
42
+ c.lastCheckedAt = Date.now();
43
+ return true;
44
+ }
45
+ function addCompactUsageToSession(session, usage) {
46
+ if (!session || !usage) return;
47
+ const inputTokens = usage.inputTokens || 0;
48
+ const outputTokens = usage.outputTokens || 0;
49
+ const cachedTokens = usage.cachedTokens || 0;
50
+ const cacheWriteTokens = usage.cacheWriteTokens || 0;
51
+ const uncachedInputTokens = uncachedInputTokensForProvider(session.provider, inputTokens, cachedTokens, cacheWriteTokens);
52
+ session.totalInputTokens = (session.totalInputTokens || 0) + inputTokens;
53
+ session.totalOutputTokens = (session.totalOutputTokens || 0) + outputTokens;
54
+ session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + cachedTokens;
55
+ session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + cacheWriteTokens;
56
+ session.totalUncachedInputTokens = (session.totalUncachedInputTokens || 0) + uncachedInputTokens;
57
+ session.tokensCumulative = (session.tokensCumulative || 0) + inputTokens + outputTokens;
58
+ }
59
+ async function runRecallFastTrackForSession(session, messages, opts = {}) {
60
+ const sessionId = opts.sessionId || session?.id || null;
61
+ if (!sessionId) throw new Error('recall-fasttrack requires a session id');
62
+ const query = `session:${sessionId}:all-chunks`;
63
+ const querySha = createHash('sha256').update(query).digest('hex').slice(0, 16);
64
+ const callerCtx = {
65
+ callerSessionId: sessionId,
66
+ callerCwd: session?.cwd || undefined,
67
+ routingSessionId: sessionId,
68
+ clientHostPid: session?.clientHostPid,
69
+ signal: opts.signal || null,
70
+ };
71
+ const hydrateLimit = positiveContextWindow(session?.compaction?.recallIngestLimit)
72
+ || Math.max(500, Math.min(5000, messages.length || 0));
73
+ try {
74
+ await executeInternalTool('memory', {
75
+ action: 'ingest_session',
76
+ sessionId,
77
+ messages,
78
+ cwd: session?.cwd,
79
+ limit: hydrateLimit,
80
+ }, callerCtx);
81
+ } catch (err) {
82
+ try { process.stderr.write(`[session] recall-fasttrack ingest skipped (sess=${sessionId}): ${err?.message || err}\n`); } catch {}
83
+ }
84
+ const dumpArgs = {
85
+ action: 'dump_session_roots',
86
+ sessionId,
87
+ includeRaw: true,
88
+ limit: positiveContextWindow(session?.compaction?.recallChunkLimit ?? session?.compaction?.recallLimit) || hydrateLimit,
89
+ };
90
+ const runTool = (name, args) => executeInternalTool(name, args, callerCtx);
91
+ let recallText = await executeInternalTool('memory', dumpArgs, callerCtx);
92
+ let cycle1Text = '';
93
+ let cycle1Error = null;
94
+ const hasRawRows = /(?:^|\n)# raw_pending\s+\d+\s+id=/i.test(String(recallText || ''));
95
+ if (hasRawRows) {
96
+ try {
97
+ // Drain this session's cycle1 in window×concurrency units until no
98
+ // raw rows remain, so the injected root is fully chunked rather than
99
+ // carrying the unprocessed transcript tail (single-pass left raw in).
100
+ const drained = await drainSessionCycle1(runTool, {
101
+ sessionId,
102
+ dumpArgs,
103
+ deadlineMs: positiveContextWindow(session?.compaction?.recallCycle1DeadlineMs) || 120_000,
104
+ maxPasses: positiveContextWindow(session?.compaction?.recallCycle1MaxPasses) || 0,
105
+ cycleArgs: {
106
+ min_batch: 1,
107
+ session_cap: 1,
108
+ batch_size: positiveContextWindow(session?.compaction?.recallCycle1BatchSize) || 100,
109
+ rows_per_session: positiveContextWindow(session?.compaction?.recallRowsPerSession) || 100,
110
+ window_size: positiveContextWindow(session?.compaction?.recallWindowSize) || 20,
111
+ concurrency: positiveContextWindow(session?.compaction?.recallConcurrency) || 5,
112
+ },
113
+ });
114
+ recallText = drained.recallText;
115
+ cycle1Text = drained.cycle1Text;
116
+ if (drained.error) {
117
+ cycle1Error = drained.error;
118
+ try { process.stderr.write(`[session] recall-fasttrack cycle1 error (sess=${sessionId}): ${drained.error}\n`); } catch {}
119
+ }
120
+ if (drained.rawRemaining > 0) {
121
+ try { process.stderr.write(`[session] recall-fasttrack drained passes=${drained.passes} rawRemaining=${drained.rawRemaining} (sess=${sessionId})\n`); } catch {}
122
+ }
123
+ } catch (err) {
124
+ try { process.stderr.write(`[session] recall-fasttrack cycle1 skipped (sess=${sessionId}): ${err?.message || err}\n`); } catch {}
125
+ }
126
+ } else {
127
+ cycle1Text = 'cycle1: skipped (session chunks already hydrated)';
128
+ }
129
+ return { query, querySha, cycle1Error, recallText: [`session_id=${sessionId}`, cycle1Text, recallText].map(v => String(v || '').trim()).filter(Boolean).join('\n\n') };
130
+ }
131
+ // Element-identity change detection (same approach as loop.mjs messagesArrayChanged): two
132
+ // arrays are "unchanged" only when same length AND every slot is the same object
133
+ // reference. Used to reject a no-op prune (which returns a fresh array whose
134
+ // elements are the untouched originals) from being accepted as a recovery.
135
+ function messagesChanged(before, after) {
136
+ if (!Array.isArray(before) || !Array.isArray(after)) return before !== after;
137
+ if (before.length !== after.length) return true;
138
+ for (let i = 0; i < before.length; i += 1) {
139
+ if (before[i] !== after[i]) return true;
140
+ }
141
+ return false;
142
+ }
143
+ export async function runSessionCompaction(session, opts = {}) {
144
+ if (!session || session.closed === true) return null;
145
+ const mode = opts.mode === 'auto' ? 'auto' : 'manual';
146
+ const force = opts.force === true || mode === 'manual';
147
+ if (mode === 'auto' && session.compaction?.auto === false) return null;
148
+ const messages = Array.isArray(session.messages) ? session.messages : [];
149
+ if (messages.length < 3 && !force) return null;
150
+ const boundary = positiveContextWindow(session.compactBoundaryTokens)
151
+ || positiveContextWindow(session.autoCompactTokenLimit)
152
+ || positiveContextWindow(session.contextWindow);
153
+ if (!boundary) {
154
+ if (force) throw new Error('compact: no context window is available for this session');
155
+ return null;
156
+ }
157
+ // Reserve must mirror loop.mjs (buildCompactPolicy): request reserve (tool
158
+ // schema) PLUS the configured reserve (session.compaction.reservedTokens or
159
+ // MIXDOG_AGENT_COMPACT_RESERVED_TOKENS env). The old request-only value left
160
+ // the manual / auto-clear compact budget without the configured headroom the
161
+ // loop path reserves, so a compacted transcript could overflow on next send.
162
+ const requestReserveTokens = estimateRequestReserveTokens(session.tools || []);
163
+ const configuredReserveTokens = positiveContextWindow(session.compaction?.reservedTokens)
164
+ || positiveContextWindow(process.env.MIXDOG_AGENT_COMPACT_RESERVED_TOKENS)
165
+ || 0;
166
+ const reserveTokens = requestReserveTokens + configuredReserveTokens;
167
+ const beforeMessageTokens = estimateMessagesTokens(messages);
168
+ const triggerTokens = compactTriggerForSession(session, boundary)
169
+ || positiveContextWindow(session.compaction?.triggerTokens)
170
+ || boundary;
171
+ const bufferTokens = Math.max(0, boundary - triggerTokens);
172
+ const bufferRatio = boundary ? (bufferTokens / boundary) : resolveCompactBufferRatio(session.compaction || {});
173
+ const pressureTokens = estimateTranscriptContextUsage(messages, session.tools || []);
174
+ const beforeTokens = pressureTokens;
175
+ const compactType = compactTypeForSession(session);
176
+ if (!force && pressureTokens < triggerTokens) return {
177
+ changed: false,
178
+ reason: 'below threshold',
179
+ compactType,
180
+ beforeMessages: messages.length,
181
+ afterMessages: messages.length,
182
+ beforeTokens,
183
+ afterTokens: beforeTokens,
184
+ beforeMessageTokens,
185
+ afterMessageTokens: beforeMessageTokens,
186
+ pressureTokens,
187
+ triggerTokens,
188
+ bufferTokens,
189
+ bufferRatio,
190
+ boundaryTokens: boundary,
191
+ budgetTokens: boundary,
192
+ targetBudgetTokens: boundary,
193
+ reserveTokens,
194
+ semanticCompact: false,
195
+ };
196
+ const budgetSourceTokens = force ? Math.max(pressureTokens, triggerTokens) : pressureTokens;
197
+ const compactBudget = compactTargetBudget(boundary, reserveTokens, budgetSourceTokens);
198
+ const budget = compactBudget || boundary;
199
+ try { await opts.onStageChange?.('compacting'); } catch { /* best-effort */ }
200
+ const provider = opts.provider || getProvider(session.provider) || null;
201
+ let compacted;
202
+ let compactError = null;
203
+ let semanticCompactResult = null;
204
+ let semanticCompactError = null;
205
+ let recallFastTrackResult = null;
206
+ let recallFastTrackError = null;
207
+ if (compactTypeIsRecallFastTrack(compactType)) {
208
+ try {
209
+ const recallPayload = await runRecallFastTrackForSession(session, messages, opts);
210
+ recallFastTrackResult = recallFastTrackCompactMessages(messages, budget, {
211
+ reserveTokens,
212
+ force: true,
213
+ recallText: recallPayload.recallText,
214
+ query: recallPayload.query,
215
+ querySha: recallPayload.querySha,
216
+ // Ingest just ran on the live transcript, so an empty recall dump
217
+ // means the memory pipeline is broken — do NOT erase history
218
+ // behind an empty summary shell. Empty recall now throws and is
219
+ // handled by the semantic fallback below (or recorded failure).
220
+ allowEmptyRecall: false,
221
+ tailTurns: positiveContextWindow(session.compaction?.tailTurns) || 2,
222
+ keepTokens: positiveContextWindow(session.compaction?.keepTokens ?? session.compaction?.keep?.tokens),
223
+ preserveRecentTokens: positiveContextWindow(session.compaction?.preserveRecentTokens),
224
+ });
225
+ if (Array.isArray(recallFastTrackResult?.messages)) {
226
+ compacted = recallFastTrackResult.messages;
227
+ }
228
+ if (recallPayload.cycle1Error) {
229
+ recallFastTrackError = new Error(String(recallPayload.cycle1Error));
230
+ }
231
+ } catch (err) {
232
+ recallFastTrackError = err;
233
+ compactError = err;
234
+ try {
235
+ process.stderr.write(`[session] recall-fasttrack ${mode} compact failed (sess=${session.id || 'unknown'}): ${err?.message || err}\n`);
236
+ } catch { /* best-effort */ }
237
+ // Degraded-compact fallback: recall-fasttrack failed (empty recall,
238
+ // ingest error, fit failure). Before recording a hard failure, try
239
+ // the semantic path once so auto-clear/manual compaction still makes
240
+ // progress WITHOUT shipping an empty-recall summary. History is only
241
+ // replaced when the semantic summary actually succeeds.
242
+ if (semanticCompactionEnabledForSession(session)
243
+ && provider && typeof provider.send === 'function') {
244
+ try {
245
+ semanticCompactResult = await semanticCompactMessages(
246
+ provider,
247
+ messages,
248
+ opts.model || session.model,
249
+ budget,
250
+ {
251
+ reserveTokens,
252
+ providerName: session.provider || provider?.name || null,
253
+ sessionId: opts.sessionId || session.id || null,
254
+ signal: opts.signal || null,
255
+ promptCacheKey: session.promptCacheKey || null,
256
+ providerCacheKey: session.promptCacheKey || null,
257
+ timeoutMs: positiveContextWindow(session.compaction?.timeoutMs) || 30_000,
258
+ tailTurns: positiveContextWindow(session.compaction?.tailTurns) || 2,
259
+ keepTokens: positiveContextWindow(session.compaction?.keepTokens ?? session.compaction?.keep?.tokens),
260
+ preserveRecentTokens: positiveContextWindow(session.compaction?.preserveRecentTokens),
261
+ force: true,
262
+ },
263
+ );
264
+ if (Array.isArray(semanticCompactResult?.messages)) {
265
+ compacted = semanticCompactResult.messages;
266
+ compactError = null;
267
+ addCompactUsageToSession(session, semanticCompactResult.usage);
268
+ try {
269
+ process.stderr.write(`[session] degraded compact: recall-fasttrack failed, semantic fallback succeeded (sess=${session.id || 'unknown'}, mode=${mode})\n`);
270
+ } catch { /* best-effort */ }
271
+ }
272
+ } catch (fallbackErr) {
273
+ semanticCompactError = fallbackErr;
274
+ try {
275
+ process.stderr.write(`[session] degraded compact: semantic fallback also failed (sess=${session.id || 'unknown'}): ${fallbackErr?.message || fallbackErr}\n`);
276
+ } catch { /* best-effort */ }
277
+ }
278
+ }
279
+ }
280
+ } else if (compactTypeIsSemantic(compactType)) {
281
+ try {
282
+ if (!semanticCompactionEnabledForSession(session)) {
283
+ throw new Error('semantic compact is disabled for this session');
284
+ }
285
+ if (!provider || typeof provider.send !== 'function') {
286
+ throw new Error(`semantic compact provider unavailable: ${session.provider || 'unknown'}`);
287
+ }
288
+ semanticCompactResult = await semanticCompactMessages(
289
+ provider,
290
+ messages,
291
+ opts.model || session.model,
292
+ budget,
293
+ {
294
+ reserveTokens,
295
+ providerName: session.provider || provider?.name || null,
296
+ sessionId: opts.sessionId || session.id || null,
297
+ signal: opts.signal || null,
298
+ promptCacheKey: session.promptCacheKey || null,
299
+ providerCacheKey: session.promptCacheKey || null,
300
+ timeoutMs: positiveContextWindow(session.compaction?.timeoutMs) || 30_000,
301
+ tailTurns: positiveContextWindow(session.compaction?.tailTurns) || 2,
302
+ keepTokens: positiveContextWindow(session.compaction?.keepTokens ?? session.compaction?.keep?.tokens),
303
+ preserveRecentTokens: positiveContextWindow(session.compaction?.preserveRecentTokens),
304
+ force: true,
305
+ },
306
+ );
307
+ if (Array.isArray(semanticCompactResult?.messages)) {
308
+ compacted = semanticCompactResult.messages;
309
+ addCompactUsageToSession(session, semanticCompactResult.usage);
310
+ }
311
+ } catch (err) {
312
+ semanticCompactError = err;
313
+ compactError = err;
314
+ try {
315
+ process.stderr.write(`[session] semantic ${mode} compact failed (sess=${session.id || 'unknown'}): ${err?.message || err}\n`);
316
+ } catch { /* best-effort */ }
317
+ }
318
+ }
319
+ if (!compacted && !compactError) {
320
+ compactError = new Error(`${compactType} compact produced no messages`);
321
+ }
322
+ // Anchor-independent prune safety net (mirror loop.mjs compact catch): when a
323
+ // non-recall (semantic) compact failed, try one non-LLM prune that needs no
324
+ // user anchor before recording failure, so Lead manual / auto-clear paths
325
+ // recover the same transcripts the loop path does. Gated off the recall
326
+ // path — a recall failure keeps its original contract (no silent prune).
327
+ if (!compacted && !recallFastTrackError) {
328
+ try {
329
+ const acceptThreshold = compactEffectiveBudget(budget, { reserveTokens });
330
+ const salvaged = pruneToolOutputsUnanchored(messages, budget, { reserveTokens });
331
+ // pruneToolOutputsUnanchored ALWAYS returns a fresh reconciled array
332
+ // (never the input identity), so `salvaged !== messages` is always
333
+ // true and cannot detect a no-op. Compare by element identity so a
334
+ // transcript that already fit (nothing pruned) is NOT falsely accepted
335
+ // as a recovery — that would clear compactError and unconditionally
336
+ // invalidate providerState for an unchanged transcript.
337
+ if (Array.isArray(salvaged)
338
+ && messagesChanged(messages, salvaged)
339
+ && estimateMessagesTokens(salvaged) <= acceptThreshold) {
340
+ compacted = salvaged;
341
+ compactError = null;
342
+ try {
343
+ process.stderr.write(`[session] compact fallback prune recovered (sess=${session.id || 'unknown'}, mode=${mode})\n`);
344
+ } catch { /* best-effort */ }
345
+ }
346
+ } catch { /* fall through to failure record */ }
347
+ }
348
+ if (!compacted) {
349
+ const now = Date.now();
350
+ session.compaction = {
351
+ ...(session.compaction || {}),
352
+ auto: mode === 'auto' ? true : session.compaction?.auto !== false,
353
+ boundaryTokens: boundary,
354
+ triggerTokens,
355
+ bufferTokens,
356
+ bufferRatio,
357
+ reserveTokens,
358
+ lastStage: mode === 'auto' ? 'post_turn_failed' : 'manual_failed',
359
+ lastBeforeTokens: beforeTokens,
360
+ lastAfterTokens: beforeTokens,
361
+ lastBeforeMessageTokens: beforeMessageTokens,
362
+ lastAfterMessageTokens: beforeMessageTokens,
363
+ lastPressureTokens: pressureTokens,
364
+ lastCheckedAt: now,
365
+ lastChanged: false,
366
+ type: compactType,
367
+ compactType,
368
+ lastCompactType: compactType,
369
+ lastSemantic: false,
370
+ lastSemanticError: semanticCompactError?.message || null,
371
+ lastRecallFastTrack: false,
372
+ lastRecallFastTrackError: recallFastTrackError?.message || null,
373
+ lastError: compactError?.message || semanticCompactError?.message || recallFastTrackError?.message || String(compactError || semanticCompactError || recallFastTrackError || 'compact failed'),
374
+ };
375
+ return {
376
+ changed: false,
377
+ error: session.compaction.lastError,
378
+ compactType,
379
+ beforeMessages: messages.length,
380
+ afterMessages: messages.length,
381
+ beforeTokens,
382
+ afterTokens: beforeTokens,
383
+ beforeMessageTokens,
384
+ afterMessageTokens: beforeMessageTokens,
385
+ pressureTokens,
386
+ triggerTokens,
387
+ bufferTokens,
388
+ bufferRatio,
389
+ boundaryTokens: boundary,
390
+ budgetTokens: boundary,
391
+ targetBudgetTokens: budget,
392
+ reserveTokens,
393
+ semanticCompact: false,
394
+ semanticError: semanticCompactError?.message || null,
395
+ recallFastTrack: false,
396
+ recallFastTrackError: recallFastTrackError?.message || null,
397
+ };
398
+ }
399
+ let beforeEncoded = '';
400
+ let afterEncoded = '';
401
+ try { beforeEncoded = JSON.stringify(messages); } catch { beforeEncoded = ''; }
402
+ try { afterEncoded = JSON.stringify(compacted); } catch { afterEncoded = ''; }
403
+ const afterMessageTokens = estimateMessagesTokens(compacted);
404
+ const afterTokens = afterMessageTokens + reserveTokens;
405
+ const changed = beforeEncoded && afterEncoded
406
+ ? beforeEncoded !== afterEncoded
407
+ : (compacted.length !== messages.length || afterMessageTokens !== beforeMessageTokens);
408
+ const unchangedReason = changed ? null : (force ? 'nothing to compact' : 'below threshold');
409
+ const now = Date.now();
410
+ session.messages = compacted;
411
+ session.providerState = undefined;
412
+ session.compaction = {
413
+ ...(session.compaction || {}),
414
+ auto: mode === 'auto' ? true : session.compaction?.auto !== false,
415
+ boundaryTokens: boundary,
416
+ triggerTokens,
417
+ bufferTokens,
418
+ bufferRatio,
419
+ reserveTokens,
420
+ type: compactType,
421
+ compactType,
422
+ lastCompactType: compactType,
423
+ lastStage: mode === 'auto' ? 'post_turn' : 'manual',
424
+ lastBeforeTokens: beforeTokens,
425
+ lastAfterTokens: afterTokens,
426
+ lastBeforeMessageTokens: beforeMessageTokens,
427
+ lastAfterMessageTokens: afterMessageTokens,
428
+ lastPressureTokens: pressureTokens,
429
+ lastCheckedAt: now,
430
+ lastChanged: changed,
431
+ lastChangedAt: changed ? now : session.compaction?.lastChangedAt || null,
432
+ lastCompactAt: changed ? now : session.compaction?.lastCompactAt || null,
433
+ lastSemantic: semanticCompactResult?.semantic === true,
434
+ lastSemanticError: semanticCompactError?.message || null,
435
+ lastRecallFastTrack: recallFastTrackResult?.recallFastTrack === true,
436
+ lastRecallFastTrackError: recallFastTrackError?.message || null,
437
+ lastRecallFastTrackQuerySha: recallFastTrackResult?.query ? createHash('sha256').update(recallFastTrackResult.query).digest('hex').slice(0, 16) : null,
438
+ lastSemanticUsage: semanticCompactResult?.usage ? {
439
+ inputTokens: semanticCompactResult.usage.inputTokens || 0,
440
+ outputTokens: semanticCompactResult.usage.outputTokens || 0,
441
+ cachedTokens: semanticCompactResult.usage.cachedTokens || 0,
442
+ cacheWriteTokens: semanticCompactResult.usage.cacheWriteTokens || 0,
443
+ } : null,
444
+ compactCount: (session.compaction?.compactCount || 0) + (changed ? 1 : 0),
445
+ };
446
+ if (changed && mode === 'auto') session.lastContextTokensStaleAfterCompact = true;
447
+ return {
448
+ changed,
449
+ reason: unchangedReason,
450
+ compactType,
451
+ beforeMessages: messages.length,
452
+ afterMessages: compacted.length,
453
+ beforeTokens,
454
+ afterTokens,
455
+ beforeMessageTokens,
456
+ afterMessageTokens,
457
+ pressureTokens,
458
+ triggerTokens,
459
+ bufferTokens,
460
+ bufferRatio,
461
+ boundaryTokens: boundary,
462
+ budgetTokens: boundary,
463
+ targetBudgetTokens: budget,
464
+ reserveTokens,
465
+ semanticCompact: semanticCompactResult?.semantic === true,
466
+ semanticError: semanticCompactError?.message || null,
467
+ recallFastTrack: recallFastTrackResult?.recallFastTrack === true,
468
+ recallFastTrackError: recallFastTrackError?.message || null,
469
+ usage: semanticCompactResult?.usage || null,
470
+ };
471
+ }
@@ -35,14 +35,17 @@ const CONTEXT_WINDOWS = {
35
35
  // runtime agree on the boundary the first time a model is routed. Local models
36
36
  // (llama/mistral/phi/qwen/gemma) stay small so an unknown local id never claims
37
37
  // a giant window.
38
- export function guessContextWindow(model) {
38
+ const LOCAL_PROVIDERS = new Set(['ollama', 'lmstudio', 'llamacpp', 'llama.cpp', 'local', '']);
39
+ export function guessContextWindow(model, provider = null) {
39
40
  if (CONTEXT_WINDOWS[model])
40
41
  return CONTEXT_WINDOWS[model];
41
42
  const m = String(model || '').toLowerCase();
43
+ const p = String(provider || '').toLowerCase();
44
+ const isLocalProvider = LOCAL_PROVIDERS.has(p);
42
45
  // Local/self-hosted families — never inflate an unknown local id.
43
- if (m.includes('llama') || m.includes('mistral') || m.includes('mixtral')
46
+ if (isLocalProvider && (m.includes('llama') || m.includes('mistral') || m.includes('mixtral')
44
47
  || m.includes('phi') || m.includes('qwen') || m.includes('gemma')
45
- || m.includes('deepseek-r1') || m.includes('codellama'))
48
+ || m.includes('deepseek-r1') || m.includes('codellama')))
46
49
  return 8192;
47
50
  // Current hosted families by name pattern.
48
51
  if (m.startsWith('claude-opus') || m.startsWith('claude-sonnet')) return 1000000;
@@ -148,7 +151,7 @@ export function resolveSessionContextMeta(provider, model, seed = {}) {
148
151
  || positiveContextWindow(seed.rawContextWindow)
149
152
  || positiveContextWindow(seed.raw_context_window)
150
153
  || positiveContextWindow(seed.contextWindow)
151
- || guessContextWindow(model);
154
+ || guessContextWindow(model, providerNameOf(provider));
152
155
  const effectiveContextWindowPercent = boundedPercent(
153
156
  seed.effectiveContextWindowPercent
154
157
  ?? seed.effective_context_window_percent
@@ -1,6 +1,7 @@
1
1
  // Prompt content + temporal helpers, extracted verbatim from manager.mjs
2
2
  // (behavior-preserving). Pure string/date utilities with no session state.
3
3
  import { isInternalRuntimeNotificationText as contractIsInternalRuntimeNotificationText } from '../../../../shared/tool-execution-contract.mjs';
4
+ import { SUMMARY_PREFIX } from '../compact.mjs';
4
5
 
5
6
  export function promptContentText(content) {
6
7
  if (typeof content === 'string') return content;
@@ -124,11 +125,22 @@ export function isProtectedContextUserMessage(message) {
124
125
  && message.content.trimStart().startsWith('<system-reminder>');
125
126
  }
126
127
 
128
+ // Compact summary messages (role:'user', content startsWith SUMMARY_PREFIX)
129
+ // are synthetic anchors, not a real human turn — they must not count as
130
+ // "user conversation" or the post-clear/post-compact session-start block
131
+ // would be wrongly suppressed on the next real user turn.
132
+ function isSummaryAnchorMessage(message) {
133
+ return message?.role === 'user'
134
+ && typeof message.content === 'string'
135
+ && message.content.startsWith(SUMMARY_PREFIX);
136
+ }
137
+
127
138
  export function hasUserConversationMessage(messages) {
128
139
  return (Array.isArray(messages) ? messages : []).some((message) => (
129
140
  message?.role === 'user'
130
141
  && !isProtectedContextUserMessage(message)
131
142
  && !isReferenceFilesMessage(message)
143
+ && !isSummaryAnchorMessage(message)
132
144
  ));
133
145
  }
134
146