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,643 @@
1
+ // Summarization system prompt, schema validation/repair, semantic + recall
2
+ // fast-track summary-message construction and fitting, root-block fitting, and
3
+ // the recall query builder. Extracted verbatim from compact.mjs
4
+ // (behavior-preserving).
5
+ import { estimateMessagesTokens } from '../context-utils.mjs';
6
+ import {
7
+ SUMMARY_PREFIX,
8
+ COMPACT_TYPE_SEMANTIC,
9
+ COMPACT_TYPE_RECALL_FASTTRACK,
10
+ } from './constants.mjs';
11
+ import {
12
+ extractText,
13
+ truncateMiddle,
14
+ toolCallSummary,
15
+ toolCallArgBudget,
16
+ toolResultId,
17
+ } from './text-utils.mjs';
18
+ import {
19
+ compactHeader,
20
+ makeSummaryMessage,
21
+ isProtectedContextUserMessage,
22
+ isInjectedSkillBodyMessage,
23
+ } from './messages.mjs';
24
+ import {
25
+ summaryIsSchemaValid,
26
+ summaryHasUnrecognizedHeadings,
27
+ repairSemanticSummary,
28
+ minimalSchemaSummary,
29
+ truncateSummaryBySections,
30
+ } from './summary-schema.mjs';
31
+
32
+ export { repairSemanticSummary } from './summary-schema.mjs';
33
+
34
+ const COMPACTION_INPUT_MAX_CHARS = 2_000;
35
+
36
+ export const COMPACTION_SYSTEM_PROMPT = [
37
+ 'You are an anchored context summarization assistant for coding sessions.',
38
+ '',
39
+ 'Summarize only the conversation history you are given. The newest turns may be kept verbatim outside your summary, so focus on the older context that still matters for continuing the work.',
40
+ '',
41
+ 'If the prompt includes a <previous-summary> block, treat it as the current anchored summary. Update it with the new history by preserving still-true details, removing stale details, and merging in new facts.',
42
+ '',
43
+ 'Always follow the exact output structure requested by the user prompt. Keep every section, preserve exact file paths and identifiers when known, and prefer terse bullets over paragraphs.',
44
+ '',
45
+ 'Do not answer the conversation itself. Do not mention that you are summarizing, compacting, or merging context. Respond in the same language as the conversation.',
46
+ ].join('\n');
47
+ const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
48
+ <template>
49
+ ## Goal
50
+ - [single-sentence task summary]
51
+
52
+ ## Constraints & Preferences
53
+ - [user constraints, preferences, specs, or "(none)"]
54
+
55
+ ## Progress
56
+ ### Done
57
+ - [completed work or "(none)"]
58
+
59
+ ### In Progress
60
+ - [current work or "(none)"]
61
+
62
+ ### Blocked
63
+ - [blockers or "(none)"]
64
+
65
+ ## Key Decisions
66
+ - [decision and why, or "(none)"]
67
+
68
+ ## Next Steps
69
+ - [ordered next actions or "(none)"]
70
+
71
+ ## Critical Context
72
+ - [important technical facts, errors, open questions, or "(none)"]
73
+
74
+ ## Relevant Files
75
+ - [file or directory path: why it matters, or "(none)"]
76
+ </template>
77
+
78
+ Rules:
79
+ - Keep every section, even when empty.
80
+ - Use terse bullets, not prose paragraphs.
81
+ - Preserve exact file paths, commands, error strings, and identifiers when known.
82
+ - Use the same language as the active user thread when it is clear.
83
+ - Do not mention the summary process or that context was compacted.`;
84
+
85
+ function transcriptLineForCompaction(m, index, perMessageChars) {
86
+ const role = m?.role || 'unknown';
87
+ const text = truncateMiddle(extractText(m).trim(), perMessageChars);
88
+ const meta = `${toolCallSummary(m, toolCallArgBudget(perMessageChars))}${toolResultId(m)}`;
89
+ if (!text) return `${index + 1}. ${role}${meta}`;
90
+ return `${index + 1}. ${role}${meta}:\n${text}`;
91
+ }
92
+
93
+ export function buildCompactionPrompt({ head, previousSummary, preservedFacts }, perMessageChars) {
94
+ const lines = [
95
+ previousSummary
96
+ ? 'Update the anchored summary below using the conversation history that follows. Preserve still-true details, remove stale details, and merge in the new facts.'
97
+ : 'Create a new anchored summary from the conversation history below.',
98
+ SUMMARY_TEMPLATE,
99
+ ];
100
+ if (previousSummary) {
101
+ lines.push('', '<previous-summary>', previousSummary, '</previous-summary>');
102
+ }
103
+ if (preservedFacts) {
104
+ lines.push('', '<preserved-facts>', preservedFacts, '</preserved-facts>');
105
+ }
106
+ lines.push('', '<conversation-history>');
107
+ if (head.length === 0) {
108
+ lines.push('[No additional older messages before the preserved recent tail.]');
109
+ } else {
110
+ for (let i = 0; i < head.length; i += 1) {
111
+ lines.push(transcriptLineForCompaction(head[i], i, perMessageChars));
112
+ }
113
+ }
114
+ lines.push('</conversation-history>');
115
+ return lines.join('\n');
116
+ }
117
+
118
+ function estimateCompactionPromptTokens(input, perMessageChars) {
119
+ const prompt = buildCompactionPrompt(input, perMessageChars);
120
+ return estimateMessagesTokens([
121
+ { role: 'system', content: COMPACTION_SYSTEM_PROMPT },
122
+ { role: 'user', content: prompt },
123
+ ]);
124
+ }
125
+
126
+ function previousSummaryBodyForCompactionPrompt(previousSummary) {
127
+ const text = String(previousSummary || '').trim();
128
+ if (!text) return '';
129
+ return stripNestedSummaryHeaderLines(text);
130
+ }
131
+
132
+ function priorSummaryNeedsNormalization(text) {
133
+ const body = String(text || '').trim();
134
+ if (!body) return false;
135
+ if (!/^##\s+/m.test(body)) return true;
136
+ if (!summaryIsSchemaValid(body)) return true;
137
+ return summaryHasUnrecognizedHeadings(body);
138
+ }
139
+
140
+ function normalizePriorSummaryForCompactionPrompt(fullBody) {
141
+ const text = String(fullBody || '').trim();
142
+ if (!text) return '';
143
+ if (!priorSummaryNeedsNormalization(text)) return text;
144
+ return repairSemanticSummary(text, { head: [], tail: [] });
145
+ }
146
+
147
+ // Shrink or drop a prior anchored summary so the compaction provider prompt fits
148
+ // the call budget. Unstructured/legacy priors are repaired first; section
149
+ // anchors are preserved via truncateSummaryBySections;
150
+ // the last resort is omitting <previous-summary> entirely.
151
+ function fitPreviousSummaryForCompactionPrompt(input, perMessageChars, targetTokens) {
152
+ if (!input?.previousSummary) return input;
153
+ const fullBody = normalizePriorSummaryForCompactionPrompt(
154
+ previousSummaryBodyForCompactionPrompt(input.previousSummary),
155
+ );
156
+ const withSummary = (summaryText) => {
157
+ const trimmed = String(summaryText || '').trim();
158
+ if (!trimmed) return { ...input, previousSummary: null };
159
+ return { ...input, previousSummary: trimmed };
160
+ };
161
+
162
+ if (estimateCompactionPromptTokens(withSummary(fullBody), perMessageChars) <= targetTokens) {
163
+ return withSummary(fullBody);
164
+ }
165
+
166
+ if (fullBody) {
167
+ let lo = 0;
168
+ let hi = fullBody.length;
169
+ let bestChars = -1;
170
+ while (lo <= hi) {
171
+ const mid = Math.floor((lo + hi) / 2);
172
+ const truncated = truncateSummaryBySections(fullBody, mid);
173
+ const candidate = withSummary(truncated);
174
+ if (estimateCompactionPromptTokens(candidate, perMessageChars) <= targetTokens) {
175
+ bestChars = mid;
176
+ lo = mid + 1;
177
+ } else {
178
+ hi = mid - 1;
179
+ }
180
+ }
181
+ if (bestChars >= 0) {
182
+ return withSummary(truncateSummaryBySections(fullBody, bestChars));
183
+ }
184
+ }
185
+
186
+ const minimalPrior = minimalSchemaSummary();
187
+ if (estimateCompactionPromptTokens(withSummary(minimalPrior), perMessageChars) <= targetTokens) {
188
+ return withSummary(minimalPrior);
189
+ }
190
+
191
+ const withoutPrior = withSummary(null);
192
+ if (estimateCompactionPromptTokens(withoutPrior, perMessageChars) <= targetTokens) {
193
+ return withoutPrior;
194
+ }
195
+
196
+ return null;
197
+ }
198
+
199
+ export function fitCompactionPrompt(input, targetTokens) {
200
+ const tryFit = (withFacts) => {
201
+ const baseInp = withFacts ? input : { ...input, preservedFacts: null };
202
+
203
+ const fitAt = (perMessageChars) => {
204
+ let inp = baseInp;
205
+ if (estimateCompactionPromptTokens(inp, perMessageChars) > targetTokens) {
206
+ const fitted = fitPreviousSummaryForCompactionPrompt(inp, perMessageChars, targetTokens);
207
+ if (!fitted) return null;
208
+ inp = fitted;
209
+ if (estimateCompactionPromptTokens(inp, perMessageChars) > targetTokens) return null;
210
+ }
211
+ return buildCompactionPrompt(inp, perMessageChars);
212
+ };
213
+
214
+ const minimalPrompt = fitAt(0);
215
+ if (!minimalPrompt) return null;
216
+
217
+ let maxText = 0;
218
+ for (const m of baseInp.head) maxText = Math.max(maxText, extractText(m).length);
219
+ let lo = 0;
220
+ let hi = Math.min(COMPACTION_INPUT_MAX_CHARS, Math.max(maxText, 0));
221
+ let best = minimalPrompt;
222
+ while (lo <= hi) {
223
+ const mid = Math.floor((lo + hi) / 2);
224
+ const candidate = fitAt(mid);
225
+ if (candidate) {
226
+ best = candidate;
227
+ lo = mid + 1;
228
+ } else {
229
+ hi = mid - 1;
230
+ }
231
+ }
232
+ return best;
233
+ };
234
+ if (input.preservedFacts) {
235
+ const withFacts = tryFit(true);
236
+ if (withFacts && estimateMessagesTokens([
237
+ { role: 'system', content: COMPACTION_SYSTEM_PROMPT },
238
+ { role: 'user', content: withFacts },
239
+ ]) <= targetTokens) return withFacts;
240
+ }
241
+ const fitted = tryFit(false);
242
+ if (fitted) return fitted;
243
+
244
+ // Emergency deterministic reduction: even at perMessageChars=0 the prompt can
245
+ // overflow when the head carries a very large NUMBER of messages (each still
246
+ // emits a `N. role` line). Keep only the newest K head messages and collapse
247
+ // the rest into a single `[K older messages omitted]` stub line, binary
248
+ // searching the largest K that fits. This bounds the head by COUNT, not just
249
+ // per-message chars, so a huge-head transcript still yields a minimal prompt
250
+ // instead of null (which surfaced as a hard compaction throw).
251
+ const head = Array.isArray(input.head) ? input.head : [];
252
+ const baseNoFacts = { ...input, preservedFacts: null };
253
+ const buildReduced = (k) => {
254
+ const kept = k > 0 ? head.slice(head.length - k) : [];
255
+ const omitted = head.length - kept.length;
256
+ const stubHead = omitted > 0
257
+ ? [{ role: 'user', content: `[${omitted} older messages omitted]` }, ...kept]
258
+ : kept;
259
+ let inp = { ...baseNoFacts, head: stubHead };
260
+ // Also shrink/drop a prior <previous-summary> (same as the normal fitAt
261
+ // path) — a large prior summary can keep the prompt over budget even at
262
+ // K=0. fitPreviousSummaryForCompactionPrompt is a no-op when there is no
263
+ // previousSummary, so this is safe for the summary-less case.
264
+ if (estimateCompactionPromptTokens(inp, 0) > targetTokens) {
265
+ const fitted = fitPreviousSummaryForCompactionPrompt(inp, 0, targetTokens);
266
+ if (!fitted) return null;
267
+ inp = fitted;
268
+ if (estimateCompactionPromptTokens(inp, 0) > targetTokens) return null;
269
+ }
270
+ return buildCompactionPrompt(inp, 0);
271
+ };
272
+ let lo = 0;
273
+ let hi = head.length;
274
+ let best = null;
275
+ while (lo <= hi) {
276
+ const mid = Math.floor((lo + hi) / 2);
277
+ const candidate = buildReduced(mid);
278
+ if (candidate) { best = candidate; lo = mid + 1; }
279
+ else hi = mid - 1;
280
+ }
281
+ return best;
282
+ }
283
+
284
+ export function extractResponseText(response) {
285
+ if (!response) return '';
286
+ if (typeof response.content === 'string') return response.content.trim();
287
+ if (Array.isArray(response.content)) {
288
+ return response.content
289
+ .map((item) => {
290
+ if (typeof item === 'string') return item;
291
+ if (typeof item?.text === 'string') return item.text;
292
+ if (typeof item?.content === 'string') return item.content;
293
+ return '';
294
+ })
295
+ .filter(Boolean)
296
+ .join('\n')
297
+ .trim();
298
+ }
299
+ return '';
300
+ }
301
+
302
+ // Validate the provider summary against the required template sections; when it
303
+ // is missing ANY required section anchor (fully or partially malformed) repair
304
+ // it deterministically so a non-empty-but-broken response is never injected as
305
+ // the sole summary. Returns { summary, repaired }.
306
+ export function enforceSemanticSummarySchema(summary, ctx = {}) {
307
+ const text = String(summary || '').trim();
308
+ if (!text) return { summary: text, repaired: false };
309
+ if (summaryIsSchemaValid(text)) {
310
+ return { summary: text, repaired: false };
311
+ }
312
+ return { summary: repairSemanticSummary(text, ctx), repaired: true };
313
+ }
314
+
315
+ function makeSemanticSummaryMessage(oldHistory, summary, semanticMeta = {}, preservedFacts = '') {
316
+ const header = compactHeader(oldHistory);
317
+ header.push(`compact_type=${COMPACT_TYPE_SEMANTIC}`);
318
+ header.push(`semantic=true provider=${semanticMeta.provider || 'unknown'} model=${semanticMeta.model || 'unknown'}`);
319
+ const facts = String(preservedFacts || '').trim();
320
+ const body = String(summary || '').trim();
321
+ const parts = [header.join('\n')];
322
+ if (facts) parts.push(facts);
323
+ if (body) parts.push(body);
324
+ return makeSummaryMessage(parts.join('\n\n'));
325
+ }
326
+
327
+ export function buildRecallFastTrackQuery(messages, opts = {}) {
328
+ const maxChars = Math.max(200, Number(opts.maxChars) || 2_000);
329
+ const hints = String(opts.hints || 'current task decisions constraints file paths changed files verification failures next steps').trim();
330
+ let latestUser = '';
331
+ const recent = [];
332
+ const input = Array.isArray(messages) ? messages : [];
333
+ for (let i = input.length - 1; i >= 0; i -= 1) {
334
+ const m = input[i];
335
+ const text = extractText(m).trim();
336
+ if (!text) continue;
337
+ if (recent.length < 6) recent.unshift(text);
338
+ if (!latestUser && m?.role === 'user' && !isProtectedContextUserMessage(m) && !isInjectedSkillBodyMessage(m)) {
339
+ latestUser = text;
340
+ }
341
+ if (latestUser && recent.length >= 6) break;
342
+ }
343
+ const parts = [latestUser, hints, recent.join('\n')]
344
+ .map((s) => String(s || '').trim())
345
+ .filter(Boolean);
346
+ return truncateMiddle([...new Set(parts)].join('\n'), maxChars);
347
+ }
348
+
349
+ // Fit the structured semantic summary into the remaining token budget WITHOUT
350
+ // dropping any required section. The incoming `summary` is already schema-valid
351
+ // (enforceSemanticSummarySchema ran upstream); here we shrink section bodies via
352
+ // section-aware truncation, fall back to a headings-only schema-valid summary,
353
+ // and finally revalidate so the injected SUMMARY_PREFIX message always carries
354
+ // every required anchor. Returns null only when even the minimal schema-valid
355
+ // summary cannot fit (caller throws).
356
+ export function fitSemanticSummaryMessage(oldHistory, summary, remainingTokens, semanticMeta, preservedFacts = '') {
357
+ const tryFit = (factsText) => {
358
+ const text = String(summary || '').trim();
359
+ // Minimal schema-valid body (headings + "(none)"). If even this does
360
+ // not fit, this facts variant cannot produce a valid message.
361
+ const minimalBody = text ? minimalSchemaSummary() : '';
362
+ const minimal = makeSemanticSummaryMessage(oldHistory, minimalBody, semanticMeta, factsText);
363
+ if (estimateMessagesTokens([minimal]) > remainingTokens) return null;
364
+ if (!text) return minimal;
365
+ // Binary search the per-section body budget; keep all anchors intact.
366
+ let lo = 0;
367
+ let hi = text.length;
368
+ let best = minimal;
369
+ while (lo <= hi) {
370
+ const mid = Math.floor((lo + hi) / 2);
371
+ const body = truncateSummaryBySections(text, mid);
372
+ const candidate = makeSemanticSummaryMessage(oldHistory, body, semanticMeta, factsText);
373
+ if (estimateMessagesTokens([candidate]) <= remainingTokens && summaryIsSchemaValid(body)) {
374
+ best = candidate;
375
+ lo = mid + 1;
376
+ } else {
377
+ hi = mid - 1;
378
+ }
379
+ }
380
+ return best;
381
+ };
382
+ let result = null;
383
+ if (preservedFacts) result = tryFit(preservedFacts);
384
+ if (!result) result = tryFit('');
385
+ return result;
386
+ }
387
+
388
+ export const RECALL_TAIL_TRUNCATION_MARKER = '[... truncated during recall tail preservation ...]';
389
+ export const RECALL_TAIL_SHORT_TRUNCATION_MARKER = '[truncated]';
390
+
391
+ const PRIOR_COMPACTED_CONTEXT_OPEN = '<prior-compacted-context>';
392
+ const PRIOR_COMPACTED_CONTEXT_CLOSE = '</prior-compacted-context>';
393
+
394
+ function formatPriorCompactedContextBlock(priorText) {
395
+ const prior = String(priorText || '').trim();
396
+ if (!prior) return '';
397
+ return `${PRIOR_COMPACTED_CONTEXT_OPEN}\n${prior}\n${PRIOR_COMPACTED_CONTEXT_CLOSE}`;
398
+ }
399
+
400
+ export function stripNestedSummaryHeaderLines(text) {
401
+ const lines = String(text ?? '').split('\n');
402
+ const out = [];
403
+ for (const line of lines) {
404
+ if (line.startsWith(SUMMARY_PREFIX)) continue;
405
+ if (/^messages=\d+\s+(?:sha256=|compact_type=)/.test(line.trim())) continue;
406
+ if (/^compact_type=/.test(line.trim())) continue;
407
+ out.push(line);
408
+ }
409
+ return out.join('\n').trim();
410
+ }
411
+
412
+ function makeRecallFastTrackSummaryMessageParts(oldHistory, recallPart, priorPart, recallMeta = {}) {
413
+ const header = compactHeader(oldHistory);
414
+ header.push(`compact_type=${COMPACT_TYPE_RECALL_FASTTRACK} source=recall-fasttrack query_sha=${recallMeta.querySha || 'none'}`);
415
+ const parts = [header.join('\n')];
416
+ const priorBlock = formatPriorCompactedContextBlock(priorPart);
417
+ if (priorBlock) parts.push(priorBlock);
418
+ const recall = String(recallPart || '').trim();
419
+ if (recall) parts.push(recall);
420
+ return makeSummaryMessage(parts.join('\n\n'));
421
+ }
422
+
423
+ export function fitRecallFastTrackSummaryMessage(oldHistory, recallText, remainingTokens, recallMeta = {}, priorPart = '') {
424
+ const recall = String(recallText || '').trim();
425
+ const prior = String(priorPart || '').trim();
426
+
427
+ let fittedPrior = prior;
428
+ if (prior) {
429
+ let lo = 0;
430
+ let hi = prior.length;
431
+ let bestPriorLen = 0;
432
+ while (lo <= hi) {
433
+ const mid = Math.floor((lo + hi) / 2);
434
+ const candidate = makeRecallFastTrackSummaryMessageParts(oldHistory, '', prior.slice(0, mid), recallMeta);
435
+ if (estimateMessagesTokens([candidate]) <= remainingTokens) {
436
+ bestPriorLen = mid;
437
+ lo = mid + 1;
438
+ } else {
439
+ hi = mid - 1;
440
+ }
441
+ }
442
+ fittedPrior = prior.slice(0, bestPriorLen);
443
+ if (!fittedPrior && prior) {
444
+ const markerOnly = makeRecallFastTrackSummaryMessageParts(oldHistory, '', RECALL_TAIL_TRUNCATION_MARKER, recallMeta);
445
+ if (estimateMessagesTokens([markerOnly]) <= remainingTokens) {
446
+ fittedPrior = RECALL_TAIL_TRUNCATION_MARKER;
447
+ }
448
+ }
449
+ }
450
+
451
+ const minimal = makeRecallFastTrackSummaryMessageParts(oldHistory, '', fittedPrior, recallMeta);
452
+ if (estimateMessagesTokens([minimal]) > remainingTokens) return null;
453
+ if (!recall) return minimal;
454
+
455
+ const { preamble, blocks } = splitRecallRootBlocks(recall);
456
+ if (blocks.length > 0) {
457
+ // Root-block granularity fit: drop the OLDEST blocks WHOLE (never cut
458
+ // a `# chunk` / `# raw_pending` / `# raw_terminal` block mid-entry);
459
+ // dropping more leading blocks only shrinks the body, so binary-search
460
+ // the minimal drop count (mirrors fitRecallRootsMessage).
461
+ let loB = 0;
462
+ let hiB = blocks.length;
463
+ let bestLo = -1;
464
+ while (loB <= hiB) {
465
+ const midB = Math.floor((loB + hiB) / 2);
466
+ const body = [preamble, ...blocks.slice(midB)].filter(Boolean).join('\n\n');
467
+ const candidate = makeRecallFastTrackSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
468
+ if (estimateMessagesTokens([candidate]) <= remainingTokens) {
469
+ bestLo = midB;
470
+ hiB = midB - 1;
471
+ } else {
472
+ loB = midB + 1;
473
+ }
474
+ }
475
+ if (bestLo >= 0) {
476
+ const body = [preamble, ...blocks.slice(bestLo)].filter(Boolean).join('\n\n');
477
+ return makeRecallFastTrackSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
478
+ }
479
+ // Even zero blocks (preamble alone) overflows alongside the fitted
480
+ // prior - try preamble alone, else the no-recall minimal message.
481
+ if (preamble) {
482
+ const preambleOnly = makeRecallFastTrackSummaryMessageParts(oldHistory, preamble, fittedPrior, recallMeta);
483
+ if (estimateMessagesTokens([preambleOnly]) <= remainingTokens) return preambleOnly;
484
+ }
485
+ return minimal;
486
+ }
487
+
488
+ // No parseable root-block boundaries (plain recall text): fall back to the
489
+ // character-slice binary search below.
490
+
491
+ let lo = 0;
492
+ let hi = recall.length;
493
+ let best = minimal;
494
+ while (lo <= hi) {
495
+ const mid = Math.floor((lo + hi) / 2);
496
+ const candidate = makeRecallFastTrackSummaryMessageParts(oldHistory, recall.slice(0, mid), fittedPrior, recallMeta);
497
+ if (estimateMessagesTokens([candidate]) <= remainingTokens) {
498
+ best = candidate;
499
+ lo = mid + 1;
500
+ } else {
501
+ hi = mid - 1;
502
+ }
503
+ }
504
+ return best;
505
+ }
506
+
507
+ // --- Smart-compact root-based fitting (arrival-time replacement) -----------
508
+ //
509
+ // dump_session_roots (memory/index.mjs dumpSessionRootChunks) renders chunks
510
+ // TIME-ORDERED (oldest first; chunks.sort by sourceTurn/ts/id ascending),
511
+ // each root/raw block starting with one of:
512
+ // # chunk N root=ID[ category=X]
513
+ // # raw_pending N id=ID
514
+ // # raw_terminal N id=ID
515
+ // and blocks joined by "\n\n". runRecallFastTrackForSession additionally
516
+ // prepends a "session_id=..." / cycle1-drain-status preamble before the dump
517
+ // text (also "\n\n"-joined) — preserved verbatim as a non-block segment.
518
+ //
519
+ // Unlike fitRecallFastTrackSummaryMessage (character-slice binary search,
520
+ // used by the LLM-summary-free but still-mid-turn recall-fasttrack compact
521
+ // path), the smart-compact arrival path must never cut a root block
522
+ // mid-entry — losing half a root's content silently corrupts that entry.
523
+ // This splitter finds block boundaries by the label pattern (robust to
524
+ // blank lines inside member/raw content, since it anchors on the distinctive
525
+ // "# chunk /raw_pending/raw_terminal" line rather than a blank-line split).
526
+ const RECALL_ROOT_BLOCK_HEADER_RE = /^# (?:chunk \d+ root=\d+(?: category=\S+)?|raw_pending \d+ id=\d+|raw_terminal \d+ id=\d+)[ \t]*$/;
527
+
528
+ export function splitRecallRootBlocks(text) {
529
+ const value = String(text || '');
530
+ if (!value.trim()) return { preamble: '', blocks: [] };
531
+ const re = new RegExp(RECALL_ROOT_BLOCK_HEADER_RE.source, 'gm');
532
+ const starts = [];
533
+ let m;
534
+ while ((m = re.exec(value)) !== null) {
535
+ starts.push(m.index);
536
+ if (re.lastIndex === m.index) re.lastIndex += 1; // zero-width guard, defensive
537
+ }
538
+ if (starts.length === 0) return { preamble: value.trim(), blocks: [] };
539
+ const preamble = value.slice(0, starts[0]).trim();
540
+ const blocks = [];
541
+ for (let i = 0; i < starts.length; i += 1) {
542
+ const start = starts[i];
543
+ const end = i + 1 < starts.length ? starts[i + 1] : value.length;
544
+ const raw = value.slice(start, end).trim();
545
+ if (raw) blocks.push(raw);
546
+ }
547
+ return { preamble, blocks };
548
+ }
549
+
550
+ // Minimal-header summary-message wrapper for the smart-compact roots path.
551
+ // Keeps the SUMMARY_PREFIX anchor (isSummaryMessage / selectCompactionWindow
552
+ // / clear-preserve / TUI all key off startsWith(SUMMARY_PREFIX)) but skips the
553
+ // full sha256/roleCounts header line that fitRecallFastTrackSummaryMessage
554
+ // computes — smart-arrival replacement is a lightweight prefix swap, not the
555
+ // anchored LLM-summary compact, so a heavy per-call header is unneeded cost.
556
+ function makeRecallRootsSummaryMessageParts(oldHistory, rootsPart, priorPart, recallMeta = {}) {
557
+ const header = `${SUMMARY_PREFIX}\nmessages=${(oldHistory || []).length} compact_type=${COMPACT_TYPE_RECALL_FASTTRACK} source=smart-arrival query_sha=${recallMeta.querySha || 'none'}`;
558
+ const parts = [header];
559
+ const priorBlock = formatPriorCompactedContextBlock(priorPart);
560
+ if (priorBlock) parts.push(priorBlock);
561
+ const roots = String(rootsPart || '').trim();
562
+ if (roots) parts.push(roots);
563
+ return makeSummaryMessage(parts.join('\n\n'));
564
+ }
565
+
566
+ // Root-block-aware fit for the smart-compact arrival path (Step1). Mirrors
567
+ // fitRecallFastTrackSummaryMessage's prior-block binary-search fit, but the
568
+ // recall body is fit at ROOT-BLOCK granularity: when the full set of root
569
+ // blocks (kept in original time order) exceeds remainingTokens, the OLDEST
570
+ // blocks are dropped WHOLE (never character-truncated mid-block) until the
571
+ // remaining (newest-biased) suffix fits. Because dropping more leading
572
+ // blocks can only shrink (never grow) the serialized size, the minimal-drop
573
+ // threshold is found via binary search on the drop count.
574
+ export function fitRecallRootsMessage(oldHistory, recallText, remainingTokens, recallMeta = {}, priorPart = '') {
575
+ const prior = String(priorPart || '').trim();
576
+
577
+ let fittedPrior = prior;
578
+ if (prior) {
579
+ let lo = 0;
580
+ let hi = prior.length;
581
+ let bestPriorLen = 0;
582
+ while (lo <= hi) {
583
+ const mid = Math.floor((lo + hi) / 2);
584
+ const candidate = makeRecallRootsSummaryMessageParts(oldHistory, '', prior.slice(0, mid), recallMeta);
585
+ if (estimateMessagesTokens([candidate]) <= remainingTokens) {
586
+ bestPriorLen = mid;
587
+ lo = mid + 1;
588
+ } else {
589
+ hi = mid - 1;
590
+ }
591
+ }
592
+ fittedPrior = prior.slice(0, bestPriorLen);
593
+ if (!fittedPrior && prior) {
594
+ const markerOnly = makeRecallRootsSummaryMessageParts(oldHistory, '', RECALL_TAIL_TRUNCATION_MARKER, recallMeta);
595
+ if (estimateMessagesTokens([markerOnly]) <= remainingTokens) {
596
+ fittedPrior = RECALL_TAIL_TRUNCATION_MARKER;
597
+ }
598
+ }
599
+ }
600
+
601
+ const minimal = makeRecallRootsSummaryMessageParts(oldHistory, '', fittedPrior, recallMeta);
602
+ if (estimateMessagesTokens([minimal]) > remainingTokens) return null;
603
+
604
+ const { preamble, blocks } = splitRecallRootBlocks(recallText);
605
+ if (blocks.length === 0) {
606
+ // No parseable root-block boundaries (empty / non-dump recallText) —
607
+ // degrade to keep-whole-if-it-fits, else drop entirely. Never
608
+ // mid-truncates: a non-block body is treated as a single atomic unit.
609
+ const whole = String(recallText || '').trim();
610
+ if (!whole) return minimal;
611
+ const full = makeRecallRootsSummaryMessageParts(oldHistory, whole, fittedPrior, recallMeta);
612
+ if (estimateMessagesTokens([full]) <= remainingTokens) return full;
613
+ return minimal;
614
+ }
615
+
616
+ let loB = 0;
617
+ let hiB = blocks.length;
618
+ let bestLo = -1;
619
+ while (loB <= hiB) {
620
+ const mid = Math.floor((loB + hiB) / 2);
621
+ const kept = blocks.slice(mid);
622
+ const body = [preamble, ...kept].filter(Boolean).join('\n\n');
623
+ const candidate = makeRecallRootsSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
624
+ if (estimateMessagesTokens([candidate]) <= remainingTokens) {
625
+ bestLo = mid;
626
+ hiB = mid - 1;
627
+ } else {
628
+ loB = mid + 1;
629
+ }
630
+ }
631
+ if (bestLo >= 0) {
632
+ const kept = blocks.slice(bestLo);
633
+ const body = [preamble, ...kept].filter(Boolean).join('\n\n');
634
+ return makeRecallRootsSummaryMessageParts(oldHistory, body, fittedPrior, recallMeta);
635
+ }
636
+ // Even zero root blocks (preamble alone) overflows alongside the fitted
637
+ // prior — try preamble alone, else the no-recall minimal header.
638
+ if (preamble) {
639
+ const preambleOnly = makeRecallRootsSummaryMessageParts(oldHistory, preamble, fittedPrior, recallMeta);
640
+ if (estimateMessagesTokens([preambleOnly]) <= remainingTokens) return preambleOnly;
641
+ }
642
+ return minimal;
643
+ }